VMware: Ensure compute_node.hypervisor_hostname is unique

The VMware driver currently identifies a hypervisor as:

  <mo_id>(<cluster name>)

Cluster name is not unique, and mo_id is only unique in the scope of a
single vCenter, so the above can produce duplicate names when managing
clusters with the same name in multiple vCenters. This is especially
likely to occur in lab setups.

This change alters the above format to the following, which uniquely
identifies a cluster globally:

  <mo_id>.<vCenter uuid>

Closes-bug: #1329261
Co-Authored-By: Kanagaraj Manickam <kanagaraj.manickam@hp.com>
Change-Id: I2f3b5d224cc653d0465598de0788116e71d1ca0d
This commit is contained in:
Kanagaraj Manickam 2014-06-12 16:14:23 +05:30 committed by Matthew Booth
parent 07d3c0d6d1
commit ce690113c6
3 changed files with 95 additions and 32 deletions

View File

@ -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 = {}
@ -1180,6 +1181,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

View File

@ -222,7 +222,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'
@ -2368,3 +2368,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))

View File

@ -36,6 +36,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
@ -111,6 +112,13 @@ class VMwareVCDriver(driver.ComputeDriver):
"supports_recreate": False,
}
# Legacy nodename is of the form: <mo id>(<cluster name>)
# e.g. domain-26(TestCluster)
# We assume <mo id> consists of alphanumeric, _ and -.
# We assume cluster name is everything between the first ( and the last ).
# We pull out <mo id> 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.
#
@ -161,6 +169,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 = {}
@ -320,14 +329,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)
@ -339,7 +350,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)
@ -353,22 +364,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:
<mo id>.<vcenter uuid>
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 <mo id>.<vCenter UUID>. 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
@ -376,26 +417,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']
@ -457,8 +489,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