
The updated_at and created_at columns from the baremetal database table were being switched when migrated into the ironic database table. Change-Id: Ibc8b1d699fc302b84cf0bc2d8159a5b5510c3abb Closes-Bug: #1369699
318 lines
10 KiB
Python
318 lines
10 KiB
Python
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
# not use this file except in compliance with the License. You may obtain
|
|
# a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
# License for the specific language governing permissions and limitations
|
|
# under the License.
|
|
from __future__ import print_function
|
|
import argparse
|
|
import ConfigParser
|
|
import os
|
|
import sys
|
|
|
|
import sqlalchemy as sa
|
|
from sqlalchemy.orm import sessionmaker
|
|
|
|
from ironic.common import states as ironic_states
|
|
from ironic.common import utils
|
|
from ironic.db.sqlalchemy import models as ironic_models
|
|
from ironic.migrate_nova import nova_baremetal_states as nova_states
|
|
from ironic.migrate_nova import nova_models
|
|
|
|
|
|
DESCRIPTION = """
|
|
This is an administrative utility to be used for migrating a nova-baremetal
|
|
node inventory to Ironic. It will migrate nova-baremetal node and interface
|
|
information and associated driver configuration from the Nova database to the
|
|
Ironic database. It only supports migrating from the IPMI and
|
|
VirtualPowerManager power drivers.
|
|
"""
|
|
|
|
IRONIC_ENGINE = None
|
|
NOVA_BM_ENGINE = None
|
|
|
|
|
|
# Relevant nova-baremetal config with their associated defaults as of Juno.
|
|
NOVA_BM_CONFIG_KEYS = {
|
|
# nova.virt.baremetal.driver
|
|
'driver': 'nova.virt.baremetal.pxe.PXE',
|
|
'power_manager': 'nova.virt.baremetal.ipmi.IPMI',
|
|
|
|
# nova.virt.baremetal.virtual_power_driver
|
|
'virtual_power_ssh_host': '',
|
|
'virtual_power_ssh_port': 22,
|
|
'virtual_power_type': 'virsh',
|
|
'virtual_power_host_user': '',
|
|
'virtual_power_host_pass': '',
|
|
'virtual_power_host_key': '',
|
|
}
|
|
|
|
|
|
def get_nova_nodes():
|
|
Session = sessionmaker(bind=NOVA_BM_ENGINE)
|
|
session = Session()
|
|
query = session.query(nova_models.BareMetalNode)
|
|
|
|
try:
|
|
nodes = query.all()
|
|
except sa.exc.OperationalError as err:
|
|
print("Could not get nodes from Nova:\n%s" % err, file=sys.stderr)
|
|
sys.exit(2)
|
|
|
|
session.close()
|
|
|
|
return nodes
|
|
|
|
|
|
def get_nova_ports():
|
|
Session = sessionmaker(bind=NOVA_BM_ENGINE)
|
|
session = Session()
|
|
|
|
query = session.query(nova_models.BareMetalInterface)
|
|
|
|
try:
|
|
ports = query.all()
|
|
except sa.exc.OperationalError as err:
|
|
print("Could not get ports from Nova:\n%s" % err, file=sys.stderr)
|
|
sys.exit(2)
|
|
|
|
session.close()
|
|
|
|
return ports
|
|
|
|
|
|
def convert_nova_nodes(nodes, cpu_arch, nova_conf):
|
|
ironic_nodes = []
|
|
|
|
for n_node in nodes:
|
|
# Create an empty Ironic Node
|
|
i_node = ironic_models.Node()
|
|
|
|
# Populate basic properties
|
|
i_node.id = n_node.id
|
|
i_node.uuid = n_node.uuid
|
|
i_node.chassis_id = None
|
|
i_node.last_error = None
|
|
i_node.instance_uuid = n_node.instance_uuid
|
|
i_node.reservation = None
|
|
i_node.maintenance = False
|
|
i_node.updated_at = n_node.updated_at
|
|
i_node.created_at = n_node.created_at
|
|
|
|
# Populate states
|
|
if n_node.task_state == nova_states.ACTIVE:
|
|
i_node.power_state = ironic_states.POWER_ON
|
|
else:
|
|
i_node.power_state = ironic_states.POWER_OFF
|
|
|
|
i_node.target_power_state = None
|
|
|
|
if i_node.instance_uuid:
|
|
prov_state = ironic_states.ACTIVE
|
|
else:
|
|
prov_state = ironic_states.NOSTATE
|
|
|
|
i_node.provision_state = prov_state
|
|
i_node.target_provision_state = None
|
|
|
|
# Populate extra properties
|
|
i_node.extra = {}
|
|
|
|
# Populate driver_info
|
|
i_node.driver_info = {}
|
|
|
|
power_manager = nova_conf['power_manager']
|
|
if power_manager.endswith('IPMI'):
|
|
i_node.driver = 'pxe_ipmitool'
|
|
if n_node.pm_address:
|
|
i_node.driver_info['ipmi_address'] = n_node.pm_address
|
|
if n_node.pm_user:
|
|
i_node.driver_info['ipmi_username'] = n_node.pm_user
|
|
if n_node.pm_password:
|
|
i_node.driver_info['ipmi_password'] = n_node.pm_password
|
|
elif power_manager.endswith('VirtualPowerManager'):
|
|
i_node.driver = 'pxe_ssh'
|
|
i_node.driver_info = {
|
|
'ssh_virt_type': nova_conf['virtual_power_type'],
|
|
'ssh_address': nova_conf['virtual_power_ssh_host'],
|
|
'ssh_username': nova_conf['virtual_power_host_user'],
|
|
}
|
|
|
|
ssh_port = nova_conf.get('ssh_port')
|
|
if ssh_port:
|
|
ssh_port = nova_conf['virtual_power_ssh_port']
|
|
|
|
ssh_key = nova_conf.get('virtual_power_host_key')
|
|
if ssh_key:
|
|
i_node.driver_info['ssh_key_filename'] = ssh_key
|
|
|
|
ssh_pass = nova_conf.get('virtual_power_host_pass')
|
|
if ssh_pass:
|
|
i_node.driver_info['ssh_password'] = ssh_pass
|
|
else:
|
|
print("This does not support migration from power driver: "
|
|
"%s\n" % nova_conf['driver'], file=sys.stderr)
|
|
sys.exit(2)
|
|
|
|
# Populate instance_info
|
|
i_node.instance_info = {}
|
|
|
|
if n_node.root_mb:
|
|
i_node.instance_info['root_mb'] = n_node.root_mb
|
|
if n_node.swap_mb:
|
|
i_node.instance_info['swap_mb'] = n_node.swap_mb
|
|
if n_node.ephemeral_mb:
|
|
i_node.instance_info['ephemeral_mb'] = n_node.ephemeral_mb
|
|
|
|
i_node.properties = {'cpu_arch': cpu_arch,
|
|
'cpus': n_node.cpus,
|
|
'local_gb': n_node.local_gb,
|
|
'memory_mb': n_node.memory_mb}
|
|
|
|
ironic_nodes.append(i_node)
|
|
|
|
return ironic_nodes
|
|
|
|
|
|
def convert_nova_ports(ports):
|
|
ironic_ports = []
|
|
|
|
for n_port in ports:
|
|
i_port = ironic_models.Port()
|
|
|
|
i_port.id = n_port.id
|
|
i_port.uuid = utils.generate_uuid()
|
|
i_port.address = n_port.address
|
|
i_port.node_id = n_port.bm_node_id
|
|
|
|
i_port.extra = {}
|
|
|
|
if n_port.vif_uuid:
|
|
i_port.extra['vif_uuid'] = n_port.vif_uuid
|
|
|
|
ironic_ports.append(i_port)
|
|
|
|
return ironic_ports
|
|
|
|
|
|
def save_ironic_objects(objects):
|
|
Session = sessionmaker(bind=IRONIC_ENGINE)
|
|
session = Session()
|
|
|
|
try:
|
|
session.add_all(objects)
|
|
session.commit()
|
|
except sa.exc.OperationalError as err:
|
|
print("Could not send data to Ironic:\n%s" % err, file=sys.stderr)
|
|
sys.exit(2)
|
|
|
|
session.close()
|
|
|
|
|
|
def parse_nova_config(config_file):
|
|
"""Parse nova.conf and return known defaults if setting is not present.
|
|
This avoids having to import nova code from this script and risk conflicts
|
|
with Ironic's tree around oslo.config resources.
|
|
"""
|
|
if not os.path.isfile(config_file):
|
|
print("nova.conf not found at %s. Please specify the location via "
|
|
"the --nova-config option." % config_file, file=sys.stderr)
|
|
sys.exit(1)
|
|
nova_conf = ConfigParser.SafeConfigParser()
|
|
nova_conf.read(config_file)
|
|
|
|
conf = {}
|
|
for setting, default in NOVA_BM_CONFIG_KEYS.items():
|
|
try:
|
|
conf[setting] = nova_conf.get('baremetal', setting)
|
|
except ConfigParser.NoOptionError:
|
|
conf[setting] = default
|
|
return conf
|
|
|
|
|
|
def validate_config(config):
|
|
"""Early validation of required configuration prior to touching the db."""
|
|
if config['power_manager'].endswith('VirtualPowerManager'):
|
|
# confirm nova.conf contains all required ssh info, as per
|
|
# ironic.drivers.ssh.REQUIRED_PROPERTIES.
|
|
req = ['virtual_power_host_user', 'virtual_power_type',
|
|
'virtual_power_ssh_host']
|
|
missing = []
|
|
for r in req:
|
|
if not config.get(r):
|
|
missing.append(r)
|
|
if missing:
|
|
print('nova.conf is missing required settings in the '
|
|
'[baremetal] section to migrate VirtualPowerManager: %s' %
|
|
' '.join(missing), file=sys.stderr)
|
|
sys.exit(1)
|
|
|
|
|
|
def parse_args():
|
|
parser = argparse.ArgumentParser(description=DESCRIPTION)
|
|
parser.add_argument('--nova-bm-db', '-b', type=str,
|
|
required=True, dest='nova_bm_conn_string',
|
|
help='Connection string to Nova baremetal database.')
|
|
parser.add_argument('--ironic-db', '-i', type=str,
|
|
required=True, dest='ironic_conn_string',
|
|
help='Connection string to Ironic database.')
|
|
parser.add_argument('--node-arch', '-a', type=str,
|
|
required=True, dest='cpu_arch',
|
|
help='CPU architecture of the nodes.')
|
|
parser.add_argument('--nova-config', '-c', type=str,
|
|
required=False, dest='nova_config',
|
|
default='/etc/nova/nova.conf',
|
|
help='Path to nova.conf. (default: '
|
|
'/etc/nova/nova.conf)')
|
|
return parser.parse_args(sys.argv[1:])
|
|
|
|
|
|
def main():
|
|
args = parse_args()
|
|
|
|
global IRONIC_ENGINE
|
|
global NOVA_BM_ENGINE
|
|
|
|
IRONIC_ENGINE = sa.create_engine(args.ironic_conn_string)
|
|
NOVA_BM_ENGINE = sa.create_engine(args.nova_bm_conn_string)
|
|
|
|
# Load and validate nova.conf
|
|
nova_conf = parse_nova_config(args.nova_config)
|
|
validate_config(nova_conf)
|
|
|
|
# Process nodes
|
|
print("Getting data for baremetal nodes from Nova...")
|
|
nova_nodes = get_nova_nodes()
|
|
print("Got %d nodes from Nova..." % len(nova_nodes))
|
|
|
|
print("Converting information for Nova nodes to Ironic...")
|
|
ironic_nodes = convert_nova_nodes(nova_nodes, args.cpu_arch,
|
|
nova_conf)
|
|
|
|
print("Saving nodes to Ironic...")
|
|
save_ironic_objects(ironic_nodes)
|
|
|
|
# Process ports
|
|
print("Getting baremetal ports from Nova...")
|
|
nova_ports = get_nova_ports()
|
|
print("Got %d ports from Nova." % len(nova_ports))
|
|
|
|
print("Converting Nova ports...")
|
|
ironic_ports = convert_nova_ports(nova_ports)
|
|
print("Saving ports to Ironic...")
|
|
save_ironic_objects(ironic_ports)
|
|
|
|
# Printing summary
|
|
print("All done!")
|
|
print("%d nodes and %d ports have been migrated from "
|
|
"Nova to Ironic." % (len(nova_nodes), len(nova_ports)))
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main()
|