diff options
11 files changed, 186 insertions, 65 deletions
diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_spawn_no_vswitch_exception_nova.utils.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_spawn_no_vswitch_exception_nova.utils.p.gz Binary files differindex 861c1ee8e..df40b08c0 100644 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_spawn_no_vswitch_exception_nova.utils.p.gz +++ b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_spawn_no_vswitch_exception_nova.utils.p.gz diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_spawn_no_vswitch_exception_nova.virt.configdrive.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_spawn_no_vswitch_exception_nova.virt.configdrive.p.gz Binary files differnew file mode 100644 index 000000000..b51766f75 --- /dev/null +++ b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_spawn_no_vswitch_exception_nova.virt.configdrive.p.gz diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_spawn_no_vswitch_exception_os.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_spawn_no_vswitch_exception_os.p.gz Binary files differindex acf47a4f6..092a1f933 100644 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_spawn_no_vswitch_exception_os.p.gz +++ b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_spawn_no_vswitch_exception_os.p.gz diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_spawn_no_vswitch_exception_shutil.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_spawn_no_vswitch_exception_shutil.p.gz Binary files differindex af57ccc47..77f333c00 100644 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_spawn_no_vswitch_exception_shutil.p.gz +++ b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_spawn_no_vswitch_exception_shutil.p.gz diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_spawn_no_vswitch_exception_time.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_spawn_no_vswitch_exception_time.p.gz Binary files differindex b67b1a894..8ab166a60 100644 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_spawn_no_vswitch_exception_time.p.gz +++ b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_spawn_no_vswitch_exception_time.p.gz diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_spawn_no_vswitch_exception_uuid.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_spawn_no_vswitch_exception_uuid.p.gz Binary files differindex 24fb6e539..97e96be17 100644 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_spawn_no_vswitch_exception_uuid.p.gz +++ b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_spawn_no_vswitch_exception_uuid.p.gz diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_spawn_no_vswitch_exception_wmi.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_spawn_no_vswitch_exception_wmi.p.gz Binary files differindex 0634adcba..728464ca9 100644 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_spawn_no_vswitch_exception_wmi.p.gz +++ b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_spawn_no_vswitch_exception_wmi.p.gz diff --git a/nova/tests/test_hypervapi.py b/nova/tests/test_hypervapi.py index f5713c457..9fec9d151 100644 --- a/nova/tests/test_hypervapi.py +++ b/nova/tests/test_hypervapi.py @@ -68,7 +68,8 @@ class HyperVAPITestCase(basetestcase.BaseTestCase): self._setup_stubs() self.flags(instances_path=r'C:\Hyper-V\test\instances', - vswitch_name='external') + vswitch_name='external', + network_api_class='nova.network.quantumv2.api.API') self._hypervutils = hypervutils.HyperVUtils() self._conn = driver_hyperv.HyperVDriver(None) @@ -119,6 +120,7 @@ class HyperVAPITestCase(basetestcase.BaseTestCase): from nova.virt.hyperv import hostops from nova.virt.hyperv import livemigrationops from nova.virt.hyperv import snapshotops + from nova.virt.hyperv import vif from nova.virt.hyperv import vmops from nova.virt.hyperv import volumeops from nova.virt.hyperv import volumeutils @@ -129,6 +131,7 @@ class HyperVAPITestCase(basetestcase.BaseTestCase): basevolumeutils, baseops, hostops, + vif, vmops, vmutils, volumeops, @@ -240,6 +243,9 @@ class HyperVAPITestCase(basetestcase.BaseTestCase): self.assertEquals(len(dvd_paths), 0) def test_spawn_no_vswitch_exception(self): + self.flags(network_api_class='nova.network.api.API') + # Reinstantiate driver, as the VIF plugin is loaded during __init__ + self._conn = driver_hyperv.HyperVDriver(None) # Set flag to a non existing vswitch self.flags(vswitch_name=str(uuid.uuid4())) self.assertRaises(vmutils.HyperVException, self._spawn_instance, True) diff --git a/nova/virt/hyperv/vif.py b/nova/virt/hyperv/vif.py new file mode 100644 index 000000000..a898d3ac2 --- /dev/null +++ b/nova/virt/hyperv/vif.py @@ -0,0 +1,133 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 +# +# Copyright 2013 Cloudbase Solutions Srl +# Copyright 2013 Pedro Navarro Perez +# 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 sys +import uuid + +# Check needed for unit testing on Unix +if sys.platform == 'win32': + import wmi + +from abc import abstractmethod +from nova.openstack.common import cfg +from nova.openstack.common import log as logging +from nova.virt.hyperv import vmutils + +hyperv_opts = [ + cfg.StrOpt('vswitch_name', + default=None, + help='External virtual switch Name, ' + 'if not provided, the first external virtual ' + 'switch is used'), +] + +CONF = cfg.CONF +CONF.register_opts(hyperv_opts) + +LOG = logging.getLogger(__name__) + + +class HyperVBaseVIFDriver(object): + @abstractmethod + def plug(self, instance, vif): + pass + + @abstractmethod + def unplug(self, instance, vif): + pass + + +class HyperVQuantumVIFDriver(HyperVBaseVIFDriver): + """Quantum VIF driver.""" + + def plug(self, instance, vif): + # Quantum takes care of plugging the port + pass + + def unplug(self, instance, vif): + # Quantum takes care of unplugging the port + pass + + +class HyperVNovaNetworkVIFDriver(HyperVBaseVIFDriver): + """Nova network VIF driver.""" + + def __init__(self): + self._vmutils = vmutils.VMUtils() + self._conn = wmi.WMI(moniker='//./root/virtualization') + + def _find_external_network(self): + """Find the vswitch that is connected to the physical nic. + Assumes only one physical nic on the host + """ + #If there are no physical nics connected to networks, return. + LOG.debug(_("Attempting to bind NIC to %s ") + % CONF.vswitch_name) + if CONF.vswitch_name: + LOG.debug(_("Attempting to bind NIC to %s ") + % CONF.vswitch_name) + bound = self._conn.Msvm_VirtualSwitch( + ElementName=CONF.vswitch_name) + else: + LOG.debug(_("No vSwitch specified, attaching to default")) + self._conn.Msvm_ExternalEthernetPort(IsBound='TRUE') + if len(bound) == 0: + return None + if CONF.vswitch_name: + return self._conn.Msvm_VirtualSwitch( + ElementName=CONF.vswitch_name)[0]\ + .associators(wmi_result_class='Msvm_SwitchPort')[0]\ + .associators(wmi_result_class='Msvm_VirtualSwitch')[0] + else: + return self._conn.Msvm_ExternalEthernetPort(IsBound='TRUE')\ + .associators(wmi_result_class='Msvm_SwitchPort')[0]\ + .associators(wmi_result_class='Msvm_VirtualSwitch')[0] + + def plug(self, instance, vif): + extswitch = self._find_external_network() + if extswitch is None: + raise vmutils.HyperVException(_('Cannot find vSwitch')) + + vm_name = instance['name'] + + nic_data = self._conn.Msvm_SyntheticEthernetPortSettingData( + ElementName=vif['id'])[0] + + switch_svc = self._conn.Msvm_VirtualSwitchManagementService()[0] + #Create a port on the vswitch. + (new_port, ret_val) = switch_svc.CreateSwitchPort( + Name=str(uuid.uuid4()), + FriendlyName=vm_name, + ScopeOfResidence="", + VirtualSwitch=extswitch.path_()) + if ret_val != 0: + LOG.error(_('Failed creating a port on the external vswitch')) + raise vmutils.HyperVException(_('Failed creating port for %s') % + vm_name) + ext_path = extswitch.path_() + LOG.debug(_("Created switch port %(vm_name)s on switch %(ext_path)s") + % locals()) + + vms = self._conn.MSVM_ComputerSystem(ElementName=vm_name) + vm = vms[0] + + nic_data.Connection = [new_port] + self._vmutils.modify_virt_resource(self._conn, nic_data, vm) + + def unplug(self, instance, vif): + #TODO(alepilotti) Not implemented + pass diff --git a/nova/virt/hyperv/vmops.py b/nova/virt/hyperv/vmops.py index 1fba15506..3d8958266 100644 --- a/nova/virt/hyperv/vmops.py +++ b/nova/virt/hyperv/vmops.py @@ -24,6 +24,7 @@ import uuid from nova.api.metadata import base as instance_metadata from nova import exception from nova.openstack.common import cfg +from nova.openstack.common import importutils from nova.openstack.common import lockutils from nova.openstack.common import log as logging from nova import utils @@ -35,10 +36,6 @@ from nova.virt.hyperv import vmutils LOG = logging.getLogger(__name__) hyperv_opts = [ - cfg.StrOpt('vswitch_name', - default=None, - help='Default vSwitch Name, ' - 'if none provided first external is used'), cfg.BoolOpt('limit_cpu_features', default=False, help='Required for live migration among ' @@ -59,14 +56,32 @@ hyperv_opts = [ CONF = cfg.CONF CONF.register_opts(hyperv_opts) CONF.import_opt('use_cow_images', 'nova.virt.driver') +CONF.import_opt('network_api_class', 'nova.network') class VMOps(baseops.BaseOps): + _vif_driver_class_map = { + 'nova.network.quantumv2.api.API': + 'nova.virt.hyperv.vif.HyperVQuantumVIFDriver', + 'nova.network.api.API': + 'nova.virt.hyperv.vif.HyperVNovaNetworkVIFDriver', + } + def __init__(self, volumeops): super(VMOps, self).__init__() self._vmutils = vmutils.VMUtils() self._volumeops = volumeops + self._load_vif_driver_class() + + def _load_vif_driver_class(self): + try: + class_name = self._vif_driver_class_map[CONF.network_api_class] + self._vif_driver = importutils.import_object(class_name) + except KeyError: + raise TypeError(_("VIF driver not found for " + "network_api_class: %s") % + CONF.network_api_class) def list_instances(self): """Return the names of all the instances known to Hyper-V.""" @@ -158,8 +173,8 @@ class VMOps(baseops.BaseOps): self._create_scsi_controller(instance['name']) for vif in network_info: - mac_address = vif['address'].replace(':', '') - self._create_nic(instance['name'], mac_address) + self._create_nic(instance['name'], vif) + self._vif_driver.plug(instance, vif) if configdrive.required_by(instance): self._create_config_drive(instance, injected_files, @@ -367,46 +382,28 @@ class VMOps(baseops.BaseOps): LOG.info(_('Created drive type %(drive_type)s for %(vm_name)s') % locals()) - def _create_nic(self, vm_name, mac): + def _create_nic(self, vm_name, vif): """Create a (synthetic) nic and attach it to the vm.""" LOG.debug(_('Creating nic for %s '), vm_name) - #Find the vswitch that is connected to the physical nic. - vms = self._conn.Msvm_ComputerSystem(ElementName=vm_name) - extswitch = self._find_external_network() - if extswitch is None: - raise vmutils.HyperVException(_('Cannot find vSwitch')) - vm = vms[0] - switch_svc = self._conn.Msvm_VirtualSwitchManagementService()[0] - #Find the default nic and clone it to create a new nic for the vm. - #Use Msvm_SyntheticEthernetPortSettingData for Windows or Linux with - #Linux Integration Components installed. + #Create a new nic syntheticnics_data = self._conn.Msvm_SyntheticEthernetPortSettingData() default_nic_data = [n for n in syntheticnics_data if n.InstanceID.rfind('Default') > 0] new_nic_data = self._vmutils.clone_wmi_obj(self._conn, 'Msvm_SyntheticEthernetPortSettingData', default_nic_data[0]) - #Create a port on the vswitch. - (new_port, ret_val) = switch_svc.CreateSwitchPort( - Name=str(uuid.uuid4()), - FriendlyName=vm_name, - ScopeOfResidence="", - VirtualSwitch=extswitch.path_()) - if ret_val != 0: - LOG.error(_('Failed creating a port on the external vswitch')) - raise vmutils.HyperVException(_('Failed creating port for %s') % - vm_name) - ext_path = extswitch.path_() - LOG.debug(_("Created switch port %(vm_name)s on switch %(ext_path)s") - % locals()) - #Connect the new nic to the new port. - new_nic_data.Connection = [new_port] - new_nic_data.ElementName = vm_name + ' nic' - new_nic_data.Address = mac + + #Configure the nic + new_nic_data.ElementName = vif['id'] + new_nic_data.Address = vif['address'].replace(':', '') new_nic_data.StaticMacAddress = 'True' new_nic_data.VirtualSystemIdentifiers = ['{' + str(uuid.uuid4()) + '}'] - #Add the new nic to the vm. + + #Add the new nic to the vm + vms = self._conn.Msvm_ComputerSystem(ElementName=vm_name) + vm = vms[0] + new_resources = self._vmutils.add_virt_resource(self._conn, new_nic_data, vm) if new_resources is None: @@ -414,33 +411,6 @@ class VMOps(baseops.BaseOps): vm_name) LOG.info(_("Created nic for %s "), vm_name) - def _find_external_network(self): - """Find the vswitch that is connected to the physical nic. - Assumes only one physical nic on the host - """ - #If there are no physical nics connected to networks, return. - LOG.debug(_("Attempting to bind NIC to %s ") - % CONF.vswitch_name) - if CONF.vswitch_name: - LOG.debug(_("Attempting to bind NIC to %s ") - % CONF.vswitch_name) - bound = self._conn.Msvm_VirtualSwitch( - ElementName=CONF.vswitch_name) - else: - LOG.debug(_("No vSwitch specified, attaching to default")) - self._conn.Msvm_ExternalEthernetPort(IsBound='TRUE') - if len(bound) == 0: - return None - if CONF.vswitch_name: - return self._conn.Msvm_VirtualSwitch( - ElementName=CONF.vswitch_name)[0]\ - .associators(wmi_result_class='Msvm_SwitchPort')[0]\ - .associators(wmi_result_class='Msvm_VirtualSwitch')[0] - else: - return self._conn.Msvm_ExternalEthernetPort(IsBound='TRUE')\ - .associators(wmi_result_class='Msvm_SwitchPort')[0]\ - .associators(wmi_result_class='Msvm_VirtualSwitch')[0] - def reboot(self, instance, network_info, reboot_type): """Reboot the specified instance.""" vm = self._vmutils.lookup(self._conn, instance['name']) diff --git a/nova/virt/hyperv/vmutils.py b/nova/virt/hyperv/vmutils.py index bae8a1f1a..d899f977d 100644 --- a/nova/virt/hyperv/vmutils.py +++ b/nova/virt/hyperv/vmutils.py @@ -130,7 +130,7 @@ class VMUtils(object): return newinst def add_virt_resource(self, conn, res_setting_data, target_vm): - """Add a new resource (disk/nic) to the VM.""" + """Adds a new resource to the VM.""" vs_man_svc = conn.Msvm_VirtualSystemManagementService()[0] (job, new_resources, ret_val) = vs_man_svc.\ AddVirtualSystemResources([res_setting_data.GetText_(1)], @@ -145,8 +145,20 @@ class VMUtils(object): else: return None + def modify_virt_resource(self, conn, res_setting_data, target_vm): + """Updates a VM resource.""" + vs_man_svc = conn.Msvm_VirtualSystemManagementService()[0] + (job, ret_val) = vs_man_svc.ModifyVirtualSystemResources( + ResourceSettingData=[res_setting_data.GetText_(1)], + ComputerSystem=target_vm.path_()) + if ret_val == constants.WMI_JOB_STATUS_STARTED: + success = self.check_job_status(job) + else: + success = (ret_val == 0) + return success + def remove_virt_resource(self, conn, res_setting_data, target_vm): - """Add a new resource (disk/nic) to the VM.""" + """Removes a VM resource.""" vs_man_svc = conn.Msvm_VirtualSystemManagementService()[0] (job, ret_val) = vs_man_svc.\ RemoveVirtualSystemResources([res_setting_data.path_()], |