247 lines
8.4 KiB
Python
247 lines
8.4 KiB
Python
# Copyright 2017 VMware, Inc.
|
|
# All Rights Reserved
|
|
#
|
|
# 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.
|
|
|
|
import getopt
|
|
import logging
|
|
import re
|
|
import sys
|
|
import xml.etree.ElementTree as et
|
|
|
|
from keystoneauth1 import identity
|
|
from keystoneauth1 import session
|
|
import libvirt
|
|
from neutronclient.v2_0 import client
|
|
import nova.conf
|
|
|
|
CONF = nova.conf.CONF
|
|
logging.basicConfig(level=logging.INFO)
|
|
LOG = logging.getLogger(__name__)
|
|
|
|
|
|
def usage():
|
|
print ("python nsx_instance_if_migrate.py --username=<username> "
|
|
"--password=<password> --project=<project> "
|
|
"--auth-url=<keystone auth URL> "
|
|
"[--project-domain-id=<project domain>] "
|
|
"[--user-domain-id=<user domain>] "
|
|
"[--machine-type=<migrated machine type] "
|
|
"[--logfile=<log file>] "
|
|
"[--nsx-bridge=<NSX managed vSwitch>]\n\n"
|
|
"Convert libvirt interface definitions on a KVM host, to NSX "
|
|
"managed vSwitch definitions\n\n"
|
|
" username: Admin user's username\n"
|
|
" password: Admin user's password\n"
|
|
" keystone auth URL: URL to keystone's authentication service\n"
|
|
" project domain: Keystone project domain\n"
|
|
" user domain: Keystone user domain\n"
|
|
" migrated machine type: Overwrites libvirt's machine type\n"
|
|
" log file: Output log of the command execution\n"
|
|
" NSX managed vSwitch: vSwitch on host, managed by NSX\n\n")
|
|
|
|
sys.exit()
|
|
|
|
|
|
def get_opts():
|
|
opts = {}
|
|
o = []
|
|
p = re.compile('^-+')
|
|
try:
|
|
o, a = getopt.getopt(sys.argv[1:], 'h', ['help',
|
|
'username=',
|
|
'password=',
|
|
'project=',
|
|
'project-domain-id=',
|
|
'user-domain-id=',
|
|
'auth-url=',
|
|
'machine-type=',
|
|
'logfile=',
|
|
'nsx-bridge='])
|
|
except getopt.GetoptError as err:
|
|
LOG.error(err)
|
|
usage()
|
|
for opt, val in o:
|
|
if opt in ('h', 'help'):
|
|
usage()
|
|
else:
|
|
opts[p.sub('', opt)] = val
|
|
|
|
for mandatory_key in ['username', 'password', 'project', 'auth-url']:
|
|
if opts.get(mandatory_key) is None:
|
|
LOG.error("%s must be specified!", mandatory_key)
|
|
usage()
|
|
|
|
return opts
|
|
|
|
|
|
def xmltag_text_get(obj, tag_name):
|
|
tag_obj = obj.find(tag_name)
|
|
if tag_obj is not None:
|
|
return tag_obj.text
|
|
|
|
|
|
def xmltag_attr_get(obj, tag, attr):
|
|
tag_obj = obj.find(tag)
|
|
if tag_obj is not None:
|
|
return tag_obj.get(attr)
|
|
|
|
|
|
def xmltag_set(elem, tag, **kwargs):
|
|
sub_elem = elem.find(tag)
|
|
if sub_elem is None:
|
|
sub_elem = et.SubElement(elem, tag)
|
|
for attr in kwargs.keys():
|
|
sub_elem.set(attr, kwargs.get(attr))
|
|
return sub_elem
|
|
|
|
|
|
def iface_migrate(neutron, instance_name, iface, nsx_switch):
|
|
iface.set('type', 'bridge')
|
|
xmltag_set(iface, 'source', bridge=nsx_switch)
|
|
virt_port = xmltag_set(iface, 'virtualport', type='openvswitch')
|
|
instance_mac = xmltag_attr_get(iface, 'mac', 'address')
|
|
if instance_mac is None:
|
|
LOG.error("Couldn't find MAC address for instance %s", instance_name)
|
|
return
|
|
|
|
ports = neutron.list_ports(fields=['id'], mac_address=instance_mac)
|
|
if len(ports['ports']) != 1:
|
|
LOG.error('For instance %(vm)s, invalid ports received from neutron: '
|
|
'%(ports)s', {'vm': instance_name, 'ports': ports})
|
|
return
|
|
|
|
neutron_port_id = ports['ports'][0]['id']
|
|
xmltag_set(virt_port, 'parameters', interfaceid=neutron_port_id)
|
|
xmltag_set(iface, 'driver', name='qemu')
|
|
|
|
tap_dev = xmltag_attr_get(iface, 'target', 'dev')
|
|
if tap_dev is None:
|
|
LOG.error("For instance %(vm)s, couldn't find tap device for "
|
|
"interface", instance_name)
|
|
|
|
# remove script tag if found
|
|
script_tag = iface.find('script')
|
|
if script_tag is not None:
|
|
iface.remove(script_tag)
|
|
|
|
|
|
def is_valid_os_data(libvirt_conn, os_type, os_arch, os_machine):
|
|
caps_xml = libvirt_conn.getCapabilities()
|
|
caps_root = et.fromstring(caps_xml)
|
|
for guest_tag in caps_root.findall('guest'):
|
|
if (xmltag_text_get(guest_tag, 'os_type') == os_type
|
|
and xmltag_attr_get(guest_tag, 'arch', 'name') == os_arch):
|
|
for machine_tag in guest_tag.find('arch').findall('machine'):
|
|
if machine_tag.text == os_machine:
|
|
return True
|
|
return False
|
|
|
|
|
|
def instance_migrate(libvirt_conn, neutron, instance, machine_type,
|
|
nsx_switch):
|
|
xml = instance.XMLDesc()
|
|
root = et.fromstring(xml)
|
|
|
|
instance_name = xmltag_text_get(root, 'name')
|
|
if instance_name is None:
|
|
LOG.error("Couldn't find instance name in XML")
|
|
return
|
|
|
|
instance_uuid = xmltag_text_get(root, 'uuid')
|
|
if instance_uuid is None:
|
|
LOG.error("Couldn't find UUID for instance %s", instance_name)
|
|
return
|
|
|
|
# Validate that os is supported by hypervisor
|
|
os_tag = root.find('os')
|
|
if os_tag is None:
|
|
LOG.error("Couldn't find OS tag for instance %s", instance_name)
|
|
return
|
|
type_tag = os_tag.find('type')
|
|
if not is_valid_os_data(libvirt_conn, type_tag.text, type_tag.get('arch'),
|
|
type_tag.get('machine')):
|
|
LOG.error("Instance %s OS data is invalid or not supported by "
|
|
"hypervisor", instance_name)
|
|
return
|
|
|
|
if machine_type is not None:
|
|
type_tag.set('machine', machine_type)
|
|
|
|
devs = root.find('devices')
|
|
ifaces = devs.findall('interface')
|
|
|
|
if not ifaces:
|
|
LOG.error('No interfaces to migrate for instance %s', instance_name)
|
|
|
|
for iface in ifaces:
|
|
iface_migrate(neutron, instance_name, iface, nsx_switch)
|
|
|
|
instance.undefine()
|
|
libvirt_conn.defineXML(et.tostring(root))
|
|
LOG.info('Migrated instance %(vm)s (%(uuid)s) successfully!',
|
|
{'vm': instance_name, 'uuid': instance_uuid})
|
|
|
|
|
|
def main():
|
|
opts = get_opts()
|
|
if opts.get('logfile'):
|
|
f_handler = logging.FileHandler(opts.get('logfile'))
|
|
f_formatter = logging.Formatter(
|
|
'%(asctime)s %(levelname)s %(message)s')
|
|
f_handler.setFormatter(f_formatter)
|
|
LOG.addHandler(f_handler)
|
|
|
|
conn = libvirt.open('qemu:///system')
|
|
if conn is None:
|
|
LOG.error('Failed to connect to libvirt')
|
|
exit(1)
|
|
|
|
auth = identity.Password(username=opts['username'],
|
|
password=opts['password'],
|
|
project_name=opts['project'],
|
|
project_domain_id=opts.get('project-domain-id',
|
|
'default'),
|
|
user_domain_id=opts.get('user-domain-id',
|
|
'default'),
|
|
auth_url=opts['auth-url'])
|
|
|
|
if auth is None:
|
|
LOG.error('Failed to authenticate with keystone')
|
|
exit(1)
|
|
|
|
sess = session.Session(auth=auth)
|
|
if sess is None:
|
|
LOG.error('Failed to create keystone session')
|
|
exit(1)
|
|
|
|
neutron = client.Client(session=sess)
|
|
if neutron is None:
|
|
LOG.error('Failed to create neutron session')
|
|
exit(1)
|
|
|
|
instances = conn.listAllDomains()
|
|
if not instances:
|
|
LOG.error('No instances to migrate')
|
|
|
|
for instance in instances:
|
|
try:
|
|
instance_migrate(conn, neutron, instance, opts.get('machine-type'),
|
|
opts.get('nsx-bridge', CONF.neutron.ovs_bridge))
|
|
except Exception as e:
|
|
LOG.error('Failed to migrate instance with exception %s', e)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|