Skip to content

Commit

Permalink
Fix VMwareVCDriver to support multi-datastore
Browse files Browse the repository at this point in the history
This fixes the issue of VCDriver selecting the first datastore of
Cluster while provisioning instances. User can provide a regex to
match with the desired datastores.

Fixes: bug #1104994

Change-Id: I319b0d8afd03e9fef00ea0c8c799790714685104
  • Loading branch information
neosab committed Aug 12, 2013
1 parent a9f1b01 commit 9579ece
Show file tree
Hide file tree
Showing 7 changed files with 93 additions and 11 deletions.
4 changes: 4 additions & 0 deletions etc/nova/nova.conf.sample
Expand Up @@ -3130,6 +3130,10 @@
# compute_driver is vmwareapi.VMwareVCDriver. (string value)
#cluster_name=<None>

# Regex to match the name of a datastore. Used only if
# compute_driver is vmwareapi.VMwareVCDriver. (string value)
#datastore_regex=<None>

# The interval used for polling of remote tasks. Used only if
# compute_driver is vmwareapi.VMwareESXDriver or
# vmwareapi.VMwareVCDriver. (floating point value)
Expand Down
9 changes: 8 additions & 1 deletion nova/tests/virt/vmwareapi/test_vmwareapi.py
Expand Up @@ -645,7 +645,7 @@ class VMwareAPIVCDriverTestCase(VMwareAPIVMTestCase):
def setUp(self):
super(VMwareAPIVCDriverTestCase, self).setUp()
self.flags(cluster_name='test_cluster',
task_poll_interval=10, group='vmware')
task_poll_interval=10, datastore_regex='.*', group='vmware')
self.flags(vnc_enabled=False)
self.conn = driver.VMwareVCDriver(None, False)

Expand All @@ -663,3 +663,10 @@ def test_get_available_resource(self):
self.assertEquals(stats['hypervisor_type'], 'VMware ESXi')
self.assertEquals(stats['hypervisor_version'], '5.0.0')
self.assertEquals(stats['hypervisor_hostname'], 'test_url')

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',
group='vmware')
self.assertRaises(exception.InvalidInput, driver.VMwareVCDriver, None)
45 changes: 45 additions & 0 deletions nova/tests/virt/vmwareapi/test_vmwareapi_vm_util.py
Expand Up @@ -17,8 +17,10 @@
# under the License.

from collections import namedtuple
import re

from nova import exception
from nova.openstack.common.gettextutils import _
from nova import test
from nova.virt.vmwareapi import fake
from nova.virt.vmwareapi import vm_util
Expand Down Expand Up @@ -51,6 +53,49 @@ def test_get_datastore_ref_and_name(self):
self.assertEquals(result[2], 1024 * 1024 * 1024 * 1024)
self.assertEquals(result[3], 1024 * 1024 * 500 * 1024)

def test_get_datastore_ref_and_name_with_regex(self):
# Test with a regex that matches with a datastore
datastore_valid_regex = re.compile("^openstack.*\d$")
fake_objects = fake.FakeRetrieveResult()
fake_objects.add_object(fake.Datastore("openstack-ds0"))
fake_objects.add_object(fake.Datastore("fake-ds0"))
fake_objects.add_object(fake.Datastore("fake-ds1"))
result = vm_util.get_datastore_ref_and_name(
fake_session(fake_objects), None, None, datastore_valid_regex)
self.assertEquals("openstack-ds0", result[1])

def test_get_datastore_ref_and_name_with_list(self):
# Test with a regex containing whitelist of datastores
datastore_valid_regex = re.compile("(openstack-ds0|openstack-ds2)")
fake_objects = fake.FakeRetrieveResult()
fake_objects.add_object(fake.Datastore("openstack-ds0"))
fake_objects.add_object(fake.Datastore("openstack-ds1"))
fake_objects.add_object(fake.Datastore("openstack-ds2"))
result = vm_util.get_datastore_ref_and_name(
fake_session(fake_objects), None, None, datastore_valid_regex)
self.assertNotEquals("openstack-ds1", result[1])

def test_get_datastore_ref_and_name_with_regex_error(self):
# Test with a regex that has no match
# Checks if code raises DatastoreNotFound with a specific message
datastore_invalid_regex = re.compile("unknown-ds")
exp_message = (_("Datastore regex %s did not match any datastores")
% datastore_invalid_regex.pattern)
fake_objects = fake.FakeRetrieveResult()
fake_objects.add_object(fake.Datastore("fake-ds0"))
fake_objects.add_object(fake.Datastore("fake-ds1"))
# assertRaisesRegExp would have been a good choice instead of
# try/catch block, but it's available only from Py 2.7.
try:
vm_util.get_datastore_ref_and_name(
fake_session(fake_objects), None, None,
datastore_invalid_regex)
except exception.DatastoreNotFound as e:
self.assertEquals(exp_message, e.args[0])
else:
self.fail("DatastoreNotFound Exception was not raised with "
"message: %s" % exp_message)

def test_get_datastore_ref_and_name_without_datastore(self):

self.assertRaises(exception.DatastoreNotFound,
Expand Down
17 changes: 16 additions & 1 deletion nova/virt/vmwareapi/driver.py
Expand Up @@ -38,6 +38,7 @@
:use_linked_clone: Whether to use linked clone (default: True)
"""

import re
import time

from eventlet import event
Expand Down Expand Up @@ -90,6 +91,11 @@
help='Name of a VMware Cluster ComputeResource. '
'Used only if compute_driver is '
'vmwareapi.VMwareVCDriver.'),
cfg.StrOpt('datastore_regex',
default=None,
help='Regex to match the name of a datastore. '
'Used only if compute_driver is '
'vmwareapi.VMwareVCDriver.'),
cfg.FloatOpt('task_poll_interval',
default=5.0,
deprecated_name='vmwareapi_task_poll_interval',
Expand Down Expand Up @@ -406,10 +412,19 @@ def __init__(self, virtapi, read_only=False, scheme="https"):
if self._cluster is None:
raise exception.NotFound(_("VMware Cluster %s is not found")
% self._cluster_name)
self._datastore_regex = None
if CONF.vmware.datastore_regex:
try:
self._datastore_regex = re.compile(CONF.vmware.datastore_regex)
except re.error:
raise exception.InvalidInput(reason=
_("Invalid Regular Expression %s")
% CONF.vmware.datastore_regex)
self._volumeops = volumeops.VMwareVolumeOps(self._session,
self._cluster)
self._vmops = vmops.VMwareVMOps(self._session, self.virtapi,
self._volumeops, self._cluster)
self._volumeops, self._cluster,
self._datastore_regex)
self._vc_state = None

@property
Expand Down
4 changes: 2 additions & 2 deletions nova/virt/vmwareapi/fake.py
Expand Up @@ -347,10 +347,10 @@ def __init__(self, **kwargs):
class Datastore(ManagedObject):
"""Datastore class."""

def __init__(self):
def __init__(self, name="fake-ds"):
super(Datastore, self).__init__("Datastore")
self.set("summary.type", "VMFS")
self.set("summary.name", "fake-ds")
self.set("summary.name", name)
self.set("summary.capacity", 1024 * 1024 * 1024 * 1024)
self.set("summary.freeSpace", 500 * 1024 * 1024 * 1024)

Expand Down
18 changes: 13 additions & 5 deletions nova/virt/vmwareapi/vm_util.py
Expand Up @@ -22,6 +22,7 @@
import copy

from nova import exception
from nova.openstack.common.gettextutils import _
from nova.virt.vmwareapi import vim_util


Expand Down Expand Up @@ -706,7 +707,7 @@ def get_host_ref(session, cluster=None):
return host_mor


def _get_datastore_ref_and_name(data_stores):
def _get_datastore_ref_and_name(data_stores, datastore_regex=None):
for elem in data_stores.objects:
ds_name = None
ds_type = None
Expand All @@ -723,10 +724,12 @@ def _get_datastore_ref_and_name(data_stores):
ds_free = prop.val
# Local storage identifier
if ds_type == "VMFS" or ds_type == "NFS":
return elem.obj, ds_name, ds_cap, ds_free
if not datastore_regex or datastore_regex.match(ds_name):
return elem.obj, ds_name, ds_cap, ds_free


def get_datastore_ref_and_name(session, cluster=None, host=None):
def get_datastore_ref_and_name(session, cluster=None, host=None,
datastore_regex=None):
"""Get the datastore list and choose the first local storage."""
if cluster is None and host is None:
data_stores = session._call_method(vim_util, "get_objects",
Expand Down Expand Up @@ -755,7 +758,7 @@ def get_datastore_ref_and_name(session, cluster=None, host=None):

while data_stores:
token = _get_token(data_stores)
results = _get_datastore_ref_and_name(data_stores)
results = _get_datastore_ref_and_name(data_stores, datastore_regex)
if results:
if token:
session._call_method(vim_util,
Expand All @@ -767,5 +770,10 @@ def get_datastore_ref_and_name(session, cluster=None, host=None):
"continue_to_get_objects",
token)
else:
raise exception.DatastoreNotFound()
if datastore_regex:
raise exception.DatastoreNotFound(
_("Datastore regex %s did not match any datastores")
% datastore_regex.pattern)
else:
raise exception.DatastoreNotFound()
raise exception.DatastoreNotFound()
7 changes: 5 additions & 2 deletions nova/virt/vmwareapi/vmops.py
Expand Up @@ -76,13 +76,15 @@
class VMwareVMOps(object):
"""Management class for VM-related tasks."""

def __init__(self, session, virtapi, volumeops, cluster=None):
def __init__(self, session, virtapi, volumeops, cluster=None,
datastore_regex=None):
"""Initializer."""
self.conductor_api = conductor.API()
self._session = session
self._virtapi = virtapi
self._volumeops = volumeops
self._cluster = cluster
self._datastore_regex = datastore_regex
self._instance_path_base = VMWARE_PREFIX + CONF.base_dir_name
self._default_root_device = 'vda'
self._rescue_suffix = '-rescue'
Expand Down Expand Up @@ -144,7 +146,8 @@ def spawn(self, context, instance, image_meta, network_info,

client_factory = self._session._get_vim().client.factory
service_content = self._session._get_vim().get_service_content()
ds = vm_util.get_datastore_ref_and_name(self._session, self._cluster)
ds = vm_util.get_datastore_ref_and_name(self._session, self._cluster,
datastore_regex=self._datastore_regex)
data_store_ref = ds[0]
data_store_name = ds[1]

Expand Down

0 comments on commit 9579ece

Please sign in to comment.