diff --git a/doc/source/enforcer.py b/doc/source/enforcer.py index 6eea11e40..94fd8d3dc 100644 --- a/doc/source/enforcer.py +++ b/doc/source/enforcer.py @@ -33,7 +33,8 @@ class EnforcementError(errors.SphinxError): def get_proxy_methods(): """Return a set of public names on all proxies""" - names = ["openstack.baremetal.v1._proxy", + names = ["openstack.accelerator.v2._proxy", + "openstack.baremetal.v1._proxy", "openstack.clustering.v1._proxy", "openstack.block_storage.v2._proxy", "openstack.compute.v2._proxy", diff --git a/doc/source/user/index.rst b/doc/source/user/index.rst index 9daef05f2..9559f41de 100644 --- a/doc/source/user/index.rst +++ b/doc/source/user/index.rst @@ -95,6 +95,7 @@ control which services can be used. .. toctree:: :maxdepth: 1 + Accelerator Baremetal Baremetal Introspection Block Storage @@ -129,6 +130,7 @@ The following services have exposed *Resource* classes. .. toctree:: :maxdepth: 1 + Accelerator Baremetal Baremetal Introspection Block Storage diff --git a/doc/source/user/proxies/accelerator.rst b/doc/source/user/proxies/accelerator.rst new file mode 100644 index 000000000..b5688a518 --- /dev/null +++ b/doc/source/user/proxies/accelerator.rst @@ -0,0 +1,51 @@ +Accelerator API +=============== + +.. automodule:: openstack.accelerator.v2._proxy + +The Accelerator Class +--------------------- + +The accelerator high-level interface is available through the ``accelerator`` +member of a :class:`~openstack.connection.Connection` object. +The ``accelerator`` member will only be added if the service is detected. + + +Device Operations +^^^^^^^^^^^^^^^^^ + +.. autoclass:: openstack.accelerator.v2._proxy.Proxy + + .. automethod:: openstack.accelerator.v2._proxy.Proxy.get_device + .. automethod:: openstack.accelerator.v2._proxy.Proxy.devices + +Deployable Operations +^^^^^^^^^^^^^^^^^^^^^ + +.. autoclass:: openstack.accelerator.v2._proxy.Proxy + + .. automethod:: openstack.accelerator.v2._proxy.Proxy.update_deployable + .. automethod:: openstack.accelerator.v2._proxy.Proxy.get_deployable + .. automethod:: openstack.accelerator.v2._proxy.Proxy.deployables + +Device Profile Operations +^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. autoclass:: openstack.accelerator.v2._proxy.Proxy + + .. automethod:: openstack.accelerator.v2._proxy.Proxy.create_device_profile + .. automethod:: openstack.accelerator.v2._proxy.Proxy.delete_device_profile + .. automethod:: openstack.accelerator.v2._proxy.Proxy.get_device_profile + .. automethod:: openstack.accelerator.v2._proxy.Proxy.device_profiles + +Accelerator Request Operations +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. autoclass:: openstack.accelerator.v2._proxy.Proxy + + .. automethod:: openstack.accelerator.v2._proxy.Proxy.create_accelerator_request + .. automethod:: openstack.accelerator.v2._proxy.Proxy.delete_accelerator_request + .. automethod:: openstack.accelerator.v2._proxy.Proxy.get_accelerator_request + .. automethod:: openstack.accelerator.v2._proxy.Proxy.accelerator_requests + .. automethod:: openstack.accelerator.v2._proxy.Proxy.update_accelerator_request + diff --git a/doc/source/user/resources/accelerator/index.rst b/doc/source/user/resources/accelerator/index.rst new file mode 100644 index 000000000..5e09cf616 --- /dev/null +++ b/doc/source/user/resources/accelerator/index.rst @@ -0,0 +1,11 @@ +Accelerator v2 Resources +======================== + +.. toctree:: + :maxdepth: 1 + + v2/device + v2/deployable + v2/device_profile + v2/accelerator_request + diff --git a/doc/source/user/resources/accelerator/v2/accelerator_request.rst b/doc/source/user/resources/accelerator/v2/accelerator_request.rst new file mode 100644 index 000000000..172511407 --- /dev/null +++ b/doc/source/user/resources/accelerator/v2/accelerator_request.rst @@ -0,0 +1,13 @@ +openstack.accelerator.v2.accelerator_request +============================================ + +.. automodule:: openstack.accelerator.v2.accelerator_request + +The AcceleratorRequest Class +---------------------------- + +The ``AcceleratorRequest`` class inherits from +:class:`~openstack.resource.Resource`. + +.. autoclass:: openstack.accelerator.v2.accelerator_request.AcceleratorRequest + :members: diff --git a/doc/source/user/resources/accelerator/v2/deployable.rst b/doc/source/user/resources/accelerator/v2/deployable.rst new file mode 100644 index 000000000..383795c2a --- /dev/null +++ b/doc/source/user/resources/accelerator/v2/deployable.rst @@ -0,0 +1,13 @@ +openstack.accelerator.v2.deployable +============================================ + +.. automodule:: openstack.accelerator.v2.deployable + +The Deployable Class +-------------------- + +The ``Deployable`` class inherits from :class:`~openstack.resource.Resource`. + +.. autoclass:: openstack.accelerator.v2.deployable.Deployable + :members: + diff --git a/doc/source/user/resources/accelerator/v2/device.rst b/doc/source/user/resources/accelerator/v2/device.rst new file mode 100644 index 000000000..943743546 --- /dev/null +++ b/doc/source/user/resources/accelerator/v2/device.rst @@ -0,0 +1,13 @@ +openstack.accelerator.v2.device +============================================ + +.. automodule:: openstack.accelerator.v2.device + +The Device Class +-------------------- + +The ``Device`` class inherits from :class:`~openstack.resource.Resource`. + +.. autoclass:: openstack.accelerator.v2.device.Device + :members: + diff --git a/doc/source/user/resources/accelerator/v2/device_profile.rst b/doc/source/user/resources/accelerator/v2/device_profile.rst new file mode 100644 index 000000000..9849c7833 --- /dev/null +++ b/doc/source/user/resources/accelerator/v2/device_profile.rst @@ -0,0 +1,14 @@ +openstack.accelerator.v2.device_profile +============================================ + +.. automodule:: openstack.accelerator.v2.device_profile + +The DeviceProfile Class +----------------------- + +The ``DeviceProfile`` class inherits from +:class:`~openstack.resource.Resource`. + +.. autoclass:: openstack.accelerator.v2.device_profile.DeviceProfile + :members: + diff --git a/openstack/_services_mixin.py b/openstack/_services_mixin.py index 5f8fbedf6..eb58e83b8 100644 --- a/openstack/_services_mixin.py +++ b/openstack/_services_mixin.py @@ -1,5 +1,6 @@ # Generated file, to change, run tools/print-services.py from openstack import service_description +from openstack.accelerator import accelerator_service from openstack.baremetal import baremetal_service from openstack.baremetal_introspection import baremetal_introspection_service from openstack.block_storage import block_storage_service @@ -124,7 +125,7 @@ class ServicesMixin(object): function_engine = service_description.ServiceDescription(service_type='function-engine') - accelerator = service_description.ServiceDescription(service_type='accelerator') + accelerator = accelerator_service.AcceleratorService(service_type='accelerator') admin_logic = service_description.ServiceDescription(service_type='admin-logic') registration = admin_logic diff --git a/openstack/accelerator/__init__.py b/openstack/accelerator/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/openstack/accelerator/accelerator_service.py b/openstack/accelerator/accelerator_service.py new file mode 100644 index 000000000..9d7f1784a --- /dev/null +++ b/openstack/accelerator/accelerator_service.py @@ -0,0 +1,21 @@ +# 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 service_description +from openstack.accelerator.v2 import _proxy as _proxy_v2 + + +class AcceleratorService(service_description.ServiceDescription): + """The accelerator service.""" + supported_versions = { + '2': _proxy_v2.Proxy, + } diff --git a/openstack/accelerator/v2/__init__.py b/openstack/accelerator/v2/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/openstack/accelerator/v2/_proxy.py b/openstack/accelerator/v2/_proxy.py new file mode 100644 index 000000000..8be5ac754 --- /dev/null +++ b/openstack/accelerator/v2/_proxy.py @@ -0,0 +1,173 @@ +# 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 proxy +from openstack.accelerator.v2 import deployable as _deployable +from openstack.accelerator.v2 import device as _device +from openstack.accelerator.v2 import device_profile as _device_profile +from openstack.accelerator.v2 import accelerator_request as _arq + + +class Proxy(proxy.Proxy): + + def deployables(self, **query): + """Retrieve a generator of deployables. + + :param kwargs query: Optional query parameters to be sent to + restrict the deployables to be returned. + :returns: A generator of deployable instances. + """ + return self._list(_deployable.Deployable, **query) + + def get_deployable(self, uuid, fields=None): + """Get a single deployable. + + :param uuid: The value can be the UUID of a deployable. + :returns: One :class:`~openstack.accelerator.v2.deployable.Deployable` + :raises: :class:`~openstack.exceptions.ResourceNotFound` when no + deployable matching the criteria could be found. + """ + return self._get(_deployable.Deployable, uuid) + + def update_deployable(self, uuid, patch): + """Reconfig the FPGA with new bitstream. + + :param uuid: The value can be the UUID of a deployable + :param patch: The infomation of to reconfig. + :returns: The results of FPGA reconfig. + """ + return self._get_resource(_deployable.Deployable, + uuid).patch(self, patch) + + def devices(self, **query): + """Retrieve a generator of devices. + + :param kwargs query: Optional query parameters to be sent to + restrict the devices to be returned. Available parameters include: + * hostname: The hostname of the device. + * type: The type of the device. + * vendor: The vendor ID of the device. + * sort: A list of sorting keys separated by commas. Each sorting + key can optionally be attached with a sorting direction + modifier which can be ``asc`` or ``desc``. + * limit: Requests a specified size of returned items from the + query. Returns a number of items up to the specified limit + value. + * marker: Specifies the ID of the last-seen item. Use the limit + parameter to make an initial limited request and use the ID of + the last-seen item from the response as the marker parameter + value in a subsequent limited request. + :returns: A generator of device instances. + """ + return self._list(_device.Device, **query) + + def get_device(self, uuid, fields=None): + """Get a single device. + + :param uuid: The value can be the UUID of a device. + :returns: One :class:`~openstack.accelerator.v2.device.Device` + :raises: :class:`~openstack.exceptions.ResourceNotFound` when no + deployable matching the criteria could be found. + """ + return self._get(_device.Device, uuid) + + def device_profiles(self, **query): + """Retrieve a generator of device profiles. + + :param kwargs query: Optional query parameters to be sent to + restrict the device profiles to be returned. + :returns: A generator of device profile instances. + """ + return self._list(_device_profile.DeviceProfile, **query) + + def create_device_profile(self, **attrs): + """Create a device_profiles. + + :param kwargs attrs: a list of device_profiles. + :returns: The list of created device profiles + """ + return self._create(_device_profile.DeviceProfile, **attrs) + + def delete_device_profile(self, name_or_id, ignore_missing=True): + """Delete an device profile + + :param name_or_id: The value can be either the ID of + an device profile. + :param bool ignore_missing: When set to ``False`` + :class:`~openstack.exceptions.ResourceNotFound` will be + raised when the device profile does not exist. + When set to ``True``, no exception will be set when + attempting to delete a nonexistent device profile. + :returns: ``None`` + """ + return self._delete(_device_profile.DeviceProfile, + name_or_id, ignore_missing=ignore_missing) + + def get_device_profile(self, uuid, fields=None): + """Get a single device profile. + + :param uuid: The value can be the UUID of a device profile. + :returns: One :class: + `~openstack.accelerator.v2.device_profile.DeviceProfile` + :raises: :class:`~openstack.exceptions.ResourceNotFound` when no + device profile matching the criteria could be found. + """ + return self._get(_device_profile.DeviceProfile, uuid) + + def accelerator_requests(self, **query): + """Retrieve a generator of accelerator requests. + + :param kwargs query: Optional query parameters to be sent to + restrict the accelerator requests to be returned. + :returns: A generator of accelerator request instances. + """ + return self._list(_arq.AcceleratorRequest, **query) + + def create_accelerator_request(self, **attrs): + """Create an ARQs for a single device profile. + + :param kwargs attrs: request body. + """ + return self._create(_arq.AcceleratorRequest, **attrs) + + def delete_accelerator_request(self, name_or_id, ignore_missing=True): + """Delete an device profile + :param name_or_id: The value can be either the ID of + an accelerator request. + :param bool ignore_missing: When set to ``False`` + :class:`~openstack.exceptions.ResourceNotFound` will be + raised when the device profile does not exist. + When set to ``True``, no exception will be set when + attempting to delete a nonexistent accelerator request. + :returns: ``None`` + """ + return self._delete(_arq.AcceleratorRequest, name_or_id, + ignore_missing=ignore_missing) + + def get_accelerator_request(self, uuid, fields=None): + """Get a single accelerator request. + :param uuid: The value can be the UUID of a accelerator request. + :returns: One :class: + `~openstack.accelerator.v2.accelerator_request.AcceleratorRequest` + :raises: :class:`~openstack.exceptions.ResourceNotFound` when no + accelerator request matching the criteria could be found. + """ + return self._get(_arq.AcceleratorRequest, uuid) + + def update_accelerator_request(self, uuid, properties): + """Bind/Unbind an accelerator to VM. + :param uuid: The uuid of the accelerator_request to be binded/unbinded. + :param properties: The info of VM + that will bind/unbind the accelerator. + :returns: True if bind/unbind succeeded, False otherwise. + """ + return self._get_resource(_arq.AcceleratorRequest, + uuid).patch(self, properties) diff --git a/openstack/accelerator/v2/accelerator_request.py b/openstack/accelerator/v2/accelerator_request.py new file mode 100644 index 000000000..b45bed453 --- /dev/null +++ b/openstack/accelerator/v2/accelerator_request.py @@ -0,0 +1,98 @@ +# 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 +from openstack import exceptions + + +class AcceleratorRequest(resource.Resource): + resource_key = 'arq' + resources_key = 'arqs' + base_path = '/accelerator_requests' + + # capabilities + allow_create = True + allow_fetch = True + allow_delete = True + allow_list = True + #: Allow patch operation for binding. + allow_patch = True + + #: The device address associated with this ARQ (if any) + attach_handle_info = resource.Body('attach_handle_info') + #: The type of attach handle (e.g. PCI, mdev...) + attach_handle_type = resource.Body('attach_handle_type') + #: The name of the device profile + device_profile_name = resource.Body('device_profile_name') + #: The id of the device profile group + device_profile_group_id = resource.Body('device_profile_group_id') + #: The UUID of the bound device RP (if any) + device_rp_uuid = resource.Body('device_rp_uuid') + #: The host name to which ARQ is bound. (if any) + hostname = resource.Body('hostname') + #: The UUID of the instance associated with this ARQ (if any) + instance_uuid = resource.Body('instance_uuid') + #: The state of the ARQ + state = resource.Body('state') + #: The UUID of the ARQ + uuid = resource.Body('uuid', alternate_id=True) + + def _convert_patch(self, patch): + # This overrides the default behavior of _convert_patch because + # the PATCH method consumes JSON, its key is the ARQ uuid + # and its value is an ordinary JSON patch. spec: + # https://specs.openstack.org/openstack/cyborg-specs/specs/train/approved/cyborg-api + + converted = super(AcceleratorRequest, self)._convert_patch(patch) + converted = {self.id: converted} + return converted + + def patch(self, session, patch=None, prepend_key=True, has_body=True, + retry_on_conflict=None, base_path=None): + # This overrides the default behavior of patch because + # the PATCH method consumes a dict rather than a list. spec: + # https://specs.openstack.org/openstack/cyborg-specs/specs/train/approved/cyborg-api + + # The id cannot be dirty for an commit + self._body._dirty.discard("id") + + # Only try to update if we actually have anything to commit. + if not patch and not self.requires_commit: + return self + + if not self.allow_patch: + raise exceptions.MethodNotSupported(self, "patch") + + request = self._prepare_request(prepend_key=prepend_key, + base_path=base_path, patch=True) + microversion = self._get_microversion_for(session, 'patch') + if patch: + request.body = self._convert_patch(patch) + + return self._commit(session, request, 'PATCH', microversion, + has_body=has_body, + retry_on_conflict=retry_on_conflict) + + def _consume_attrs(self, mapping, attrs): + # This overrides the default behavior of _consume_attrs because + # cyborg api returns an ARQ as list. spec: + # https://specs.openstack.org/openstack/cyborg-specs/specs/train/approved/cyborg-api + if isinstance(self, AcceleratorRequest): + if self.resources_key in attrs: + attrs = attrs[self.resources_key][0] + return super(AcceleratorRequest, self)._consume_attrs(mapping, attrs) + + def create(self, session, base_path=None): + # This overrides the default behavior of resource creation because + # cyborg doesn't accept resource_key in its request. + return super(AcceleratorRequest, self).create( + session, prepend_key=False, base_path=base_path) diff --git a/openstack/accelerator/v2/deployable.py b/openstack/accelerator/v2/deployable.py new file mode 100644 index 000000000..ebfee3197 --- /dev/null +++ b/openstack/accelerator/v2/deployable.py @@ -0,0 +1,66 @@ +# 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 import resource + + +class Deployable(resource.Resource): + resource_key = 'deployable' + resources_key = 'deployables' + base_path = '/deployables' + # capabilities + allow_create = False + allow_fetch = True + allow_commit = False + allow_delete = False + allow_list = True + allow_patch = True + #: The timestamp when this deployable was created. + created_at = resource.Body('created_at') + #: The device_id of the deployable. + device_id = resource.Body('device_id') + #: The UUID of the deployable. + id = resource.Body('uuid', alternate_id=True) + #: The name of the deployable. + name = resource.Body('name') + #: The num_accelerator of the deployable. + num_accelerators = resource.Body('num_accelerators') + #: The parent_id of the deployable. + parent_id = resource.Body('parent_id') + #: The root_id of the deployable. + root_id = resource.Body('root_id') + #: The timestamp when this deployable was updated. + updated_at = resource.Body('updated_at') + + def _commit(self, session, request, method, microversion, has_body=True, + retry_on_conflict=None): + session = self._get_session(session) + kwargs = {} + retriable_status_codes = set(session.retriable_status_codes or ()) + if retry_on_conflict: + kwargs['retriable_status_codes'] = retriable_status_codes | {409} + elif retry_on_conflict is not None and retriable_status_codes: + # The baremetal proxy defaults to retrying on conflict, allow + # overriding it via an explicit retry_on_conflict=False. + kwargs['retriable_status_codes'] = retriable_status_codes - {409} + try: + call = getattr(session, method.lower()) + except AttributeError: + raise exceptions.ResourceFailure( + msg="Invalid commit method: %s" % method) + request.url = request.url + "/program" + response = call(request.url, json=request.body, + headers=request.headers, microversion=microversion, + **kwargs) + self.microversion = microversion + self._translate_response(response, has_body=has_body) + return self diff --git a/openstack/accelerator/v2/device.py b/openstack/accelerator/v2/device.py new file mode 100644 index 000000000..73799669d --- /dev/null +++ b/openstack/accelerator/v2/device.py @@ -0,0 +1,44 @@ +# 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 Device(resource.Resource): + resource_key = 'device' + resources_key = 'devices' + base_path = '/devices' + # capabilities + allow_create = False + allow_fetch = True + allow_commit = False + allow_delete = False + allow_list = True + #: The timestamp when this device was created. + created_at = resource.Body('created_at') + #: The hostname of the device. + hostname = resource.Body('hostname') + #: The ID of the device. + id = resource.Body('id') + #: The model of the device. + model = resource.Body('model') + #: The std board information of the device. + std_board_info = resource.Body('std_board_info') + #: The type of the device. + type = resource.Body('type') + #: The timestamp when this device was updated. + updated_at = resource.Body('updated_at') + #: The UUID of the device. + uuid = resource.Body('uuid', alternate_id=True) + #: The vendor ID of the device. + vendor = resource.Body('vendor') + #: The vendor board information of the device. + vendor_board_info = resource.Body('vendor_board_info') diff --git a/openstack/accelerator/v2/device_profile.py b/openstack/accelerator/v2/device_profile.py new file mode 100644 index 000000000..a09f48167 --- /dev/null +++ b/openstack/accelerator/v2/device_profile.py @@ -0,0 +1,48 @@ +# 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 DeviceProfile(resource.Resource): + resource_key = 'device_profile' + resources_key = 'device_profiles' + base_path = '/device_profiles' + # capabilities + allow_create = True + allow_fetch = True + allow_commit = False + allow_delete = True + allow_list = True + + #: The timestamp when this device_profile was created. + created_at = resource.Body('created_at') + #: The groups of the device profile + groups = resource.Body('groups') + #: The name of the device profile + name = resource.Body('name') + #: The timestamp when this device_profile was updated. + updated_at = resource.Body('updated_at') + #: The uuid of the device profile + uuid = resource.Body('uuid', alternate_id=True) + + # TODO(s_shogo): This implementation only treat [ DeviceProfile ], and + # cannot treat multiple DeviceProfiles in list. + def _prepare_request_body(self, patch, prepend_key): + body = super(DeviceProfile, self)._prepare_request_body( + patch, prepend_key) + return [body] + + def create(self, session, base_path=None): + # This overrides the default behavior of resource creation because + # cyborg doesn't accept resource_key in its request. + return super(DeviceProfile, self).create( + session, prepend_key=False, base_path=base_path) diff --git a/openstack/accelerator/version.py b/openstack/accelerator/version.py new file mode 100644 index 000000000..692230a19 --- /dev/null +++ b/openstack/accelerator/version.py @@ -0,0 +1,27 @@ +# 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 Version(resource.Resource): + resource_key = 'version' + resources_key = 'versions' + base_path = '/' + + # capabilities + allow_list = True + + # Properties + links = resource.Body('links') + status = resource.Body('status') diff --git a/openstack/cloud/_accelerator.py b/openstack/cloud/_accelerator.py new file mode 100644 index 000000000..b28ac1f6b --- /dev/null +++ b/openstack/cloud/_accelerator.py @@ -0,0 +1,154 @@ +# 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 types so that we can reference ListType in sphinx param declarations. +# We can't just use list, because sphinx gets confused by +# openstack.resource.Resource.list and openstack.resource2.Resource.list + +from openstack.cloud import _normalize + + +class AcceleratorCloudMixin(_normalize.Normalizer): + + def list_deployables(self, filters=None): + """List all available deployables. + :param filters: (optional) dict of filter conditions to push down + :returns: A list of deployable info. + """ + + # Translate None from search interface to empty {} for kwargs below + if not filters: + filters = {} + return list(self.accelerator.deployables(**filters)) + + def list_devices(self, filters=None): + """List all devices. + :param filters: (optional) dict of filter conditions to push down + :returns: A list of device info. + """ + + # Translate None from search interface to empty {} for kwargs below + if not filters: + filters = {} + return list(self.accelerator.devices(**filters)) + + def list_device_profiles(self, filters=None): + """List all device_profiles. + :param filters: (optional) dict of filter conditions to push down + :returns: A list of device profile info. + """ + + # Translate None from search interface to empty {} for kwargs below + if not filters: + filters = {} + return list(self.accelerator.device_profiles(**filters)) + + def create_device_profile(self, attrs): + """Create a device_profile. + :param attrs: The info of device_profile to be created. + :returns: A ``munch.Munch`` of the created device_profile. + """ + + return self.accelerator.create_device_profile(**attrs) + + def delete_device_profile(self, name_or_id, filters): + """Delete a device_profile. + :param name_or_id: The Name(or uuid) of device_profile to be deleted. + :returns: True if delete succeeded, False otherwise. + """ + + device_profile = self.accelerator.get_device_profile( + name_or_id, + filters + ) + if device_profile is None: + self.log.debug( + "device_profile %s not found for deleting", + name_or_id + ) + return False + + self.accelerator.delete_device_profile(name_or_id=name_or_id) + + return True + + def list_accelerator_requests(self, filters=None): + """List all accelerator_requests. + :param filters: (optional) dict of filter conditions to push down + :returns: A list of accelerator request info. + """ + + # Translate None from search interface to empty {} for kwargs below + if not filters: + filters = {} + return list(self.accelerator.accelerator_requests(**filters)) + + def delete_accelerator_request(self, name_or_id, filters): + """Delete a accelerator_request. + :param name_or_id: The Name(or uuid) of accelerator_request. + :returns: True if delete succeeded, False otherwise. + """ + + accelerator_request = self.accelerator.get_accelerator_request( + name_or_id, + filters + ) + if accelerator_request is None: + self.log.debug( + "accelerator_request %s not found for deleting", + name_or_id + ) + return False + + self.accelerator.delete_accelerator_request(name_or_id=name_or_id) + + return True + + def create_accelerator_request(self, attrs): + """Create an accelerator_request. + :param attrs: The info of accelerator_request to be created. + :returns: A ``munch.Munch`` of the created accelerator_request. + """ + + return self.accelerator.create_accelerator_request(**attrs) + + def bind_accelerator_request(self, uuid, properties): + """Bind an accelerator to VM. + :param uuid: The uuid of the accelerator_request to be binded. + :param properties: The info of VM that will bind the accelerator. + :returns: True if bind succeeded, False otherwise. + """ + + accelerator_request = self.accelerator.get_accelerator_request(uuid) + if accelerator_request is None: + self.log.debug( + "accelerator_request %s not found for unbinding", uuid + ) + return False + + return self.accelerator.update_accelerator_request(uuid, properties) + + def unbind_accelerator_request(self, uuid, properties): + """Unbind an accelerator from VM. + :param uuid: The uuid of the accelerator_request to be unbinded. + :param properties: The info of VM that will unbind the accelerator. + :returns:True if unbind succeeded, False otherwise. + """ + + accelerator_request = self.accelerator.get_accelerator_request(uuid) + if accelerator_request is None: + self.log.debug( + "accelerator_request %s not found for unbinding", uuid + ) + return False + + return self.accelerator.update_accelerator_request(uuid, properties) diff --git a/openstack/connection.py b/openstack/connection.py index d77a81f9a..1fae88e40 100644 --- a/openstack/connection.py +++ b/openstack/connection.py @@ -186,6 +186,7 @@ import six from openstack import _log from openstack import _services_mixin from openstack.cloud import openstackcloud as _cloud +from openstack.cloud import _accelerator from openstack.cloud import _baremetal from openstack.cloud import _block_storage from openstack.cloud import _compute @@ -249,6 +250,7 @@ def from_config(cloud=None, config=None, options=None, **kwargs): class Connection( _services_mixin.ServicesMixin, _cloud._OpenStackCloudMixin, + _accelerator.AcceleratorCloudMixin, _baremetal.BaremetalCloudMixin, _block_storage.BlockStorageCloudMixin, _compute.ComputeCloudMixin, @@ -385,6 +387,7 @@ class Connection( # Call the _*CloudMixin constructors while we work on # integrating things better. _cloud._OpenStackCloudMixin.__init__(self) + _accelerator.AcceleratorCloudMixin.__init__(self) _baremetal.BaremetalCloudMixin.__init__(self) _block_storage.BlockStorageCloudMixin.__init__(self) _clustering.ClusteringCloudMixin.__init__(self) diff --git a/openstack/tests/unit/accelerator/__init__.py b/openstack/tests/unit/accelerator/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/openstack/tests/unit/accelerator/test_version.py b/openstack/tests/unit/accelerator/test_version.py new file mode 100644 index 000000000..43d6378e7 --- /dev/null +++ b/openstack/tests/unit/accelerator/test_version.py @@ -0,0 +1,42 @@ +# 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.accelerator import version + +IDENTIFIER = 'IDENTIFIER' +EXAMPLE = { + 'id': IDENTIFIER, + 'links': '2', + 'status': '3', +} + + +class TestVersion(base.TestCase): + + def test_basic(self): + sot = version.Version() + self.assertEqual('version', sot.resource_key) + self.assertEqual('versions', sot.resources_key) + self.assertEqual('/', sot.base_path) + self.assertFalse(sot.allow_create) + self.assertFalse(sot.allow_fetch) + self.assertFalse(sot.allow_commit) + self.assertFalse(sot.allow_delete) + self.assertTrue(sot.allow_list) + + def test_make_it(self): + sot = version.Version(**EXAMPLE) + self.assertEqual(EXAMPLE['id'], sot.id) + self.assertEqual(EXAMPLE['links'], sot.links) + self.assertEqual(EXAMPLE['status'], sot.status) diff --git a/openstack/tests/unit/accelerator/v2/__init__.py b/openstack/tests/unit/accelerator/v2/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/openstack/tests/unit/accelerator/v2/test_accelerator_request.py b/openstack/tests/unit/accelerator/v2/test_accelerator_request.py new file mode 100644 index 000000000..8b36a717f --- /dev/null +++ b/openstack/tests/unit/accelerator/v2/test_accelerator_request.py @@ -0,0 +1,59 @@ +# 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.accelerator.v2 import accelerator_request as arq + +FAKE_ID = '0725b527-e51a-41df-ad22-adad5f4546ad' +FAKE_RP_UUID = 'f4b7fe6c-8ab4-4914-a113-547af022935b' +FAKE_INSTANCE_UUID = '1ce4a597-9836-4e02-bea1-a3a6cbe7b9f9' +FAKE_ATTACH_INFO_STR = '{"bus": "5e", '\ + '"device": "00", '\ + '"domain": "0000", '\ + '"function": "1"}' + +FAKE = { + 'uuid': FAKE_ID, + 'device_profile_name': 'fake-devprof', + 'device_profile_group_id': 0, + 'device_rp_uuid': FAKE_RP_UUID, + 'instance_uuid': FAKE_INSTANCE_UUID, + 'attach_handle_type': 'PCI', + 'attach_handle_info': FAKE_ATTACH_INFO_STR, +} + + +class TestAcceleratorRequest(base.TestCase): + + def test_basic(self): + sot = arq.AcceleratorRequest() + self.assertEqual('arq', sot.resource_key) + self.assertEqual('arqs', sot.resources_key) + self.assertEqual('/accelerator_requests', sot.base_path) + self.assertTrue(sot.allow_create) + self.assertTrue(sot.allow_fetch) + self.assertFalse(sot.allow_commit) + self.assertTrue(sot.allow_delete) + self.assertTrue(sot.allow_list) + self.assertTrue(sot.allow_patch) + + def test_make_it(self): + sot = arq.AcceleratorRequest(**FAKE) + self.assertEqual(FAKE_ID, sot.uuid) + self.assertEqual(FAKE['device_profile_name'], sot.device_profile_name) + self.assertEqual(FAKE['device_profile_group_id'], + sot.device_profile_group_id) + self.assertEqual(FAKE_RP_UUID, sot.device_rp_uuid) + self.assertEqual(FAKE_INSTANCE_UUID, sot.instance_uuid) + self.assertEqual(FAKE['attach_handle_type'], sot.attach_handle_type) + self.assertEqual(FAKE_ATTACH_INFO_STR, sot.attach_handle_info) diff --git a/openstack/tests/unit/accelerator/v2/test_deployable.py b/openstack/tests/unit/accelerator/v2/test_deployable.py new file mode 100644 index 000000000..dcb0af554 --- /dev/null +++ b/openstack/tests/unit/accelerator/v2/test_deployable.py @@ -0,0 +1,52 @@ +# 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 uuid + +from openstack.tests.unit import base + +from openstack.accelerator.v2 import deployable + +EXAMPLE = { + 'uuid': uuid.uuid4(), + 'created_at': '2019-08-09T12:14:57.233772', + 'updated_at': '2019-08-09T12:15:57.233772', + 'parent_id': '1', + 'root_id': '1', + 'name': 'test_name', + 'num_accelerators': '1', + 'device_id': '1', +} + + +class TestDeployable(base.TestCase): + + def test_basic(self): + sot = deployable.Deployable() + self.assertEqual('deployable', sot.resource_key) + self.assertEqual('deployables', sot.resources_key) + self.assertEqual('/deployables', sot.base_path) + self.assertFalse(sot.allow_create) + self.assertTrue(sot.allow_fetch) + self.assertFalse(sot.allow_commit) + self.assertFalse(sot.allow_delete) + self.assertTrue(sot.allow_list) + + def test_make_it(self): + sot = deployable.Deployable(**EXAMPLE) + self.assertEqual(EXAMPLE['uuid'], sot.id) + self.assertEqual(EXAMPLE['parent_id'], sot.parent_id) + self.assertEqual(EXAMPLE['root_id'], sot.root_id) + self.assertEqual(EXAMPLE['name'], sot.name) + self.assertEqual(EXAMPLE['num_accelerators'], sot.num_accelerators) + self.assertEqual(EXAMPLE['device_id'], sot.device_id) + self.assertEqual(EXAMPLE['created_at'], sot.created_at) + self.assertEqual(EXAMPLE['updated_at'], sot.updated_at) diff --git a/openstack/tests/unit/accelerator/v2/test_device.py b/openstack/tests/unit/accelerator/v2/test_device.py new file mode 100644 index 000000000..22b17b336 --- /dev/null +++ b/openstack/tests/unit/accelerator/v2/test_device.py @@ -0,0 +1,53 @@ +# 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 uuid + +from openstack.tests.unit import base +from openstack.accelerator.v2 import device + +EXAMPLE = { + 'id': '1', + 'uuid': uuid.uuid4(), + 'created_at': '2019-08-09T12:14:57.233772', + 'updated_at': '2019-08-09T12:15:57.233772', + 'type': 'test_type', + 'vendor': '0x8086', + 'model': 'test_model', + 'std_board_info': '{"product_id": "0x09c4"}', + 'vendor_board_info': 'test_vb_info', +} + + +class TestDevice(base.TestCase): + + def test_basic(self): + sot = device.Device() + self.assertEqual('device', sot.resource_key) + self.assertEqual('devices', sot.resources_key) + self.assertEqual('/devices', sot.base_path) + self.assertFalse(sot.allow_create) + self.assertTrue(sot.allow_fetch) + self.assertFalse(sot.allow_commit) + self.assertFalse(sot.allow_delete) + self.assertTrue(sot.allow_list) + + def test_make_it(self): + sot = device.Device(**EXAMPLE) + self.assertEqual(EXAMPLE['id'], sot.id) + self.assertEqual(EXAMPLE['uuid'], sot.uuid) + self.assertEqual(EXAMPLE['type'], sot.type) + self.assertEqual(EXAMPLE['vendor'], sot.vendor) + self.assertEqual(EXAMPLE['model'], sot.model) + self.assertEqual(EXAMPLE['std_board_info'], sot.std_board_info) + self.assertEqual(EXAMPLE['vendor_board_info'], sot.vendor_board_info) + self.assertEqual(EXAMPLE['created_at'], sot.created_at) + self.assertEqual(EXAMPLE['updated_at'], sot.updated_at) diff --git a/openstack/tests/unit/accelerator/v2/test_device_profile.py b/openstack/tests/unit/accelerator/v2/test_device_profile.py new file mode 100644 index 000000000..f1708713b --- /dev/null +++ b/openstack/tests/unit/accelerator/v2/test_device_profile.py @@ -0,0 +1,54 @@ +# 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.accelerator.v2 import device_profile + + +FAKE = { + "id": 1, + "uuid": u"a95e10ae-b3e3-4eab-a513-1afae6f17c51", + "name": u'afaas_example_1', + "groups": [ + {"resources:ACCELERATOR_FPGA": "1", + "trait:CUSTOM_FPGA_INTEL_PAC_ARRIA10": "required", + "trait:CUSTOM_FUNCTION_ID_3AFB": "required", + }, + {"resources:CUSTOM_ACCELERATOR_FOO": "2", + "resources:CUSTOM_MEMORY": "200", + "trait:CUSTOM_TRAIT_ALWAYS": "required", + } + ] +} + + +class TestDeviceProfile(base.TestCase): + + def test_basic(self): + sot = device_profile.DeviceProfile() + self.assertEqual('device_profile', sot.resource_key) + self.assertEqual('device_profiles', sot.resources_key) + self.assertEqual('/device_profiles', sot.base_path) + self.assertTrue(sot.allow_create) + self.assertTrue(sot.allow_fetch) + self.assertFalse(sot.allow_commit) + self.assertTrue(sot.allow_delete) + self.assertTrue(sot.allow_list) + self.assertFalse(sot.allow_patch) + + def test_make_it(self): + sot = device_profile.DeviceProfile(**FAKE) + self.assertEqual(FAKE['id'], sot.id) + self.assertEqual(FAKE['uuid'], sot.uuid) + self.assertEqual(FAKE['name'], sot.name) + self.assertEqual(FAKE['groups'], sot.groups) diff --git a/openstack/tests/unit/accelerator/v2/test_proxy.py b/openstack/tests/unit/accelerator/v2/test_proxy.py new file mode 100644 index 000000000..b9fd45867 --- /dev/null +++ b/openstack/tests/unit/accelerator/v2/test_proxy.py @@ -0,0 +1,66 @@ +# 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.accelerator.v2 import _proxy +from openstack.accelerator.v2 import deployable +from openstack.accelerator.v2 import device_profile +from openstack.accelerator.v2 import accelerator_request +from openstack.tests.unit import test_proxy_base as test_proxy_base + + +class TestAcceleratorProxy(test_proxy_base.TestProxyBase): + def setUp(self): + super(TestAcceleratorProxy, self).setUp() + self.proxy = _proxy.Proxy(self.session) + + def test_list_deployables(self): + self.verify_list(self.proxy.deployables, deployable.Deployable) + + def test_list_device_profile(self): + self.verify_list(self.proxy.device_profiles, + device_profile.DeviceProfile) + + def test_create_device_profile(self): + self.verify_create(self.proxy.create_device_profile, + device_profile.DeviceProfile) + + def test_delete_device_profile(self): + self.verify_delete(self.proxy.delete_device_profile, + device_profile.DeviceProfile, False) + + def test_delete_device_profile_ignore(self): + self.verify_delete(self.proxy.delete_device_profile, + device_profile.DeviceProfile, True) + + def test_get_device_profile(self): + self.verify_get(self.proxy.get_device_profile, + device_profile.DeviceProfile) + + def test_list_accelerator_request(self): + self.verify_list(self.proxy.accelerator_requests, + accelerator_request.AcceleratorRequest) + + def test_create_accelerator_request(self): + self.verify_create(self.proxy.create_accelerator_request, + accelerator_request.AcceleratorRequest) + + def test_delete_accelerator_request(self): + self.verify_delete(self.proxy.delete_accelerator_request, + accelerator_request.AcceleratorRequest, False) + + def test_delete_accelerator_request_ignore(self): + self.verify_delete(self.proxy.delete_accelerator_request, + accelerator_request.AcceleratorRequest, True) + + def test_get_accelerator_request(self): + self.verify_get(self.proxy.get_accelerator_request, + accelerator_request.AcceleratorRequest) diff --git a/openstack/tests/unit/base.py b/openstack/tests/unit/base.py index 9bb7a4d03..d39c265a9 100644 --- a/openstack/tests/unit/base.py +++ b/openstack/tests/unit/base.py @@ -564,6 +564,12 @@ class TestCase(base.TestCase): compute_version_json, compute_discovery_url), ]) + def get_cyborg_discovery_mock_dict(self): + discovery_fixture = os.path.join( + self.fixtures_directory, "accelerator.json") + return dict(method='GET', uri="https://accelerator.example.com/", + text=open(discovery_fixture, 'r').read()) + def use_glance( self, image_version_json='image-version.json', image_discovery_url='https://image.example.com/'): @@ -611,6 +617,15 @@ class TestCase(base.TestCase): self.__do_register_uris([ self.get_senlin_discovery_mock_dict()]) + def use_cyborg(self): + # NOTE(s_shogo): This method is only meant to be used in "setUp" + # where the ordering of the url being registered is tightly controlled + # if the functionality of .use_cyborg is meant to be used during an + # actual test case, use .get_cyborg_discovery_mock and apply to the + # right location in the mock_uris when calling .register_uris + self.__do_register_uris([ + self.get_cyborg_discovery_mock_dict()]) + def register_uris(self, uri_mock_list=None): """Mock a list of URIs and responses via requests mock. diff --git a/openstack/tests/unit/cloud/test_accelerator.py b/openstack/tests/unit/cloud/test_accelerator.py new file mode 100644 index 000000000..5b09a56e8 --- /dev/null +++ b/openstack/tests/unit/cloud/test_accelerator.py @@ -0,0 +1,328 @@ +# 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 +import copy +import uuid + +DEP_UUID = uuid.uuid4().hex +DEP_DICT = { + 'uuid': DEP_UUID, + 'name': 'dep_name', + 'parent_id': None, + 'root_id': 1, + 'num_accelerators': 4, + 'device_id': 0 +} + +DEV_UUID = uuid.uuid4().hex +DEV_DICT = { + 'id': 1, + 'uuid': DEV_UUID, + 'name': 'dev_name', + 'type': 'test_type', + 'vendor': '0x8086', + 'model': 'test_model', + 'std_board_info': '{"product_id": "0x09c4"}', + 'vendor_board_info': 'test_vb_info', +} + +DEV_PROF_UUID = uuid.uuid4().hex +DEV_PROF_GROUPS = [ + {"resources:ACCELERATOR_FPGA": "1", + "trait:CUSTOM_FPGA_INTEL_PAC_ARRIA10": "required", + "trait:CUSTOM_FUNCTION_ID_3AFB": "required", + }, + {"resources:CUSTOM_ACCELERATOR_FOO": "2", + "resources:CUSTOM_MEMORY": "200", + "trait:CUSTOM_TRAIT_ALWAYS": "required", + } +] +DEV_PROF_DICT = { + "id": 1, + "uuid": DEV_PROF_UUID, + "name": 'afaas_example_1', + "groups": DEV_PROF_GROUPS, +} + +NEW_DEV_PROF_DICT = copy.copy(DEV_PROF_DICT) + +ARQ_UUID = uuid.uuid4().hex +ARQ_DEV_RP_UUID = uuid.uuid4().hex +ARQ_INSTANCE_UUID = uuid.uuid4().hex +ARQ_ATTACH_INFO_STR = '{"bus": "5e", '\ + '"device": "00", '\ + '"domain": "0000", '\ + '"function": "1"}' +ARQ_DICT = { + 'uuid': ARQ_UUID, + 'hostname': 'test_hostname', + 'device_profile_name': 'fake-devprof', + 'device_profile_group_id': 0, + 'device_rp_uuid': ARQ_DEV_RP_UUID, + 'instance_uuid': ARQ_INSTANCE_UUID, + 'attach_handle_type': 'PCI', + 'attach_handle_info': ARQ_ATTACH_INFO_STR, +} + +NEW_ARQ_DICT = copy.copy(ARQ_DICT) + + +class TestAccelerator(base.TestCase): + def setUp(self): + super(TestAccelerator, self).setUp() + self.use_cyborg() + + def test_list_deployables(self): + self.register_uris([ + dict(method='GET', + uri=self.get_mock_url( + 'accelerator', + 'public', + append=['v2', 'deployables']), + json={'deployables': [DEP_DICT]} + ), + ]) + dep_list = self.cloud.list_deployables() + self.assertEqual(len(dep_list), 1) + self.assertEqual(dep_list[0].id, DEP_DICT['uuid']) + self.assertEqual(dep_list[0].name, DEP_DICT['name']) + self.assertEqual(dep_list[0].parent_id, DEP_DICT['parent_id']) + self.assertEqual(dep_list[0].root_id, DEP_DICT['root_id']) + self.assertEqual(dep_list[0].num_accelerators, + DEP_DICT['num_accelerators']) + self.assertEqual(dep_list[0].device_id, DEP_DICT['device_id']) + self.assert_calls() + + def test_list_devices(self): + self.register_uris([ + dict(method='GET', + uri=self.get_mock_url( + 'accelerator', + 'public', + append=['v2', 'devices']), + json={'devices': [DEV_DICT]} + ), + ]) + dev_list = self.cloud.list_devices() + self.assertEqual(len(dev_list), 1) + self.assertEqual(dev_list[0].id, DEV_DICT['id']) + self.assertEqual(dev_list[0].uuid, DEV_DICT['uuid']) + self.assertEqual(dev_list[0].name, DEV_DICT['name']) + self.assertEqual(dev_list[0].type, DEV_DICT['type']) + self.assertEqual(dev_list[0].vendor, DEV_DICT['vendor']) + self.assertEqual(dev_list[0].model, DEV_DICT['model']) + self.assertEqual(dev_list[0].std_board_info, + DEV_DICT['std_board_info']) + self.assertEqual(dev_list[0].vendor_board_info, + DEV_DICT['vendor_board_info']) + self.assert_calls() + + def test_list_device_profiles(self): + self.register_uris([ + dict(method='GET', + uri=self.get_mock_url( + 'accelerator', + 'public', + append=['v2', 'device_profiles']), + json={'device_profiles': [DEV_PROF_DICT]} + ), + ]) + dev_prof_list = self.cloud.list_device_profiles() + self.assertEqual(len(dev_prof_list), 1) + self.assertEqual(dev_prof_list[0].id, DEV_PROF_DICT['id']) + self.assertEqual(dev_prof_list[0].uuid, DEV_PROF_DICT['uuid']) + self.assertEqual(dev_prof_list[0].name, DEV_PROF_DICT['name']) + self.assertEqual(dev_prof_list[0].groups, DEV_PROF_DICT['groups']) + self.assert_calls() + + def test_create_device_profile(self): + self.register_uris([ + dict(method='POST', + uri=self.get_mock_url( + 'accelerator', + 'public', + append=['v2', 'device_profiles']), + json=NEW_DEV_PROF_DICT) + ]) + + attrs = { + 'name': NEW_DEV_PROF_DICT['name'], + 'groups': NEW_DEV_PROF_DICT['groups'] + } + + self.assertTrue( + self.cloud.create_device_profile( + attrs + ) + ) + self.assert_calls() + + def test_delete_device_profile(self, filters=None): + self.register_uris([ + dict(method='GET', + uri=self.get_mock_url( + 'accelerator', + 'public', + append=['v2', 'device_profiles', DEV_PROF_DICT['name']]), + json={"device_profiles": [DEV_PROF_DICT]}), + dict(method='DELETE', + uri=self.get_mock_url( + 'accelerator', + 'public', + append=['v2', 'device_profiles', DEV_PROF_DICT['name']]), + json=DEV_PROF_DICT) + + ]) + self.assertTrue( + self.cloud.delete_device_profile( + DEV_PROF_DICT['name'], + filters + ) + ) + self.assert_calls() + + def test_list_accelerator_requests(self): + self.register_uris([ + dict(method='GET', + uri=self.get_mock_url( + 'accelerator', + 'public', + append=['v2', 'accelerator_requests']), + json={'arqs': [ARQ_DICT]} + ), + ]) + arq_list = self.cloud.list_accelerator_requests() + self.assertEqual(len(arq_list), 1) + self.assertEqual(arq_list[0].uuid, ARQ_DICT['uuid']) + self.assertEqual(arq_list[0].device_profile_name, + ARQ_DICT['device_profile_name']) + self.assertEqual(arq_list[0].device_profile_group_id, + ARQ_DICT['device_profile_group_id']) + self.assertEqual(arq_list[0].device_rp_uuid, + ARQ_DICT['device_rp_uuid']) + self.assertEqual(arq_list[0].instance_uuid, + ARQ_DICT['instance_uuid']) + self.assertEqual(arq_list[0].attach_handle_type, + ARQ_DICT['attach_handle_type']) + self.assertEqual(arq_list[0].attach_handle_info, + ARQ_DICT['attach_handle_info']) + self.assert_calls() + + def test_create_accelerator_request(self): + self.register_uris([ + dict(method='POST', + uri=self.get_mock_url( + 'accelerator', + 'public', + append=['v2', 'accelerator_requests']), + json=NEW_ARQ_DICT + ), + ]) + + attrs = { + 'device_profile_name': NEW_ARQ_DICT['device_profile_name'], + 'device_profile_group_id': NEW_ARQ_DICT['device_profile_group_id'] + } + + self.assertTrue( + self.cloud.create_accelerator_request( + attrs + ) + ) + self.assert_calls() + + def test_delete_accelerator_request(self, filters=None): + self.register_uris([ + dict(method='GET', + uri=self.get_mock_url( + 'accelerator', + 'public', + append=['v2', 'accelerator_requests', ARQ_DICT['uuid']]), + json={"accelerator_requests": [ARQ_DICT]}), + dict(method='DELETE', + uri=self.get_mock_url( + 'accelerator', + 'public', + append=['v2', 'accelerator_requests', ARQ_DICT['uuid']]), + json=ARQ_DICT) + + ]) + self.assertTrue( + self.cloud.delete_accelerator_request( + ARQ_DICT['uuid'], + filters + ) + ) + self.assert_calls() + + def test_bind_accelerator_request(self): + self.register_uris([ + dict(method='GET', + uri=self.get_mock_url( + 'accelerator', + 'public', + append=['v2', 'accelerator_requests', ARQ_DICT['uuid']]), + json={"accelerator_requests": [ARQ_DICT]}), + dict(method='PATCH', + uri=self.get_mock_url( + 'accelerator', + 'public', + append=['v2', 'accelerator_requests', ARQ_DICT['uuid']]), + json=ARQ_DICT) + ]) + properties = [{'path': '/hostname', + 'value': ARQ_DICT['hostname'], + 'op': 'add'}, + {'path': '/instance_uuid', + 'value': ARQ_DICT['instance_uuid'], + 'op': 'add'}, + {'path': '/device_rp_uuid', + 'value': ARQ_DICT['device_rp_uuid'], + 'op': 'add'}] + + self.assertTrue( + self.cloud.bind_accelerator_request( + ARQ_DICT['uuid'], properties + ) + ) + self.assert_calls() + + def test_unbind_accelerator_request(self): + self.register_uris([ + dict(method='GET', + uri=self.get_mock_url( + 'accelerator', + 'public', + append=['v2', 'accelerator_requests', ARQ_DICT['uuid']]), + json={"accelerator_requests": [ARQ_DICT]}), + dict(method='PATCH', + uri=self.get_mock_url( + 'accelerator', + 'public', + append=['v2', 'accelerator_requests', ARQ_DICT['uuid']]), + json=ARQ_DICT) + ]) + + properties = [{'path': '/hostname', + 'op': 'remove'}, + {'path': '/instance_uuid', + 'op': 'remove'}, + {'path': '/device_rp_uuid', + 'op': 'remove'}] + + self.assertTrue( + self.cloud.unbind_accelerator_request( + ARQ_DICT['uuid'], properties + ) + ) + self.assert_calls() diff --git a/openstack/tests/unit/fixtures/accelerator.json b/openstack/tests/unit/fixtures/accelerator.json new file mode 100644 index 000000000..bf2c04691 --- /dev/null +++ b/openstack/tests/unit/fixtures/accelerator.json @@ -0,0 +1,27 @@ +{ + "versions": [ + { + "id": "2.0", + "links": [ + { + "href": "/v2/", + "rel": "self" + }, + { + "href": "https://accelerator.example.com/api-ref/accelerator", + "rel": "help" + } + ], + "max_version": "2.0", + "media-types": [ + { + "base": "application/json", + "type": "application/vnd.openstack.accelerator-v1+json" + } + ], + "min_version": "2.0", + "status": "CURRENT", + "updated": "2019-09-01T00:00:00Z" + } + ] +} diff --git a/releasenotes/notes/add-cyborg-support-b9afca69f709c048.yaml b/releasenotes/notes/add-cyborg-support-b9afca69f709c048.yaml new file mode 100644 index 000000000..9688a5e24 --- /dev/null +++ b/releasenotes/notes/add-cyborg-support-b9afca69f709c048.yaml @@ -0,0 +1,3 @@ +--- +features: + - Add support for Cyborg(accelerator)