VMware: Multiple cluster support using single compute service
To allow a single VC driver to model multiple clusters in vCenter
as multiple nova-compute nodes. The VC driver will be configured
to represent a set of clusters as compute nodes.
For example to specify two clusters named cluster-A and cluster-B,
the nova.conf should have:
cluster_name = cluster-A
cluster_name = cluster-B
DocImpact
Change-Id: Ia5464948cc30b9b744450f9c301c4f3afaff717b
Blueprint: multiple-clusters-managed-by-one-service
This commit is contained in:
@@ -3228,7 +3228,7 @@
|
||||
#host_password=<None>
|
||||
|
||||
# Name of a VMware Cluster ComputeResource. Used only if
|
||||
# compute_driver is vmwareapi.VMwareVCDriver. (string value)
|
||||
# compute_driver is vmwareapi.VMwareVCDriver. (multi valued)
|
||||
#cluster_name=<None>
|
||||
|
||||
# Regex to match the name of a datastore. Used only if
|
||||
|
||||
@@ -35,7 +35,9 @@ class ConfigDriveTestCase(test.TestCase):
|
||||
def setUp(self):
|
||||
super(ConfigDriveTestCase, self).setUp()
|
||||
self.context = context.RequestContext('fake', 'fake', is_admin=False)
|
||||
self.flags(host_ip='test_url',
|
||||
cluster_name = 'test_cluster'
|
||||
self.flags(cluster_name=[cluster_name],
|
||||
host_ip='test_url',
|
||||
host_username='test_username',
|
||||
host_password='test_pass',
|
||||
use_linked_clone=False, group='vmware')
|
||||
@@ -50,6 +52,8 @@ class ConfigDriveTestCase(test.TestCase):
|
||||
'disk_format': 'vhd',
|
||||
'size': 512,
|
||||
}
|
||||
self.node_name = '%s(%s)' % (self.conn.dict_mors.keys()[0],
|
||||
cluster_name)
|
||||
self.test_instance = {'node': 'test_url',
|
||||
'vm_state': 'building',
|
||||
'project_id': 'fake',
|
||||
@@ -70,7 +74,8 @@ class ConfigDriveTestCase(test.TestCase):
|
||||
'scheduling',
|
||||
'reservation_id': 'r-3t8muvr0',
|
||||
'id': 1,
|
||||
'uuid': 'fake-uuid'}
|
||||
'uuid': 'fake-uuid',
|
||||
'node': self.node_name}
|
||||
|
||||
class FakeInstanceMetadata(object):
|
||||
def __init__(self, instance, content=None, extra_md=None):
|
||||
|
||||
@@ -121,7 +121,7 @@ class VMwareAPIVMTestCase(test.TestCase):
|
||||
vmwareapi_fake.reset()
|
||||
db_fakes.stub_out_db_instance_api(self.stubs)
|
||||
stubs.set_stubs(self.stubs)
|
||||
self.conn = driver.VMwareVCDriver(fake.FakeVirtAPI)
|
||||
self.conn = driver.VMwareESXDriver(fake.FakeVirtAPI)
|
||||
# NOTE(vish): none of the network plugging code is actually
|
||||
# being tested
|
||||
self.network_info = utils.get_test_network_info()
|
||||
@@ -138,7 +138,9 @@ class VMwareAPIVMTestCase(test.TestCase):
|
||||
vmwareapi_fake.cleanup()
|
||||
nova.tests.image.fake.FakeImageService_reset()
|
||||
|
||||
def _create_instance_in_the_db(self):
|
||||
def _create_instance_in_the_db(self, node=None):
|
||||
if not node:
|
||||
node = self.node_name
|
||||
values = {'name': '1',
|
||||
'id': 1,
|
||||
'uuid': "fake-uuid",
|
||||
@@ -149,27 +151,29 @@ class VMwareAPIVMTestCase(test.TestCase):
|
||||
'ramdisk_id': "1",
|
||||
'mac_address': "de:ad:be:ef:be:ef",
|
||||
'instance_type': 'm1.large',
|
||||
'node': self.node_name,
|
||||
'node': node,
|
||||
}
|
||||
self.instance = db.instance_create(None, values)
|
||||
|
||||
def _create_vm(self):
|
||||
def _create_vm(self, node=None, num_instances=1):
|
||||
"""Create and spawn the VM."""
|
||||
self._create_instance_in_the_db()
|
||||
if not node:
|
||||
node = self.node_name
|
||||
self._create_instance_in_the_db(node=node)
|
||||
self.type_data = db.flavor_get_by_name(None, 'm1.large')
|
||||
self.conn.spawn(self.context, self.instance, self.image,
|
||||
injected_files=[], admin_password=None,
|
||||
network_info=self.network_info,
|
||||
block_device_info=None)
|
||||
self._check_vm_record()
|
||||
self._check_vm_record(num_instances=num_instances)
|
||||
|
||||
def _check_vm_record(self):
|
||||
def _check_vm_record(self, num_instances=1):
|
||||
"""
|
||||
Check if the spawned VM's properties correspond to the instance in
|
||||
the db.
|
||||
"""
|
||||
instances = self.conn.list_instances()
|
||||
self.assertEquals(len(instances), 1)
|
||||
self.assertEquals(len(instances), num_instances)
|
||||
|
||||
# Get Nova record for VM
|
||||
vm_info = self.conn.get_info({'uuid': 'fake-uuid',
|
||||
@@ -473,9 +477,7 @@ class VMwareAPIVMTestCase(test.TestCase):
|
||||
|
||||
def _test_finish_migration(self, power_on):
|
||||
"""
|
||||
Tests the finish_migration method on vmops via the
|
||||
VMwareVCDriver. Results are checked against whether or not
|
||||
the underlying instance should have been powered on.
|
||||
Tests the finish_migration method on vmops
|
||||
"""
|
||||
|
||||
self.power_on_called = False
|
||||
@@ -504,22 +506,21 @@ class VMwareAPIVMTestCase(test.TestCase):
|
||||
disk_info=None,
|
||||
network_info=None,
|
||||
block_device_info=None,
|
||||
resize_instance=False,
|
||||
image_meta=None,
|
||||
power_on=power_on)
|
||||
# verify the results
|
||||
self.assertEquals(power_on, self.power_on_called)
|
||||
|
||||
def test_finish_migration_power_on(self):
|
||||
self._test_finish_migration(power_on=True)
|
||||
self.assertRaises(NotImplementedError,
|
||||
self._test_finish_migration, power_on=True)
|
||||
|
||||
def test_finish_migration_power_off(self):
|
||||
self._test_finish_migration(power_on=False)
|
||||
self.assertRaises(NotImplementedError,
|
||||
self._test_finish_migration, power_on=False)
|
||||
|
||||
def _test_finish_revert_migration(self, power_on):
|
||||
"""
|
||||
Tests the finish_revert_migration method on vmops via the
|
||||
VMwareVCDriver. Results are checked against whether or not
|
||||
the underlying instance should have been powered on.
|
||||
Tests the finish_revert_migration method on vmops
|
||||
"""
|
||||
|
||||
# setup the test instance in the database
|
||||
@@ -564,14 +565,14 @@ class VMwareAPIVMTestCase(test.TestCase):
|
||||
self.conn.finish_revert_migration(instance=self.instance,
|
||||
network_info=None,
|
||||
power_on=power_on)
|
||||
# verify the results
|
||||
self.assertEquals(power_on, self.power_on_called)
|
||||
|
||||
def test_finish_revert_migration_power_on(self):
|
||||
self._test_finish_revert_migration(power_on=True)
|
||||
self.assertRaises(NotImplementedError,
|
||||
self._test_finish_migration, power_on=True)
|
||||
|
||||
def test_finish_revert_migration_power_off(self):
|
||||
self._test_finish_revert_migration(power_on=False)
|
||||
self.assertRaises(NotImplementedError,
|
||||
self._test_finish_migration, power_on=False)
|
||||
|
||||
def test_diagnostics_non_existent_vm(self):
|
||||
self._create_instance_in_the_db()
|
||||
@@ -596,7 +597,7 @@ class VMwareAPIVMTestCase(test.TestCase):
|
||||
fake_vm = vmwareapi_fake._get_objects("VirtualMachine").objects[0]
|
||||
fake_vm_id = int(fake_vm.obj.value.replace('vm-', ''))
|
||||
vnc_dict = self.conn.get_vnc_console(self.instance)
|
||||
self.assertEquals(vnc_dict['host'], "ha-host")
|
||||
self.assertEquals(vnc_dict['host'], 'test_url')
|
||||
self.assertEquals(vnc_dict['port'], cfg.CONF.vmware.vnc_port +
|
||||
fake_vm_id % cfg.CONF.vmware.vnc_port_total)
|
||||
|
||||
@@ -804,10 +805,18 @@ class VMwareAPIVCDriverTestCase(VMwareAPIVMTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(VMwareAPIVCDriverTestCase, self).setUp()
|
||||
self.flags(cluster_name='test_cluster',
|
||||
cluster_name = 'test_cluster'
|
||||
cluster_name2 = 'test_cluster2'
|
||||
self.flags(cluster_name=[cluster_name, cluster_name2],
|
||||
task_poll_interval=10, datastore_regex='.*', group='vmware')
|
||||
self.flags(vnc_enabled=False)
|
||||
self.conn = driver.VMwareVCDriver(None, False)
|
||||
node = self.conn._resources.keys()[0]
|
||||
self.node_name = '%s(%s)' % (node,
|
||||
self.conn._resources[node]['name'])
|
||||
node = self.conn._resources.keys()[1]
|
||||
self.node_name2 = '%s(%s)' % (node,
|
||||
self.conn._resources[node]['name'])
|
||||
|
||||
def tearDown(self):
|
||||
super(VMwareAPIVCDriverTestCase, self).tearDown()
|
||||
@@ -822,13 +831,51 @@ class VMwareAPIVCDriverTestCase(VMwareAPIVMTestCase):
|
||||
self.assertEquals(stats['memory_mb_used'], 1024 - 524)
|
||||
self.assertEquals(stats['hypervisor_type'], 'VMware ESXi')
|
||||
self.assertEquals(stats['hypervisor_version'], '5.0.0')
|
||||
self.assertEquals(stats['hypervisor_hostname'], 'test_url')
|
||||
self.assertEquals(stats['hypervisor_hostname'], self.node_name)
|
||||
self.assertEquals(stats['supported_instances'],
|
||||
'[["i686", "vmware", "hvm"], ["x86_64", "vmware", "hvm"]]')
|
||||
|
||||
def test_invalid_datastore_regex(self):
|
||||
# Tests if we raise an exception for Invalid Regular Expression in
|
||||
# vmware_datastore_regex
|
||||
self.flags(cluster_name='test_cluster', datastore_regex='fake-ds(01',
|
||||
self.flags(cluster_name=['test_cluster'], datastore_regex='fake-ds(01',
|
||||
group='vmware')
|
||||
self.assertRaises(exception.InvalidInput, driver.VMwareVCDriver, None)
|
||||
|
||||
def test_get_available_nodes(self):
|
||||
nodelist = self.conn.get_available_nodes()
|
||||
self.assertEquals(nodelist, [self.node_name, self.node_name2])
|
||||
|
||||
def test_spawn_multiple_node(self):
|
||||
self._create_vm(node=self.node_name, num_instances=1)
|
||||
info = self.conn.get_info({'uuid': 'fake-uuid'})
|
||||
self._check_vm_info(info, power_state.RUNNING)
|
||||
self._create_vm(node=self.node_name2, num_instances=2)
|
||||
info = self.conn.get_info({'uuid': 'fake-uuid'})
|
||||
self._check_vm_info(info, power_state.RUNNING)
|
||||
|
||||
def test_finish_migration_power_on(self):
|
||||
self._test_finish_migration(power_on=True)
|
||||
self.assertEquals(True, self.power_on_called)
|
||||
|
||||
def test_finish_migration_power_off(self):
|
||||
self._test_finish_migration(power_on=False)
|
||||
self.assertEquals(False, self.power_on_called)
|
||||
|
||||
def test_finish_revert_migration_power_on(self):
|
||||
self._test_finish_revert_migration(power_on=True)
|
||||
self.assertEquals(True, self.power_on_called)
|
||||
|
||||
def test_finish_revert_migration_power_off(self):
|
||||
self._test_finish_revert_migration(power_on=False)
|
||||
self.assertEquals(False, self.power_on_called)
|
||||
|
||||
def test_get_vnc_console(self):
|
||||
self._create_instance_in_the_db()
|
||||
self._create_vm()
|
||||
fake_vm = vmwareapi_fake._get_objects("VirtualMachine").objects[0]
|
||||
fake_vm_id = int(fake_vm.obj.value.replace('vm-', ''))
|
||||
vnc_dict = self.conn.get_vnc_console(self.instance)
|
||||
self.assertEquals(vnc_dict['host'], "ha-host")
|
||||
self.assertEquals(vnc_dict['port'], cfg.CONF.vmware.vnc_port +
|
||||
fake_vm_id % cfg.CONF.vmware.vnc_port_total)
|
||||
|
||||
@@ -64,12 +64,11 @@ vmwareapi_opts = [
|
||||
'Used only if compute_driver is '
|
||||
'vmwareapi.VMwareESXDriver or vmwareapi.VMwareVCDriver.',
|
||||
secret=True),
|
||||
cfg.StrOpt('cluster_name',
|
||||
cfg.MultiStrOpt('cluster_name',
|
||||
deprecated_name='vmwareapi_cluster_name',
|
||||
deprecated_group='DEFAULT',
|
||||
help='Name of a VMware Cluster ComputeResource. '
|
||||
'Used only if compute_driver is '
|
||||
'vmwareapi.VMwareVCDriver.'),
|
||||
help='Name of a VMware Cluster ComputeResource. Used only if '
|
||||
'compute_driver is vmwareapi.VMwareVCDriver.'),
|
||||
cfg.StrOpt('datastore_regex',
|
||||
help='Regex to match the name of a datastore. '
|
||||
'Used only if compute_driver is '
|
||||
@@ -302,19 +301,8 @@ class VMwareESXDriver(driver.ComputeDriver):
|
||||
'username': CONF.vmware.host_username,
|
||||
'password': CONF.vmware.host_password}
|
||||
|
||||
def get_available_resource(self, nodename):
|
||||
"""Retrieve resource information.
|
||||
|
||||
This method is called when nova-compute launches, and
|
||||
as part of a periodic task that records the results in the DB.
|
||||
|
||||
:returns: dictionary describing resources
|
||||
|
||||
"""
|
||||
host_stats = self.get_host_stats(refresh=True)
|
||||
|
||||
# Updating host information
|
||||
dic = {'vcpus': host_stats["vcpus"],
|
||||
def _get_available_resources(self, host_stats):
|
||||
return {'vcpus': host_stats['vcpus'],
|
||||
'memory_mb': host_stats['host_memory_total'],
|
||||
'local_gb': host_stats['disk_total'],
|
||||
'vcpus_used': 0,
|
||||
@@ -329,7 +317,19 @@ class VMwareESXDriver(driver.ComputeDriver):
|
||||
host_stats['supported_instances']),
|
||||
}
|
||||
|
||||
return dic
|
||||
def get_available_resource(self, nodename):
|
||||
"""Retrieve resource information.
|
||||
|
||||
This method is called when nova-compute launches, and
|
||||
as part of a periodic task that records the results in the DB.
|
||||
|
||||
:returns: dictionary describing resources
|
||||
|
||||
"""
|
||||
host_stats = self.get_host_stats(refresh=True)
|
||||
|
||||
# Updating host information
|
||||
return self._get_available_resources(host_stats)
|
||||
|
||||
def update_host_status(self):
|
||||
"""Update the status info of the host, and return those values
|
||||
@@ -384,15 +384,24 @@ class VMwareVCDriver(VMwareESXDriver):
|
||||
|
||||
def __init__(self, virtapi, read_only=False, scheme="https"):
|
||||
super(VMwareVCDriver, self).__init__(virtapi)
|
||||
self._cluster_name = CONF.vmware.cluster_name
|
||||
if not self._cluster_name:
|
||||
self._cluster = None
|
||||
else:
|
||||
self._cluster = vm_util.get_cluster_ref_from_name(
|
||||
self._session, self._cluster_name)
|
||||
if self._cluster is None:
|
||||
raise exception.NotFound(_("VMware Cluster %s is not found")
|
||||
% self._cluster_name)
|
||||
|
||||
# Get the list of clusters to be used
|
||||
self._cluster_names = CONF.vmware.cluster_name
|
||||
self.dict_mors = vm_util.get_all_cluster_refs_by_name(self._session,
|
||||
self._cluster_names)
|
||||
if not self.dict_mors:
|
||||
raise exception.NotFound(_("All clusters specified %s were not"
|
||||
" found in the vCenter")
|
||||
% self._cluster_names)
|
||||
|
||||
# Check if there are any clusters that were specified in the nova.conf
|
||||
# but are not in the vCenter, for missing clusters log a warning.
|
||||
clusters_found = [v.get('name') for k, v in self.dict_mors.iteritems()]
|
||||
missing_clusters = set(self._cluster_names) - set(clusters_found)
|
||||
if missing_clusters:
|
||||
LOG.warn(_("The following clusters could not be found in the"
|
||||
" vCenter %s") % list(missing_clusters))
|
||||
|
||||
self._datastore_regex = None
|
||||
if CONF.vmware.datastore_regex:
|
||||
try:
|
||||
@@ -401,21 +410,18 @@ class VMwareVCDriver(VMwareESXDriver):
|
||||
raise exception.InvalidInput(reason=
|
||||
_("Invalid Regular Expression %s")
|
||||
% CONF.vmware.datastore_regex)
|
||||
self._volumeops = volumeops.VMwareVolumeOps(self._session,
|
||||
cluster=self._cluster,
|
||||
vc_support=True)
|
||||
self._vmops = vmops.VMwareVMOps(self._session, self.virtapi,
|
||||
self._volumeops, self._cluster,
|
||||
self._datastore_regex)
|
||||
self._vc_state = None
|
||||
# The _resources is used to maintain the vmops, volumeops and vcstate
|
||||
# objects per cluster
|
||||
self._resources = {}
|
||||
self._virtapi = virtapi
|
||||
self._update_resources()
|
||||
|
||||
@property
|
||||
def host_state(self):
|
||||
if not self._vc_state:
|
||||
self._vc_state = host.VCState(self._session,
|
||||
self._host_ip,
|
||||
self._cluster)
|
||||
return self._vc_state
|
||||
# The following initialization is necessary since the base class does
|
||||
# not use VC state.
|
||||
first_cluster = self._resources.keys()[0]
|
||||
self._vmops = self._resources.get(first_cluster).get('vmops')
|
||||
self._volumeops = self._resources.get(first_cluster).get('volumeops')
|
||||
self._vc_state = self._resources.get(first_cluster).get('vcstate')
|
||||
|
||||
def migrate_disk_and_power_off(self, context, instance, dest,
|
||||
instance_type, network_info,
|
||||
@@ -461,6 +467,161 @@ class VMwareVCDriver(VMwareESXDriver):
|
||||
# itself. You must talk to the VNC host underneath vCenter.
|
||||
return self._vmops.get_vnc_console_vcenter(instance)
|
||||
|
||||
def _update_resources(self):
|
||||
"""This method creates a dictionary of VMOps, VolumeOps and VCState.
|
||||
|
||||
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},
|
||||
}
|
||||
"""
|
||||
|
||||
# TODO(kirankv) we can avoid creating multiple vmops and volumeops
|
||||
# if we make them utility class so that cluster is passed as a
|
||||
# parameter to the method
|
||||
added_nodes = set(self.dict_mors.keys()) - set(self._resources.keys())
|
||||
for node in added_nodes:
|
||||
_volumeops = volumeops.VMwareVolumeOps(self._session,
|
||||
self.dict_mors[node]['cluster_mor'],
|
||||
vc_support=True)
|
||||
_vmops = vmops.VMwareVMOps(self._session, self._virtapi,
|
||||
_volumeops,
|
||||
self.dict_mors[node]['cluster_mor'])
|
||||
name = self.dict_mors.get(node)['name']
|
||||
_vc_state = host.VCState(self._session,
|
||||
self._create_nodename(node, name),
|
||||
self.dict_mors.get(node)['cluster_mor'])
|
||||
self._resources[node] = {'vmops': _vmops,
|
||||
'volumeops': _volumeops,
|
||||
'vcstate': _vc_state,
|
||||
'name': name,
|
||||
}
|
||||
deleted_nodes = (set(self._resources.keys()) -
|
||||
set(self.dict_mors.keys()))
|
||||
for node in deleted_nodes:
|
||||
LOG.debug(_("Removing node %s since its removed from"
|
||||
" nova.conf") % node)
|
||||
del self._resources[node]
|
||||
|
||||
def _create_nodename(self, mo_id, display_name):
|
||||
"""Creates the name that is stored in hypervisor_hostname column.
|
||||
|
||||
The name will be of the form similar to
|
||||
domain-1000(MyCluster)
|
||||
resgroup-1000(MyResourcePool)
|
||||
"""
|
||||
return mo_id + '(' + display_name + ')'
|
||||
|
||||
def _get_mo_id(self, nodename):
|
||||
return nodename.partition('(')[0]
|
||||
|
||||
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)
|
||||
"""
|
||||
return self._resources.get(self._get_mo_id(nodename)).get('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)
|
||||
"""
|
||||
return self._resources.get(self._get_mo_id(nodename)).get('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)
|
||||
"""
|
||||
return self._resources.get(self._get_mo_id(nodename)).get('vcstate')
|
||||
|
||||
def get_available_resource(self, nodename):
|
||||
"""Retrieve resource info.
|
||||
|
||||
This method is called when nova-compute launches, and
|
||||
as part of a periodic task.
|
||||
|
||||
:returns: dictionary describing resources
|
||||
|
||||
"""
|
||||
stats_dict = {}
|
||||
vc_state = self._get_vc_state_for_compute_node(nodename)
|
||||
if vc_state:
|
||||
host_stats = vc_state.get_host_stats(refresh=True)
|
||||
|
||||
# Updating host information
|
||||
stats_dict = self._get_available_resources(host_stats)
|
||||
|
||||
else:
|
||||
LOG.info(_("Invalid cluster or resource pool"
|
||||
" name : %s") % nodename)
|
||||
|
||||
return stats_dict
|
||||
|
||||
def get_available_nodes(self):
|
||||
"""Returns nodenames of all nodes managed by the compute service.
|
||||
|
||||
This method is for multi compute-nodes support. If a driver supports
|
||||
multi compute-nodes, this method returns a list of nodenames managed
|
||||
by the service. Otherwise, this method should return
|
||||
[hypervisor_hostname].
|
||||
"""
|
||||
self.dict_mors = vm_util.get_all_cluster_refs_by_name(
|
||||
self._session,
|
||||
CONF.vmware.cluster_name)
|
||||
nodes = self.dict_mors.keys()
|
||||
node_list = []
|
||||
self._update_resources()
|
||||
for node in self.dict_mors.keys():
|
||||
nodename = self._create_nodename(node,
|
||||
self.dict_mors.get(node)['name'])
|
||||
node_list.append(nodename)
|
||||
LOG.debug(_("The available nodes are: %s") % node_list)
|
||||
return node_list
|
||||
|
||||
def get_host_stats(self, refresh=True):
|
||||
"""Return currently known host stats."""
|
||||
stats_list = []
|
||||
nodes = self.get_available_nodes()
|
||||
for node in nodes:
|
||||
stats_list.append(self.get_available_resource(node))
|
||||
return stats_list
|
||||
|
||||
def spawn(self, context, instance, image_meta, injected_files,
|
||||
admin_password, network_info=None, block_device_info=None):
|
||||
"""Create VM instance."""
|
||||
_vmops = self._get_vmops_for_compute_node(instance['node'])
|
||||
_vmops.spawn(context, instance, image_meta, injected_files,
|
||||
admin_password, network_info, block_device_info)
|
||||
|
||||
def attach_volume(self, connection_info, instance, mountpoint):
|
||||
"""Attach volume storage to VM instance."""
|
||||
_volumeops = self._get_volumeops_for_compute_node(instance['node'])
|
||||
return _volumeops.attach_volume(connection_info,
|
||||
instance,
|
||||
mountpoint)
|
||||
|
||||
def detach_volume(self, connection_info, instance, mountpoint):
|
||||
"""Detach volume storage to VM instance."""
|
||||
_volumeops = self._get_volumeops_for_compute_node(instance['node'])
|
||||
return _volumeops.detach_volume(connection_info,
|
||||
instance,
|
||||
mountpoint)
|
||||
|
||||
def get_volume_connector(self, instance):
|
||||
"""Return volume connector information."""
|
||||
_volumeops = self._get_volumeops_for_compute_node(instance['node'])
|
||||
return _volumeops.get_volume_connector(instance)
|
||||
|
||||
|
||||
class VMwareAPISession(object):
|
||||
"""
|
||||
|
||||
@@ -63,7 +63,8 @@ def reset():
|
||||
create_datacenter()
|
||||
create_datastore()
|
||||
create_res_pool()
|
||||
create_cluster()
|
||||
create_cluster('test_cluster')
|
||||
create_cluster('test_cluster2')
|
||||
|
||||
|
||||
def cleanup():
|
||||
@@ -350,9 +351,50 @@ class Network(ManagedObject):
|
||||
class ResourcePool(ManagedObject):
|
||||
"""Resource Pool class."""
|
||||
|
||||
def __init__(self, name="test-rpool"):
|
||||
def __init__(self, name="test-rpool", value="resgroup-test"):
|
||||
super(ResourcePool, self).__init__("rp")
|
||||
self.set("name", name)
|
||||
self.set("name", "test_ResPool")
|
||||
summary = DataObject()
|
||||
runtime = DataObject()
|
||||
config = DataObject()
|
||||
memory = DataObject()
|
||||
cpu = DataObject()
|
||||
|
||||
memoryAllocation = DataObject()
|
||||
cpuAllocation = DataObject()
|
||||
|
||||
memory.maxUsage = 1000 * 1024 * 1024
|
||||
memory.overallUsage = 500 * 1024 * 1024
|
||||
cpu.maxUsage = 10000
|
||||
cpu.overallUsage = 1000
|
||||
runtime.cpu = cpu
|
||||
runtime.memory = memory
|
||||
summary.runtime = runtime
|
||||
cpuAllocation.limit = 10000
|
||||
memoryAllocation.limit = 1024
|
||||
memoryAllocation.reservation = 1024
|
||||
config.memoryAllocation = memoryAllocation
|
||||
config.cpuAllocation = cpuAllocation
|
||||
self.set("summary", summary)
|
||||
self.set("config", config)
|
||||
parent = ManagedObjectReference(value=value,
|
||||
name=name)
|
||||
owner = ManagedObjectReference(value=value,
|
||||
name=name)
|
||||
self.set("parent", parent)
|
||||
self.set("owner", owner)
|
||||
|
||||
|
||||
class DatastoreHostMount(DataObject):
|
||||
def __init__(self, value='host-100'):
|
||||
super(DatastoreHostMount, self).__init__()
|
||||
host_ref = (_db_content["HostSystem"]
|
||||
[_db_content["HostSystem"].keys()[0]].obj)
|
||||
host_system = DataObject()
|
||||
host_system.ManagedObjectReference = [host_ref]
|
||||
host_system.value = value
|
||||
self.key = host_system
|
||||
|
||||
|
||||
class ClusterComputeResource(ManagedObject):
|
||||
@@ -373,6 +415,7 @@ class ClusterComputeResource(ManagedObject):
|
||||
summary.totalMemory = 0
|
||||
summary.effectiveMemory = 0
|
||||
self.set("summary", summary)
|
||||
self.set("summary.effectiveCpu", 10000)
|
||||
|
||||
def _add_resource_pool(self, r_pool):
|
||||
if r_pool:
|
||||
@@ -477,6 +520,7 @@ class HostSystem(ManagedObject):
|
||||
hardware.numCpuThreads = 16
|
||||
hardware.vendor = "Intel"
|
||||
hardware.cpuModel = "Intel(R) Xeon(R)"
|
||||
hardware.uuid = "host-uuid"
|
||||
hardware.memorySize = 1024 * 1024 * 1024
|
||||
summary.hardware = hardware
|
||||
|
||||
@@ -497,7 +541,9 @@ class HostSystem(ManagedObject):
|
||||
net_info_pnic.PhysicalNic = [pnic_do]
|
||||
|
||||
self.set("summary", summary)
|
||||
self.set("summary.hardware", hardware)
|
||||
self.set("capability.maxHostSupportedVcpus", 600)
|
||||
self.set("summary.runtime.inMaintenanceMode", False)
|
||||
self.set("runtime.connectionState", "connected")
|
||||
self.set("config.network.pnic", net_info_pnic)
|
||||
self.set("connected", connected)
|
||||
|
||||
@@ -623,10 +669,11 @@ def create_network():
|
||||
_create_object('Network', network)
|
||||
|
||||
|
||||
def create_cluster():
|
||||
cluster = ClusterComputeResource()
|
||||
def create_cluster(name):
|
||||
cluster = ClusterComputeResource(name=name)
|
||||
cluster._add_host(_get_object_refs("HostSystem")[0])
|
||||
cluster._add_datastore(_get_object_refs("Datastore")[0])
|
||||
cluster._add_resource_pool(_get_object_refs("ResourcePool")[0])
|
||||
_create_object('ClusterComputeResource', cluster)
|
||||
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright (c) 2013 Hewlett-Packard Development Company, L.P.
|
||||
# Copyright (c) 2012 VMware, Inc.
|
||||
# Copyright (c) 2011 Citrix Systems, Inc.
|
||||
# Copyright 2011 OpenStack Foundation
|
||||
@@ -23,9 +24,13 @@ import copy
|
||||
|
||||
from nova import exception
|
||||
from nova.openstack.common.gettextutils import _
|
||||
from nova.openstack.common import log as logging
|
||||
from nova.virt.vmwareapi import vim_util
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def build_datastore_path(datastore_name, path):
|
||||
"""Build the datastore compliant path."""
|
||||
return "[%s] %s" % (datastore_name, path)
|
||||
@@ -882,3 +887,128 @@ def get_vmdk_volume_disk(hardware_devices):
|
||||
for device in hardware_devices:
|
||||
if (device.__class__.__name__ == "VirtualDisk"):
|
||||
return device
|
||||
|
||||
|
||||
def get_res_pool_ref(session, cluster, node_mo_id):
|
||||
"""Get the resource pool."""
|
||||
if cluster is None:
|
||||
# With no cluster named, use the root resource pool.
|
||||
results = session._call_method(vim_util, "get_objects",
|
||||
"ResourcePool")
|
||||
_cancel_retrieve_if_necessary(session, results)
|
||||
# The 0th resource pool is always the root resource pool on both ESX
|
||||
# and vCenter.
|
||||
res_pool_ref = results.objects[0].obj
|
||||
else:
|
||||
if cluster.value == node_mo_id:
|
||||
# Get the root resource pool of the cluster
|
||||
res_pool_ref = session._call_method(vim_util,
|
||||
"get_dynamic_property",
|
||||
cluster,
|
||||
"ClusterComputeResource",
|
||||
"resourcePool")
|
||||
|
||||
return res_pool_ref
|
||||
|
||||
|
||||
def get_all_cluster_mors(session):
|
||||
"""Get all the clusters in the vCenter."""
|
||||
try:
|
||||
results = session._call_method(vim_util, "get_objects",
|
||||
"ClusterComputeResource", ["name"])
|
||||
_cancel_retrieve_if_necessary(session, results)
|
||||
return results.objects
|
||||
|
||||
except Exception as excep:
|
||||
LOG.warn(_("Failed to get cluster references %s") % excep)
|
||||
|
||||
|
||||
def get_all_res_pool_mors(session):
|
||||
"""Get all the resource pools in the vCenter."""
|
||||
try:
|
||||
results = session._call_method(vim_util, "get_objects",
|
||||
"ResourcePool")
|
||||
|
||||
_cancel_retrieve_if_necessary(session, results)
|
||||
return results.objects
|
||||
except Exception as excep:
|
||||
LOG.warn(_("Failed to get resource pool references " "%s") % excep)
|
||||
|
||||
|
||||
def get_dynamic_property_mor(session, mor_ref, attribute):
|
||||
"""Get the value of an attribute for a given managed object."""
|
||||
return session._call_method(vim_util, "get_dynamic_property",
|
||||
mor_ref, mor_ref._type, attribute)
|
||||
|
||||
|
||||
def find_entity_mor(entity_list, entity_name):
|
||||
"""Returns managed object ref for given cluster or resource pool name."""
|
||||
return [mor for mor in entity_list if mor.propSet[0].val == entity_name]
|
||||
|
||||
|
||||
def get_all_cluster_refs_by_name(session, path_list):
|
||||
"""Get reference to the Cluster, ResourcePool with the path specified.
|
||||
|
||||
The path is the display name. This can be the full path as well.
|
||||
The input will have the list of clusters and resource pool names
|
||||
"""
|
||||
cls = get_all_cluster_mors(session)
|
||||
res = get_all_res_pool_mors(session)
|
||||
path_list = [path.strip() for path in path_list]
|
||||
list_obj = []
|
||||
for entity_path in path_list:
|
||||
# entity_path could be unique cluster and/or resource-pool name
|
||||
res_mor = find_entity_mor(res, entity_path)
|
||||
cls_mor = find_entity_mor(cls, entity_path)
|
||||
cls_mor.extend(res_mor)
|
||||
for mor in cls_mor:
|
||||
list_obj.append((mor.obj, mor.propSet[0].val))
|
||||
return get_dict_mor(session, list_obj)
|
||||
|
||||
|
||||
def get_dict_mor(session, list_obj):
|
||||
"""The input is a list of objects in the form
|
||||
(manage_object,display_name)
|
||||
The managed object will be in the form
|
||||
{ value = "domain-1002", _type = "ClusterComputeResource" }
|
||||
|
||||
Output data format:
|
||||
dict_mors = {
|
||||
'respool-1001': { 'cluster_mor': clusterMor,
|
||||
'res_pool_mor': resourcePoolMor,
|
||||
'name': display_name },
|
||||
'domain-1002': { 'cluster_mor': clusterMor,
|
||||
'res_pool_mor': resourcePoolMor,
|
||||
'name': display_name },
|
||||
}
|
||||
"""
|
||||
dict_mors = {}
|
||||
for obj_ref, path in list_obj:
|
||||
if obj_ref._type == "ResourcePool":
|
||||
# Get owner cluster-ref mor
|
||||
cluster_ref = get_dynamic_property_mor(session, obj_ref, "owner")
|
||||
dict_mors[obj_ref.value] = {'cluster_mor': cluster_ref,
|
||||
'res_pool_mor': obj_ref,
|
||||
'name': path,
|
||||
}
|
||||
else:
|
||||
# Get default resource pool of the cluster
|
||||
res_pool_ref = get_dynamic_property_mor(session,
|
||||
obj_ref, "resourcePool")
|
||||
dict_mors[obj_ref.value] = {'cluster_mor': obj_ref,
|
||||
'res_pool_mor': res_pool_ref,
|
||||
'name': path,
|
||||
}
|
||||
return dict_mors
|
||||
|
||||
|
||||
def get_mo_id_from_instance(instance):
|
||||
"""Return the managed object ID from the instance.
|
||||
|
||||
The instance['node'] will have the hypervisor_hostname field of the
|
||||
compute node on which the instance exists or will be provisioned.
|
||||
This will be of the form
|
||||
'respool-1001(MyResPoolName)'
|
||||
'domain-1001(MyClusterName)'
|
||||
"""
|
||||
return instance['node'].partition('(')[0]
|
||||
|
||||
@@ -180,7 +180,9 @@ class VMwareVMOps(object):
|
||||
disk_type, vif_model) = _get_image_properties()
|
||||
|
||||
vm_folder_ref = self._get_vmfolder_ref()
|
||||
res_pool_ref = self._get_res_pool_ref()
|
||||
node_mo_id = vm_util.get_mo_id_from_instance(instance)
|
||||
res_pool_ref = vm_util.get_res_pool_ref(self._session,
|
||||
self._cluster, node_mo_id)
|
||||
|
||||
def _get_vif_infos():
|
||||
vif_infos = []
|
||||
|
||||
Reference in New Issue
Block a user