diff --git a/vmware_nsx/shell/nsx_instance_if_migrate.py b/vmware_nsx/shell/nsx_instance_if_migrate.py new file mode 100644 index 0000000000..6de4da3099 --- /dev/null +++ b/vmware_nsx/shell/nsx_instance_if_migrate.py @@ -0,0 +1,229 @@ +# 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 libvirt +import logging +import re +import sys +import xml.etree.ElementTree as et + +from keystoneauth1 import identity +from keystoneauth1 import session +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= " + "--password= --project= " + "--auth-url= " + "[--project-domain-id=] " + "[--user-domain-id=] " + "[--machine-type=]\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" + " 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=', + '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') + 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() + 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() + 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()