diff --git a/nova/tests/unit/virt/vmwareapi/fake.py b/nova/tests/unit/virt/vmwareapi/fake.py index 3f5ce8efbfe4..e1eddb597813 100644 --- a/nova/tests/unit/virt/vmwareapi/fake.py +++ b/nova/tests/unit/virt/vmwareapi/fake.py @@ -36,6 +36,7 @@ _CLASSES = ['Datacenter', 'Datastore', 'ResourcePool', 'VirtualMachine', 'files', 'ClusterComputeResource', 'HostStorageSystem'] _FAKE_FILE_SIZE = 1024 +_FAKE_VCENTER_UUID = '497c514c-ef5e-4e7f-8d93-ec921993b93a' _db_content = {} _array_types = {} @@ -1195,6 +1196,8 @@ class FakeVim(object): about_info = DataObject() about_info.name = "VMware vCenter Server" about_info.version = "5.1.0" + about_info.instanceUuid = _FAKE_VCENTER_UUID + service_content.about = about_info self._service_content = service_content diff --git a/nova/tests/unit/virt/vmwareapi/test_driver_api.py b/nova/tests/unit/virt/vmwareapi/test_driver_api.py index de83a727ee9c..35be43c72461 100644 --- a/nova/tests/unit/virt/vmwareapi/test_driver_api.py +++ b/nova/tests/unit/virt/vmwareapi/test_driver_api.py @@ -220,7 +220,7 @@ class VMwareAPIVMTestCase(test.NoDBTestCase): self._set_exception_vars() self.node_name = self.conn._resources.keys()[0] self.node_name2 = self.conn._resources.keys()[1] - if cluster_name2 in self.node_name2: + if self.conn._resources[self.node_name2]['name'] == cluster_name2: self.ds = 'ds1' else: self.ds = 'ds2' @@ -2310,3 +2310,32 @@ class VMwareAPIVMTestCase(test.NoDBTestCase): self.conn._update_pbm_location() self.assertEqual('fira', self.conn._session._pbm_wsdl_loc) self.assertIsNone(self.conn._session._pbm) + + def test_nodename(self): + test_mor = "domain-26" + self.assertEqual("%s.%s" % (test_mor, + vmwareapi_fake._FAKE_VCENTER_UUID), + self.conn._create_nodename(test_mor), + "VC driver failed to create the proper node name") + + def test_normalize_nodename_old(self): + test_mor = "domain-26" + sample_cluster_names = ["Cluster1", + "Cluster:2", + "Cluster:3)", + "(Cluster:4", + "(Cluster:5)", + "Test Cluster"] + + for cluster_name in sample_cluster_names: + old_format = "%s(%s)" % (test_mor, cluster_name) + self.assertEqual(self.conn._create_nodename(test_mor), + self.conn._normalize_nodename(old_format), + 'VC driver failed to normalize cluster name %s' % + cluster_name) + + def test_normalize_nodename_new(self): + # Assert that _normalize_nodename doesn't touch the new format + test_mor = "domain-26" + nodename = "%s.%s" % (test_mor, vmwareapi_fake._FAKE_VCENTER_UUID) + self.assertEqual(nodename, self.conn._normalize_nodename(nodename)) diff --git a/nova/virt/vmwareapi/driver.py b/nova/virt/vmwareapi/driver.py index 1eafe615217c..63b15e2d00ab 100644 --- a/nova/virt/vmwareapi/driver.py +++ b/nova/virt/vmwareapi/driver.py @@ -37,6 +37,7 @@ from nova.virt import driver from nova.virt.vmwareapi import constants from nova.virt.vmwareapi import error_util from nova.virt.vmwareapi import host +from nova.virt.vmwareapi import vim_util as nova_vim_util from nova.virt.vmwareapi import vm_util from nova.virt.vmwareapi import vmops from nova.virt.vmwareapi import volumeops @@ -113,6 +114,13 @@ class VMwareVCDriver(driver.ComputeDriver): "supports_migrate_to_same_host": True } + # Legacy nodename is of the form: () + # e.g. domain-26(TestCluster) + # We assume consists of alphanumeric, _ and -. + # We assume cluster name is everything between the first ( and the last ). + # We pull out for re-use. + LEGACY_NODENAME = re.compile('([\w-]+)\(.+\)') + # The vCenter driver includes API that acts on ESX hosts or groups # of ESX hosts in clusters or non-cluster logical-groupings. # @@ -171,6 +179,7 @@ class VMwareVCDriver(driver.ComputeDriver): LOG.warning(_LW("The following clusters could not be found in the " "vCenter %s"), list(missing_clusters)) + self._vcenter_uuid = self._get_vcenter_uuid() # The _resources is used to maintain the vmops, volumeops and vcstate # objects per cluster self._resources = {} @@ -330,14 +339,16 @@ class VMwareVCDriver(driver.ComputeDriver): The VMwareVMOps, VMwareVolumeOps and VCState object is for each cluster/rp. The dictionary is of the form { - domain-1000 : {'vmops': vmops_obj, - 'volumeops': volumeops_obj, - 'vcstate': vcstate_obj, - 'name': MyCluster}, - resgroup-1000 : {'vmops': vmops_obj, - 'volumeops': volumeops_obj, - 'vcstate': vcstate_obj, - 'name': MyRP}, + 'domain-1000.497c514c-ef5e-4e7f-8d93-ec921993b93a' : { + 'vmops': vmops_obj, + 'volumeops': volumeops_obj, + 'vcstate': vcstate_obj, + 'name': MyCluster}, + 'resgroup-1000.497c514c-ef5e-4e7f-8d93-ec921993b93a' : { + 'vmops': vmops_obj, + 'volumeops': volumeops_obj, + 'vcstate': vcstate_obj, + 'name': MyRP}, } """ added_nodes = set(self.dict_mors.keys()) - set(self._resource_keys) @@ -349,7 +360,7 @@ class VMwareVCDriver(driver.ComputeDriver): self.dict_mors[node]['cluster_mor'], datastore_regex=self._datastore_regex) name = self.dict_mors.get(node)['name'] - nodename = self._create_nodename(node, name) + nodename = self._create_nodename(node) _vc_state = host.VCState(self._session, nodename, self.dict_mors.get(node)['cluster_mor'], self._datastore_regex) @@ -363,22 +374,52 @@ class VMwareVCDriver(driver.ComputeDriver): deleted_nodes = (set(self._resource_keys) - set(self.dict_mors.keys())) for node in deleted_nodes: - name = self.dict_mors.get(node)['name'] - nodename = self._create_nodename(node, name) + nodename = self._create_nodename(node) del self._resources[nodename] self._resource_keys.discard(node) - def _create_nodename(self, mo_id, display_name): - """Creates the name that is stored in hypervisor_hostname column. + def _get_vcenter_uuid(self): + """Retrieves the vCenter UUID.""" - The name will be of the form similar to - domain-1000(MyCluster) - resgroup-1000(MyResourcePool) + about = self._session._call_method(nova_vim_util, 'get_about_info') + return about.instanceUuid + + def _create_nodename(self, mo_id): + """Return a nodename which uniquely describes a cluster. + + The name will be of the form: + . + e.g. + domain-26.9d51f082-58a4-4449-beed-6fd205a5726b """ - return mo_id + '(' + display_name + ')' + + return '%s.%s' % (mo_id, self._vcenter_uuid) + + def _normalize_nodename(self, nodename): + """Change I2f3b5d224cc653d0465598de0788116e71d1ca0d altered the format + of nodename to .. This function matches legacy + nodenames and translates them to the new format. + + Note that the legacy format did not contain the vCenter UUID, which we + are adding here. We can safely assume that we are adding the correct + vCenter UUID because instance.host has caused it to be scheduled to + this compute, which can only be configured with a single vCenter. + """ + + match = self.LEGACY_NODENAME.match(nodename) + + # Return it unmodified if it's not in the legacy format + if match is None: + return nodename + + mo_id = match.group(1) + return self._create_nodename(mo_id) def _get_resource_for_node(self, nodename): """Gets the resource information for the specific node.""" + + nodename = self._normalize_nodename(nodename) + resource = self._resources.get(nodename) if not resource: msg = _("The resource %s does not exist") % nodename @@ -386,26 +427,17 @@ class VMwareVCDriver(driver.ComputeDriver): return resource def _get_vmops_for_compute_node(self, nodename): - """Retrieve vmops object from mo_id stored in the node name. - - Node name is of the form domain-1000(MyCluster) - """ + """Retrieve vmops object for this node.""" resource = self._get_resource_for_node(nodename) return resource['vmops'] def _get_volumeops_for_compute_node(self, nodename): - """Retrieve vmops object from mo_id stored in the node name. - - Node name is of the form domain-1000(MyCluster) - """ + """Retrieve vmops object for this node.""" resource = self._get_resource_for_node(nodename) return resource['volumeops'] def _get_vc_state_for_compute_node(self, nodename): - """Retrieve VCState object from mo_id stored in the node name. - - Node name is of the form domain-1000(MyCluster) - """ + """Retrieve VCState object for this node.""" resource = self._get_resource_for_node(nodename) return resource['vcstate'] @@ -467,8 +499,7 @@ class VMwareVCDriver(driver.ComputeDriver): node_list = [] self._update_resources() for node in self.dict_mors.keys(): - nodename = self._create_nodename(node, - self.dict_mors.get(node)['name']) + nodename = self._create_nodename(node) node_list.append(nodename) LOG.debug("The available nodes are: %s", node_list) return node_list