diff --git a/cyborgclient/common/utils.py b/cyborgclient/common/utils.py index f6160e3..149fb08 100644 --- a/cyborgclient/common/utils.py +++ b/cyborgclient/common/utils.py @@ -14,9 +14,12 @@ # import logging +from cyborgclient import exceptions as exc LOG = logging.getLogger(__name__) +from oslo_serialization import jsonutils + def common_filters(marker=None, limit=None, sort_key=None, sort_dir=None): """Generate common filters for any list request. @@ -110,3 +113,47 @@ def clean_listing_columns(headers, columns, data_sample): col_headers.append(header) cols.append(col) return tuple(col_headers), tuple(cols) + + +def json_formatter(js): + return jsonutils.dumps(js, indent=2, ensure_ascii=False) + + +def split_and_deserialize(string): + """Split and try to JSON deserialize a string. + + Gets a string with the KEY=VALUE format, split it (using '=' as the + separator) and try to JSON deserialize the VALUE. + :returns: A tuple of (key, value). + """ + + try: + key, value = string.split("=", 1) + except ValueError: + raise exc.CommandError(_('Attributes must be a list of ' + 'PATH=VALUE not "%s"') % string) + try: + value = jsonutils.loads(value) + except ValueError: + pass + + return (key, value) + + +def args_array_to_patch(op, attributes): + patch = [] + for attr in attributes: + # Sanitize + if not attr.startswith('/'): + attr = '/' + attr + + if op in ['add', 'replace']: + path, value = split_and_deserialize(attr) + patch.append({'op': op, 'path': path, 'value': value}) + + elif op == "remove": + # For remove only the key is needed + patch.append({'op': op, 'path': attr}) + else: + raise exc.CommandError(_('Unknown PATCH operation: %s') % op) + return patch diff --git a/cyborgclient/osc/plugin.py b/cyborgclient/osc/plugin.py index e841b8f..89ba0b1 100644 --- a/cyborgclient/osc/plugin.py +++ b/cyborgclient/osc/plugin.py @@ -1,5 +1,3 @@ -# Copyright 2016 Huawei, Inc. All rights reserved. -# # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at @@ -13,49 +11,94 @@ # under the License. # +"""OpenStackClient plugin for Accelerator management service.""" + import logging +from openstack.config import cloud_region +from openstack.config import defaults as config_defaults +from openstack import connection from osc_lib import utils -from cyborgclient.i18n import _ - LOG = logging.getLogger(__name__) -DEFAULT_ACCELERATOR_API_VERSION = '1' +DEFAULT_ACCELERATOR_API_VERSION = '2' API_VERSION_OPTION = 'os_accelerator_api_version' API_NAME = 'accelerator' -API_VERSIONS = { - '1': 'cyborgclient.v1.client.Client', -} +CURRENT_API_VERSION = '2' + + +def _make_key(service_type, key): + if not service_type: + return key + else: + service_type = service_type.lower().replace('-', '_') + return "_".join([service_type, key]) + + +def _get_config_from_profile(profile, **kwargs): + # Deal with clients still trying to use legacy profile objects + region_name = None + for service in profile.get_services(): + if service.region: + region_name = service.region + service_type = service.service_type + if service.interface: + key = _make_key(service_type, 'interface') + kwargs[key] = service.interface + if service.version: + version = service.version + if version.startswith('v'): + version = version[2:] + key = _make_key(service_type, 'api_version') + kwargs[key] = version + if service.api_version: + version = service.api_version + key = _make_key(service_type, 'default_microversion') + kwargs[key] = version + + config_kwargs = config_defaults.get_defaults() + config_kwargs.update(kwargs) + config = cloud_region.CloudRegion( + region_name=region_name, config=config_kwargs) + return config + + +def create_connection(prof=None, cloud_region=None, **kwargs): + version_key = _make_key(API_NAME, 'api_version') + kwargs[version_key] = CURRENT_API_VERSION + + if not cloud_region: + if prof: + cloud_region = _get_config_from_profile(prof, **kwargs) + else: + # If we got the CloudRegion from python-openstackclient and it doesn't + # already have a default microversion set, set it here. + microversion_key = _make_key(API_NAME, 'default_microversion') + cloud_region.config.setdefault(microversion_key, CURRENT_API_VERSION) + + user_agent = kwargs.pop('user_agent', None) + app_name = kwargs.pop('app_name', None) + app_version = kwargs.pop('app_version', None) + if user_agent is not None and (not app_name and not app_version): + app_name, app_version = user_agent.split('/', 1) + + return connection.Connection( + config=cloud_region, + app_name=app_name, + app_version=app_version, **kwargs) def make_client(instance): - """Returns an accelerators service client""" - cyborg_client = utils.get_client_class( - API_NAME, - instance._api_version[API_NAME], - API_VERSIONS) - LOG.debug('Instantiating accelerators client: %s', cyborg_client) - - endpoint = instance.get_endpoint_for_service_type( - API_NAME, - region_name=instance.region_name, - interface=instance.interface, + """Returns a accelerator proxy""" + conn = create_connection( + cloud_region=instance._cli_options, ) - kwargs = {'endpoint': endpoint, - 'auth_url': instance.auth.auth_url, - 'region_name': instance.region_name, - 'username': instance.auth_ref.username} - - if instance.session: - kwargs.update(session=instance.session) - else: - kwargs.update(token=instance.auth_ref.auth_token) - - client = cyborg_client(**kwargs) - - return client + LOG.debug('Connection: %s', conn) + LOG.debug('Accelerator client initialized using OpenStackSDK: %s', + conn.accelerator) + return conn.accelerator def build_option_parser(parser): @@ -66,8 +109,7 @@ def build_option_parser(parser): default=utils.env( 'OS_ACCELERATOR_API_VERSION', default=DEFAULT_ACCELERATOR_API_VERSION), - help=(_('Accelerations compute API version, default=%s ' - '(Env: OS_ACCELERATOR_API_VERSION)') % - DEFAULT_ACCELERATOR_API_VERSION) - ) + help='Accelerator API version, default=' + + DEFAULT_ACCELERATOR_API_VERSION + + ' (Env: OS_ACCELERATOR_API_VERSION)') return parser diff --git a/cyborgclient/osc/v2/__init__.py b/cyborgclient/osc/v2/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/cyborgclient/osc/v2/accelerator_request.py b/cyborgclient/osc/v2/accelerator_request.py new file mode 100644 index 0000000..b41f905 --- /dev/null +++ b/cyborgclient/osc/v2/accelerator_request.py @@ -0,0 +1,305 @@ +# 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. +# + + +"""Cyborg v2 Acceleration accelerator action implementations""" + +import logging + +from openstack import exceptions as sdk_exc +from osc_lib.command import command +from osc_lib import utils as oscutils + +from cyborgclient.common import utils +from cyborgclient import exceptions as exc +from cyborgclient.i18n import _ + + +class ListAcceleratorRequest(command.Lister): + """List all accelerator requests""" + + def get_parser(self, prog_name): + parser = super(ListAcceleratorRequest, self).get_parser(prog_name) + parser.add_argument( + '--long', + dest='detail', + action='store_true', + default=False, + help=_("List additional fields in output") + ) + return parser + + def take_action(self, parsed_args): + acc_client = self.app.client_manager.accelerator + + if parsed_args.detail: + column_headers = ( + "uuid", + "state", + "device_profile_name", + "hostname", + "device_rp_uuid", + "instance_uuid", + "attach_handle_type", + "attach_handle_info", + ) + columns = ( + "uuid", + "state", + "device_profile_name", + "hostname", + "device_rp_uuid", + "instance_uuid", + "attach_handle_type", + "attach_handle_info", + ) + else: + column_headers = ( + "uuid", + "state", + "device_profile_name", + "instance_uuid", + "attach_handle_type", + "attach_handle_info", + ) + columns = ( + "uuid", + "state", + "device_profile_name", + "instance_uuid", + "attach_handle_type", + "attach_handle_info", + ) + + data = acc_client.accelerator_requests() + if not data: + return (), () + formatters = {} + return (column_headers, + (oscutils.get_item_properties( + s, columns, formatters=formatters) for s in data)) + + +class CreateAcceleratorRequest(command.ShowOne): + """Register a new accelerator_request with the accelerator service""" + + log = logging.getLogger(__name__ + ".CreateAcceleratorRequest") + + def get_parser(self, prog_name): + parser = super(CreateAcceleratorRequest, self).get_parser(prog_name) + + parser.add_argument( + 'device_profile_name', + metavar='', + help=_("The name of device_profile for accelerator_request.")) + parser.add_argument( + 'device_profile_group_id', + metavar='', + help=_("The group id of device_profile \ + for the accelerator_request.")) + parser.add_argument( + '--image-uuid', + metavar='', + dest='img_uuid', + help=_("The uuid of image saved in glance.")) + + return parser + + def take_action(self, parsed_args): + self.log.debug("take_action(%s)", parsed_args) + + acc_client = self.app.client_manager.accelerator + + attrs = { + 'device_profile_name': parsed_args.device_profile_name, + 'device_profile_group_id': parsed_args.device_profile_group_id, + 'image_uuid': parsed_args.img_uuid, + } + + accelerator_request = acc_client.create_accelerator_request(**attrs) + + return _show_accelerator_request(acc_client, + accelerator_request.uuid) + + +class DeleteAcceleratorRequest(command.Command): + """Delete accelerator request(s).""" + + log = logging.getLogger(__name__ + ".DeleteAcceleratorRequest") + + def get_parser(self, prog_name): + parser = super(DeleteAcceleratorRequest, self).get_parser(prog_name) + parser.add_argument( + "accelerator_requests", + metavar="", + nargs="+", + help=_("UUID(s) of the accelerator_request(s) to delete.") + ) + + return parser + + def take_action(self, parsed_args): + self.log.debug("take_action(%s)", parsed_args) + + acc_client = self.app.client_manager.accelerator + + failures = [] + for uuid in parsed_args.accelerator_requests: + try: + acc_client.delete_accelerator_request(uuid, False) + print(_('Deleted accelerator_request %s') % uuid) + except exc.ClientException as e: + failures.append(_("Failed to delete accelerator_request\ + %(accelerator_request)s: %(error)s") + % {'uuid': uuid, 'error': e}) + + if failures: + raise exc.ClientException("\n".join(failures)) + + +class ShowAcceleratorRequest(command.ShowOne): + """Show accelerator_request details.""" + + log = logging.getLogger(__name__ + ".ShowAcceleratorRequest") + + def get_parser(self, prog_name): + parser = super(ShowAcceleratorRequest, self).get_parser(prog_name) + parser.add_argument( + "accelerator_request", + metavar="", + help=_("UUID of the accelerator_request.") + ) + return parser + + def take_action(self, parsed_args): + self.log.debug("take_action(%s)", parsed_args) + + acc_client = self.app.client_manager.accelerator + + return _show_accelerator_request(acc_client, + parsed_args.accelerator_request) + + +def _show_accelerator_request(acc_client, uuid): + """Show detailed info about accelerator_request.""" + + columns = ( + "uuid", + "state", + "device_profile_name", + "hostname", + "device_rp_uuid", + "instance_uuid", + "attach_handle_type", + "attach_handle_info", + ) + + try: + accelerator_request = acc_client.get_accelerator_request(uuid) + except sdk_exc.ResourceNotFound: + raise exc.CommandError(_('accelerator_request not found: %s') % uuid) + + formatters = { + 'data': utils.json_formatter, + } + data = accelerator_request.to_dict() + return columns, oscutils.get_dict_properties(data, columns, + formatters=formatters) + + +class BindAcceleratorRequest(command.ShowOne): + """Bind accelerator to instance.""" + + log = logging.getLogger(__name__ + ".BindAcceleratorRequest") + + def get_parser(self, prog_name): + parser = super(BindAcceleratorRequest, self).get_parser(prog_name) + + parser.add_argument( + 'accelerator_request', + metavar='', + help=_("UUID of the accelerator request") + ) + parser.add_argument( + 'hostname', + metavar='', + help=_("Bind hostname of the accelerator request") + ) + parser.add_argument( + "instance_uuid", + metavar="", + help=_("Bind instance_uuid of the accelerator request") + ) + parser.add_argument( + "device_rp_uuid", + metavar="", + help=_("Bind device_rp_uuid of the accelerator request") + ) + + return parser + + def take_action(self, parsed_args): + self.log.debug("take_action(%s)", parsed_args) + + acc_client = self.app.client_manager.accelerator + + properties = [] + if parsed_args.hostname: + hostname = ["hostname=%s" % parsed_args.hostname] + properties.extend(utils.args_array_to_patch('add', hostname)) + if parsed_args.instance_uuid: + instance_uuid = ["instance_uuid=%s" % parsed_args.instance_uuid] + properties.extend(utils.args_array_to_patch('add', instance_uuid)) + if parsed_args.device_rp_uuid: + device_rp_uuid = ["device_rp_uuid=%s" % parsed_args.device_rp_uuid] + properties.extend(utils.args_array_to_patch('add', device_rp_uuid)) + + if properties: + acc_client.update_accelerator_request( + parsed_args.accelerator_request, properties) + return _show_accelerator_request(acc_client, + parsed_args.accelerator_request) + else: + self.log.warning("Please specify what to set.") + + +class UnbindAcceleratorRequest(command.ShowOne): + """Unbind accelerator from instance.""" + + log = logging.getLogger(__name__ + ".UnbindAcceleratorRequest") + + def get_parser(self, prog_name): + parser = super(UnbindAcceleratorRequest, self).get_parser(prog_name) + + parser.add_argument( + 'accelerator_request', + metavar='', + help=_("UUID of the accelerator request") + ) + + return parser + + def take_action(self, parsed_args): + self.log.debug("take_action(%s)", parsed_args) + + acc_client = self.app.client_manager.accelerator + + properties = [{'path': '/hostname', 'op': 'remove'}, + {'path': '/instance_uuid', 'op': 'remove'}, + {'path': '/device_rp_uuid', 'op': 'remove'}] + + acc_client.update_accelerator_request(parsed_args.accelerator_request, + properties) + + return _show_accelerator_request(acc_client, + parsed_args.accelerator_request) diff --git a/cyborgclient/osc/v2/deployable.py b/cyborgclient/osc/v2/deployable.py new file mode 100644 index 0000000..bee1233 --- /dev/null +++ b/cyborgclient/osc/v2/deployable.py @@ -0,0 +1,167 @@ +# 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. +# + + +"""Cyborg v2 Acceleration accelerator action implementations""" +import logging + +from openstack import exceptions as sdk_exc +from osc_lib.command import command +from osc_lib import utils as oscutils + +from cyborgclient.common import utils +from cyborgclient import exceptions as exc +from cyborgclient.i18n import _ + + +class ListDeployable(command.Lister): + """List all deployables""" + + def get_parser(self, prog_name): + parser = super(ListDeployable, self).get_parser(prog_name) + parser.add_argument( + '--long', + dest='detail', + action='store_true', + default=False, + help=_("List additional fields in output") + ) + return parser + + def take_action(self, parsed_args): + acc_client = self.app.client_manager.accelerator + + if parsed_args.detail: + column_headers = ( + "created_at", + "updated_at", + "uuid", + "parent_id", + "root_id", + "name", + "num_accelerators", + "device_id" + ) + + columns = ( + "created_at", + "updated_at", + "id", + "parent_id", + "root_id", + "name", + "num_accelerators", + "device_id" + ) + else: + column_headers = ( + "uuid", + "name", + "device_id", + ) + columns = ( + "id", + "name", + "device_id", + ) + data = acc_client.deployables() + if not data: + return (), () + formatters = {} + return (column_headers, + (oscutils.get_item_properties( + s, columns, formatters=formatters) for s in data)) + + +class ShowDeployable(command.ShowOne): + """Show deployable details.""" + log = logging.getLogger(__name__ + ".ShowDeployable") + + def get_parser(self, prog_name): + parser = super(ShowDeployable, self).get_parser(prog_name) + parser.add_argument( + "deployable", + metavar="", + help=_("UUID of the deployable.") + ) + return parser + + def take_action(self, parsed_args): + self.log.debug("take_action(%s)", parsed_args) + acc_client = self.app.client_manager.accelerator + return _show_deployable(acc_client, + parsed_args.deployable) + + +def _show_deployable(acc_client, uuid): + """Show detailed info about deployable.""" + columns = ( + "created_at", + "updated_at", + "uuid", + "name", + ) + try: + deployable = acc_client.get_deployable(uuid) + except sdk_exc.ResourceNotFound: + raise exc.CommandError(_('deployable not found: %s') % uuid) + formatters = { + 'data': utils.json_formatter, + } + data = deployable.to_dict() + data['uuid'] = data.pop('id', uuid) + return columns, oscutils.get_dict_properties(data, columns, + formatters=formatters) + + +class ProgramDeployable(command.ShowOne): + """Reconfigure deployable.""" + log = logging.getLogger(__name__ + ".ProgramDeployable") + + def get_parser(self, prog_name): + parser = super(ProgramDeployable, self).get_parser(prog_name) + parser.add_argument( + 'deployable_uuid', + metavar='', + default=False, + help=_("Deployable UUID for reconfigure.") + ) + parser.add_argument( + 'image_uuid', + metavar='', + default=False, + help=_("Image UUID for reconfigure.") + ) + return parser + + def take_action(self, parsed_args): + self.log.debug("take_action(%s)", parsed_args) + acc_client = self.app.client_manager.accelerator + dep_uuid = parsed_args.deployable_uuid + try: + acc_client.get_deployable(dep_uuid) + except sdk_exc.ResourceNotFound: + raise exc.CommandError(_('deployable not found: %s') % dep_uuid) + + image_uuid = parsed_args.image_uuid + image_client = self.app.client_manager.image.images + try: + image_client.get(image_uuid) + except sdk_exc.ResourceNotFound: + raise exc.CommandError(_('image not found: %s') % image_uuid) + + program_info = [{'path': '/program', + 'value': [{'image_uuid': image_uuid}], + 'op': 'replace'}] + acc_client.update_deployable(dep_uuid, program_info) + return _show_deployable(acc_client, dep_uuid) diff --git a/cyborgclient/osc/v2/device.py b/cyborgclient/osc/v2/device.py new file mode 100644 index 0000000..2bba707 --- /dev/null +++ b/cyborgclient/osc/v2/device.py @@ -0,0 +1,135 @@ +# 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. +# + + +"""Cyborg v2 Acceleration accelerator action implementations""" +import logging + +from openstack import exceptions as sdk_exc +from osc_lib.command import command +from osc_lib import utils as oscutils + +from cyborgclient.common import utils +from cyborgclient import exceptions as exc +from cyborgclient.i18n import _ + + +class ListDevice(command.Lister): + """List all devices""" + + def get_parser(self, prog_name): + parser = super(ListDevice, self).get_parser(prog_name) + parser.add_argument( + '--long', + dest='detail', + action='store_true', + default=False, + help=_("List additional fields in output") + ) + return parser + + def take_action(self, parsed_args): + acc_client = self.app.client_manager.accelerator + + if parsed_args.detail: + column_headers = ( + "created_at", + "updated_at", + "uuid", + "type", + "vendor", + "model", + "hostname", + "std_board_info", + "vendor_board_info" + ) + + columns = ( + "created_at", + "updated_at", + "uuid", + "type", + "vendor", + "model", + "hostname", + "std_board_info", + "vendor_board_info" + ) + else: + column_headers = ( + "uuid", + "type", + "vendor", + "hostname", + "std_board_info", + ) + columns = ( + "uuid", + "type", + "vendor", + "hostname", + "std_board_info", + ) + + data = acc_client.devices() + if not data: + return (), () + formatters = {} + return (column_headers, + (oscutils.get_item_properties( + s, columns, formatters=formatters) for s in data)) + + +class ShowDevice(command.ShowOne): + """Show device details.""" + log = logging.getLogger(__name__ + ".ShowDevice") + + def get_parser(self, prog_name): + parser = super(ShowDevice, self).get_parser(prog_name) + parser.add_argument( + "device", + metavar="", + help=_("UUID of the device.") + ) + return parser + + def take_action(self, parsed_args): + self.log.debug("take_action(%s)", parsed_args) + acc_client = self.app.client_manager.accelerator + return _show_device(acc_client, + parsed_args.device) + + +def _show_device(acc_client, uuid): + """Show detailed info about device.""" + columns = ( + "created_at", + "updated_at", + "uuid", + "type", + "vendor", + "model", + "hostname", + "std_board_info", + "vendor_board_info" + ) + try: + device = acc_client.get_device(uuid) + except sdk_exc.ResourceNotFound: + raise exc.CommandError(_('device not found: %s') % uuid) + formatters = { + 'data': utils.json_formatter, + } + data = device.to_dict() + return columns, oscutils.get_dict_properties(data, columns, + formatters=formatters) diff --git a/cyborgclient/osc/v2/device_profile.py b/cyborgclient/osc/v2/device_profile.py new file mode 100644 index 0000000..d329746 --- /dev/null +++ b/cyborgclient/osc/v2/device_profile.py @@ -0,0 +1,182 @@ +# 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. +# + + +"""Cyborg v2 Acceleration accelerator action implementations""" + +import logging + +from openstack import exceptions as sdk_exc +from osc_lib.command import command +from osc_lib import utils as oscutils +from oslo_serialization import jsonutils + +from cyborgclient.common import utils +from cyborgclient import exceptions as exc +from cyborgclient.i18n import _ + + +class ListDeviceProfile(command.Lister): + """List all device profiles""" + + def get_parser(self, prog_name): + parser = super(ListDeviceProfile, self).get_parser(prog_name) + parser.add_argument( + '--long', + dest='detail', + action='store_true', + default=False, + help=_("List additional fields in output") + ) + return parser + + def take_action(self, parsed_args): + acc_client = self.app.client_manager.accelerator + + if parsed_args.detail: + column_headers = ( + "created_at", + "updated_at", + "uuid", + "name", + "groups" + ) + columns = ( + "created_at", + "updated_at", + "uuid", + "name", + "groups" + ) + else: + column_headers = ( + "uuid", + "name", + "groups", + ) + columns = ( + "uuid", + "name", + "groups", + ) + + data = acc_client.device_profiles() + if not data: + return (), () + formatters = {} + return (column_headers, + (oscutils.get_item_properties( + s, columns, formatters=formatters) for s in data)) + + +class CreateDeviceProfile(command.ShowOne): + """Register a new device_profile with the accelerator service""" + log = logging.getLogger(__name__ + ".CreateDeviceProfile") + + def get_parser(self, prog_name): + parser = super(CreateDeviceProfile, self).get_parser(prog_name) + parser.add_argument( + 'name', + metavar='', + help=_("Unique name for the device_profile.")) + parser.add_argument( + 'groups', + metavar='', + help=_("groups for the device_profile.")) + return parser + + def take_action(self, parsed_args): + self.log.debug("take_action(%s)", parsed_args) + acc_client = self.app.client_manager.accelerator + + attrs = { + 'name': parsed_args.name, + 'groups': list(jsonutils.loads(parsed_args.groups)), + } + device_profile = acc_client.create_device_profile(**attrs) + return _show_device_profile(acc_client, device_profile.uuid) + + +class DeleteDeviceProfile(command.Command): + """Delete deviceProfile(s).""" + + log = logging.getLogger(__name__ + ".DeleteDeviceProfile") + + def get_parser(self, prog_name): + parser = super(DeleteDeviceProfile, self).get_parser(prog_name) + parser.add_argument( + "device_profiles", + metavar="", + nargs="+", + help=_("UUID(s) of the device_profile(s) to delete.") + ) + + return parser + + def take_action(self, parsed_args): + self.log.debug("take_action(%s)", parsed_args) + + acc_client = self.app.client_manager.accelerator + + failures = [] + for uuid in parsed_args.device_profiles: + try: + acc_client.delete_device_profile(uuid, False) + print(_('Deleted device_profile %s') % uuid) + except exc.ClientException as e: + failures.append(_("Failed to delete device_profile \ + %(device_profile)s: %(error)s") + % {'uuid': uuid, 'error': e}) + if failures: + raise exc.ClientException("\n".join(failures)) + + +class ShowDeviceProfile(command.ShowOne): + """Show device_profile details.""" + log = logging.getLogger(__name__ + ".ShowDeviceProfile") + + def get_parser(self, prog_name): + parser = super(ShowDeviceProfile, self).get_parser(prog_name) + parser.add_argument( + "device_profile", + metavar="", + help=_("UUID of the device_profile.") + ) + return parser + + def take_action(self, parsed_args): + self.log.debug("take_action(%s)", parsed_args) + acc_client = self.app.client_manager.accelerator + return _show_device_profile(acc_client, + parsed_args.device_profile) + + +def _show_device_profile(acc_client, uuid): + """Show detailed info about device_profile.""" + columns = ( + "created_at", + "updated_at", + "uuid", + "name", + "groups", + ) + try: + device_profile = acc_client.get_device_profile(uuid) + except sdk_exc.ResourceNotFound: + raise exc.CommandError(_('device_profile not found: %s') % uuid) + formatters = { + 'data': utils.json_formatter, + } + data = device_profile.to_dict() + return columns, oscutils.get_dict_properties(data, columns, + formatters=formatters) diff --git a/cyborgclient/tests/unit/osc/fakes.py b/cyborgclient/tests/unit/osc/fakes.py new file mode 100644 index 0000000..2751cad --- /dev/null +++ b/cyborgclient/tests/unit/osc/fakes.py @@ -0,0 +1,58 @@ +# 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 sys + + +AUTH_TOKEN = "foobar" +AUTH_URL = "http://0.0.0.0" +API_VERSION = '2' + + +class FakeApp(object): + def __init__(self): + _stdout = None + self.client_manager = None + self.stdin = sys.stdin + self.stdout = _stdout or sys.stdout + self.stderr = sys.stderr + self.restapi = None + + +class FakeClientManager(object): + def __init__(self): + self.identity = None + self.auth_ref = None + self.interface = 'public' + self._region_name = 'RegionOne' + self.session = 'fake session' + self._api_version = {'accelerator': API_VERSION} + + +class FakeResource(object): + def __init__(self, manager, info, loaded=False): + self.__name__ = type(self).__name__ + self.manager = manager + self._info = info + self._add_details(info) + self._loaded = loaded + + def _add_details(self, info): + for (k, v) in info.items(): + setattr(self, k, v) + + def __repr__(self): + reprkeys = sorted(k for k in self.__dict__.keys() if k[0] != '_' and + k != 'manager') + info = ", ".join("%s=%s" % (k, getattr(self, k)) for k in reprkeys) + return "<%s %s>" % (self.__class__.__name__, info) diff --git a/cyborgclient/tests/unit/osc/test_plugin.py b/cyborgclient/tests/unit/osc/test_plugin.py new file mode 100644 index 0000000..10d0af0 --- /dev/null +++ b/cyborgclient/tests/unit/osc/test_plugin.py @@ -0,0 +1,72 @@ +# 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 mock +from openstack import connection as sdk_connection +import testtools + +from cyborgclient.osc import plugin + + +class TestPlugin(testtools.TestCase): + + @mock.patch.object(sdk_connection, 'Connection') + def test_create_connection_with_profile(self, mock_connection): + class FakeService(object): + interface = 'public' + region = 'a_region' + version = '2' + api_version = None + service_type = 'accelerator' + + mock_prof = mock.Mock() + mock_prof.get_services.return_value = [FakeService()] + mock_conn = mock.Mock() + mock_connection.return_value = mock_conn + kwargs = { + 'user_id': '123', + 'password': 'abc', + 'auth_url': 'test_url' + } + res = plugin.create_connection(mock_prof, **kwargs) + mock_connection.assert_called_once_with( + app_name=None, app_version=None, + config=mock.ANY, + accelerator_api_version=plugin.CURRENT_API_VERSION, + **kwargs + ) + self.assertEqual(mock_conn, res) + + @mock.patch.object(sdk_connection, 'Connection') + def test_create_connection_without_profile(self, mock_connection): + mock_conn = mock.Mock() + mock_connection.return_value = mock_conn + kwargs = { + 'interface': 'public', + 'region_name': 'RegionOne', + 'user_id': '123', + 'password': 'abc', + 'auth_url': 'test_url' + } + res = plugin.create_connection(**kwargs) + + mock_connection.assert_called_once_with( + app_name=None, app_version=None, + auth_url='test_url', + accelerator_api_version=plugin.CURRENT_API_VERSION, + config=None, + interface='public', + password='abc', + region_name='RegionOne', + user_id='123' + ) + self.assertEqual(mock_conn, res) diff --git a/cyborgclient/tests/unit/osc/v2/__init__.py b/cyborgclient/tests/unit/osc/v2/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/cyborgclient/tests/unit/osc/v2/fakes.py b/cyborgclient/tests/unit/osc/v2/fakes.py new file mode 100644 index 0000000..bbbb9df --- /dev/null +++ b/cyborgclient/tests/unit/osc/v2/fakes.py @@ -0,0 +1,122 @@ +# 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 cyborgclient.tests.unit.osc import fakes +import mock +from osc_lib.tests import utils +import uuid + +deployable_created_at = '2019-06-24T00:00:00.000000+00:00' +deployable_updated_at = '2019-06-24T11:11:11.111111+11:11' +deployable_uuid = uuid.uuid4().hex +deployable_name = 'fake_dep_name' +deployable_parent_id = None +deployable_root_id = 1 +deployable_num_accelerators = 4 +deployable_device_id = 0 + +DEPLOYABLE = { + 'created_at': deployable_created_at, + 'updated_at': deployable_updated_at, + 'id': deployable_uuid, + 'name': deployable_name, + 'parent_id': deployable_parent_id, + 'root_id': deployable_root_id, + 'num_accelerators': deployable_num_accelerators, + 'device_id': deployable_device_id, +} + +device_created_at = '2019-06-24T00:00:00.000000+00:00' +device_updated_at = '2019-06-24T22:22:22.222222+22:22' +device_id = 1 +device_uuid = uuid.uuid4().hex +device_name = 'fake_dev_name' +device_type = 'fake_dev_type' +device_vendor = '0x8086' +device_model = 'fake_dev_model' +device_hostname = 'fake_host' +device_std_board_info = '{"product_id": "0x09c4"}' +device_vendor_board_info = 'fake_vb_info' + +DEVICE = { + 'created_at': device_created_at, + 'updated_at': device_updated_at, + 'id': device_id, + 'uuid': device_uuid, + 'type': device_type, + 'vendor': device_vendor, + 'model': device_model, + 'hostname': device_hostname, + 'std_board_info': device_std_board_info, + 'vendor_board_info': device_vendor_board_info +} + +device_profile_created_at = '2019-06-24T00:00:00.000000+00:00' +device_profile_updated_at = '2019-06-24T11:11:11.111111+11:11' +device_profile_id = 1 +device_profile_uuid = uuid.uuid4().hex +device_profile_name = 'fake_devprof_name' +device_profile_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", + } +] + +DEVICE_PROFILE = { + 'created_at': device_profile_created_at, + 'updated_at': device_profile_updated_at, + 'id': device_profile_id, + 'uuid': device_profile_uuid, + 'name': device_profile_name, + 'groups': device_profile_groups, +} + +accelerator_request_uuid = uuid.uuid4().hex +accelerator_request_state = 'fake_state' +accelerator_request_device_profile_name = 'fake_arq_devprof_name' +accelerator_request_hostname = 'fake_arq_hostname' +accelerator_request_device_rp_uuid = 1 +accelerator_request_instance_uuid = 2 +accelerator_request_attach_handle_type = 3 +accelerator_request_attach_handle_info = 4 + +ACCELERATOR_REQUEST = { + 'uuid': accelerator_request_uuid, + 'state': accelerator_request_state, + 'device_profile_name': accelerator_request_device_profile_name, + 'hostname': accelerator_request_hostname, + 'device_rp_uuid': accelerator_request_device_rp_uuid, + 'instance_uuid': accelerator_request_instance_uuid, + 'attach_handle_type': accelerator_request_attach_handle_type, + 'attach_handle_info': accelerator_request_attach_handle_info, +} + + +class TestAccelerator(utils.TestCommand): + + def setUp(self): + super(TestAccelerator, self).setUp() + + self.app.client_manager.auth_ref = mock.MagicMock(auth_token="TOKEN") + self.app.client_manager.accelerator = mock.MagicMock() + + +class FakeAcceleratorResource(fakes.FakeResource): + + def get_keys(self): + return {'property': 'value'} diff --git a/cyborgclient/tests/unit/osc/v2/test_accelerator_request.py b/cyborgclient/tests/unit/osc/v2/test_accelerator_request.py new file mode 100644 index 0000000..c45b610 --- /dev/null +++ b/cyborgclient/tests/unit/osc/v2/test_accelerator_request.py @@ -0,0 +1,104 @@ +# 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 copy + +from cyborgclient.osc.v2 import accelerator_request as osc_accelerator_request +from cyborgclient.tests.unit.osc.v2 import fakes as acc_fakes + + +class TestAcceleratorRequest(acc_fakes.TestAccelerator): + + def setUp(self): + super(TestAcceleratorRequest, self).setUp() + + self.mock_acc_client = self.app.client_manager.accelerator + self.mock_acc_client.reset_mock() + + +class TestAcceleratorRequestList(TestAcceleratorRequest): + + def setUp(self): + super(TestAcceleratorRequestList, self).setUp() + + self.mock_acc_client.accelerator_requests.return_value = [ + acc_fakes.FakeAcceleratorResource( + None, + copy.deepcopy(acc_fakes.ACCELERATOR_REQUEST), + loaded=True) + ] + + self.cmd = osc_accelerator_request.ListAcceleratorRequest( + self.app, None + ) + + def test_accelerator_request_list(self): + arglist = [] + verifylist = [] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + kwargs = {} + + self.mock_acc_client.accelerator_requests.assert_called_with(**kwargs) + + collist = ( + 'uuid', + 'state', + 'device_profile_name', + 'instance_uuid', + 'attach_handle_type', + 'attach_handle_info', + ) + self.assertEqual(collist, columns) + + datalist = [( + acc_fakes.accelerator_request_uuid, + acc_fakes.accelerator_request_state, + acc_fakes.accelerator_request_device_profile_name, + acc_fakes.accelerator_request_instance_uuid, + acc_fakes.accelerator_request_attach_handle_type, + acc_fakes.accelerator_request_attach_handle_info, + ), ] + self.assertEqual(datalist, list(data)) + + def test_accelerator_request_list_long(self): + arglist = ['--long'] + verifylist = [] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + kwargs = {} + + self.mock_acc_client.accelerator_requests.assert_called_with(**kwargs) + + collist = ( + 'uuid', + 'state', + 'device_profile_name', + 'hostname', + 'device_rp_uuid', + 'instance_uuid', + 'attach_handle_type', + 'attach_handle_info', + ) + self.assertEqual(collist, columns) + + datalist = [( + acc_fakes.accelerator_request_uuid, + acc_fakes.accelerator_request_state, + acc_fakes.accelerator_request_device_profile_name, + acc_fakes.accelerator_request_hostname, + acc_fakes.accelerator_request_device_rp_uuid, + acc_fakes.accelerator_request_instance_uuid, + acc_fakes.accelerator_request_attach_handle_type, + acc_fakes.accelerator_request_attach_handle_info, + ), ] + self.assertEqual(datalist, list(data)) diff --git a/cyborgclient/tests/unit/osc/v2/test_deployable.py b/cyborgclient/tests/unit/osc/v2/test_deployable.py new file mode 100644 index 0000000..50a42f2 --- /dev/null +++ b/cyborgclient/tests/unit/osc/v2/test_deployable.py @@ -0,0 +1,96 @@ +# 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 copy + +from cyborgclient.osc.v2 import deployable as osc_deployable +from cyborgclient.tests.unit.osc.v2 import fakes as acc_fakes + + +class TestDeployable(acc_fakes.TestAccelerator): + + def setUp(self): + super(TestDeployable, self).setUp() + + self.mock_acc_client = self.app.client_manager.accelerator + self.mock_acc_client.reset_mock() + + +class TestDeployableList(TestDeployable): + + def setUp(self): + super(TestDeployableList, self).setUp() + + self.mock_acc_client.deployables.return_value = [ + acc_fakes.FakeAcceleratorResource( + None, + copy.deepcopy(acc_fakes.DEPLOYABLE), + loaded=True) + ] + + self.cmd = osc_deployable.ListDeployable(self.app, None) + + def test_deployable_list(self): + arglist = [] + verifylist = [] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + kwargs = {} + + self.mock_acc_client.deployables.assert_called_with(**kwargs) + + collist = ( + 'uuid', + 'name', + 'device_id' + ) + self.assertEqual(collist, columns) + + datalist = [( + acc_fakes.deployable_uuid, + acc_fakes.deployable_name, + acc_fakes.deployable_device_id, + ), ] + self.assertEqual(datalist, list(data)) + + def test_deployable_list_long(self): + arglist = ['--long'] + verifylist = [] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + kwargs = {} + + self.mock_acc_client.deployables.assert_called_with(**kwargs) + + collist = ( + 'created_at', + 'updated_at', + 'uuid', + 'parent_id', + 'root_id', + 'name', + 'num_accelerators', + 'device_id' + ) + self.assertEqual(collist, columns) + + datalist = [( + acc_fakes.deployable_created_at, + acc_fakes.deployable_updated_at, + acc_fakes.deployable_uuid, + acc_fakes.deployable_parent_id, + acc_fakes.deployable_root_id, + acc_fakes.deployable_name, + acc_fakes.deployable_num_accelerators, + acc_fakes.deployable_device_id, + ), ] + self.assertEqual(datalist, list(data)) diff --git a/cyborgclient/tests/unit/osc/v2/test_device.py b/cyborgclient/tests/unit/osc/v2/test_device.py new file mode 100644 index 0000000..98be272 --- /dev/null +++ b/cyborgclient/tests/unit/osc/v2/test_device.py @@ -0,0 +1,102 @@ +# 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 copy + +from cyborgclient.osc.v2 import device as osc_device +from cyborgclient.tests.unit.osc.v2 import fakes as acc_fakes + + +class TestDevice(acc_fakes.TestAccelerator): + + def setUp(self): + super(TestDevice, self).setUp() + + self.mock_acc_client = self.app.client_manager.accelerator + self.mock_acc_client.reset_mock() + + +class TestDeviceList(TestDevice): + + def setUp(self): + super(TestDeviceList, self).setUp() + + self.mock_acc_client.devices.return_value = [ + acc_fakes.FakeAcceleratorResource( + None, + copy.deepcopy(acc_fakes.DEVICE), + loaded=True) + ] + + self.cmd = osc_device.ListDevice(self.app, None) + + def test_device_list(self): + arglist = [] + verifylist = [] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + kwargs = {} + + self.mock_acc_client.devices.assert_called_with(**kwargs) + + collist = ( + 'uuid', + 'type', + 'vendor', + 'hostname', + 'std_board_info' + ) + self.assertEqual(collist, columns) + + datalist = [( + acc_fakes.device_uuid, + acc_fakes.device_type, + acc_fakes.device_vendor, + acc_fakes.device_hostname, + acc_fakes.device_std_board_info, + ), ] + self.assertEqual(datalist, list(data)) + + def test_device_list_long(self): + arglist = ['--long'] + verifylist = [] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + kwargs = {} + + self.mock_acc_client.devices.assert_called_with(**kwargs) + + collist = ( + 'created_at', + 'updated_at', + 'uuid', + 'type', + 'vendor', + 'model', + 'hostname', + 'std_board_info', + 'vendor_board_info', + ) + self.assertEqual(collist, columns) + + datalist = [( + acc_fakes.device_created_at, + acc_fakes.device_updated_at, + acc_fakes.device_uuid, + acc_fakes.device_type, + acc_fakes.device_vendor, + acc_fakes.device_model, + acc_fakes.device_hostname, + acc_fakes.device_std_board_info, + acc_fakes.device_vendor_board_info, + ), ] + self.assertEqual(datalist, list(data)) diff --git a/cyborgclient/tests/unit/osc/v2/test_device_profile.py b/cyborgclient/tests/unit/osc/v2/test_device_profile.py new file mode 100644 index 0000000..881b0ad --- /dev/null +++ b/cyborgclient/tests/unit/osc/v2/test_device_profile.py @@ -0,0 +1,90 @@ +# 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 copy + +from cyborgclient.osc.v2 import device_profile as osc_device_profile +from cyborgclient.tests.unit.osc.v2 import fakes as acc_fakes + + +class TestDeviceProfile(acc_fakes.TestAccelerator): + + def setUp(self): + super(TestDeviceProfile, self).setUp() + + self.mock_acc_client = self.app.client_manager.accelerator + self.mock_acc_client.reset_mock() + + +class TestDeviceProfileList(TestDeviceProfile): + + def setUp(self): + super(TestDeviceProfileList, self).setUp() + + self.mock_acc_client.device_profiles.return_value = [ + acc_fakes.FakeAcceleratorResource( + None, + copy.deepcopy(acc_fakes.DEVICE_PROFILE), + loaded=True) + ] + + self.cmd = osc_device_profile.ListDeviceProfile(self.app, None) + + def test_device_profile_list(self): + arglist = [] + verifylist = [] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + kwargs = {} + + self.mock_acc_client.device_profiles.assert_called_with(**kwargs) + + collist = ( + 'uuid', + 'name', + 'groups', + ) + self.assertEqual(collist, columns) + + datalist = [( + acc_fakes.device_profile_uuid, + acc_fakes.device_profile_name, + acc_fakes.device_profile_groups, + ), ] + self.assertEqual(datalist, list(data)) + + def test_device_profile_list_long(self): + arglist = ['--long'] + verifylist = [] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + kwargs = {} + + self.mock_acc_client.device_profiles.assert_called_with(**kwargs) + + collist = ( + 'created_at', + 'updated_at', + 'uuid', + 'name', + 'groups', + ) + self.assertEqual(collist, columns) + + datalist = [( + acc_fakes.device_profile_created_at, + acc_fakes.device_profile_updated_at, + acc_fakes.device_profile_uuid, + acc_fakes.device_profile_name, + acc_fakes.device_profile_groups, + ), ] + self.assertEqual(datalist, list(data)) diff --git a/setup.cfg b/setup.cfg index 225712c..f39ce39 100644 --- a/setup.cfg +++ b/setup.cfg @@ -31,6 +31,23 @@ openstack.cli.extension = openstack.accelerator.v1 = accelerator_list = cyborgclient.osc.v1.accelerator:ListAccelerator +openstack.accelerator.v2 = + accelerator_deployable_list = cyborgclient.osc.v2.deployable:ListDeployable + accelerator_deployable_program = cyborgclient.osc.v2.deployable:ProgramDeployable + accelerator_deployable_show = cyborgclient.osc.v2.deployable:ShowDeployable + accelerator_device_list = cyborgclient.osc.v2.device:ListDevice + accelerator_device_show = cyborgclient.osc.v2.device.ShowDevice + accelerator_device_profile_list = cyborgclient.osc.v2.device_profile:ListDeviceProfile + accelerator_device_profile_create = cyborgclient.osc.v2.device_profile:CreateDeviceProfile + accelerator_device_profile_delete = cyborgclient.osc.v2.device_profile:DeleteDeviceProfile + accelerator_device_profile_show = cyborgclient.osc.v2.device_profile:ShowDeviceProfile + accelerator_arq_list = cyborgclient.osc.v2.accelerator_request:ListAcceleratorRequest + accelerator_arq_create = cyborgclient.osc.v2.accelerator_request:CreateAcceleratorRequest + accelerator_arq_delete = cyborgclient.osc.v2.accelerator_request:DeleteAcceleratorRequest + accelerator_arq_show = cyborgclient.osc.v2.accelerator_request:ShowAcceleratorRequest + accelerator_arq_bind = cyborgclient.osc.v2.accelerator_request:BindAcceleratorRequest + accelerator_arq_unbind = cyborgclient.osc.v2.accelerator_request:UnbindAcceleratorRequest + [build_sphinx] all-files = 1 warning-is-error = 1 diff --git a/test-requirements.txt b/test-requirements.txt index a6ef908..1675347 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -12,5 +12,6 @@ oslotest>=1.10.0 # Apache-2.0 stestr>=1.0.0 # Apache-2.0 testtools>=1.4.0 # MIT openstackdocstheme>=1.20.0 # Apache-2.0 +requests-mock>=0.6.0 # Apache-2.0 # releasenotes reno>=1.8.0 # Apache-2.0 diff --git a/tox.ini b/tox.ini index 996478c..472342a 100644 --- a/tox.ini +++ b/tox.ini @@ -45,6 +45,20 @@ commands = [testenv:debug] commands = oslo_debug_helper {posargs} +[testenv:osc_plugins] +deps = + -c{env:UPPER_CONSTRAINTS_FILE:https://opendev.org/openstack/requirements/raw/branch/master/upper-constraints.txt} + -r{toxinidir}/requirements.txt + ../../x/pbrx +whitelist_externals = + bash +commands = + # bash wrapper is required to handle a subshell of find. + bash ./tests/install-siblings.sh + pbr freeze + openstack --version + python tests/check_osc_commands.py + [flake8] # E123, E125 skipped as they are invalid PEP-8.