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

(cherry picked from commit 9579ece)

Conflicts:

        etc/nova/nova.conf.sample
        nova/tests/test_vmwareapi.py
        nova/tests/test_vmwareapi_vm_util.py
        nova/virt/vmwareapi/driver.py
        nova/virt/vmwareapi/vm_util.py
        nova/virt/vmwareapi/vmops.py

Change-Id: I319b0d8afd03e9fef00ea0c8c799790714685104
  • Loading branch information
neosab authored and gkotton committed Aug 19, 2013
1 parent 37e3b55 commit dbd2afb
Show file tree
Hide file tree
Showing 6 changed files with 93 additions and 6 deletions.
12 changes: 12 additions & 0 deletions nova/tests/test_vmwareapi.py
Expand Up @@ -65,6 +65,7 @@ def setUp(self):
vmwareapi_host_password='test_pass',
vnc_enabled=False,
use_linked_clone=False)
self.flags(datastore_regex='.*', group='vmware')
self.user_id = 'fake'
self.project_id = 'fake'
self.context = context.RequestContext(self.user_id, self.project_id)
Expand Down Expand Up @@ -447,3 +448,14 @@ def test_host_maintenance_on(self):

def test_host_maintenance_off(self):
self._test_host_action(self.conn.host_maintenance_mode, False)

def test_invalid_datastore_regex(self):
# Tests if we raise an exception for Invalid Regular Expression in
# vmware_datastore_regex
self.flags(vmwareapi_host_ip='test_url',
vmwareapi_host_username='test_username',
vmwareapi_host_password='test_pass',
vnc_enabled=False,
use_linked_clone=False)
self.flags(datastore_regex='fake-ds(01', group='vmware')
self.assertRaises(exception.InvalidInput, driver.VMwareVCDriver, None)
41 changes: 41 additions & 0 deletions nova/tests/test_vmwareapi_vm_util.py
Expand Up @@ -17,6 +17,7 @@
# under the License.

import collections
import re

from nova import exception
from nova import test
Expand Down Expand Up @@ -49,6 +50,46 @@ 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.Datastore("openstack-ds0"),
fake.Datastore("fake-ds0"),
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.Datastore("openstack-ds0"),
fake.Datastore("fake-ds0"),
fake.Datastore("fake-ds1")]
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.Datastore("fake-ds0"),
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
24 changes: 24 additions & 0 deletions nova/virt/vmwareapi/driver.py
Expand Up @@ -37,6 +37,7 @@
:use_linked_clone: Whether to use linked clone (default: True)
"""

import re
import time

from eventlet import event
Expand Down Expand Up @@ -107,8 +108,17 @@
help='Whether to use linked clone'),
]

vmware_opts = [
cfg.StrOpt('datastore_regex',
default=None,
help='Regex to match the name of a datastore. '
'Used only if compute_driver is '
'vmwareapi.VMwareVCDriver.'),
]

CONF = cfg.CONF
CONF.register_opts(vmwareapi_opts)
CONF.register_opts(vmware_opts, 'vmware')

TIME_BETWEEN_API_CALL_RETRIES = 2.0

Expand Down Expand Up @@ -359,6 +369,20 @@ 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_name)
self._vmops = vmops.VMwareVMOps(self._session, self.virtapi,
self._volumeops, self._cluster_name,
self._datastore_regex)
self._vc_state = None

@property
Expand Down
4 changes: 2 additions & 2 deletions nova/virt/vmwareapi/fake.py
Expand Up @@ -310,10 +310,10 @@ def __init__(self):
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
11 changes: 9 additions & 2 deletions nova/virt/vmwareapi/vm_util.py
Expand Up @@ -656,7 +656,8 @@ def get_host_ref(session, cluster=None):
return host_mor


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 @@ -698,6 +699,12 @@ def get_datastore_ref_and_name(session, cluster=None, host=None):
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

if datastore_regex:
raise exception.DatastoreNotFound(
_("Datastore regex %s did not match any datastores")
% datastore_regex.pattern)
else:
raise exception.DatastoreNotFound()
7 changes: 5 additions & 2 deletions nova/virt/vmwareapi/vmops.py
Expand Up @@ -74,7 +74,8 @@
class VMwareVMOps(object):
"""Management class for VM-related tasks."""

def __init__(self, session, virtapi, volumeops, cluster_name=None):
def __init__(self, session, virtapi, volumeops, cluster_name=None,
datastore_regex=None):
"""Initializer."""
self.compute_api = compute.API()
self._session = session
Expand All @@ -85,6 +86,7 @@ def __init__(self, session, virtapi, volumeops, cluster_name=None):
else:
self._cluster = vm_util.get_cluster_ref_from_name(
self._session, cluster_name)
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 @@ -136,7 +138,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 dbd2afb

Please sign in to comment.