From 0d691f78e12798f56ecad4588554e4da769b50e4 Mon Sep 17 00:00:00 2001 From: subham rai Date: Thu, 9 Apr 2020 21:50:16 +0530 Subject: [PATCH] baremetal: support for volume connectors API adding volume connectors feature. Change-Id: Id021456e4b52928bd6303a8d400a75de8d1cbf01 Story: #2007416 Task: #39036 Signed-off-by: subham rai --- doc/source/user/proxies/baremetal.rst | 8 + doc/source/user/resources/baremetal/index.rst | 1 + .../baremetal/v1/volume_connector.rst | 13 ++ openstack/baremetal/v1/_proxy.py | 143 ++++++++++++++++ openstack/baremetal/v1/volume_connector.py | 56 ++++++ openstack/tests/functional/baremetal/base.py | 11 ++ .../test_baremetal_volume_connector.py | 161 ++++++++++++++++++ .../tests/unit/baremetal/v1/test_proxy.py | 25 +++ .../baremetal/v1/test_volume_connector.py | 61 +++++++ ...volume_connector-api-f001e6f5fc4d1688.yaml | 4 + 10 files changed, 483 insertions(+) create mode 100644 doc/source/user/resources/baremetal/v1/volume_connector.rst create mode 100644 openstack/baremetal/v1/volume_connector.py create mode 100644 openstack/tests/functional/baremetal/test_baremetal_volume_connector.py create mode 100644 openstack/tests/unit/baremetal/v1/test_volume_connector.py create mode 100644 releasenotes/notes/volume_connector-api-f001e6f5fc4d1688.yaml diff --git a/doc/source/user/proxies/baremetal.rst b/doc/source/user/proxies/baremetal.rst index c38a72045..04538c39e 100644 --- a/doc/source/user/proxies/baremetal.rst +++ b/doc/source/user/proxies/baremetal.rst @@ -61,6 +61,14 @@ Allocation Operations update_allocation, patch_allocation, delete_allocation, wait_for_allocation +Volume Connector Operations +^^^^^^^^^^^^^^^^^^^^^^^^^^^ +.. autoclass:: openstack.baremetal.v1._proxy.Proxy + :noindex: + :members: volume_connectors, find_volume_connector, get_volume_connector, + create_volume_connector, update_volume_connector, + patch_volume_connector, delete_volume_connector + Utilities --------- diff --git a/doc/source/user/resources/baremetal/index.rst b/doc/source/user/resources/baremetal/index.rst index 3f7c56fa0..eccda6908 100644 --- a/doc/source/user/resources/baremetal/index.rst +++ b/doc/source/user/resources/baremetal/index.rst @@ -10,3 +10,4 @@ Baremetal Resources v1/port v1/port_group v1/allocation + v1/volume_connector diff --git a/doc/source/user/resources/baremetal/v1/volume_connector.rst b/doc/source/user/resources/baremetal/v1/volume_connector.rst new file mode 100644 index 000000000..3ffae2212 --- /dev/null +++ b/doc/source/user/resources/baremetal/v1/volume_connector.rst @@ -0,0 +1,13 @@ +openstack.baremetal.v1.volume_connector +======================================= + +.. automodule:: openstack.baremetal.v1.volume_connector + +The VolumeConnector Class +------------------------- + +The ``VolumeConnector`` class inherits +from :class:`~openstack.resource.Resource`. + +.. autoclass:: openstack.baremetal.v1.volume_connector.VolumeConnector + :members: diff --git a/openstack/baremetal/v1/_proxy.py b/openstack/baremetal/v1/_proxy.py index 0c4e5b4e9..903e584c3 100644 --- a/openstack/baremetal/v1/_proxy.py +++ b/openstack/baremetal/v1/_proxy.py @@ -17,6 +17,7 @@ from openstack.baremetal.v1 import driver as _driver from openstack.baremetal.v1 import node as _node from openstack.baremetal.v1 import port as _port from openstack.baremetal.v1 import port_group as _portgroup +from openstack.baremetal.v1 import volume_connector as _volumeconnector from openstack import exceptions from openstack import proxy from openstack import utils @@ -1003,3 +1004,145 @@ class Proxy(proxy.Proxy): """ res = self._get_resource(_node.Node, node) return res.set_traits(self, traits) + + def volume_connectors(self, details=False, **query): + """Retrieve a generator of volume_connector. + + :param details: A boolean indicating whether the detailed information + for every volume_connector should be returned. + :param dict query: Optional query parameters to be sent to restrict + the volume_connectors returned. Available parameters include: + + * ``fields``: A list containing one or more fields to be returned + in the response. This may lead to some performance gain + because other fields of the resource are not refreshed. + * ``limit``: Requests at most the specified number of + volume_connector be returned from the query. + * ``marker``: Specifies the ID of the last-seen volume_connector. + Use the ``limit`` parameter to make an initial limited request + and use the ID of the last-seen volume_connector from the + response as the ``marker`` value in subsequent limited request. + * ``node``:only return the ones associated with this specific node + (name or UUID), or an empty set if not found. + * ``sort_dir``:Sorts the response by the requested sort direction. + A valid value is ``asc`` (ascending) or ``desc`` + (descending). Default is ``asc``. You can specify multiple + pairs of sort key and sort direction query parameters. If + you omit the sort direction in a pair, the API uses the + natural sorting direction of the server attribute that is + provided as the ``sort_key``. + * ``sort_key``: Sorts the response by the this attribute value. + Default is ``id``. You can specify multiple pairs of sort + key and sort direction query parameters. If you omit the + sort direction in a pair, the API uses the natural sorting + direction of the server attribute that is provided as the + ``sort_key``. + + :returns: A generator of volume_connector instances. + """ + if details: + query['detail'] = True + return _volumeconnector.VolumeConnector.list(self, **query) + + def create_volume_connector(self, **attrs): + """Create a new volume_connector from attributes. + + :param dict attrs: Keyword arguments that will be used to create a + :class: + `~openstack.baremetal.v1.volume_connector.VolumeConnector`. + + :returns: The results of volume_connector creation. + :rtype::class: + `~openstack.baremetal.v1.volume_connector.VolumeConnector`. + """ + return self._create(_volumeconnector.VolumeConnector, **attrs) + + def find_volume_connector(self, vc_id, ignore_missing=True): + """Find a single volume connector. + + :param str vc_id: The ID of a volume connector. + + :param bool ignore_missing: When set to ``False``, an exception of + :class:`~openstack.exceptions.ResourceNotFound` will be raised + when the volume connector does not exist. When set to `True``, + None will be returned when attempting to find a nonexistent + volume connector. + :returns: One :class: + `~openstack.baremetal.v1.volumeconnector.VolumeConnector` + object or None. + """ + return self._find(_volumeconnector.VolumeConnector, vc_id, + ignore_missing=ignore_missing) + + def get_volume_connector(self, volume_connector, fields=None): + """Get a specific volume_connector. + + :param volume_connector: The value can be the ID of a + volume_connector or a :class: + `~openstack.baremetal.v1.volume_connector.VolumeConnector + instance.` + :param fields: Limit the resource fields to fetch.` + + :returns: One + :class: + `~openstack.baremetal.v1.volume_connector.VolumeConnector` + :raises: :class:`~openstack.exceptions.ResourceNotFound` when no + volume_connector matching the name or ID could be found.` + """ + return self._get_with_fields(_volumeconnector.VolumeConnector, + volume_connector, + fields=fields) + + def update_volume_connector(self, volume_connector, **attrs): + """Update a volume_connector. + + :param volume_connector:Either the ID of a volume_connector + or an instance of + :class:`~openstack.baremetal.v1.volume_connector.VolumeConnector.` + :param dict attrs: The attributes to update on the + volume_connector represented by the ``volume_connector`` parameter.` + + :returns: The updated volume_connector. + :rtype::class: + `~openstack.baremetal.v1.volume_connector.VolumeConnector.` + """ + return self._update(_volumeconnector.VolumeConnector, + volume_connector, **attrs) + + def patch_volume_connector(self, volume_connector, patch): + """Apply a JSON patch to the volume_connector. + + :param volume_connector: The value can be the ID of a + volume_connector or a :class: + `~openstack.baremetal.v1.volume_connector.VolumeConnector` + instance. + :param patch: JSON patch to apply. + + :returns: The updated volume_connector. + :rtype::class: + `~openstack.baremetal.v1.volume_connector.VolumeConnector.` + """ + return self._get_resource(_volumeconnector.VolumeConnector, + volume_connector).patch(self, patch) + + def delete_volume_connector(self, volume_connector, + ignore_missing=True): + """Delete an volume_connector. + + :param volume_connector: The value can be either the ID of a + volume_connector.VolumeConnector or a + :class: + `~openstack.baremetal.v1.volume_connector.VolumeConnector` + instance. + :param bool ignore_missing: When set to ``False``, an exception + :class:`~openstack.exceptions.ResourceNotFound` will be raised + when the volume_connector could not be found. + When set to ``True``, no exception will be raised when + attempting to delete a non-existent volume_connector. + + :returns: The instance of the volume_connector which was deleted. + :rtype::class: + `~openstack.baremetal.v1.volume_connector.VolumeConnector`. + """ + return self._delete(_volumeconnector.VolumeConnector, + volume_connector, ignore_missing=ignore_missing) diff --git a/openstack/baremetal/v1/volume_connector.py b/openstack/baremetal/v1/volume_connector.py new file mode 100644 index 000000000..f7a1a2752 --- /dev/null +++ b/openstack/baremetal/v1/volume_connector.py @@ -0,0 +1,56 @@ +# 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.baremetal.v1 import _common +from openstack import resource + + +class VolumeConnector(_common.ListMixin, resource.Resource): + + resources_key = 'connectors' + base_path = '/volume/connectors' + + # capabilities + allow_create = True + allow_fetch = True + allow_commit = True + allow_delete = True + allow_list = True + allow_patch = True + commit_method = 'PATCH' + commit_jsonpatch = True + + _query_mapping = resource.QueryParameters( + 'node', 'detail', + fields={'type': _common.fields_type}, + ) + + # Volume Connectors is available since 1.32 + _max_microversion = '1.32' + + #: The identifier of Volume connector and this field depends on the "type" + # of the volume_connector + connector_id = resource.Body('connector_id') + #: Timestamp at which the port was created. + created_at = resource.Body('created_at') + #: A set of one or more arbitrary metadata key and value pairs. + extra = resource.Body('extra') + #: A list of relative links, including the self and bookmark links. + links = resource.Body('links', type=list) + #: The UUID of node this port belongs to + node_id = resource.Body('node_uuid') + #: The types of Volume connector + type = resource.Body('type') + #: Timestamp at which the port was last updated. + updated_at = resource.Body('updated_at') + #: The UUID of the port + id = resource.Body('uuid', alternate_id=True) diff --git a/openstack/tests/functional/baremetal/base.py b/openstack/tests/functional/baremetal/base.py index 77796096a..bc0c4cafc 100644 --- a/openstack/tests/functional/baremetal/base.py +++ b/openstack/tests/functional/baremetal/base.py @@ -62,3 +62,14 @@ class BaseBaremetalTest(base.BaseFunctionalTest): lambda: self.conn.baremetal.delete_port_group(port_group.id, ignore_missing=True)) return port_group + + def create_volume_connector(self, node_id=None, **kwargs): + node_id = node_id or self.node_id + volume_connector = self.conn.baremetal.create_volume_connector( + node_uuid=node_id, **kwargs) + + self.addCleanup( + lambda: + self.conn.baremetal.delete_volume_connector(volume_connector.id, + ignore_missing=True)) + return volume_connector diff --git a/openstack/tests/functional/baremetal/test_baremetal_volume_connector.py b/openstack/tests/functional/baremetal/test_baremetal_volume_connector.py new file mode 100644 index 000000000..77ac90272 --- /dev/null +++ b/openstack/tests/functional/baremetal/test_baremetal_volume_connector.py @@ -0,0 +1,161 @@ +# 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 exceptions +from openstack.tests.functional.baremetal import base + + +class TestBareMetalVolumeconnector(base.BaseBaremetalTest): + + min_microversion = '1.32' + + def setUp(self): + super(TestBareMetalVolumeconnector, self).setUp() + self.node = self.create_node(provision_state='enroll') + + def test_volume_connector_create_get_delete(self): + self.conn.baremetal.set_node_provision_state( + self.node, 'manage', wait=True) + self.conn.baremetal.set_node_power_state(self.node, 'power off') + volume_connector = self.create_volume_connector( + connector_id='iqn.2017-07.org.openstack:01:d9a51732c3f', + type='iqn') + + loaded = self.conn.baremetal.get_volume_connector( + volume_connector.id) + self.assertEqual(loaded.id, volume_connector.id) + self.assertIsNotNone(loaded.node_id) + + with_fields = self.conn.baremetal.get_volume_connector( + volume_connector.id, fields=['uuid', 'extra']) + self.assertEqual(volume_connector.id, with_fields.id) + self.assertIsNone(with_fields.node_id) + + self.conn.baremetal.delete_volume_connector(volume_connector, + ignore_missing=False) + self.assertRaises(exceptions.ResourceNotFound, + self.conn.baremetal.get_volume_connector, + volume_connector.id) + + def test_volume_connector_list(self): + node2 = self.create_node(name='test-node') + self.conn.baremetal.set_node_provision_state( + node2, 'manage', wait=True) + self.conn.baremetal.set_node_power_state(node2, 'power off') + self.conn.baremetal.set_node_provision_state( + self.node, 'manage', wait=True) + self.conn.baremetal.set_node_power_state(self.node, 'power off') + vc1 = self.create_volume_connector( + connector_id='iqn.2018-07.org.openstack:01:d9a514g2c32', + node_id=node2.id, + type='iqn') + vc2 = self.create_volume_connector( + connector_id='iqn.2017-07.org.openstack:01:d9a51732c4g', + node_id=self.node.id, + type='iqn') + + vcs = self.conn.baremetal.volume_connectors( + node=self.node.id) + self.assertEqual([v.id for v in vcs], [vc2.id]) + + vcs = self.conn.baremetal.volume_connectors(node=node2.id) + self.assertEqual([v.id for v in vcs], [vc1.id]) + + vcs = self.conn.baremetal.volume_connectors(node='test-node') + self.assertEqual([v.id for v in vcs], [vc1.id]) + + def test_volume_connector_list_update_delete(self): + self.conn.baremetal.set_node_provision_state( + self.node, 'manage', wait=True) + self.conn.baremetal.set_node_power_state(self.node, 'power off') + self.create_volume_connector( + connector_id='iqn.2020-07.org.openstack:02:d9451472ce2', + node_id=self.node.id, + type='iqn', + extra={'foo': 'bar'}) + volume_connector = next(self.conn.baremetal.volume_connectors( + details=True, + node=self.node.id)) + self.assertEqual(volume_connector.extra, {'foo': 'bar'}) + + # This test checks that resources returned from listing are usable + self.conn.baremetal.update_volume_connector(volume_connector, + extra={'foo': 42}) + self.conn.baremetal.delete_volume_connector(volume_connector, + ignore_missing=False) + + def test_volume_connector_update(self): + self.conn.baremetal.set_node_provision_state( + self.node, 'manage', wait=True) + self.conn.baremetal.set_node_power_state(self.node, 'power off') + volume_connector = self.create_volume_connector( + connector_id='iqn.2019-07.org.openstack:03:de45b472c40', + node_id=self.node.id, + type='iqn') + volume_connector.extra = {'answer': 42} + + volume_connector = self.conn.baremetal.update_volume_connector( + volume_connector) + self.assertEqual({'answer': 42}, volume_connector.extra) + + volume_connector = self.conn.baremetal.get_volume_connector( + volume_connector.id) + self.assertEqual({'answer': 42}, volume_connector.extra) + + def test_volume_connector_patch(self): + vol_conn_id = 'iqn.2020-07.org.openstack:04:de45b472c40' + self.conn.baremetal.set_node_provision_state( + self.node, 'manage', wait=True) + self.conn.baremetal.set_node_power_state(self.node, 'power off') + volume_connector = self.create_volume_connector( + connector_id=vol_conn_id, + node_id=self.node.id, + type='iqn') + + volume_connector = self.conn.baremetal.patch_volume_connector( + volume_connector, dict(path='/extra/answer', op='add', value=42)) + self.assertEqual({'answer': 42}, volume_connector.extra) + self.assertEqual(vol_conn_id, + volume_connector.connector_id) + + volume_connector = self.conn.baremetal.get_volume_connector( + volume_connector.id) + self.assertEqual({'answer': 42}, volume_connector.extra) + + def test_volume_connector_negative_non_existing(self): + uuid = "5c9dcd04-2073-49bc-9618-99ae634d8971" + self.assertRaises(exceptions.ResourceNotFound, + self.conn.baremetal.get_volume_connector, uuid) + self.assertRaises(exceptions.ResourceNotFound, + self.conn.baremetal.find_volume_connector, uuid, + ignore_missing=False) + self.assertRaises(exceptions.ResourceNotFound, + self.conn.baremetal.delete_volume_connector, uuid, + ignore_missing=False) + self.assertIsNone(self.conn.baremetal.find_volume_connector(uuid)) + self.assertIsNone(self.conn.baremetal.delete_volume_connector(uuid)) + + def test_volume_connector_fields(self): + self.create_node() + self.conn.baremetal.set_node_provision_state( + self.node, 'manage', wait=True) + self.conn.baremetal.set_node_power_state(self.node, 'power off') + self.create_volume_connector( + connector_id='iqn.2018-08.org.openstack:04:de45f37c48', + node_id=self.node.id, + type='iqn') + result = self.conn.baremetal.volume_connectors( + fields=['uuid', 'node_id']) + for item in result: + self.assertIsNotNone(item.id) + self.assertIsNone(item.connector_id) diff --git a/openstack/tests/unit/baremetal/v1/test_proxy.py b/openstack/tests/unit/baremetal/v1/test_proxy.py index 9d5dfc99c..e83a4a37b 100644 --- a/openstack/tests/unit/baremetal/v1/test_proxy.py +++ b/openstack/tests/unit/baremetal/v1/test_proxy.py @@ -19,6 +19,7 @@ from openstack.baremetal.v1 import driver from openstack.baremetal.v1 import node from openstack.baremetal.v1 import port from openstack.baremetal.v1 import port_group +from openstack.baremetal.v1 import volume_connector from openstack import exceptions from openstack.tests.unit import base from openstack.tests.unit import test_proxy_base @@ -180,6 +181,30 @@ class TestBaremetalProxy(test_proxy_base.TestProxyBase): self.verify_delete(self.proxy.delete_allocation, allocation.Allocation, True) + def test_create_volume_connector(self): + self.verify_create(self.proxy.create_volume_connector, + volume_connector.VolumeConnector) + + def test_find_volume_connector(self): + self.verify_find(self.proxy.find_volume_connector, + volume_connector.VolumeConnector) + + def test_get_volume_connector(self): + self.verify_get(self.proxy.get_volume_connector, + volume_connector.VolumeConnector, + mock_method=_MOCK_METHOD, + expected_kwargs={'fields': None}) + + def test_delete_volume_connector(self): + self.verify_delete(self.proxy.delete_volume_connector, + volume_connector.VolumeConnector, + False) + + def test_delete_volume_connector_ignore(self): + self.verify_delete(self.proxy.delete_volume_connector, + volume_connector.VolumeConnector, + True) + @mock.patch.object(node.Node, 'fetch', autospec=True) def test__get_with_fields_none(self, mock_fetch): result = self.proxy._get_with_fields(node.Node, 'value') diff --git a/openstack/tests/unit/baremetal/v1/test_volume_connector.py b/openstack/tests/unit/baremetal/v1/test_volume_connector.py new file mode 100644 index 000000000..9277ef795 --- /dev/null +++ b/openstack/tests/unit/baremetal/v1/test_volume_connector.py @@ -0,0 +1,61 @@ +# 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.tests.unit import base + +from openstack.baremetal.v1 import volume_connector + +FAKE = { + "connector_id": "iqn.2017-07.org.openstack:01:d9a51732c3f", + "created_at": "2016-08-18T22:28:48.643434+11:11", + "extra": {}, + "links": [ + { + "href": "http://127.0.0.1:6385/v1/volume/connector/", + "rel": "self" + }, + { + "href": "http://127.0.0.1:6385/volume/connector/", + "rel": "bookmark" + } + ], + "node_uuid": "6d85703a-565d-469a-96ce-30b6de53079d", + "type": "iqn", + "updated_at": None, + "uuid": "9bf93e01-d728-47a3-ad4b-5e66a835037c" +} + + +class TestVolumeconnector(base.TestCase): + + def test_basic(self): + sot = volume_connector.VolumeConnector() + self.assertIsNone(sot.resource_key) + self.assertEqual('connectors', sot.resources_key) + self.assertEqual('/volume/connectors', 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) + self.assertEqual('PATCH', sot.commit_method) + + def test_instantiate(self): + sot = volume_connector.VolumeConnector(**FAKE) + self.assertEqual(FAKE['connector_id'], sot.connector_id) + self.assertEqual(FAKE['created_at'], sot.created_at) + self.assertEqual(FAKE['extra'], sot.extra) + self.assertEqual(FAKE['links'], sot.links) + self.assertEqual(FAKE['node_uuid'], sot.node_id) + self.assertEqual(FAKE['type'], sot.type) + self.assertEqual(FAKE['updated_at'], sot.updated_at) + self.assertEqual(FAKE['uuid'], sot.id) diff --git a/releasenotes/notes/volume_connector-api-f001e6f5fc4d1688.yaml b/releasenotes/notes/volume_connector-api-f001e6f5fc4d1688.yaml new file mode 100644 index 000000000..776263cf7 --- /dev/null +++ b/releasenotes/notes/volume_connector-api-f001e6f5fc4d1688.yaml @@ -0,0 +1,4 @@ +--- +features: + - | + Adds support for the baremetal volume connector API.