v2 API Support(using openstack-sdk)

Modifying cyborgclient to supporting v2 API,
and using openstack sdk.
This patch depends openstacksdk patch[1].

command usage:
$openstack accelerator *commands below*

deployable ( list | show | program )
device list
device profile ( list | create | delete | show )
arq ( list | create | delete | show | bind | unbind )

[1] https://review.opendev.org/#/c/679914/

Change-Id: If6bb52b453c1aa7fb328fd08ebbb79312fa7d299
Story: 2006930
This commit is contained in:
Shogo Saito
2020-01-27 18:22:57 +09:00
parent 817602eb98
commit c4770df453
18 changed files with 1590 additions and 36 deletions

View File

@@ -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

View File

@@ -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

View File

View File

@@ -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='<device_profile_name>',
help=_("The name of device_profile for accelerator_request."))
parser.add_argument(
'device_profile_group_id',
metavar='<device_profile_group_id>',
help=_("The group id of device_profile \
for the accelerator_request."))
parser.add_argument(
'--image-uuid',
metavar='<glance_image_uuid>',
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="<uuid>",
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="<uuid>",
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='<accelerator_request>',
help=_("UUID of the accelerator request")
)
parser.add_argument(
'hostname',
metavar='<hostname>',
help=_("Bind hostname of the accelerator request")
)
parser.add_argument(
"instance_uuid",
metavar="<instance_uuid>",
help=_("Bind instance_uuid of the accelerator request")
)
parser.add_argument(
"device_rp_uuid",
metavar="<device_rp_uuid>",
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='<accelerator_request>',
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)

View File

@@ -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="<uuid>",
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='<deployable_uuid>',
default=False,
help=_("Deployable UUID for reconfigure.")
)
parser.add_argument(
'image_uuid',
metavar='<image_uuid>',
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)

View File

@@ -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="<uuid>",
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)

View File

@@ -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='<name>',
help=_("Unique name for the device_profile."))
parser.add_argument(
'groups',
metavar='<groups>',
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="<uuid>",
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="<uuid>",
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)

View File

@@ -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)

View File

@@ -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)

View File

@@ -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'}

View File

@@ -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))

View File

@@ -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))

View File

@@ -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))

View File

@@ -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))

View File

@@ -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

View File

@@ -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

14
tox.ini
View File

@@ -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.