diff options
author | Jenkins <jenkins@review.openstack.org> | 2014-08-13 01:37:02 +0000 |
---|---|---|
committer | Gerrit Code Review <review@openstack.org> | 2014-08-13 01:37:02 +0000 |
commit | 81beebbc60b40a4e674269585108161767bda883 (patch) | |
tree | c2b7e5e24c7a486127581faddb92e6436d280f3e | |
parent | 2e31f9bdc5a6aa5c70450a10d7425a36927bf9d7 (diff) | |
parent | 6316a6ff47d1a1247e9ebdd9f432dda4fd2c4df7 (diff) | |
download | oslo-vmware-81beebbc60b40a4e674269585108161767bda883.tar.gz |
Merge "Port the Datastore and DatastorePath objects"
-rw-r--r-- | openstack-common.conf | 1 | ||||
-rw-r--r-- | oslo/vmware/objects/__init__.py | 0 | ||||
-rw-r--r-- | oslo/vmware/objects/datastore.py | 165 | ||||
-rw-r--r-- | oslo/vmware/openstack/common/units.py | 38 | ||||
-rw-r--r-- | tests/objects/__init__.py | 0 | ||||
-rw-r--r-- | tests/objects/test_datastore.py | 191 |
6 files changed, 395 insertions, 0 deletions
diff --git a/openstack-common.conf b/openstack-common.conf index bb5d255..e229456 100644 --- a/openstack-common.conf +++ b/openstack-common.conf @@ -3,6 +3,7 @@ # The list of modules to copy from oslo-incubator.git module=excutils module=gettextutils +module=units script=tools/run_cross_tests.sh diff --git a/oslo/vmware/objects/__init__.py b/oslo/vmware/objects/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/oslo/vmware/objects/__init__.py diff --git a/oslo/vmware/objects/datastore.py b/oslo/vmware/objects/datastore.py new file mode 100644 index 0000000..83b09a4 --- /dev/null +++ b/oslo/vmware/objects/datastore.py @@ -0,0 +1,165 @@ +# Copyright (c) 2014 VMware, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import posixpath + +from oslo.vmware.openstack.common.gettextutils import _ + + +class Datastore(object): + + def __init__(self, ref, name, capacity=None, freespace=None): + """Datastore object holds ref and name together for convenience. + + :param ref: a vSphere reference to a datastore + :param name: vSphere unique name for this datastore + :param capacity: (optional) capacity in bytes of this datastore + :param freespace: (optional) free space in bytes of datastore + """ + if name is None: + raise ValueError(_("Datastore name cannot be None")) + if ref is None: + raise ValueError(_("Datastore reference cannot be None")) + if freespace is not None and capacity is None: + raise ValueError(_("Invalid capacity")) + if capacity is not None and freespace is not None: + if capacity < freespace: + raise ValueError(_("Capacity is smaller than free space")) + + self._ref = ref + self._name = name + self._capacity = capacity + self._freespace = freespace + + @property + def ref(self): + return self._ref + + @property + def name(self): + return self._name + + @property + def capacity(self): + return self._capacity + + @property + def freespace(self): + return self._freespace + + def build_path(self, *paths): + """Constructs and returns a DatastorePath. + + :param paths: list of path components, for constructing a path relative + to the root directory of the datastore + :return: a DatastorePath object + """ + return DatastorePath(self._name, *paths) + + def __str__(self): + return '[%s]' % self._name + + +class DatastorePath(object): + + """Class for representing a directory or file path in a vSphere datatore. + + This provides various helper methods to access components and useful + variants of the datastore path. + + Example usage: + + DatastorePath("datastore1", "_base/foo", "foo.vmdk") creates an + object that describes the "[datastore1] _base/foo/foo.vmdk" datastore + file path to a virtual disk. + + Note: + - Datastore path representations always uses forward slash as separator + (hence the use of the posixpath module). + - Datastore names are enclosed in square brackets. + - Path part of datastore path is relative to the root directory + of the datastore, and is always separated from the [ds_name] part with + a single space. + """ + + def __init__(self, datastore_name, *paths): + if datastore_name is None or datastore_name == '': + raise ValueError(_("Datastore name cannot be empty")) + self._datastore_name = datastore_name + self._rel_path = '' + if paths: + if None in paths: + raise ValueError(_("Path component cannot be None")) + self._rel_path = posixpath.join(*paths) + + def __str__(self): + """Full datastore path to the file or directory.""" + if self._rel_path != '': + return "[%s] %s" % (self._datastore_name, self.rel_path) + return "[%s]" % self._datastore_name + + @property + def datastore(self): + return self._datastore_name + + @property + def parent(self): + return DatastorePath(self.datastore, posixpath.dirname(self._rel_path)) + + @property + def basename(self): + return posixpath.basename(self._rel_path) + + @property + def dirname(self): + return posixpath.dirname(self._rel_path) + + @property + def rel_path(self): + return self._rel_path + + def join(self, *paths): + """Join one or more path components intelligently into a datastore path. + + If any component is an absolute path, all previous components are + thrown away, and joining continues. The return value is the + concatenation of the paths with exactly one slash ('/') inserted + between components, unless p is empty. + + :return: A datastore path + """ + if paths: + if None in paths: + raise ValueError(_("Path component cannot be None")) + return DatastorePath(self.datastore, self._rel_path, *paths) + return self + + def __eq__(self, other): + return (isinstance(other, DatastorePath) and + self._datastore_name == other._datastore_name and + self._rel_path == other._rel_path) + + @classmethod + def parse(cls, datastore_path): + """Constructs a DatastorePath object given a datastore path string.""" + if not datastore_path: + raise ValueError(_("Datastore path cannot be empty")) + + spl = datastore_path.split('[', 1)[1].split(']', 1) + path = "" + if len(spl) == 1: + datastore_name = spl[0] + else: + datastore_name, path = spl + return cls(datastore_name, path.strip()) diff --git a/oslo/vmware/openstack/common/units.py b/oslo/vmware/openstack/common/units.py new file mode 100644 index 0000000..4817da5 --- /dev/null +++ b/oslo/vmware/openstack/common/units.py @@ -0,0 +1,38 @@ +# Copyright 2013 IBM Corp +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +""" +Unit constants +""" + +# Binary unit constants. +Ki = 1024 +Mi = 1024 ** 2 +Gi = 1024 ** 3 +Ti = 1024 ** 4 +Pi = 1024 ** 5 +Ei = 1024 ** 6 +Zi = 1024 ** 7 +Yi = 1024 ** 8 + +# Decimal unit constants. +k = 1000 +M = 1000 ** 2 +G = 1000 ** 3 +T = 1000 ** 4 +P = 1000 ** 5 +E = 1000 ** 6 +Z = 1000 ** 7 +Y = 1000 ** 8 diff --git a/tests/objects/__init__.py b/tests/objects/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/tests/objects/__init__.py diff --git a/tests/objects/test_datastore.py b/tests/objects/test_datastore.py new file mode 100644 index 0000000..7044cfb --- /dev/null +++ b/tests/objects/test_datastore.py @@ -0,0 +1,191 @@ +# Copyright (c) 2014 VMware, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from oslo.vmware.objects import datastore +from oslo.vmware.openstack.common import units +from tests import base + + +class DatastoreTestCase(base.TestCase): + + """Test the Datastore object.""" + + def test_ds(self): + ds = datastore.Datastore( + "fake_ref", "ds_name", 2 * units.Gi, 1 * units.Gi) + self.assertEqual('ds_name', ds.name) + self.assertEqual('fake_ref', ds.ref) + self.assertEqual(2 * units.Gi, ds.capacity) + self.assertEqual(1 * units.Gi, ds.freespace) + + def test_ds_invalid_space(self): + self.assertRaises(ValueError, datastore.Datastore, + "fake_ref", "ds_name", 1 * units.Gi, 2 * units.Gi) + self.assertRaises(ValueError, datastore.Datastore, + "fake_ref", "ds_name", None, 2 * units.Gi) + + def test_ds_no_capacity_no_freespace(self): + ds = datastore.Datastore("fake_ref", "ds_name") + self.assertIsNone(ds.capacity) + self.assertIsNone(ds.freespace) + + def test_ds_invalid(self): + self.assertRaises(ValueError, datastore.Datastore, None, "ds_name") + self.assertRaises(ValueError, datastore.Datastore, "fake_ref", None) + + def test_build_path(self): + ds = datastore.Datastore("fake_ref", "ds_name") + ds_path = ds.build_path("some_dir", "foo.vmdk") + self.assertEqual('[ds_name] some_dir/foo.vmdk', str(ds_path)) + + +class DatastorePathTestCase(base.TestCase): + + """Test the DatastorePath object.""" + + def test_ds_path(self): + p = datastore.DatastorePath('dsname', 'a/b/c', 'file.iso') + self.assertEqual('[dsname] a/b/c/file.iso', str(p)) + self.assertEqual('a/b/c/file.iso', p.rel_path) + self.assertEqual('a/b/c', p.parent.rel_path) + self.assertEqual('[dsname] a/b/c', str(p.parent)) + self.assertEqual('dsname', p.datastore) + self.assertEqual('file.iso', p.basename) + self.assertEqual('a/b/c', p.dirname) + + def test_ds_path_no_ds_name(self): + bad_args = [ + ('', ['a/b/c', 'file.iso']), + (None, ['a/b/c', 'file.iso'])] + for t in bad_args: + self.assertRaises( + ValueError, datastore.DatastorePath, + t[0], *t[1]) + + def test_ds_path_invalid_path_components(self): + bad_args = [ + ('dsname', [None]), + ('dsname', ['', None]), + ('dsname', ['a', None]), + ('dsname', ['a', None, 'b']), + ('dsname', [None, '']), + ('dsname', [None, 'b'])] + + for t in bad_args: + self.assertRaises( + ValueError, datastore.DatastorePath, + t[0], *t[1]) + + def test_ds_path_no_subdir(self): + args = [ + ('dsname', ['', 'x.vmdk']), + ('dsname', ['x.vmdk'])] + + canonical_p = datastore.DatastorePath('dsname', 'x.vmdk') + self.assertEqual('[dsname] x.vmdk', str(canonical_p)) + self.assertEqual('', canonical_p.dirname) + self.assertEqual('x.vmdk', canonical_p.basename) + self.assertEqual('x.vmdk', canonical_p.rel_path) + for t in args: + p = datastore.DatastorePath(t[0], *t[1]) + self.assertEqual(str(canonical_p), str(p)) + + def test_ds_path_ds_only(self): + args = [ + ('dsname', []), + ('dsname', ['']), + ('dsname', ['', ''])] + + canonical_p = datastore.DatastorePath('dsname') + self.assertEqual('[dsname]', str(canonical_p)) + self.assertEqual('', canonical_p.rel_path) + self.assertEqual('', canonical_p.basename) + self.assertEqual('', canonical_p.dirname) + for t in args: + p = datastore.DatastorePath(t[0], *t[1]) + self.assertEqual(str(canonical_p), str(p)) + self.assertEqual(canonical_p.rel_path, p.rel_path) + + def test_ds_path_equivalence(self): + args = [ + ('dsname', ['a/b/c/', 'x.vmdk']), + ('dsname', ['a/', 'b/c/', 'x.vmdk']), + ('dsname', ['a', 'b', 'c', 'x.vmdk']), + ('dsname', ['a/b/c', 'x.vmdk'])] + + canonical_p = datastore.DatastorePath('dsname', 'a/b/c', 'x.vmdk') + for t in args: + p = datastore.DatastorePath(t[0], *t[1]) + self.assertEqual(str(canonical_p), str(p)) + self.assertEqual(canonical_p.datastore, p.datastore) + self.assertEqual(canonical_p.rel_path, p.rel_path) + self.assertEqual(str(canonical_p.parent), str(p.parent)) + + def test_ds_path_non_equivalence(self): + args = [ + # leading slash + ('dsname', ['/a', 'b', 'c', 'x.vmdk']), + ('dsname', ['/a/b/c/', 'x.vmdk']), + ('dsname', ['a/b/c', '/x.vmdk']), + # leading space + ('dsname', ['a/b/c/', ' x.vmdk']), + ('dsname', ['a/', ' b/c/', 'x.vmdk']), + ('dsname', [' a', 'b', 'c', 'x.vmdk']), + # trailing space + ('dsname', ['/a/b/c/', 'x.vmdk ']), + ('dsname', ['a/b/c/ ', 'x.vmdk'])] + + canonical_p = datastore.DatastorePath('dsname', 'a/b/c', 'x.vmdk') + for t in args: + p = datastore.DatastorePath(t[0], *t[1]) + self.assertNotEqual(str(canonical_p), str(p)) + + def test_equal(self): + a = datastore.DatastorePath('ds_name', 'a') + b = datastore.DatastorePath('ds_name', 'a') + self.assertEqual(a, b) + + def test_join(self): + p = datastore.DatastorePath('ds_name', 'a') + ds_path = p.join('b') + self.assertEqual('[ds_name] a/b', str(ds_path)) + + p = datastore.DatastorePath('ds_name', 'a') + ds_path = p.join() + bad_args = [ + [None], + ['', None], + ['a', None], + ['a', None, 'b']] + for arg in bad_args: + self.assertRaises(ValueError, p.join, *arg) + + def test_ds_path_parse(self): + p = datastore.DatastorePath.parse('[dsname]') + self.assertEqual('dsname', p.datastore) + self.assertEqual('', p.rel_path) + + p = datastore.DatastorePath.parse('[dsname] folder') + self.assertEqual('dsname', p.datastore) + self.assertEqual('folder', p.rel_path) + + p = datastore.DatastorePath.parse('[dsname] folder/file') + self.assertEqual('dsname', p.datastore) + self.assertEqual('folder/file', p.rel_path) + + for p in [None, '']: + self.assertRaises(ValueError, datastore.DatastorePath.parse, p) + + for p in ['bad path', '/a/b/c', 'a/b/c']: + self.assertRaises(IndexError, datastore.DatastorePath.parse, p) |