diff --git a/sysinv/cgts-client/cgts-client/cgtsclient/v1/client.py b/sysinv/cgts-client/cgts-client/cgtsclient/v1/client.py index ef9d0bb2b3..15b3d356d6 100644 --- a/sysinv/cgts-client/cgts-client/cgtsclient/v1/client.py +++ b/sysinv/cgts-client/cgts-client/cgtsclient/v1/client.py @@ -71,6 +71,7 @@ from cgtsclient.v1 import port from cgtsclient.v1 import ptp from cgtsclient.v1 import ptp_instance from cgtsclient.v1 import ptp_interface +from cgtsclient.v1 import ptp_parameter from cgtsclient.v1 import registry_image from cgtsclient.v1 import remotelogging from cgtsclient.v1 import restore @@ -122,6 +123,7 @@ class Client(http.HTTPClient): self.ptp = ptp.ptpManager(self) self.ptp_instance = ptp_instance.PtpInstanceManager(self) self.ptp_interface = ptp_interface.PtpInterfaceManager(self) + self.ptp_parameter = ptp_parameter.PtpParameterManager(self) self.iextoam = iextoam.iextoamManager(self) self.controller_fs = controller_fs.ControllerFsManager(self) self.storage_backend = storage_backend.StorageBackendManager(self) diff --git a/sysinv/cgts-client/cgts-client/cgtsclient/v1/ptp_instance_shell.py b/sysinv/cgts-client/cgts-client/cgtsclient/v1/ptp_instance_shell.py index deac5a2afb..f8ba872e48 100644 --- a/sysinv/cgts-client/cgts-client/cgtsclient/v1/ptp_instance_shell.py +++ b/sysinv/cgts-client/cgts-client/cgtsclient/v1/ptp_instance_shell.py @@ -25,10 +25,6 @@ def _print_ptp_instance_show(ptp_instance_obj): def do_ptp_instance_list(cc, args): """List all PTP instances, in any host.""" ptp_instances = cc.ptp_instance.list() - for instance in ptp_instances[:]: - ihost = ihost_utils._find_ihost(cc, instance.host_uuid) - setattr(instance, 'hostname', ihost.hostname) - field_labels = ['uuid', 'name', 'service', 'hostname'] fields = ['uuid', 'name', 'service', 'hostname'] utils.print_list(ptp_instances, fields, field_labels) @@ -53,8 +49,6 @@ def do_host_ptp_instance_list(cc, args): def do_ptp_instance_show(cc, args): """Show PTP instance attributes.""" ptp_instance = ptp_instance_utils._find_ptp_instance(cc, args.nameoruuid) - ihost = ihost_utils._find_ihost(cc, ptp_instance.host_uuid) - setattr(ptp_instance, 'hostname', ihost.hostname) _print_ptp_instance_show(ptp_instance) @@ -87,8 +81,6 @@ def do_ptp_instance_add(cc, args): except exc.HTTPNotFound: raise exc.CommandError('PTP instance just created not found: %s' % uuid) - if ptp_instance: - setattr(ptp_instance, 'hostname', ihost.hostname) _print_ptp_instance_show(ptp_instance) diff --git a/sysinv/cgts-client/cgts-client/cgtsclient/v1/ptp_parameter.py b/sysinv/cgts-client/cgts-client/cgtsclient/v1/ptp_parameter.py new file mode 100644 index 0000000000..f0c4327e9e --- /dev/null +++ b/sysinv/cgts-client/cgts-client/cgtsclient/v1/ptp_parameter.py @@ -0,0 +1,69 @@ +######################################################################## +# +# Copyright (c) 2021 Wind River Systems, Inc. +# +# SPDX-License-Identifier: Apache-2.0 +# +######################################################################## + +from cgtsclient.common import base +from cgtsclient import exc +from cgtsclient.v1 import options + + +CREATION_ATTRIBUTES = ['name', 'value', 'type', 'foreign_uuid'] + + +class PtpParameter(base.Resource): + def __repr__(self): + return "" % self._info + + +class PtpParameterManager(base.Manager): + resource_class = PtpParameter + + @staticmethod + def _path(ptp_parameter_id=None): + return '/v1/ptp_parameters/%s' % ptp_parameter_id if ptp_parameter_id \ + else '/v1/ptp_parameters' + + def list(self, q=None): + return self._list(options.build_url(self._path(), q), "ptp_parameters") + + def list_by_ptp_instance(self, ptp_instance_uuid): + path = '/v1/ptp_instances/%s/ptp_parameters' % ptp_instance_uuid + return self._list(path, "ptp_parameters") + + def list_by_interface(self, interface_uuid): + path = '/v1/iinterfaces/%s/ptp_parameters' % interface_uuid + return self._list(path, "ptp_parameters") + + def get(self, ptp_parameter_id): + try: + return self._list(self._path(ptp_parameter_id))[0] + except IndexError: + return None + + def create(self, **kwargs): + data = {} + for (key, value) in kwargs.items(): + if key in CREATION_ATTRIBUTES: + data[key] = value + else: + raise exc.InvalidAttribute('%s' % key) + return self._create(self._path(), data) + + def update(self, ptp_parameter_id, patch): + return self._update(self._path(ptp_parameter_id), patch) + + def delete(self, ptp_parameter_id): + return self._delete(self._path(ptp_parameter_id)) + + +def _find_ptp_parameter(cc, id): + try: + parameter = cc.ptp_parameter.get(id) + except exc.HTTPNotFound: + raise exc.CommandError('PTP parameter not found: %s' % id) + else: + return parameter diff --git a/sysinv/cgts-client/cgts-client/cgtsclient/v1/ptp_parameter_shell.py b/sysinv/cgts-client/cgts-client/cgtsclient/v1/ptp_parameter_shell.py new file mode 100644 index 0000000000..d4a1a503a3 --- /dev/null +++ b/sysinv/cgts-client/cgts-client/cgtsclient/v1/ptp_parameter_shell.py @@ -0,0 +1,151 @@ +######################################################################## +# +# Copyright (c) 2021 Wind River Systems, Inc. +# +# SPDX-License-Identifier: Apache-2.0 +# +######################################################################## + +from cgtsclient.common import utils +from cgtsclient import exc +from cgtsclient.v1 import ptp_parameter as ptp_parameter_utils + + +def _owner_formatter(values): + result = [] + result.append(str(values['name'] + + " of type " + values['type'] + + " at " + values['hostname'])) + return result + + +def _print_ptp_parameter_show(ptp_parameter_obj): + fields = ['uuid', 'name', 'value', 'type', + 'owner', 'foreign_uuid', 'created_at', 'updated_at'] + labels = ['uuid', 'name', 'value', 'type', + 'owned_by', 'owner_id', 'created_at', 'updated_at'] + data = [(f, getattr(ptp_parameter_obj, f, '')) for f in fields] + utils.print_tuple_list(data, labels, + formatters={'owner': _owner_formatter}) + + +@utils.arg('-t', '--type', + metavar='', + choices=['ptp-instance', 'ptp-interface'], + help='List PTP parameters for a specific owner type') +@utils.arg('-u', '--foreign_uuid', + metavar='', + help='List PTP parameters associated to specified owner') +def do_ptp_parameter_list(cc, args): + """List all PTP parameters, in any host.""" + missing = ((args.type is None) and (args.foreign_uuid is not None)) or \ + ((args.type is not None) and (args.foreign_uuid is None)) + if missing: + raise exc.CommandError("Both 'type' and 'foreign_uuid' " + "must be provided") + ptp_parameters = None + if args.type == 'ptp-instance': + ptp_parameters = cc.ptp_parameter.list_by_ptp_instance( + args.foreign_uuid) + elif args.type == 'ptp-interface': + ptp_parameters = cc.ptp_parameter.list_by_interface( + args.foreign_uuid) + if ptp_parameters: + fields = ['uuid', 'name', 'value'] + labels = ['uuid', 'name', 'value'] + else: + ptp_parameters = cc.ptp_parameter.list() + for ptp_parameter in ptp_parameters: + owner_dict = getattr(ptp_parameter, 'owner', '') + setattr(ptp_parameter, 'owner_name', owner_dict['name']) + setattr(ptp_parameter, 'owner_host', owner_dict['hostname']) + + fields = ['uuid', + 'name', + 'value', + 'type', + 'owner_name', + 'owner_host', + 'foreign_uuid'] + labels = ['uuid', + 'name', + 'value', + 'owner_type', + 'owner_name', + 'owner_host', + 'owner_uuid'] + + utils.print_list(ptp_parameters, fields, labels) + + +@utils.arg('uuid', + metavar='', + help="UUID of PTP parameter") +def do_ptp_parameter_show(cc, args): + """Show PTP parameter attributes.""" + ptp_parameter = ptp_parameter_utils._find_ptp_parameter(cc, args.uuid) + _print_ptp_parameter_show(ptp_parameter) + + +@utils.arg('name', + metavar='', + help="Name of PTP parameter [REQUIRED]") +@utils.arg('value', + metavar='', + help="Value of PTP parameter [REQUIRED]") +@utils.arg('type', + metavar='', + choices=['ptp-instance', 'ptp-interface'], + help="Type of parameter owner ('ptp-instance' or 'ptp-interface') " + "[REQUIRED]") +@utils.arg('foreign_uuid', + metavar='', + help="UUID of parameter owner [REQUIRED]") +def do_ptp_parameter_add(cc, args): + """Add a PTP parameter.""" + + field_list = ['name', 'value', 'type', 'foreign_uuid'] + + # Prune input fields down to required/expected values + data = dict((k, v) for (k, v) in vars(args).items() + if k in field_list and not (v is None)) + + ptp_parameter = cc.ptp_parameter.create(**data) + uuid = getattr(ptp_parameter, 'uuid', '') + try: + ptp_parameter = cc.ptp_parameter.get(uuid) + except exc.HTTPNotFound: + raise exc.CommandError('PTP parameter just created not found: %s' % + uuid) + _print_ptp_parameter_show(ptp_parameter) + + +@utils.arg('uuid', + metavar='', + help="UUID of PTP parameter") +@utils.arg('value', + metavar='', + help="New value of parameter") +def do_ptp_parameter_modify(cc, args): + """Change PTP parameter value.""" + + field_list = ['value'] + + data = dict((k, v) for (k, v) in vars(args).items() + if k in field_list and not (v is None)) + + patch = [] + for (k, v) in data.items(): + patch.append({'op': 'replace', 'path': '/' + k, 'value': v}) + + ptp_parameter = cc.ptp_parameter.update(args.uuid, patch) + _print_ptp_parameter_show(ptp_parameter) + + +@utils.arg('uuid', + metavar='', + help="UUID of PTP parameter") +def do_ptp_parameter_delete(cc, args): + """Delete a PTP parameter.""" + cc.ptp_parameter.delete(args.uuid) + print('Deleted PTP parameter: %s' % args.uuid) diff --git a/sysinv/cgts-client/cgts-client/cgtsclient/v1/shell.py b/sysinv/cgts-client/cgts-client/cgtsclient/v1/shell.py index c37d9ae37d..e4c29cf410 100644 --- a/sysinv/cgts-client/cgts-client/cgtsclient/v1/shell.py +++ b/sysinv/cgts-client/cgts-client/cgtsclient/v1/shell.py @@ -56,6 +56,7 @@ from cgtsclient.v1 import pci_device_shell from cgtsclient.v1 import port_shell from cgtsclient.v1 import ptp_instance_shell from cgtsclient.v1 import ptp_interface_shell +from cgtsclient.v1 import ptp_parameter_shell from cgtsclient.v1 import ptp_shell from cgtsclient.v1 import registry_image_shell from cgtsclient.v1 import remotelogging_shell @@ -79,6 +80,7 @@ COMMAND_MODULES = [ ptp_shell, ptp_instance_shell, ptp_interface_shell, + ptp_parameter_shell, iextoam_shell, controller_fs_shell, storage_backend_shell, diff --git a/sysinv/sysinv/sysinv/sysinv/api/controllers/v1/__init__.py b/sysinv/sysinv/sysinv/sysinv/api/controllers/v1/__init__.py index e8934d3ca4..28d01742ce 100644 --- a/sysinv/sysinv/sysinv/sysinv/api/controllers/v1/__init__.py +++ b/sysinv/sysinv/sysinv/sysinv/api/controllers/v1/__init__.py @@ -67,6 +67,7 @@ from sysinv.api.controllers.v1 import port from sysinv.api.controllers.v1 import ptp from sysinv.api.controllers.v1 import ptp_instance from sysinv.api.controllers.v1 import ptp_interface +from sysinv.api.controllers.v1 import ptp_parameter from sysinv.api.controllers.v1 import pv from sysinv.api.controllers.v1 import registry_image from sysinv.api.controllers.v1 import remotelogging @@ -154,6 +155,9 @@ class V1(base.APIBase): ptp_interfaces = [link.Link] "Links to the ptp_interfaces resource" + ptp_parameters = [link.Link] + "Links to the ptp_parameters resource" + iextoam = [link.Link] "Links to the iextoam resource" @@ -469,6 +473,13 @@ class V1(base.APIBase): bookmark=True) ] + v1.ptp_parameters = [link.Link.make_link('self', pecan.request.host_url, + 'ptp_parameters', ''), + link.Link.make_link('bookmark', + pecan.request.host_url, + 'ptp_parameters', '', + bookmark=True)] + v1.iextoam = [link.Link.make_link('self', pecan.request.host_url, 'iextoam', ''), link.Link.make_link('bookmark', @@ -913,6 +924,7 @@ class Controller(rest.RestController): ptp = ptp.PTPController() ptp_instances = ptp_instance.PtpInstanceController() ptp_interfaces = ptp_interface.PtpInterfaceController() + ptp_parameters = ptp_parameter.PtpParameterController() iextoam = network_oam.OAMNetworkController() controller_fs = controller_fs.ControllerFsController() storage_backend = storage_backend.StorageBackendController() diff --git a/sysinv/sysinv/sysinv/sysinv/api/controllers/v1/interface.py b/sysinv/sysinv/sysinv/sysinv/api/controllers/v1/interface.py index ab6bac8952..8f53eca17a 100644 --- a/sysinv/sysinv/sysinv/sysinv/api/controllers/v1/interface.py +++ b/sysinv/sysinv/sysinv/sysinv/api/controllers/v1/interface.py @@ -46,6 +46,7 @@ from sysinv.api.controllers.v1 import types from sysinv.api.controllers.v1 import utils from sysinv.api.controllers.v1 import interface_network from sysinv.api.controllers.v1 import interface_datanetwork +from sysinv.api.controllers.v1 import ptp_parameter from sysinv.common import constants from sysinv.common import exception from sysinv.common import utils as cutils @@ -302,6 +303,9 @@ class InterfaceController(rest.RestController): routes = route.RouteController(parent="iinterfaces") "Expose routes as a sub-element of interface" + ptp_parameters = ptp_parameter.PtpParameterController(parent="iinterface") + "Expose PTP parameters as a sub-element of interface" + interface_networks = interface_network.InterfaceNetworkController( parent="iinterfaces") "Expose interface_networks as a sub-element of interface" diff --git a/sysinv/sysinv/sysinv/sysinv/api/controllers/v1/ptp_instance.py b/sysinv/sysinv/sysinv/sysinv/api/controllers/v1/ptp_instance.py index 6abf7dd464..b3b3befd9b 100644 --- a/sysinv/sysinv/sysinv/sysinv/api/controllers/v1/ptp_instance.py +++ b/sysinv/sysinv/sysinv/sysinv/api/controllers/v1/ptp_instance.py @@ -15,8 +15,10 @@ from oslo_log import log from sysinv._i18n import _ from sysinv.api.controllers.v1 import base from sysinv.api.controllers.v1 import collection +from sysinv.api.controllers.v1 import ptp_parameter from sysinv.api.controllers.v1 import types from sysinv.api.controllers.v1 import utils +from sysinv.common import constants from sysinv.common import exception from sysinv.common import utils as cutils from sysinv import objects @@ -53,10 +55,16 @@ class PtpInstance(base.APIBase): host_uuid = types.uuid "UUID of the host the PTP instance is associated to" + hostname = wtypes.text + "Name of the host the PTP instance is associated to" + name = wtypes.text "Name given to the PTP instance" - service = wtypes.Enum(str, 'ptp4l', 'phc2sys', 'ts2phc') + service = wtypes.Enum(str, + constants.PTP_INSTANCE_TYPE_PTP4L, + constants.PTP_INSTANCE_TYPE_PHC2SYS, + constants.PTP_INSTANCE_TYPE_TS2PHC) "Type of service of the PTP instance" capabilities = {wtypes.text: utils.ValidTypes(wtypes.text, @@ -76,6 +84,7 @@ class PtpInstance(base.APIBase): if not expand: ptp_instance.unset_fields_except(['uuid', 'host_uuid', + 'hostname', 'name', 'service', 'capabilities', @@ -114,11 +123,15 @@ LOCK_NAME = 'PtpInstanceController' class PtpInstanceController(rest.RestController): """REST controller for PTP instance.""" + ptp_parameters = ptp_parameter.PtpParameterController( + parent="ptp_instance") + "Expose PTP parameters as a sub-element of PTP instances" + def __init__(self, from_ihosts=False): self._from_ihosts = from_ihosts def _get_ptp_instance_collection( - self, host_uuid, marker=None, limit=None, sort_key=None, + self, host_uuid=None, marker=None, limit=None, sort_key=None, sort_dir=None, expand=False, resource_url=None): LOG.debug("PtpInstanceController._get_ptp_instance_collection: " "from_ihosts %s host_uuid %s" % (self._from_ihosts, @@ -186,6 +199,13 @@ class PtpInstanceController(rest.RestController): ptp_instance_dict = ptp_instance.as_dict() LOG.debug("PtpInstanceController.post: %s" % ptp_instance_dict) + # Get rid of hostname (if any) to create the PTP instance + try: + ptp_instance_dict.pop('hostname') + except KeyError: + LOG.debug("PtpInstanceController.post: no hostname in %s" % + ptp_instance_dict) + # Replace host UUID by host ID host_uuid = ptp_instance_dict.pop('host_uuid') try: @@ -195,8 +215,8 @@ class PtpInstanceController(rest.RestController): raise wsme.exc.ClientSideError(msg) ptp_instance_dict['host_id'] = ihost_obj['id'] - result = pecan.request.dbapi.ptp_instance_create(ptp_instance_dict) - return PtpInstance.convert_with_links(result) + return PtpInstance.convert_with_links( + pecan.request.dbapi.ptp_instance_create(ptp_instance_dict)) @cutils.synchronized(LOCK_NAME) @wsme_pecan.wsexpose(None, types.uuid, status_code=204) @@ -208,14 +228,24 @@ class PtpInstanceController(rest.RestController): # Only allow delete if there are no associated interfaces and # parameters - parameters = pecan.request.dbapi.ptp_parameters_get_by_foreign_uuid( + parameters = pecan.request.dbapi.ptp_parameters_get_by_owner( ptp_instance_uuid) - interfaces = pecan.request.dbapi.ptp_interfaces_get_by_instance( - ptp_instance_uuid) - if parameters or interfaces: + if parameters: + names = [str(p['name']) for p in parameters] raise wsme.exc.ClientSideError( - _("PTP instance %s has still parameters or associated " - "interfaces. Check both ptp-interfaces and ptp-parameters.") - % ptp_instance_uuid) + "PTP instance %s is still associated with PTP parameter(s): %s" + % (ptp_instance_uuid, names)) + ptp_instance_obj = objects.ptp_instance.get_by_uuid( + pecan.request.context, ptp_instance_uuid) + interfaces = pecan.request.dbapi.ptp_interfaces_get_by_instance( + ptp_instance_obj.id) + if interfaces: + names = [str(i['ifname']) for i in interfaces] + raise wsme.exc.ClientSideError( + "PTP instance %s is still associated with PTP interface(s): %s" + % (ptp_instance_uuid, names)) + + LOG.debug("PtpInstanceController.delete: all clear for %s" % + ptp_instance_uuid) pecan.request.dbapi.ptp_instance_destroy(ptp_instance_uuid) diff --git a/sysinv/sysinv/sysinv/sysinv/api/controllers/v1/ptp_interface.py b/sysinv/sysinv/sysinv/sysinv/api/controllers/v1/ptp_interface.py index aeede88a19..7cef666385 100644 --- a/sysinv/sysinv/sysinv/sysinv/api/controllers/v1/ptp_interface.py +++ b/sysinv/sysinv/sysinv/sysinv/api/controllers/v1/ptp_interface.py @@ -9,6 +9,7 @@ import pecan from pecan import rest import six +import wsme from wsme import types as wtypes import wsmeext.pecan as wsme_pecan @@ -206,8 +207,20 @@ class PtpInterfaceController(rest.RestController): def delete(self, ptp_interface_uuid): """Delete a PTP interface.""" try: - ptp_interface = objects.ptp_interface.get_by_uuid(pecan.request.context, - ptp_interface_uuid) + ptp_interface = objects.ptp_interface.get_by_uuid( + pecan.request.context, ptp_interface_uuid) except exception.PtpInterfaceNotFound: raise + + # Only allow delete if there are no associated parameters + parameters = pecan.request.dbapi.ptp_parameters_get_by_owner( + ptp_interface_uuid) + if parameters: + names = [str(p['name']) for p in parameters] + raise wsme.exc.ClientSideError( + "PTP interface %s has PTP parameter(s): %s" + % (ptp_interface_uuid, names)) + + LOG.debug("PtpInterfaceController.delete: all clear for %s" % + ptp_interface_uuid) pecan.request.dbapi.ptp_interface_destroy(ptp_interface.uuid) diff --git a/sysinv/sysinv/sysinv/sysinv/api/controllers/v1/ptp_parameter.py b/sysinv/sysinv/sysinv/sysinv/api/controllers/v1/ptp_parameter.py new file mode 100644 index 0000000000..488e795ceb --- /dev/null +++ b/sysinv/sysinv/sysinv/sysinv/api/controllers/v1/ptp_parameter.py @@ -0,0 +1,262 @@ +# +# Copyright (c) 2021 Wind River Systems, Inc. +# +# SPDX-License-Identifier: Apache-2.0 +# + +import jsonpatch +import pecan +from pecan import rest +import wsme +from wsme import types as wtypes +import wsmeext.pecan as wsme_pecan + +from oslo_log import log +from sysinv._i18n import _ +from sysinv.api.controllers.v1 import base +from sysinv.api.controllers.v1 import collection +from sysinv.api.controllers.v1 import types +from sysinv.api.controllers.v1 import utils +from sysinv.common import constants +from sysinv.common import exception +from sysinv.common import utils as cutils +from sysinv import objects + +LOG = log.getLogger(__name__) + + +class PtpParameterPatchType(types.JsonPatchType): + @staticmethod + def mandatory_attrs(): + return [] + + +class PtpParameter(base.APIBase): + """API representation of a PTP parameter. + + This class enforces type checking and value constraints, and converts + between the internal object model and the API representation of + a PTP parameter. + """ + + created_at = wtypes.datetime.datetime + "Timestamp of creation of this PTP parameter" + + updated_at = wtypes.datetime.datetime + "Timestamp of update of this PTP parameter" + + id = int + "Unique ID for this PTP parameter" + + uuid = types.uuid + "Unique UUID for this PTP parameter" + + name = wtypes.text + "Name of PTP parameter" + + value = wtypes.text + "Value of PTP parameter" + + type = wtypes.Enum(str, + constants.PTP_PARAMETER_OWNER_INSTANCE, + constants.PTP_PARAMETER_OWNER_INTERFACE) + "Type of owner of this PTP parameter" + + foreign_uuid = types.uuid + "UUID of the owner of this PTP parameter" + + owner = types.MultiType([dict]) + "Owner information: name, type, hostname" + + def __init__(self, **kwargs): + self.fields = list(objects.ptp_parameter.fields.keys()) + for k in self.fields: + if not hasattr(self, k): + continue + setattr(self, k, kwargs.get(k)) + + @classmethod + def convert_with_links(cls, rpc_ptp_parameter, expand=True): + ptp_parameter = PtpParameter(**rpc_ptp_parameter.as_dict()) + if not expand: + ptp_parameter.unset_fields_except(['uuid', + 'name', + 'value', + 'type', + 'foreign_uuid', + 'owner', + 'created_at', + 'updated_at']) + + LOG.debug("PtpParameter.convert_with_links: converted %s" % + ptp_parameter.as_dict()) + return ptp_parameter + + +class PtpParameterCollection(collection.Collection): + """API representation of a collection of PTP parameters.""" + + ptp_parameters = [PtpParameter] + "A list containing PTP parameter objects" + + def __init__(self, **kwargs): + self._type = 'ptp_parameters' + + @classmethod + def convert_with_links(cls, rpc_ptp_parameters, limit, url=None, + expand=False, **kwargs): + collection = PtpParameterCollection() + collection.ptp_parameters = [PtpParameter.convert_with_links(p, expand) + for p in rpc_ptp_parameters] + collection.next = collection.get_next(limit, url=url, **kwargs) + return collection + + +LOCK_NAME = 'PtpParameterController' + + +class PtpParameterController(rest.RestController): + """REST controller for PTP parameter.""" + + def __init__(self, parent=None): + self._parent = parent + + def _get_ptp_parameter_collection( + self, parent_uuid=None, type=None, marker=None, limit=None, + sort_key=None, sort_dir=None, expand=False, resource_url=None): + LOG.debug("PtpParameterController._get_ptp_parameter_collection: " + "parent %s uuid %s type %s" % + (self._parent, parent_uuid, type)) + if self._parent and not parent_uuid: + raise exception.InvalidParameterValue(_( + "Parent id not specified.")) + + limit = utils.validate_limit(limit) + sort_dir = utils.validate_sort_dir(sort_dir) + + LOG.debug("PtpParameterController._get_ptp_parameter_collection: " + "marker %s, limit %s, sort_dir %s" % (marker, limit, + sort_dir)) + + marker_obj = None + if marker: + marker_obj = objects.ptp_parameter.get_by_uuid( + pecan.request.context, marker) + + if parent_uuid: + ptp_parameters = pecan.request.dbapi.ptp_parameters_get_by_owner( + parent_uuid, limit, marker_obj, sort_key=sort_key, + sort_dir=sort_dir) + elif type is not None: + ptp_parameters = pecan.request.dbapi.ptp_parameters_get_by_type( + type, limit, marker_obj, sort_key=sort_key, sort_dir=sort_dir) + else: + ptp_parameters = pecan.request.dbapi.ptp_parameters_get_list( + limit, marker_obj, sort_key=sort_key, sort_dir=sort_dir) + + return PtpParameterCollection.convert_with_links( + ptp_parameters, limit, url=resource_url, expand=expand, + sort_key=sort_key, sort_dir=sort_dir) + + @wsme_pecan.wsexpose(PtpParameterCollection, types.uuid, types.uuid, + int, wtypes.text, wtypes.text) + def get_all(self, uuid=None, marker=None, limit=None, + sort_key='id', sort_dir='asc'): + """Retrieve a list of PTP parameters.""" + type = None + LOG.debug("PtpParameterController.get_all: uuid=%s, type=%s" % + (uuid, type)) + return self._get_ptp_parameter_collection(uuid, type, + marker, limit, + sort_key=sort_key, + sort_dir=sort_dir) + + @wsme_pecan.wsexpose(PtpParameter, types.uuid) + def get_one(self, ptp_parameter_uuid): + """Retrieve a single PTP parameter.""" + LOG.debug("PtpParameterController.get_one: uuid=%s" % + ptp_parameter_uuid) + try: + ptp_parameter = objects.ptp_parameter.get_by_uuid( + pecan.request.context, + ptp_parameter_uuid) + except exception.InvalidParameterValue: + raise wsme.exc.ClientSideError( + _("No PTP parameter found for %s" % ptp_parameter_uuid)) + + return PtpParameter.convert_with_links(ptp_parameter) + + def _check_foreign_exists(self, type, uuid): + LOG.debug("PtpParameterController._check_foreign_exists: " + "type %s uuid %s" % (type, uuid)) + try: + if type == constants.PTP_PARAMETER_OWNER_INSTANCE: + try: + pecan.request.dbapi.ptp_instance_get(uuid) + except exception.PtpInstanceNotFound: + raise exception.NotFound + elif type == constants.PTP_PARAMETER_OWNER_INTERFACE: + try: + pecan.request.dbapi.ptp_interface_get(uuid) + except exception.PtpInterfaceNotFound: + raise exception.NotFound + except exception.NotFound: + raise wsme.exc.ClientSideError( + _("No foreign object found with id %s" % uuid)) + + @cutils.synchronized(LOCK_NAME) + @wsme_pecan.wsexpose(PtpParameter, body=PtpParameter) + def post(self, ptp_parameter): + """Create a new PTP parameter.""" + ptp_parameter_dict = ptp_parameter.as_dict() + LOG.debug("PtpParameterController.post: %s" % ptp_parameter_dict) + + self._check_foreign_exists(ptp_parameter_dict['type'], + ptp_parameter_dict['foreign_uuid']) + + # Get rid of owner details to create the PTP parameter + try: + ptp_parameter_dict.pop('owner') + except KeyError: + LOG.debug("PtpParameterController.post: no owner data in %s" % + ptp_parameter_dict) + + result = pecan.request.dbapi.ptp_parameter_create(ptp_parameter_dict) + return PtpParameter.convert_with_links(result) + + @cutils.synchronized(LOCK_NAME) + @wsme.validate(types.uuid, [PtpParameterPatchType]) + @wsme_pecan.wsexpose(PtpParameter, types.uuid, + body=[PtpParameterPatchType]) + def patch(self, uuid, patch): + """Update the value of an existing PTP parameter.""" + if self._parent: + raise exception.OperationNotPermitted + + ptp_parameter = objects.ptp_parameter.get_by_uuid( + pecan.request.context, uuid) + + patch_obj = jsonpatch.JsonPatch(patch) + try: + patched_parameter = PtpParameter( + **jsonpatch.apply_patch(ptp_parameter.as_dict(), patch_obj)) + except utils.JSONPATCH_EXCEPTIONS as e: + raise exception.PatchError(patch=patch, reason=e) + + # Update only the fields that have changed + for field in objects.ptp_parameter.fields: + if ptp_parameter[field] != getattr(patched_parameter, field): + ptp_parameter[field] = getattr(patched_parameter, field) + + ptp_parameter.save() + return PtpParameter.convert_with_links(ptp_parameter) + + @cutils.synchronized(LOCK_NAME) + @wsme_pecan.wsexpose(None, types.uuid, status_code=204) + def delete(self, ptp_parameter_uuid): + """Delete a PTP parameter.""" + LOG.debug("PtpParameterController.delete: %s" % ptp_parameter_uuid) + if self._parent: + raise exception.OperationNotPermitted + + pecan.request.dbapi.ptp_parameter_destroy(ptp_parameter_uuid) diff --git a/sysinv/sysinv/sysinv/sysinv/common/constants.py b/sysinv/sysinv/sysinv/sysinv/common/constants.py index 05e8d38cc9..5e586b2b5b 100644 --- a/sysinv/sysinv/sysinv/sysinv/common/constants.py +++ b/sysinv/sysinv/sysinv/sysinv/common/constants.py @@ -1850,6 +1850,19 @@ PTP_TRANSPORT_UDP = 'udp' PTP_TRANSPORT_L2 = 'l2' PTP_NETWORK_TRANSPORT_IEEE_802_3 = 'L2' +# PTP instance types +PTP_INSTANCE_TYPE_PTP4L = 'ptp4l' +PTP_INSTANCE_TYPE_PHC2SYS = 'phc2sys' +PTP_INSTANCE_TYPE_TS2PHC = 'ts2phc' + +# PTP instances created during migration from service parameters +PTP_INSTANCE_DEFAULT_PTP4L = 'default-ptp4l' +PTP_INSTANCE_DEFAULT_PHC2SYS = 'default-phc2sys' + +# PTP parameter: owner types +PTP_PARAMETER_OWNER_INSTANCE = 'ptp-instance' +PTP_PARAMETER_OWNER_INTERFACE = 'ptp-interface' + # Backup & Restore FIX_INSTALL_UUID_INTERVAL_SECS = 30 diff --git a/sysinv/sysinv/sysinv/sysinv/db/api.py b/sysinv/sysinv/sysinv/sysinv/db/api.py index a8324d48f9..49cfe5dce4 100644 --- a/sysinv/sysinv/sysinv/sysinv/db/api.py +++ b/sysinv/sysinv/sysinv/sysinv/db/api.py @@ -2141,6 +2141,7 @@ class Connection(object): { 'name': 'domain', 'value': '24', + 'type': 'ptp-instance', 'foreign_uuid': 'c2abca03-2f33-413e-b60d-85133a4a37b6' } :returns: A PTP parameter. @@ -2177,18 +2178,35 @@ class Connection(object): """ @abc.abstractmethod - def ptp_parameters_get_by_foreign_uuid(self, uuid, limit=None, marker=None, - sort_key=None, sort_dir=None): - """Returns a list of PTP parameters for a given foreign UUID. + def ptp_parameters_get_by_type(self, type, limit=None, marker=None, + sort_key=None, sort_dir=None): + """Returns a list of all PTP parameters for a given owner type. - :param uuid: The uuid of a PTP instance or PTP interface association. + :param type: Type of the parameter owner (either 'ptp-instance' or + 'ptp-interface') :param limit: Maximum number of PTP parameters to return. :param marker: The last item of the previous page; we return the next result set. :param sort_key: Attribute by which results should be sorted :param sort_dir: direction in which results should be sorted (asc, desc) - :returns: A list of PTP parameters. + :returns: A list of PTP parameters for a specific owner type. + """ + + @abc.abstractmethod + def ptp_parameters_get_by_owner(self, uuid, limit=None, marker=None, + sort_key=None, sort_dir=None): + """Returns a list of the PTP parameters for a given owner identified by + its UUID. + + :param uuid: UUID of the parameter owner. + :param limit: Maximum number of PTP parameters to return. + :param marker: The last item of the previous page; we return the next + result set. + :param sort_key: Attribute by which results should be sorted + :param sort_dir: direction in which results should be sorted + (asc, desc) + :returns: A list of PTP parameters of the specified owner. """ @abc.abstractmethod diff --git a/sysinv/sysinv/sysinv/sysinv/db/sqlalchemy/api.py b/sysinv/sysinv/sysinv/sysinv/db/sqlalchemy/api.py index 9e1f4238a4..b57b6f1527 100644 --- a/sysinv/sysinv/sysinv/sysinv/db/sqlalchemy/api.py +++ b/sysinv/sysinv/sysinv/sysinv/db/sqlalchemy/api.py @@ -1178,6 +1178,43 @@ def add_deviceimage_filter(query, value): return add_identity_filter(query, value, use_name=True) +def add_ptp_instance_filter_by_host(query, hostid): + """Adds a ptp-instance-specific ihost filter to a query. + + Filters results by host id if supplied value is an integer, + otherwise attempts to filter results by host uuid. + + :param query: Initial query to add filter to. + :param hostid: host id or uuid to filter results by. + :return: Modified query. + """ + if utils.is_int_like(hostid): + return query.filter_by(host_id=hostid) + elif utils.is_uuid_like(hostid): + query = query.join(models.ihost) + return query.filter(models.ihost.uuid == hostid) + + LOG.debug("ptp_instance_filter_by_host: " + "No match for supplied filter id (%s)" % str(hostid)) + + +def add_ptp_parameter_filter_by_owner(query, type, uuid): + if type == constants.PTP_PARAMETER_OWNER_INSTANCE: + query = query.join(models.PtpInstances, + models.PtpInstances.uuid == uuid) + elif type == constants.PTP_PARAMETER_OWNER_INTERFACE: + query = (query.join(models.PtpInterfaces, + models.PtpInterfaces.uuid == uuid) + .join(models.Interfaces, models.Interfaces.id == + models.PtpInterfaces.interface_id)) + else: + LOG.error("ptp_parameter_filter_by_owner: " + "Invalid owner type (%s)" % type) + raise exception.Invalid() + + return query + + class Connection(api.Connection): """SqlAlchemy connection.""" @@ -3758,7 +3795,7 @@ class Connection(api.Connection): query = model_query(models.PtpInstances) if name is not None: query = query.filter_by(name=name) - if service is not None: + elif service is not None: query = query.filter_by(service=service) try: return query.one() @@ -3775,10 +3812,8 @@ class Connection(api.Connection): @objects.objectify(objects.ptp_instance) def ptp_instances_get_by_ihost(self, ihost_id, limit=None, marker=None, sort_key=None, sort_dir=None): - # ihost_get() to raise an exception if the ihost is not found - ihost_obj = self.ihost_get(ihost_id) query = model_query(models.PtpInstances) - query = query.filter_by(host_id=ihost_obj.id) + query = add_ptp_instance_filter_by_host(query, ihost_id) return _paginate_query(models.PtpInstances, limit, marker, sort_key, sort_dir, query) @@ -3968,20 +4003,32 @@ class Connection(api.Connection): sort_key, sort_dir, query) @objects.objectify(objects.ptp_parameter) - def ptp_parameters_get_by_foreign_uuid(self, uuid, limit=None, marker=None, - sort_key=None, sort_dir=None): - # Look for foreign UUID in PTP instances first, then in PTP interfaces - # if not found - try: - foreign_obj = self.ptp_instance_get(uuid) - except exception.PtpInstanceNotFound: - try: - foreign_obj = self.ptp_interface_get(uuid) - except exception.PtpInterfaceNotFound: - raise exception.NotFound() - + def ptp_parameters_get_by_type(self, type, limit=None, marker=None, + sort_key=None, sort_dir=None): query = model_query(models.PtpParameters) - query = query.filter_by(foreign_uuid=foreign_obj.uuid) + query = query.filter_by(type=type) + return _paginate_query(models.PtpParameters, limit, marker, + sort_key, sort_dir, query) + + def _ptp_parameter_get_type(self, uuid): + type = None + query = model_query(models.PtpParameters) + query = query.filter_by(foreign_uuid=uuid) + ptp_parameter_object = query.first() + if ptp_parameter_object: + type = ptp_parameter_object.type + + return type + + @objects.objectify(objects.ptp_parameter) + def ptp_parameters_get_by_owner(self, uuid, limit=None, marker=None, + sort_key=None, sort_dir=None): + type = self._ptp_parameter_get_type(uuid) + if not type: + return [] + query = model_query(models.PtpParameters) + query = query.filter_by(foreign_uuid=uuid) + query = add_ptp_parameter_filter_by_owner(query, type, uuid) return _paginate_query(models.PtpParameters, limit, marker, sort_key, sort_dir, query) diff --git a/sysinv/sysinv/sysinv/sysinv/db/sqlalchemy/migrate_repo/versions/120_ptp_instances.py b/sysinv/sysinv/sysinv/sysinv/db/sqlalchemy/migrate_repo/versions/120_ptp_instances.py index 2f4b1c49af..c8e56839fe 100644 --- a/sysinv/sysinv/sysinv/sysinv/db/sqlalchemy/migrate_repo/versions/120_ptp_instances.py +++ b/sysinv/sysinv/sysinv/sysinv/db/sqlalchemy/migrate_repo/versions/120_ptp_instances.py @@ -13,7 +13,7 @@ # from datetime import datetime from sqlalchemy import Integer, String, DateTime, Text -from sqlalchemy import Column, MetaData, Table, ForeignKey +from sqlalchemy import Column, MetaData, Table, ForeignKey, UniqueConstraint from sysinv.db.sqlalchemy.models import UUID_LENGTH @@ -101,9 +101,12 @@ def upgrade(migrate_engine): Column('id', Integer, primary_key=True, nullable=False), Column('uuid', String(UUID_LENGTH), unique=True), - Column('name', String(255)), + Column('name', String(255), nullable=False), Column('value', String(255)), - Column('foreign_uuid', String(UUID_LENGTH)), + Column('type', String(255)), + Column('foreign_uuid', String(UUID_LENGTH), nullable=False), + + UniqueConstraint('name', 'foreign_uuid', name='u_paramnameforeign'), mysql_engine=ENGINE, mysql_charset=CHARSET, diff --git a/sysinv/sysinv/sysinv/sysinv/db/sqlalchemy/models.py b/sysinv/sysinv/sysinv/sysinv/db/sqlalchemy/models.py index 53cb7770bb..1ddfbc3616 100644 --- a/sysinv/sysinv/sysinv/sysinv/db/sqlalchemy/models.py +++ b/sysinv/sysinv/sysinv/sysinv/db/sqlalchemy/models.py @@ -262,7 +262,7 @@ class inode(Base): forihostid = Column(Integer, ForeignKey('i_host.id', ondelete='CASCADE')) - host = relationship("ihost", backref="nodes", lazy="joined", join_depth=1) + host = relationship("ihost", backref="nodes", lazy="joined", cascade="all") UniqueConstraint('numa_node', 'forihostid', name='u_hostnuma') @@ -369,7 +369,7 @@ class Interfaces(Base): join_depth=1) host = relationship("ihost", backref="interfaces", - lazy="joined", join_depth=1) + lazy="joined", cascade="all") addresses = relationship("Addresses", backref=backref("interface", lazy="joined"), @@ -481,10 +481,10 @@ class Ports(Base): capabilities = Column(JSONEncodedDict) # JSON{'speed':1000,'MTU':9600, 'duplex':'', 'autonegotiation':'false'} - node = relationship("inode", backref="ports", lazy="joined", join_depth=1) - host = relationship("ihost", backref="ports", lazy="joined", join_depth=1) + node = relationship("inode", backref="ports", lazy="joined", cascade="all") + host = relationship("ihost", backref="ports", lazy="joined", cascade="all") interface = relationship("Interfaces", backref="port", - lazy="joined", join_depth=1) + lazy="joined", cascade="all") UniqueConstraint('pciaddr', 'dev_id', 'host_id', name='u_pciaddrdevihost') @@ -798,8 +798,8 @@ class PtpInstances(Base): # capabilities not used yet: JSON{'':"", '':''} capabilities = Column(JSONEncodedDict) - host = relationship("ihost", backref="ptp_instances", lazy="joined", - join_depth=1) + host = relationship("ihost", backref="ptp_instance", lazy="joined", + cascade="all") class PtpInterfaces(Base): @@ -811,15 +811,16 @@ class PtpInterfaces(Base): interface_id = Column(Integer, ForeignKey('interfaces.id', ondelete='CASCADE')) ptp_instance_id = Column(Integer, - ForeignKey('ptp_instances.id', ondelete='CASCADE')) + ForeignKey('ptp_instances.id', + ondelete='CASCADE')) # capabilities not used yet: JSON{'':"", '':''} capabilities = Column(JSONEncodedDict) - interface = relationship("Interfaces", backref="ptp_interfaces", - lazy="joined", join_depth=1) - ptp_instance = relationship("PtpInstances", backref="ptp_interfaces", - lazy="joined", join_depth=1) + interface = relationship("Interfaces", backref="ptp_interface", + lazy="joined", cascade="all") + ptp_instance = relationship("PtpInstances", backref="ptp_interface", + lazy="joined", cascade="all") class PtpParameters(Base): @@ -829,10 +830,25 @@ class PtpParameters(Base): uuid = Column(String(UUID_LENGTH), unique=True) name = Column(String(255), nullable=False) - value = Column(String(255), nullable=False) + value = Column(String(255)) - # Either a "PtpInstance" or "PtpInterface" uuid: - foreign_uuid = Column(String(UUID_LENGTH)) + type = Column(String(255)) + foreign_uuid = Column(String(UUID_LENGTH), nullable=False) + + ptp_instance = relationship( + "PtpInstances", + primaryjoin="PtpParameters.foreign_uuid == foreign(PtpInstances.uuid)", + lazy="subquery", + cascade="all") + + ptp_interface = relationship( + "PtpInterfaces", + primaryjoin="PtpParameters.foreign_uuid == " + "foreign(PtpInterfaces.uuid)", + lazy="subquery", + cascade="all") + + UniqueConstraint('name', 'foreign_uuid', name='u_paramnameforeign') class StorageTier(Base): diff --git a/sysinv/sysinv/sysinv/sysinv/objects/ptp_instance.py b/sysinv/sysinv/sysinv/sysinv/objects/ptp_instance.py index 2c71fe15f1..1a7b4679e4 100644 --- a/sysinv/sysinv/sysinv/sysinv/objects/ptp_instance.py +++ b/sysinv/sysinv/sysinv/sysinv/objects/ptp_instance.py @@ -22,13 +22,17 @@ class PtpInstance(base.SysinvObject): 'name': utils.str_or_none, 'service': utils.str_or_none, - 'host_uuid': utils.str_or_none, 'host_id': utils.int_or_none, + + 'host_uuid': utils.str_or_none, + 'hostname': utils.str_or_none, + 'capabilities': utils.dict_or_none } _foreign_fields = { - 'host_uuid': 'host:uuid' + 'host_uuid': 'host:uuid', + 'hostname': 'host:hostname' } @base.remotable_classmethod diff --git a/sysinv/sysinv/sysinv/sysinv/objects/ptp_parameter.py b/sysinv/sysinv/sysinv/sysinv/objects/ptp_parameter.py index 5db64932bc..21772478ee 100644 --- a/sysinv/sysinv/sysinv/sysinv/objects/ptp_parameter.py +++ b/sysinv/sysinv/sysinv/sysinv/objects/ptp_parameter.py @@ -6,11 +6,38 @@ # ######################################################################## +from sysinv.common import constants from sysinv.db import api as db_api from sysinv.objects import base from sysinv.objects import utils +def get_owner(field, db_object): + owner = {} + """Retrieves the owner details based on type and uuid.""" + if db_object['type'] == constants.PTP_PARAMETER_OWNER_INSTANCE: + ptp_instances = getattr(db_object, 'ptp_instance') + if ptp_instances: + owner['name'] = ptp_instances[0].name + owner['type'] = ptp_instances[0].service + host = getattr(ptp_instances[0], 'host') + if host: + owner['hostname'] = host.hostname + + elif db_object['type'] == constants.PTP_PARAMETER_OWNER_INTERFACE: + ptp_interfaces = getattr(db_object, 'ptp_interface') + if ptp_interfaces: + interface = getattr(ptp_interfaces[0], 'interface') + if interface: + owner['name'] = interface.ifname + owner['type'] = interface.iftype + host = getattr(interface, 'host') + if host: + owner['hostname'] = host.hostname + + return owner + + class PtpParameter(base.SysinvObject): dbapi = db_api.get_instance() @@ -22,10 +49,14 @@ class PtpParameter(base.SysinvObject): 'name': utils.str_or_none, 'value': utils.str_or_none, - 'foreign_uuid': utils.str_or_none + 'type': utils.str_or_none, + 'foreign_uuid': utils.str_or_none, + + 'owner': dict } _foreign_fields = { + 'owner': get_owner } @base.remotable_classmethod diff --git a/sysinv/sysinv/sysinv/sysinv/tests/api/test_ptp_instance.py b/sysinv/sysinv/sysinv/sysinv/tests/api/test_ptp_instance.py index 941bda90b7..3846b98f88 100644 --- a/sysinv/sysinv/sysinv/sysinv/tests/api/test_ptp_instance.py +++ b/sysinv/sysinv/sysinv/sysinv/tests/api/test_ptp_instance.py @@ -3,12 +3,11 @@ # SPDX-License-Identifier: Apache-2.0 # -from oslo_utils import uuidutils from six.moves import http_client from sysinv.common import constants -from sysinv.db import api as dbapi from sysinv.tests.api import base from sysinv.tests.db import base as dbbase +from sysinv.tests.db import utils as dbutils class BasePtpInstanceTestCase(base.FunctionalTest, dbbase.BaseHostTestCase): @@ -27,32 +26,6 @@ class BasePtpInstanceTestCase(base.FunctionalTest, dbbase.BaseHostTestCase): # Can perform API operations on this object at a sublevel of host HOST_PREFIX = '/ihosts' - # Attributes that should be populated by an API query - expected_api_fields = ['uuid', 'name', 'service'] - - # Attributes that should NOT be populated by an API query - hidden_api_fields = ['host_id'] - - def _get_ptp_instance(self, **kw): - instance = { - 'id': kw.get('id'), - 'uuid': kw.get('uuid'), - 'name': kw.get('name', None), - 'service': kw.get('service', 'ptp4l'), - 'host_id': kw.get('host_id', None) - } - return instance - - def _create_ptp_instance(self, **kw): - instance = self._get_ptp_instance(**kw) - # Let DB generate ID if isn't specified - if 'id' not in kw: - del instance['id'] - if 'uuid' in kw: - del instance['uuid'] - db_api = dbapi.get_instance() - return db_api.ptp_instance_create(instance) - def setUp(self): super(BasePtpInstanceTestCase, self).setUp() self.controller = self._create_test_host(constants.CONTROLLER) @@ -64,39 +37,38 @@ class BasePtpInstanceTestCase(base.FunctionalTest, dbbase.BaseHostTestCase): def get_host_scoped_url(self, host_uuid): return '%s/%s%s' % (self.HOST_PREFIX, host_uuid, self.API_PREFIX) - def get_post_object(self, name='test_instance', service='ptp4l', - host_id=None, host_uuid=None): - ptp_instance_db = self._get_ptp_instance(name=name, - service=service, - host_id=host_id) + def get_post_object(self, name='test_instance', + service=constants.PTP_INSTANCE_TYPE_PTP4L, + host_id=None, host_uuid=None, hostname=None): + ptp_instance_db = dbutils.get_test_ptp_instance(name=name, + service=service, + host_id=host_id) ptp_instance_db['host_uuid'] = host_uuid + ptp_instance_db['hostname'] = hostname return ptp_instance_db - def assert_fields(self, api_object): - assert(uuidutils.is_uuid_like(api_object['uuid'])) - for field in self.expected_api_fields: - self.assertIn(field, api_object) - for field in self.hidden_api_fields: - self.assertNotIn(field, api_object) - class TestCreatePtpInstance(BasePtpInstanceTestCase): - name = 'ptp-name' - service = 'ptp4l' + name = constants.PTP_INSTANCE_DEFAULT_PTP4L + service = constants.PTP_INSTANCE_TYPE_PTP4L host_id = None host_uuid = None + hostname = None def setUp(self): super(TestCreatePtpInstance, self).setUp() self.host_id = self.controller.id self.host_uuid = self.controller.uuid - self._create_ptp_instance(name=self.name, service=self.service, - host_id=self.host_id) + self.hostname = self.controller.hostname + dbutils.create_test_ptp_instance(name=self.name, service=self.service, + host_id=self.host_id) - def _create_ptp_instance_success(self, name, service, host_id, host_uuid): + def _create_ptp_instance_success(self, name, service, host_id, host_uuid, + hostname): ptp_instance_db = self.get_post_object(name=name, service=service, host_id=host_id, - host_uuid=host_uuid) + host_uuid=host_uuid, + hostname=hostname) response = self.post_json(self.API_PREFIX, ptp_instance_db, headers=self.API_HEADERS) self.assertEqual('application/json', response.content_type) @@ -105,10 +77,11 @@ class TestCreatePtpInstance(BasePtpInstanceTestCase): ptp_instance_db[self.COMMON_FIELD]) def _create_ptp_instance_failed(self, name, service, host_id, host_uuid, - status_code, error_message): + hostname, status_code, error_message): ptp_instance_db = self.get_post_object(name=name, service=service, host_id=host_id, - host_uuid=host_uuid) + host_uuid=host_uuid, + hostname=hostname) response = self.post_json(self.API_PREFIX, ptp_instance_db, headers=self.API_HEADERS, expect_errors=True) self.assertEqual('application/json', response.content_type) @@ -116,9 +89,11 @@ class TestCreatePtpInstance(BasePtpInstanceTestCase): self.assertIn(error_message, response.json['error_message']) def test_create_ptp_instance_ok(self): - self._create_ptp_instance_success('test-instance', 'ptp4l', + self._create_ptp_instance_success('test-instance', + constants.PTP_INSTANCE_TYPE_PTP4L, host_id=self.controller.id, - host_uuid=self.controller.uuid) + host_uuid=self.controller.uuid, + hostname=self.controller.hostname) def test_create_ptp_instance_invalid_service(self): self._create_ptp_instance_failed( @@ -126,6 +101,7 @@ class TestCreatePtpInstance(BasePtpInstanceTestCase): 'invalid', host_id=self.controller.id, host_uuid=self.controller.uuid, + hostname=self.controller.hostname, status_code=http_client.BAD_REQUEST, error_message='Invalid input for field/attribute service') @@ -137,6 +113,7 @@ class TestCreatePtpInstance(BasePtpInstanceTestCase): service=self.service, host_id=self.host_id, host_uuid=self.host_uuid, + hostname=self.controller.hostname, status_code=http_client.CONFLICT, error_message=error_message) @@ -145,9 +122,10 @@ class TestCreatePtpInstance(BasePtpInstanceTestCase): error_message = '%s could not be found' % bad_uuid self._create_ptp_instance_failed( 'test-invalid', - 'phc2sys', + constants.PTP_INSTANCE_TYPE_PHC2SYS, host_id=99, - host_uuid='f4c56ddf-aef3-46ed-b9aa-126a1faafd40', + host_uuid=bad_uuid, + hostname='badhost', status_code=http_client.NOT_FOUND, error_message=error_message) @@ -157,8 +135,10 @@ class TestGetPtpInstance(BasePtpInstanceTestCase): super(TestGetPtpInstance, self).setUp() def test_get_ptp_instance_found(self): - ptp_instance = self._create_ptp_instance( - name='fake-ptp4l', service='ptp4l', host_id=self.controller.id) + ptp_instance = dbutils.create_test_ptp_instance( + name=constants.PTP_INSTANCE_DEFAULT_PTP4L, + service=constants.PTP_INSTANCE_TYPE_PTP4L, + host_id=self.controller.id) uuid = ptp_instance['uuid'] response = self.get_json(self.get_single_url(uuid)) self.assertIn(self.COMMON_FIELD, response) @@ -180,13 +160,15 @@ class TestListPtpInstance(BasePtpInstanceTestCase): self._create_test_ptp_instances() def _create_test_ptp_instances(self, name_prefix='test', host_id=None): - services = ['ptp4l', 'phc2sys', 'ts2phc'] + services = [constants.PTP_INSTANCE_TYPE_PTP4L, + constants.PTP_INSTANCE_TYPE_PHC2SYS, + constants.PTP_INSTANCE_TYPE_TS2PHC] instances = [] if not host_id: host_id = self.controller.id for service in services: name = '%s-%s' % (name_prefix, service) - instance = self._create_ptp_instance( + instance = dbutils.create_test_ptp_instance( name=name, service=service, host_id=host_id) instances.append(instance) return instances @@ -214,30 +196,57 @@ class TestDeletePtpInstance(BasePtpInstanceTestCase): python2 and python3 libraries may return different content_type (None, or empty json) when NO_CONTENT returned. """ + ptp_instance = None + uuid = None def setUp(self): super(TestDeletePtpInstance, self).setUp() + self.ptp_instance = dbutils.create_test_ptp_instance( + name=constants.PTP_INSTANCE_DEFAULT_PTP4L, + service=constants.PTP_INSTANCE_TYPE_PTP4L, + host_id=self.controller.id) + self.uuid = self.ptp_instance['uuid'] - def test_delete_ptp_instance(self): - ptp_instance = self._create_ptp_instance( - name='fake-phc2sys', service='phc2sys', host_id=self.controller.id) - uuid = ptp_instance['uuid'] - response = self.delete(self.get_single_url(uuid), + def test_delete_ptp_instance_ok(self): + response = self.delete(self.get_single_url(self.uuid), headers=self.API_HEADERS) self.assertEqual(response.status_code, http_client.NO_CONTENT) # Check the instance was indeed removed - error_message = 'No PTP instance with id %s found' % uuid - response = self.get_json(self.get_single_url(uuid), + error_message = 'No PTP instance with id %s found' % self.uuid + response = self.get_json(self.get_single_url(self.uuid), expect_errors=True) self.assertEqual('application/json', response.content_type) self.assertEqual(response.status_code, http_client.NOT_FOUND) self.assertIn(error_message, response.json['error_message']) def test_delete_ptp_instance_with_parameters_failed(self): - # TODO: implement when PTP parameters API is available - pass + ptp_parameter = dbutils.create_test_ptp_parameter( + name='fake-param', value='fake-value', + type=constants.PTP_PARAMETER_OWNER_INSTANCE, + foreign_uuid=self.uuid) + self.assertEqual(self.uuid, ptp_parameter['foreign_uuid']) + + response = self.delete(self.get_single_url(self.uuid), + headers=self.API_HEADERS, expect_errors=True) + self.assertEqual('application/json', response.content_type) + self.assertEqual(response.status_code, http_client.BAD_REQUEST) + self.assertIn('still associated with PTP parameter', + response.json['error_message']) def test_delete_ptp_instance_with_interfaces_failed(self): - # TODO: implement when PTP interfaces API is available - pass + interface = dbutils.create_test_interface( + ifname='fake0', ifclass=constants.INTERFACE_CLASS_PLATFORM, + forihostid=self.controller.id, ihost_uuid=self.controller.uuid) + ptp_interface = dbutils.create_test_ptp_interface( + interface_id=interface['id'], + ptp_instance_id=self.ptp_instance['id']) + self.assertEqual(self.ptp_instance['id'], + ptp_interface['ptp_instance_id']) + + response = self.delete(self.get_single_url(self.uuid), + headers=self.API_HEADERS, expect_errors=True) + self.assertEqual('application/json', response.content_type) + self.assertEqual(response.status_code, http_client.BAD_REQUEST) + self.assertIn('still associated with PTP interface', + response.json['error_message']) diff --git a/sysinv/sysinv/sysinv/sysinv/tests/api/test_ptp_interface.py b/sysinv/sysinv/sysinv/sysinv/tests/api/test_ptp_interface.py index b454c6cc37..91977d65bf 100644 --- a/sysinv/sysinv/sysinv/sysinv/tests/api/test_ptp_interface.py +++ b/sysinv/sysinv/sysinv/sysinv/tests/api/test_ptp_interface.py @@ -81,12 +81,12 @@ class TestCreatePtpInterface(BasePtpInterfaceTestCase): self.test_instance = dbutils.create_test_ptp_instance( name='testInstance', - service='ptp4l', + service=constants.PTP_INSTANCE_TYPE_PTP4L, host_id=self.controller.id) def _create_ptp_interface_success(self, interface_uuid, ptp_instance_uuid): ptp_interface_db = self.get_post_object(interface_uuid, - ptp_instance_uuid) + ptp_instance_uuid) response = self.post_json(self.API_PREFIX, ptp_interface_db, headers=self.API_HEADERS) self.assertEqual('application/json', response.content_type) @@ -125,7 +125,7 @@ class TestCreatePtpInterface(BasePtpInterfaceTestCase): def test_create_ptp_interface_duplicate(self): self._create_ptp_interface_success(self.test_interface.uuid, - self.test_instance.uuid) + self.test_instance.uuid) self._create_ptp_interface_failed( interface_uuid=self.test_interface.uuid, @@ -145,16 +145,17 @@ class TestGetPtpInterface(BasePtpInterfaceTestCase): self.test_instance = dbutils.create_test_ptp_instance( name='testInstance', - service='ptp4l', + service=constants.PTP_INSTANCE_TYPE_PTP4L, host_id=self.controller.id) self.test_ptp_interface = dbutils.create_test_ptp_interface( - interface_id=self.test_interface.id, - ptp_instance_id=self.test_instance.id) + interface_id=self.test_interface.id, + ptp_instance_id=self.test_instance.id) def test_get_ptp_interface_found(self): - response = self.get_json(self.get_single_url(self.test_ptp_interface.uuid)) + response = self.get_json( + self.get_single_url(self.test_ptp_interface.uuid)) self.assertIn(self.COMMON_FIELD, response) def test_get_ptp_interface_not_found(self): @@ -185,7 +186,7 @@ class TestListPtpInterface(BasePtpInterfaceTestCase): self.test_instance_ptp4l = dbutils.create_test_ptp_instance( name='ptp4lInstance', - service='ptp4l', + service=constants.PTP_INSTANCE_TYPE_PTP4L, host_id=self.worker.id) self.test_instance_phc2sys = dbutils.create_test_ptp_instance( @@ -194,28 +195,30 @@ class TestListPtpInterface(BasePtpInterfaceTestCase): host_id=self.worker.id) self.ptp4l_ptp_interface = dbutils.create_test_ptp_interface( - interface_id=self.test_interface.id, - ptp_instance_id=self.test_instance_ptp4l.id) + interface_id=self.test_interface.id, + ptp_instance_id=self.test_instance_ptp4l.id) self.phc2sys_ptp_interface = dbutils.create_test_ptp_interface( - interface_id=self.test_interface.id, - ptp_instance_id=self.test_instance_phc2sys.id) + interface_id=self.test_interface.id, + ptp_instance_id=self.test_instance_phc2sys.id) self.dummy_ptp_interface = dbutils.create_test_ptp_interface( - interface_id=self.dummy_interface.id, - ptp_instance_id=self.test_instance_ptp4l.id) + interface_id=self.dummy_interface.id, + ptp_instance_id=self.test_instance_ptp4l.id) def test_list_ptp_interface_host(self): response = self.get_json(self.get_host_scoped_url(self.worker.uuid)) for result in response[self.RESULT_KEY]: self.assertEqual(self.worker.id, result['forihostid']) if result['uuid'] == self.ptp4l_ptp_interface.uuid \ - or result['uuid'] == self.dummy_interface.uuid: - self.assertEqual(self.test_instance_ptp4l.id, result['ptp_instance_id']) + or result['uuid'] == self.dummy_interface.uuid: + self.assertEqual(self.test_instance_ptp4l.id, + result['ptp_instance_id']) elif result['uuid'] == self.phc2sys_ptp_interface.uuid: - self.assertEqual(self.test_instance_phc2sys.id, result['ptp_instance_id']) + self.assertEqual(self.test_instance_phc2sys.id, + result['ptp_instance_id']) def test_list_ptp_interface_interface(self): - response = self.get_json(self.get_host_scoped_url_interface(self.worker.uuid, - self.test_interface.uuid)) + response = self.get_json(self.get_host_scoped_url_interface( + self.worker.uuid, self.test_interface.uuid)) for result in response[self.RESULT_KEY]: self.assertIn(self.COMMON_FIELD, result) self.assertNotIn(self.dummy_interface.uuid, result) @@ -243,25 +246,39 @@ class TestDeletePtpInterface(BasePtpInterfaceTestCase): self.test_instance_ptp4l = dbutils.create_test_ptp_instance( name='ptp4lInstance', - service='ptp4l', + service=constants.PTP_INSTANCE_TYPE_PTP4L, host_id=self.worker.id) self.test_ptp_interface = dbutils.create_test_ptp_interface( - interface_id=self.test_interface.id, - ptp_instance_id=self.test_instance_ptp4l.id) + interface_id=self.test_interface.id, + ptp_instance_id=self.test_instance_ptp4l.id) def test_delete_ptp_interface(self): - response = self.delete(self.get_single_url(self.test_ptp_interface.uuid), - headers=self.API_HEADERS) + response = self.delete( + self.get_single_url(self.test_ptp_interface.uuid), + headers=self.API_HEADERS) self.assertEqual(response.status_code, http_client.NO_CONTENT) - error_message = 'No PTP interface with id %s found' % self.test_ptp_interface.uuid - response = self.get_json(self.get_single_url(self.test_ptp_interface.uuid), - expect_errors=True) + error_message = \ + 'No PTP interface with id %s found' % self.test_ptp_interface.uuid + response = self.get_json( + self.get_single_url(self.test_ptp_interface.uuid), + expect_errors=True) self.assertEqual('application/json', response.content_type) self.assertEqual(response.status_code, http_client.NOT_FOUND) self.assertIn(error_message, response.json['error_message']) def test_delete_ptp_interface_with_parameters_failed(self): - # TODO: implement when PTP parameters API is available - pass + ptp_parameter = dbutils.create_test_ptp_parameter( + name='fake-param', value='fake-value', + type=constants.PTP_PARAMETER_OWNER_INTERFACE, + foreign_uuid=self.test_ptp_interface.uuid) + self.assertEqual(self.test_ptp_interface.uuid, + ptp_parameter['foreign_uuid']) + + response = self.delete( + self.get_single_url(self.test_ptp_interface.uuid), + headers=self.API_HEADERS, expect_errors=True) + self.assertEqual('application/json', response.content_type) + self.assertEqual(response.status_code, http_client.BAD_REQUEST) + self.assertIn('has PTP parameter', response.json['error_message']) diff --git a/sysinv/sysinv/sysinv/sysinv/tests/api/test_ptp_parameter.py b/sysinv/sysinv/sysinv/sysinv/tests/api/test_ptp_parameter.py new file mode 100644 index 0000000000..fcd73870b9 --- /dev/null +++ b/sysinv/sysinv/sysinv/sysinv/tests/api/test_ptp_parameter.py @@ -0,0 +1,297 @@ +# Copyright (c) 2021 Wind River Systems, Inc. +# +# SPDX-License-Identifier: Apache-2.0 +# + +from six.moves import http_client +from sysinv.common import constants +from sysinv.tests.api import base +from sysinv.tests.db import base as dbbase +from sysinv.tests.db import utils as dbutils + + +class BasePtpParameterTestCase(base.FunctionalTest, dbbase.BaseHostTestCase): + # Generic header passed to most API calls + API_HEADERS = {'User-Agent': 'sysinv-test'} + + # Prefix for the URL + API_PREFIX = '/ptp_parameters' + + # Python table key for the list of results + RESULT_KEY = 'ptp_parameters' + + # Field that is known to exist for inputs and outputs + COMMON_FIELD = 'name' + + # Can perform API operations on this object at a sublevel of PTP instances + PTP_INSTANCE_PREFIX = '/ptp_instances' + + # Can perform API operations on this object at a sublevel of interfaces + INTERFACE_PREFIX = '/iinterfaces' + + def setUp(self): + super(BasePtpParameterTestCase, self).setUp() + self.controller = self._create_test_host(constants.CONTROLLER) + self.ptp_instances = self._create_test_ptp_instance(self.controller) + self.platform_interfaces = \ + self._create_test_host_platform_interface(self.controller) + self.ptp_interfaces = self._create_test_ptp_interface( + self.ptp_instances, self.platform_interfaces) + + def get_single_url(self, ptp_parameter_uuid): + return '%s/%s' % (self.API_PREFIX, ptp_parameter_uuid) + + def get_instance_scoped_url(self, ptp_instance_uuid): + return '%s/%s%s' % (self.PTP_INSTANCE_PREFIX, ptp_instance_uuid, + self.API_PREFIX) + + def get_interface_scoped_url(self, interface_uuid): + return '%s/%s%s' % (self.INTERFACE_PREFIX, interface_uuid, + self.API_PREFIX) + + def get_post_object(self, name='test_parameter', value='test_value', + type=None, foreign_uuid=None): + return dbutils.get_test_ptp_parameter(name=name, + value=value, + type=type, + foreign_uuid=foreign_uuid) + + +class TestCreatePtpParameter(BasePtpParameterTestCase): + name = 'test-param' + value = 'test-value' + type = constants.PTP_PARAMETER_OWNER_INSTANCE + foreign_uuid = None + + def setUp(self): + super(TestCreatePtpParameter, self).setUp() + self.foreign_uuid = self.ptp_instances[0].uuid + dbutils.create_test_ptp_parameter(name=self.name, + value=self.value, + type=self.type, + foreign_uuid=self.foreign_uuid) + + def _create_ptp_parameter_success(self, name, value, type, foreign_uuid): + ptp_parameter_db = self.get_post_object(name=name, + value=value, + type=type, + foreign_uuid=foreign_uuid) + response = self.post_json(self.API_PREFIX, ptp_parameter_db, + headers=self.API_HEADERS) + self.assertEqual('application/json', response.content_type) + self.assertEqual(response.status_code, http_client.OK) + self.assertEqual(response.json[self.COMMON_FIELD], + ptp_parameter_db[self.COMMON_FIELD]) + + def _create_ptp_parameter_failed(self, name, value, type, foreign_uuid, + status_code, error_message): + ptp_parameter_db = self.get_post_object(name=name, + value=value, + type=type, + foreign_uuid=foreign_uuid) + response = self.post_json(self.API_PREFIX, ptp_parameter_db, + headers=self.API_HEADERS, expect_errors=True) + self.assertEqual('application/json', response.content_type) + self.assertEqual(response.status_code, status_code) + self.assertIn(error_message, response.json['error_message']) + + def test_create_ptp_parameter_instance_ok(self): + self._create_ptp_parameter_success( + name='instance-param', value='instance-value', + type=constants.PTP_PARAMETER_OWNER_INSTANCE, + foreign_uuid=self.ptp_instances[0].uuid) + + def test_create_ptp_parameter_interface_ok(self): + self._create_ptp_parameter_success( + name='interface-param', value='interface-value', + type=constants.PTP_PARAMETER_OWNER_INTERFACE, + foreign_uuid=self.ptp_interfaces[0].uuid) + + def test_create_ptp_parameter_invalid_type(self): + self._create_ptp_parameter_failed( + name='fake-param', value='fake-value', + type='invalid', + foreign_uuid=self.ptp_instances[0].uuid, + status_code=http_client.BAD_REQUEST, + error_message='Invalid input for field/attribute type') + + def test_create_ptp_parameter_invalid_uuid(self): + bad_uuid = 'f4c56ddf-aef3-46ed-b9aa-126a1faafd40' + error_message = 'No foreign object found with id %s' % bad_uuid + self._create_ptp_parameter_failed( + name='fake-param', value='fake-value', + type=constants.PTP_PARAMETER_OWNER_INSTANCE, + foreign_uuid=bad_uuid, + status_code=http_client.BAD_REQUEST, + error_message=error_message) + + def test_create_ptp_parameter_duplicate(self): + self._create_ptp_parameter_failed( + name=self.name, + value='another-value', + type=constants.PTP_PARAMETER_OWNER_INSTANCE, + foreign_uuid=self.foreign_uuid, + status_code=http_client.CONFLICT, + error_message='already exists') + + +class TestGetPtpParameter(BasePtpParameterTestCase): + def setUp(self): + super(TestGetPtpParameter, self).setUp() + + def test_get_ptp_parameter_found(self): + ptp_parameter = dbutils.create_test_ptp_parameter( + name='fake-param', value='fake-value', + type=constants.PTP_PARAMETER_OWNER_INSTANCE, + foreign_uuid=self.ptp_instances[0].uuid) + uuid = ptp_parameter['uuid'] + response = self.get_json(self.get_single_url(uuid)) + self.assertIn(self.COMMON_FIELD, response) + + def test_get_ptp_parameter_not_found(self): + fake_uuid = 'f4c56ddf-aef3-46ed-b9aa-126a1faafd40' + error_message = 'No PTP parameter with id %s found' % fake_uuid + + response = self.get_json(self.get_single_url(fake_uuid), + expect_errors=True) + self.assertEqual('application/json', response.content_type) + self.assertEqual(response.status_code, http_client.NOT_FOUND) + self.assertIn(error_message, response.json['error_message']) + + +class TestListPtpParameter(BasePtpParameterTestCase): + def setUp(self): + super(TestListPtpParameter, self).setUp() + self._create_test_ptp_parameters( + type=constants.PTP_PARAMETER_OWNER_INSTANCE, + prefix='ptp') + self._create_test_ptp_parameters( + type=constants.PTP_PARAMETER_OWNER_INTERFACE, + prefix='iface') + + def _create_test_ptp_parameters(self, type, prefix='test', + foreign_uuid=None): + parameters = [] + + if not foreign_uuid: + if type == constants.PTP_PARAMETER_OWNER_INSTANCE: + foreign_uuid = self.ptp_instances[0].uuid + elif type == constants.PTP_PARAMETER_OWNER_INTERFACE: + foreign_uuid = self.ptp_interfaces[0].uuid + else: + return parameters + + for i in range(2): + name = '%s-name%s' % (prefix, i) + value = '%s-value%s' % (prefix, i) + parameter = dbutils.create_test_ptp_parameter( + name=name, value=value, type=type, foreign_uuid=foreign_uuid) + parameters.append(parameter) + return parameters + + def test_list_ptp_parameter_all(self): + response = self.get_json(self.API_PREFIX) + for result in response[self.RESULT_KEY]: + self.assertIn(self.COMMON_FIELD, result) + + def test_list_ptp_parameter_empty(self): + fake_uuid = 'f4c56ddf-aef3-46ed-b9aa-126a1faafd40' + response = self.get_json(self.get_instance_scoped_url(fake_uuid)) + self.assertEqual([], response[self.RESULT_KEY]) + + def test_list_ptp_parameter_by_type(self): + self._create_test_ptp_parameters( + constants.PTP_PARAMETER_OWNER_INSTANCE, + foreign_uuid=self.ptp_instances[1].uuid) + """ + TODO: needs investigation of the reason to get this: + webtest.app.AppError: Bad response: 400 Bad Request (not 200 OK or 3xx + redirect for http:// + localhost/v1/ptp_parameters?q.field=type&q.value=ptp-instance&q.op=eq) + '{"error_message": "{\\"debuginfo\\": null, \\"faultcode\\": + \\"Client\\", \\"faultstring\\": \\"Unknown argument: \\\\\\"q.field, + q.value, q.op\\\\\\"\\"}"}' + query = [{ + 'field': 'type', + 'value': constants.PTP_PARAMETER_OWNER_INSTANCE, + 'op': 'eq' + }] + response = self.get_json(self.API_PREFIX, q=query) + for result in response[self.RESULT_KEY]: + self.assertEqual(constants.PTP_PARAMETER_OWNER_INSTANCE, + result['type']) + """ + + def test_list_ptp_parameter_by_instance(self): + self._create_test_ptp_parameters( + constants.PTP_PARAMETER_OWNER_INSTANCE, + foreign_uuid=self.ptp_instances[1].uuid) + response = self.get_json(self.get_instance_scoped_url( + self.ptp_instances[1].uuid)) + for result in response[self.RESULT_KEY]: + self.assertEqual(self.ptp_instances[1].uuid, + result['foreign_uuid']) + + def test_list_ptp_parameter_by_interface(self): + self._create_test_ptp_parameters( + constants.PTP_PARAMETER_OWNER_INTERFACE, + foreign_uuid=self.ptp_interfaces[1].uuid) + response = self.get_json(self.get_interface_scoped_url( + self.ptp_interfaces[1].uuid)) + for result in response[self.RESULT_KEY]: + self.assertEqual(self.ptp_interfaces[1].uuid, + result['foreign_uuid']) + + +class TestUpdatePtpParameter(BasePtpParameterTestCase): + def setUp(self): + super(TestUpdatePtpParameter, self).setUp() + + def test_update_ptp_parameter(self): + ptp_parameter = dbutils.create_test_ptp_parameter( + name='fake-param', value='fake-value', + type=constants.PTP_PARAMETER_OWNER_INSTANCE, + foreign_uuid=self.ptp_instances[0].uuid) + uuid = ptp_parameter['uuid'] + + response = self.patch_json(self.get_single_url(uuid), + [{'path': '/value', + 'value': 'changed-value', + 'op': 'replace'}], + headers=self.API_HEADERS) + self.assertEqual(response.content_type, 'application/json') + self.assertEqual(response.status_code, http_client.OK) + + # Check the parameter was indeed updated + response = self.get_json(self.get_single_url(uuid)) + self.assertEqual(response['value'], 'changed-value') + + +class TestDeletePtpParameter(BasePtpParameterTestCase): + """ Tests deletion. + Typically delete APIs return NO CONTENT. + python2 and python3 libraries may return different + content_type (None, or empty json) when NO_CONTENT returned. + """ + + def setUp(self): + super(TestDeletePtpParameter, self).setUp() + + def test_delete_ptp_parameter(self): + ptp_parameter = dbutils.create_test_ptp_parameter( + name='fake-param', value='fake-value', + type=constants.PTP_PARAMETER_OWNER_INSTANCE, + foreign_uuid=self.ptp_instances[0].uuid) + uuid = ptp_parameter['uuid'] + + response = self.delete(self.get_single_url(uuid), + headers=self.API_HEADERS) + self.assertEqual(response.status_code, http_client.NO_CONTENT) + + # Check the instance was indeed removed + error_message = 'No PTP parameter with id %s found' % uuid + response = self.get_json(self.get_single_url(uuid), + expect_errors=True) + self.assertEqual('application/json', response.content_type) + self.assertEqual(response.status_code, http_client.NOT_FOUND) + self.assertIn(error_message, response.json['error_message']) diff --git a/sysinv/sysinv/sysinv/sysinv/tests/db/base.py b/sysinv/sysinv/sysinv/sysinv/tests/db/base.py index 42d2f0f38e..87c7fba309 100644 --- a/sysinv/sysinv/sysinv/sysinv/tests/db/base.py +++ b/sysinv/sysinv/sysinv/sysinv/tests/db/base.py @@ -447,6 +447,29 @@ class BaseHostTestCase(BaseSystemTestCase): index = index + 1 return ifaces + def _create_test_ptp_instance(self, host): + services = [constants.PTP_INSTANCE_TYPE_PTP4L, + constants.PTP_INSTANCE_TYPE_PHC2SYS] + names = [constants.PTP_INSTANCE_DEFAULT_PTP4L, + constants.PTP_INSTANCE_DEFAULT_PHC2SYS] + ptp_instances = [] + for svc, nm in zip(services, names): + instance = dbutils.create_test_ptp_instance( + name=nm, service=svc, host_id=host['id']) + ptp_instances.append(instance) + return ptp_instances + + def _create_test_ptp_interface(self, + ptp_instances, + platform_interfaces): + ptp_interfaces = [] + for ptp_instance in ptp_instances: + ptp_interface = dbutils.create_test_ptp_interface( + ptp_instance_id=ptp_instance['id'], + interface_id=platform_interfaces[0]['id']) + ptp_interfaces.append(ptp_interface) + return ptp_interfaces + class ControllerHostTestCase(BaseHostTestCase): diff --git a/sysinv/sysinv/sysinv/sysinv/tests/db/sqlalchemy/test_migrations.py b/sysinv/sysinv/sysinv/sysinv/tests/db/sqlalchemy/test_migrations.py index eb17ad5f75..60ea7ec928 100644 --- a/sysinv/sysinv/sysinv/sysinv/tests/db/sqlalchemy/test_migrations.py +++ b/sysinv/sysinv/sysinv/sysinv/tests/db/sqlalchemy/test_migrations.py @@ -2030,6 +2030,7 @@ class TestMigrations(BaseMigrationTestCase, WalkVersionsMixin): 'uuid': 'String', 'name': 'String', 'value': 'String', + 'type': 'String', 'foreign_uuid': 'String', } for column, column_type in ptp_parameters_columns.items(): diff --git a/sysinv/sysinv/sysinv/sysinv/tests/db/utils.py b/sysinv/sysinv/sysinv/sysinv/tests/db/utils.py index 340cb09910..9691689278 100644 --- a/sysinv/sysinv/sysinv/sysinv/tests/db/utils.py +++ b/sysinv/sysinv/sysinv/sysinv/tests/db/utils.py @@ -536,25 +536,27 @@ def create_test_ptp(**kw): return dbapi.ptp_create(ptp) -# Create test ptp_instance object +# Utility functions to create a PTP instance for testing def get_test_ptp_instance(**kw): - ptp_instance = { + instance = { 'id': kw.get('id'), 'uuid': kw.get('uuid'), - 'name': kw.get('name'), - 'service': kw.get('service'), - 'host_id': kw.get('host_id'), + 'name': kw.get('name', None), + 'service': kw.get('service', constants.PTP_INSTANCE_TYPE_PTP4L), + 'host_id': kw.get('host_id', None) } - return ptp_instance + return instance def create_test_ptp_instance(**kw): - ptp_instance = get_test_ptp_instance(**kw) - # Let DB generate ID if it isn't specified explicitly + instance = get_test_ptp_instance(**kw) + # Let DB generate ID if isn't specified if 'id' not in kw: - del ptp_instance['id'] + del instance['id'] + if 'uuid' in kw: + del instance['uuid'] dbapi = db_api.get_instance() - return dbapi.ptp_instance_create(ptp_instance) + return dbapi.ptp_instance_create(instance) # Create test ptp_interface object @@ -575,6 +577,30 @@ def create_test_ptp_interface(**kw): return dbapi.ptp_interface_create(ptp_interface) +# Utility functions to create a PTP parameter for testing +def get_test_ptp_parameter(**kw): + parameter = { + 'id': kw.get('id'), + 'uuid': kw.get('uuid'), + 'name': kw.get('name', None), + 'value': kw.get('value', None), + 'type': kw.get('type', None), + 'foreign_uuid': kw.get('foreign_uuid', None) + } + return parameter + + +def create_test_ptp_parameter(**kw): + parameter = get_test_ptp_parameter(**kw) + # Let DB generate ID if isn't specified + if 'id' not in kw: + del parameter['id'] + if 'uuid' in kw: + del parameter['uuid'] + dbapi = db_api.get_instance() + return dbapi.ptp_parameter_create(parameter) + + # Create test dns object def get_test_dns(**kw): dns = {