diff --git a/doc/source/user/proxies/image_v2.rst b/doc/source/user/proxies/image_v2.rst index 83c933269..b280e8198 100644 --- a/doc/source/user/proxies/image_v2.rst +++ b/doc/source/user/proxies/image_v2.rst @@ -67,6 +67,14 @@ Metadef Namespace Operations get_metadef_namespace, metadef_namespaces, update_metadef_namespace +Metadef Object Operations +^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. autoclass:: openstack.image.v2._proxy.Proxy + :noindex: + :members: create_metadef_object, delete_metadef_object, + get_metadef_object, metadef_objects, update_metadef_object + Metadef Resource Type Operations ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/doc/source/user/resources/image/index.rst b/doc/source/user/resources/image/index.rst index 2f291f2c6..6cfe1ae65 100644 --- a/doc/source/user/resources/image/index.rst +++ b/doc/source/user/resources/image/index.rst @@ -18,6 +18,7 @@ Image v2 Resources v2/image v2/member v2/metadef_namespace + v2/metadef_object v2/metadef_resource_type v2/metadef_schema v2/task diff --git a/doc/source/user/resources/image/v2/metadef_object.rst b/doc/source/user/resources/image/v2/metadef_object.rst new file mode 100644 index 000000000..2f9548a0c --- /dev/null +++ b/doc/source/user/resources/image/v2/metadef_object.rst @@ -0,0 +1,13 @@ +openstack.image.v2.metadef_object +================================== + +.. automodule:: openstack.image.v2.metadef_object + +The MetadefObject Class +------------------------ + +The ``MetadefObject`` class inherits +from :class:`~openstack.resource.Resource`. + +.. autoclass:: openstack.image.v2.metadef_object.MetadefObject + :members: diff --git a/openstack/image/v2/_proxy.py b/openstack/image/v2/_proxy.py index f3eb12f28..f75ed0715 100644 --- a/openstack/image/v2/_proxy.py +++ b/openstack/image/v2/_proxy.py @@ -19,6 +19,7 @@ from openstack.image.v2 import cache as _cache from openstack.image.v2 import image as _image from openstack.image.v2 import member as _member from openstack.image.v2 import metadef_namespace as _metadef_namespace +from openstack.image.v2 import metadef_object as _metadef_object from openstack.image.v2 import metadef_resource_type as _metadef_resource_type from openstack.image.v2 import metadef_schema as _metadef_schema from openstack.image.v2 import schema as _schema @@ -1205,6 +1206,117 @@ class Proxy(proxy.Proxy): **attrs, ) + # ====== METADEF OBJECT ====== + def create_metadef_object(self, namespace, **attrs): + """Create a new object from namespace + + :param namespace: The value can be either the name of a metadef + namespace or a + :class:`~openstack.image.v2.metadef_namespace.MetadefNamespace` + instance. + :param dict attrs: Keyword arguments which will be used to create + a :class:`~openstack.image.v2.metadef_object.MetadefObject`, + comprised of the properties on the Metadef object class. + + :returns: A metadef namespace + :rtype: :class:`~openstack.image.v2.metadef_object.MetadefObject` + """ + namespace_name = resource.Resource._get_id(namespace) + return self._create( + _metadef_object.MetadefObject, + namespace_name=namespace_name, + **attrs, + ) + + def get_metadef_object(self, metadef_object, namespace): + """Get a single metadef object + + :param metadef_object: The value can be the ID of a metadef_object + or a + :class:`~openstack.image.v2.metadef_object.MetadefObject` + instance. + :param namespace: The value can be either the name of a metadef + namespace or a + :class:`~openstack.image.v2.metadef_namespace.MetadefNamespace` + instance. + :returns: One :class:`~openstack.image.v2.metadef_object.MetadefObject` + :raises: :class:`~openstack.exceptions.ResourceNotFound` when no + resource can be found. + """ + object_name = resource.Resource._get_id(metadef_object) + namespace_name = resource.Resource._get_id(namespace) + return self._get( + _metadef_object.MetadefObject, + namespace_name=namespace_name, + name=object_name, + ) + + def metadef_objects(self, namespace): + """Get metadef object list of the namespace + + :param namespace: The value can be either the name of a metadef + namespace or a + :class:`~openstack.image.v2.metadef_namespace.MetadefNamespace` + instance. + + :returns: One :class:`~openstack.image.v2.metadef_object.MetadefObject` + :raises: :class:`~openstack.exceptions.ResourceNotFound` when no + resource can be found. + """ + namespace_name = resource.Resource._get_id(namespace) + return self._list( + _metadef_object.MetadefObject, + namespace_name=namespace_name, + ) + + def update_metadef_object(self, metadef_object, namespace, **attrs): + """Update a single metadef object + + :param metadef_object: The value can be the ID of a metadef_object or a + :class:`~openstack.image.v2.metadef_object.MetadefObject` instance. + :param namespace: The value can be either the name of a metadef + namespace or a + :class:`~openstack.image.v2.metadef_namespace.MetadefNamespace` + instance. + :param dict attrs: Keyword arguments which will be used to update + a :class:`~openstack.image.v2.metadef_object.MetadefObject` + + :returns: One :class:`~openstack.image.v2.metadef_object.MetadefObject` + :raises: :class:`~openstack.exceptions.ResourceNotFound` when no + resource can be found. + """ + namespace_name = resource.Resource._get_id(namespace) + return self._update( + _metadef_object.MetadefObject, + metadef_object, + namespace_name=namespace_name, + **attrs, + ) + + def delete_metadef_object(self, metadef_object, namespace, **attrs): + """Removes a single metadef object + + :param metadef_object: The value can be the ID of a metadef_object or a + :class:`~openstack.image.v2.metadef_object.MetadefObject` instance. + :param namespace: The value can be either the name of a metadef + namespace or a + :class:`~openstack.image.v2.metadef_namespace.MetadefNamespace` + instance. + :param dict attrs: Keyword arguments which will be used to update + a :class:`~openstack.image.v2.metadef_object.MetadefObject` + + :returns: ``None`` + :raises: :class:`~openstack.exceptions.ResourceNotFound` when no + resource can be found. + """ + namespace_name = resource.Resource._get_id(namespace) + return self._delete( + _metadef_object.MetadefObject, + metadef_object, + namespace_name=namespace_name, + **attrs, + ) + # ====== METADEF RESOURCE TYPES ====== def metadef_resource_types(self, **query): """Return a generator of metadef resource types @@ -1213,7 +1325,7 @@ class Proxy(proxy.Proxy): :rtype: :class:`~openstack.image.v2.metadef_resource_type.MetadefResourceType` :raises: :class:`~openstack.exceptions.ResourceNotFound` - when no resource can be found. + when no resource can be found. """ return self._list(_metadef_resource_type.MetadefResourceType, **query) diff --git a/openstack/image/v2/metadef_object.py b/openstack/image/v2/metadef_object.py new file mode 100644 index 000000000..fede230ee --- /dev/null +++ b/openstack/image/v2/metadef_object.py @@ -0,0 +1,40 @@ +# 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 openstack import resource + + +class MetadefObject(resource.Resource): + resources_key = 'objects' + base_path = '/metadefs/namespaces/%(namespace_name)s/objects' + + # capabilities + allow_create = True + allow_fetch = True + allow_commit = True + allow_delete = True + allow_list = True + + _query_mapping = resource.QueryParameters( + "visibility", + "resource_types", + "sort_key", + "sort_dir", + ) + + created_at = resource.Body('created_at') + description = resource.Body('description') + name = resource.Body('name', alternate_id=True) + namespace_name = resource.URI('namespace_name') + properties = resource.Body('properties') + required = resource.Body('required') + updated_at = resource.Body('updated_at') diff --git a/openstack/tests/functional/image/v2/test_metadef_object.py b/openstack/tests/functional/image/v2/test_metadef_object.py new file mode 100644 index 000000000..2edd2e364 --- /dev/null +++ b/openstack/tests/functional/image/v2/test_metadef_object.py @@ -0,0 +1,103 @@ +# 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 openstack.image.v2 import metadef_namespace as _metadef_namespace +from openstack.image.v2 import metadef_object as _metadef_object +from openstack.tests.functional.image.v2 import base + + +class TestMetadefObject(base.BaseImageTest): + def setUp(self): + super().setUp() + + # create namespace for object + namespace = self.getUniqueString().split('.')[-1] + self.metadef_namespace = self.conn.image.create_metadef_namespace( + namespace=namespace, + ) + self.assertIsInstance( + self.metadef_namespace, + _metadef_namespace.MetadefNamespace, + ) + self.assertEqual(namespace, self.metadef_namespace.namespace) + + # create object + object = self.getUniqueString().split('.')[-1] + self.metadef_object = self.conn.image.create_metadef_object( + name=object, + namespace=self.metadef_namespace, + ) + self.assertIsInstance( + self.metadef_object, + _metadef_object.MetadefObject, + ) + self.assertEqual(object, self.metadef_object.name) + + def tearDown(self): + self.conn.image.delete_metadef_object( + self.metadef_object, + self.metadef_object.namespace_name, + ) + self.conn.image.wait_for_delete(self.metadef_object) + + self.conn.image.delete_metadef_namespace(self.metadef_namespace) + self.conn.image.wait_for_delete(self.metadef_namespace) + + super().tearDown() + + def test_metadef_objects(self): + # get + metadef_object = self.conn.image.get_metadef_object( + self.metadef_object.name, + self.metadef_namespace, + ) + self.assertEqual( + self.metadef_object.namespace_name, + metadef_object.namespace_name, + ) + self.assertEqual( + self.metadef_object.name, + metadef_object.name, + ) + + # list + metadef_objects = list( + self.conn.image.metadef_objects(self.metadef_object.namespace_name) + ) + # there are a load of default metadef objects so we don't assert + # that this is the *only* metadef objects present + self.assertIn( + self.metadef_object.name, + {o.name for o in metadef_objects}, + ) + + # update + metadef_object_new_name = 'New object name' + metadef_object_new_description = 'New object description' + metadef_object = self.conn.image.update_metadef_object( + self.metadef_object.name, + namespace=self.metadef_object.namespace_name, + name=metadef_object_new_name, + description=metadef_object_new_description, + ) + self.assertIsInstance( + metadef_object, + _metadef_object.MetadefObject, + ) + self.assertEqual( + metadef_object_new_name, + metadef_object.name, + ) + self.assertEqual( + metadef_object_new_description, + metadef_object.description, + ) diff --git a/openstack/tests/unit/image/v2/test_metadef_object.py b/openstack/tests/unit/image/v2/test_metadef_object.py new file mode 100644 index 000000000..10d36c2c9 --- /dev/null +++ b/openstack/tests/unit/image/v2/test_metadef_object.py @@ -0,0 +1,77 @@ +# 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 openstack.image.v2 import metadef_object +from openstack.tests.unit import base + + +EXAMPLE = { + 'created_at': '2014-09-19T18:20:56Z', + 'description': 'The CPU limits with control parameters.', + 'name': 'CPU Limits', + 'properties': { + 'quota:cpu_period': { + 'description': 'The enforcement interval', + 'maximum': 1000000, + 'minimum': 1000, + 'title': 'Quota: CPU Period', + 'type': 'integer', + }, + 'quota:cpu_quota': { + 'description': 'The maximum allowed bandwidth', + 'title': 'Quota: CPU Quota', + 'type': 'integer', + }, + 'quota:cpu_shares': { + 'description': 'The proportional weighted', + 'title': 'Quota: CPU Shares', + 'type': 'integer', + }, + }, + 'required': [], + 'schema': '/v2/schemas/metadefs/object', + 'updated_at': '2014-09-19T18:20:56Z', +} + + +class TestMetadefObject(base.TestCase): + def test_basic(self): + sot = metadef_object.MetadefObject() + self.assertIsNone(sot.resource_key) + self.assertEqual('objects', sot.resources_key) + test_base_path = '/metadefs/namespaces/%(namespace_name)s/objects' + self.assertEqual(test_base_path, sot.base_path) + self.assertTrue(sot.allow_create) + self.assertTrue(sot.allow_fetch) + self.assertTrue(sot.allow_commit) + self.assertTrue(sot.allow_delete) + self.assertTrue(sot.allow_list) + + def test_make_it(self): + sot = metadef_object.MetadefObject(**EXAMPLE) + self.assertEqual(EXAMPLE['created_at'], sot.created_at) + self.assertEqual(EXAMPLE['description'], sot.description) + self.assertEqual(EXAMPLE['name'], sot.name) + self.assertEqual(EXAMPLE['properties'], sot.properties) + self.assertEqual(EXAMPLE['required'], sot.required) + self.assertEqual(EXAMPLE['updated_at'], sot.updated_at) + self.assertDictEqual( + { + "limit": "limit", + "marker": "marker", + "visibility": "visibility", + "resource_types": "resource_types", + "sort_key": "sort_key", + "sort_dir": "sort_dir", + }, + sot._query_mapping._mapping, + ) diff --git a/openstack/tests/unit/image/v2/test_proxy.py b/openstack/tests/unit/image/v2/test_proxy.py index 23816cc1f..d74777cc9 100644 --- a/openstack/tests/unit/image/v2/test_proxy.py +++ b/openstack/tests/unit/image/v2/test_proxy.py @@ -21,6 +21,7 @@ from openstack.image.v2 import cache as _cache from openstack.image.v2 import image as _image from openstack.image.v2 import member as _member from openstack.image.v2 import metadef_namespace as _metadef_namespace +from openstack.image.v2 import metadef_object as _metadef_object from openstack.image.v2 import metadef_resource_type as _metadef_resource_type from openstack.image.v2 import metadef_schema as _metadef_schema from openstack.image.v2 import schema as _schema @@ -569,6 +570,53 @@ class TestMetadefNamespace(TestImageProxy): ) +class TestMetadefObject(TestImageProxy): + def test_create_metadef_object(self): + self.verify_create( + self.proxy.create_metadef_object, + _metadef_object.MetadefObject, + method_kwargs={"namespace": "test_namespace_name"}, + expected_kwargs={"namespace_name": "test_namespace_name"}, + ) + + def test_get_metadef_object(self): + self.verify_get( + self.proxy.get_metadef_object, + _metadef_object.MetadefObject, + method_kwargs={"namespace": "test_namespace_name"}, + expected_kwargs={ + "namespace_name": "test_namespace_name", + 'name': 'resource_id', + }, + expected_args=[], + ) + + def test_metadef_objects(self): + self.verify_list( + self.proxy.metadef_objects, + _metadef_object.MetadefObject, + method_kwargs={"namespace": "test_namespace_name"}, + expected_kwargs={"namespace_name": "test_namespace_name"}, + ) + + def test_update_metadef_object(self): + self.verify_update( + self.proxy.update_metadef_object, + _metadef_object.MetadefObject, + method_kwargs={"namespace": "test_namespace_name"}, + expected_kwargs={"namespace_name": "test_namespace_name"}, + ) + + def test_delete_metadef_object(self): + self.verify_delete( + self.proxy.delete_metadef_object, + _metadef_object.MetadefObject, + False, + method_kwargs={"namespace": "test_namespace_name"}, + expected_kwargs={"namespace_name": "test_namespace_name"}, + ) + + class TestMetadefResourceType(TestImageProxy): def test_metadef_resource_types(self): self.verify_list( diff --git a/releasenotes/notes/add-metadef-object-5eec168baf039e80.yaml b/releasenotes/notes/add-metadef-object-5eec168baf039e80.yaml new file mode 100644 index 000000000..a1e037f78 --- /dev/null +++ b/releasenotes/notes/add-metadef-object-5eec168baf039e80.yaml @@ -0,0 +1,4 @@ +--- +features: + - | + Add support for Image Metadef objects.