ironic/ironic/migrate_nova/migrate_db.py
David Shrewsbury f92071e501 Fix timestamp column migration
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
2014-09-15 14:46:26 -04:00

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()