[PTP dual NIC config] PTP instance: CLI, REST API
New shell commands for management of PTP instances configuration: - "system ptp-instance-add <name> <service> <hostname or id>" - "system ptp-instance-delete <name or UUID>" - "system ptp-instance-show <name or UUID>" - "system ptp-instance-list" (list all PTP instances) - "system host-ptp-instance-list <hostname or id>" (host's instances) Also added REST API to manage the PTP instance table contents. Test Plan: PASS: New unit tests for PTP instance API. Regression: PASS: Existing unit tests still running with former PTP implementation. Story: 2009248 Task: 43527 Signed-off-by: Douglas Henrique Koerich <douglashenrique.koerich@windriver.com> Change-Id: Ia74c55c486562aaac3e093f256b1dba267ed7ff5
This commit is contained in:
parent
488b4e717c
commit
1a46426e56
sysinv
cgts-client/cgts-client/cgtsclient/v1
sysinv/sysinv/sysinv
api/controllers/v1
common
conductor
db
tests/api
@ -69,6 +69,7 @@ from cgtsclient.v1 import partition
|
||||
from cgtsclient.v1 import pci_device
|
||||
from cgtsclient.v1 import port
|
||||
from cgtsclient.v1 import ptp
|
||||
from cgtsclient.v1 import ptp_instance
|
||||
from cgtsclient.v1 import registry_image
|
||||
from cgtsclient.v1 import remotelogging
|
||||
from cgtsclient.v1 import restore
|
||||
@ -118,6 +119,7 @@ class Client(http.HTTPClient):
|
||||
self.idns = idns.idnsManager(self)
|
||||
self.intp = intp.intpManager(self)
|
||||
self.ptp = ptp.ptpManager(self)
|
||||
self.ptp_instance = ptp_instance.PtpInstanceManager(self)
|
||||
self.iextoam = iextoam.iextoamManager(self)
|
||||
self.controller_fs = controller_fs.ControllerFsManager(self)
|
||||
self.storage_backend = storage_backend.StorageBackendManager(self)
|
||||
|
71
sysinv/cgts-client/cgts-client/cgtsclient/v1/ptp_instance.py
Normal file
71
sysinv/cgts-client/cgts-client/cgtsclient/v1/ptp_instance.py
Normal file
@ -0,0 +1,71 @@
|
||||
########################################################################
|
||||
#
|
||||
# Copyright (c) 2021 Wind River Systems, Inc.
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
########################################################################
|
||||
|
||||
from cgtsclient.common import base
|
||||
from cgtsclient.common import utils
|
||||
from cgtsclient import exc
|
||||
from cgtsclient.v1 import options
|
||||
|
||||
|
||||
CREATION_ATTRIBUTES = ['name', 'service', 'host_uuid']
|
||||
|
||||
|
||||
class PtpInstance(base.Resource):
|
||||
def __repr__(self):
|
||||
return "<PtpInstance %s>" % self._info
|
||||
|
||||
|
||||
class PtpInstanceManager(base.Manager):
|
||||
resource_class = PtpInstance
|
||||
|
||||
@staticmethod
|
||||
def _path(ptp_instance_id=None):
|
||||
return '/v1/ptp_instances/%s' % ptp_instance_id if ptp_instance_id \
|
||||
else '/v1/ptp_instances'
|
||||
|
||||
def list(self, q=None):
|
||||
return self._list(options.build_url(self._path(), q), "ptp_instances")
|
||||
|
||||
def list_by_host(self, ihost_uuid):
|
||||
path = '/v1/ihosts/%s/ptp_instances' % ihost_uuid
|
||||
return self._list(path, "ptp_instances")
|
||||
|
||||
def get(self, ptp_instance_id):
|
||||
try:
|
||||
return self._list(self._path(ptp_instance_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 delete(self, ptp_instance_id):
|
||||
return self._delete(self._path(ptp_instance_id))
|
||||
|
||||
|
||||
def _find_ptp_instance(cc, key):
|
||||
if key.isdigit() or utils.is_uuid_like(key):
|
||||
try:
|
||||
instance = cc.ptp_instance.get(key)
|
||||
except exc.HTTPNotFound:
|
||||
raise exc.CommandError('PTP instance not found: %s' % key)
|
||||
else:
|
||||
return instance
|
||||
else:
|
||||
ptp_instances = cc.ptp_instance.list()
|
||||
for instance in ptp_instances[:]:
|
||||
if instance.name == key:
|
||||
return instance
|
||||
else:
|
||||
raise exc.CommandError('PTP instance not found: %s' % key)
|
@ -0,0 +1,103 @@
|
||||
########################################################################
|
||||
#
|
||||
# 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 ihost as ihost_utils
|
||||
from cgtsclient.v1 import ptp_instance as ptp_instance_utils
|
||||
|
||||
|
||||
def _print_ptp_instance_show(ptp_instance_obj):
|
||||
fields = ['uuid',
|
||||
'name',
|
||||
'service',
|
||||
'hostname',
|
||||
'created_at']
|
||||
data = [(f, getattr(ptp_instance_obj, f, '')) for f in fields]
|
||||
utils.print_tuple_list(data)
|
||||
|
||||
|
||||
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 = ['name', 'service', 'hostname']
|
||||
fields = ['name', 'service', 'hostname']
|
||||
utils.print_list(ptp_instances, fields, field_labels)
|
||||
|
||||
|
||||
@utils.arg('hostnameorid',
|
||||
metavar='<hostname or id>',
|
||||
help="Name or ID of host")
|
||||
def do_host_ptp_instance_list(cc, args):
|
||||
"""List PTP instances on host."""
|
||||
ihost = ihost_utils._find_ihost(cc, args.hostnameorid)
|
||||
ptp_instances = cc.ptp_instance.list_by_host(ihost.uuid)
|
||||
|
||||
field_labels = ['name', 'service', 'uuid']
|
||||
fields = ['name', 'service', 'uuid']
|
||||
utils.print_list(ptp_instances, fields, field_labels)
|
||||
|
||||
|
||||
@utils.arg('nameoruuid',
|
||||
metavar='<name or UUID>',
|
||||
help="Name or UUID of PTP instance")
|
||||
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)
|
||||
|
||||
|
||||
@utils.arg('name',
|
||||
metavar='<name>',
|
||||
help="Name of PTP instance [REQUIRED]")
|
||||
@utils.arg('service',
|
||||
metavar='<service type>',
|
||||
choices=['ptp4l', 'phc2sys', 'ts2phc'],
|
||||
help="Service type [REQUIRED]")
|
||||
@utils.arg('hostnameorid',
|
||||
metavar='<hostname or id>',
|
||||
help="Name or ID of host [REQUIRED]")
|
||||
def do_ptp_instance_add(cc, args):
|
||||
"""Add a PTP instance."""
|
||||
|
||||
field_list = ['name', 'service']
|
||||
|
||||
# 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))
|
||||
|
||||
ihost = ihost_utils._find_ihost(cc, args.hostnameorid)
|
||||
data.update({'host_uuid': ihost.uuid})
|
||||
|
||||
ptp_instance = cc.ptp_instance.create(**data)
|
||||
uuid = getattr(ptp_instance, 'uuid', '')
|
||||
try:
|
||||
ptp_instance = cc.ptp_instance.get(uuid)
|
||||
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)
|
||||
|
||||
|
||||
@utils.arg('nameoruuid',
|
||||
metavar='<name or UUID>',
|
||||
help="Name or UUID of PTP instance")
|
||||
def do_ptp_instance_delete(cc, args):
|
||||
"""Delete a PTP instance."""
|
||||
ptp_instance = ptp_instance_utils._find_ptp_instance(cc, args.nameoruuid)
|
||||
uuid = ptp_instance.uuid
|
||||
cc.ptp_instance.delete(uuid)
|
||||
print('Deleted PTP instance: %s' % uuid)
|
@ -54,6 +54,7 @@ from cgtsclient.v1 import network_shell
|
||||
from cgtsclient.v1 import partition_shell
|
||||
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_shell
|
||||
from cgtsclient.v1 import registry_image_shell
|
||||
from cgtsclient.v1 import remotelogging_shell
|
||||
@ -75,6 +76,7 @@ COMMAND_MODULES = [
|
||||
idns_shell,
|
||||
intp_shell,
|
||||
ptp_shell,
|
||||
ptp_instance_shell,
|
||||
iextoam_shell,
|
||||
controller_fs_shell,
|
||||
storage_backend_shell,
|
||||
|
@ -66,6 +66,7 @@ from sysinv.api.controllers.v1 import pci_device
|
||||
from sysinv.api.controllers.v1 import port
|
||||
from sysinv.api.controllers.v1 import profile
|
||||
from sysinv.api.controllers.v1 import ptp
|
||||
from sysinv.api.controllers.v1 import ptp_instance
|
||||
from sysinv.api.controllers.v1 import pv
|
||||
from sysinv.api.controllers.v1 import registry_image
|
||||
from sysinv.api.controllers.v1 import remotelogging
|
||||
@ -150,6 +151,9 @@ class V1(base.APIBase):
|
||||
ptp = [link.Link]
|
||||
"Links to the ptp resource"
|
||||
|
||||
ptp_instances = [link.Link]
|
||||
"Links to the ptp_instances resource"
|
||||
|
||||
iextoam = [link.Link]
|
||||
"Links to the iextoam resource"
|
||||
|
||||
@ -458,6 +462,13 @@ class V1(base.APIBase):
|
||||
bookmark=True)
|
||||
]
|
||||
|
||||
v1.ptp_instances = [link.Link.make_link('self', pecan.request.host_url,
|
||||
'ptp_instances', ''),
|
||||
link.Link.make_link('bookmark',
|
||||
pecan.request.host_url,
|
||||
'ptp_instances', '',
|
||||
bookmark=True)]
|
||||
|
||||
v1.iextoam = [link.Link.make_link('self', pecan.request.host_url,
|
||||
'iextoam', ''),
|
||||
link.Link.make_link('bookmark',
|
||||
@ -901,6 +912,7 @@ class Controller(rest.RestController):
|
||||
idns = dns.DNSController()
|
||||
intp = ntp.NTPController()
|
||||
ptp = ptp.PTPController()
|
||||
ptp_instances = ptp_instance.PtpInstanceController()
|
||||
iextoam = network_oam.OAMNetworkController()
|
||||
controller_fs = controller_fs.ControllerFsController()
|
||||
storage_backend = storage_backend.StorageBackendController()
|
||||
|
@ -87,6 +87,7 @@ from sysinv.api.controllers.v1 import interface_network
|
||||
from sysinv.api.controllers.v1 import interface_datanetwork
|
||||
from sysinv.api.controllers.v1 import vim_api
|
||||
from sysinv.api.controllers.v1 import patch_api
|
||||
from sysinv.api.controllers.v1 import ptp_instance
|
||||
|
||||
from sysinv.common import ceph
|
||||
from sysinv.common import constants
|
||||
@ -1143,6 +1144,9 @@ class HostController(rest.RestController):
|
||||
parent="ihosts")
|
||||
"Expose interface_datanetworks as a sub-element of ihosts"
|
||||
|
||||
ptp_instances = ptp_instance.PtpInstanceController(from_ihosts=True)
|
||||
"Expose PTP instance as a sub-element of ihosts"
|
||||
|
||||
_custom_actions = {
|
||||
'detail': ['GET'],
|
||||
'bulk_add': ['POST'],
|
||||
|
215
sysinv/sysinv/sysinv/sysinv/api/controllers/v1/ptp_instance.py
Normal file
215
sysinv/sysinv/sysinv/sysinv/api/controllers/v1/ptp_instance.py
Normal file
@ -0,0 +1,215 @@
|
||||
#
|
||||
# Copyright (c) 2021 Wind River Systems, Inc.
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
|
||||
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 exception
|
||||
from sysinv.common import utils as cutils
|
||||
from sysinv import objects
|
||||
|
||||
LOG = log.getLogger(__name__)
|
||||
|
||||
|
||||
class PtpInstancePatchType(types.JsonPatchType):
|
||||
@staticmethod
|
||||
def mandatory_attrs():
|
||||
return []
|
||||
|
||||
|
||||
class PtpInstance(base.APIBase):
|
||||
"""API representation of a PTP instance.
|
||||
|
||||
This class enforces type checking and value constraints, and converts
|
||||
between the internal object model and the API representation of
|
||||
a PTP instance.
|
||||
"""
|
||||
|
||||
created_at = wtypes.datetime.datetime
|
||||
"Timestamp of creation of this PTP instance"
|
||||
|
||||
id = int
|
||||
"Unique ID for this PTP instance"
|
||||
|
||||
uuid = types.uuid
|
||||
"Unique UUID for this PTP instance"
|
||||
|
||||
host_id = int
|
||||
"ID of host the PTP instance is associated to"
|
||||
|
||||
host_uuid = types.uuid
|
||||
"UUID 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')
|
||||
"Type of service of the PTP instance"
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
self.fields = list(objects.ptp_instance.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_instance, expand=True):
|
||||
ptp_instance = PtpInstance(**rpc_ptp_instance.as_dict())
|
||||
if not expand:
|
||||
ptp_instance.unset_fields_except(['uuid',
|
||||
'host_uuid',
|
||||
'name',
|
||||
'service',
|
||||
'created_at'])
|
||||
|
||||
# do not expose the id attribute
|
||||
ptp_instance.host_id = wtypes.Unset
|
||||
|
||||
LOG.debug("PtpInstance.convert_with_links: converted %s" %
|
||||
ptp_instance.as_dict())
|
||||
return ptp_instance
|
||||
|
||||
|
||||
class PtpInstanceCollection(collection.Collection):
|
||||
"""API representation of a collection of PTP instances."""
|
||||
|
||||
ptp_instances = [PtpInstance]
|
||||
"A list containing PTP instance objects"
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
self._type = 'ptp_instances'
|
||||
|
||||
@classmethod
|
||||
def convert_with_links(cls, rpc_ptp_instances, limit, url=None,
|
||||
expand=False, **kwargs):
|
||||
collection = PtpInstanceCollection()
|
||||
collection.ptp_instances = [PtpInstance.convert_with_links(p, expand)
|
||||
for p in rpc_ptp_instances]
|
||||
collection.next = collection.get_next(limit, url=url, **kwargs)
|
||||
return collection
|
||||
|
||||
|
||||
LOCK_NAME = 'PtpInstanceController'
|
||||
|
||||
|
||||
class PtpInstanceController(rest.RestController):
|
||||
"""REST controller for PTP instance."""
|
||||
|
||||
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,
|
||||
sort_dir=None, expand=False, resource_url=None):
|
||||
LOG.debug("PtpInstanceController._get_ptp_instance_collection: "
|
||||
"from_ihosts %s host_uuid %s" % (self._from_ihosts,
|
||||
host_uuid))
|
||||
if self._from_ihosts and not host_uuid:
|
||||
raise exception.InvalidParameterValue(_(
|
||||
"Host id not specified."))
|
||||
|
||||
limit = utils.validate_limit(limit)
|
||||
sort_dir = utils.validate_sort_dir(sort_dir)
|
||||
|
||||
LOG.debug("PtpInstanceController._get_ptp_instance_collection: "
|
||||
"marker %s, limit %s, sort_dir %s" % (marker, limit,
|
||||
sort_dir))
|
||||
|
||||
marker_obj = None
|
||||
if marker:
|
||||
marker_obj = objects.ptp_instance.get_by_uuid(
|
||||
pecan.request.context, marker)
|
||||
|
||||
if self._from_ihosts or host_uuid:
|
||||
ptp_instances = pecan.request.dbapi.ptp_instances_get_by_ihost(
|
||||
host_uuid, limit,
|
||||
marker_obj,
|
||||
sort_key=sort_key,
|
||||
sort_dir=sort_dir)
|
||||
else:
|
||||
ptp_instances = pecan.request.dbapi.ptp_instances_get_list(
|
||||
limit, marker_obj,
|
||||
sort_key=sort_key,
|
||||
sort_dir=sort_dir)
|
||||
|
||||
return PtpInstanceCollection.convert_with_links(
|
||||
ptp_instances, limit, url=resource_url, expand=expand,
|
||||
sort_key=sort_key, sort_dir=sort_dir)
|
||||
|
||||
@wsme_pecan.wsexpose(PtpInstanceCollection, 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 instances."""
|
||||
LOG.debug("PtpInstanceController.get_all: uuid=%s" % uuid)
|
||||
return self._get_ptp_instance_collection(uuid, marker, limit,
|
||||
sort_key=sort_key,
|
||||
sort_dir=sort_dir)
|
||||
|
||||
@wsme_pecan.wsexpose(PtpInstance, types.uuid)
|
||||
def get_one(self, ptp_instance_uuid):
|
||||
"""Retrieve a single PTP instance."""
|
||||
LOG.debug("PtpInstanceController.get_one: uuid=%s" % ptp_instance_uuid)
|
||||
try:
|
||||
ptp_instance = objects.ptp_instance.get_by_uuid(
|
||||
pecan.request.context,
|
||||
ptp_instance_uuid)
|
||||
except exception.InvalidParameterValue:
|
||||
raise wsme.exc.ClientSideError(
|
||||
_("No PTP instance found for %s" % ptp_instance_uuid))
|
||||
|
||||
return PtpInstance.convert_with_links(ptp_instance)
|
||||
|
||||
@cutils.synchronized(LOCK_NAME)
|
||||
@wsme_pecan.wsexpose(PtpInstance, body=PtpInstance)
|
||||
def post(self, ptp_instance):
|
||||
"""Create a new PTP instance."""
|
||||
ptp_instance_dict = ptp_instance.as_dict()
|
||||
LOG.debug("PtpInstanceController.post: %s" % ptp_instance_dict)
|
||||
|
||||
# Replace host UUID by host ID
|
||||
host_uuid = ptp_instance_dict.pop('host_uuid')
|
||||
try:
|
||||
ihost_obj = pecan.request.dbapi.ihost_get(host_uuid)
|
||||
except exception.HostNotFound:
|
||||
msg = _("Host with uuid '%s' does not exist. " % host_uuid)
|
||||
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)
|
||||
|
||||
@cutils.synchronized(LOCK_NAME)
|
||||
@wsme_pecan.wsexpose(None, types.uuid, status_code=204)
|
||||
def delete(self, ptp_instance_uuid):
|
||||
"""Delete a PTP instance."""
|
||||
LOG.debug("PtpInstanceController.delete: %s" % ptp_instance_uuid)
|
||||
if self._from_ihosts:
|
||||
raise exception.OperationNotPermitted
|
||||
|
||||
# Only allow delete if there are no associated interfaces and
|
||||
# parameters
|
||||
parameters = pecan.request.dbapi.ptp_parameters_get_by_foreign_uuid(
|
||||
ptp_instance_uuid)
|
||||
interfaces = pecan.request.dbapi.ptp_interfaces_get_by_instance(
|
||||
ptp_instance_uuid)
|
||||
if parameters or interfaces:
|
||||
raise wsme.exc.ClientSideError(
|
||||
_("PTP instance %s has still parameters or associated "
|
||||
"interfaces. Check both ptp-interfaces and ptp-parameters.")
|
||||
% ptp_instance_uuid)
|
||||
|
||||
pecan.request.dbapi.ptp_instance_destroy(ptp_instance_uuid)
|
@ -453,7 +453,7 @@ class PTPAlreadyExists(Conflict):
|
||||
|
||||
|
||||
class PtpInstanceAlreadyExists(Conflict):
|
||||
message = _("A PTP instance with UUID %(uuid)s already exists.")
|
||||
message = _("A PTP instance with name '%(name)s' already exists.")
|
||||
|
||||
|
||||
class PtpInterfaceAlreadyExists(Conflict):
|
||||
|
@ -7021,6 +7021,19 @@ class ConductorManager(service.PeriodicService):
|
||||
}
|
||||
self._config_apply_runtime_manifest(context, config_uuid, config_dict)
|
||||
|
||||
def update_ptp_instances_config(self, context, host_uuid,
|
||||
ptp_instances_dict):
|
||||
try:
|
||||
host = self.dbapi.ihost_get(host_uuid)
|
||||
if host: # TODO: put here to make variable used; remove it later
|
||||
pass
|
||||
except exception.ServerNotFound:
|
||||
LOG.error("Cannot find host by id %s" % host_uuid)
|
||||
return
|
||||
# TODO: apply configuration of PTP instance to host (will probably go
|
||||
# through PTP parameters instead to set the 'conf' files for services)
|
||||
pass
|
||||
|
||||
def update_system_mode_config(self, context):
|
||||
"""Update the system mode configuration"""
|
||||
personalities = [constants.CONTROLLER]
|
||||
|
@ -807,6 +807,19 @@ class ConductorAPI(sysinv.openstack.common.rpc.proxy.RpcProxy):
|
||||
"""
|
||||
return self.call(context, self.make_msg('update_ptp_config', do_apply=do_apply))
|
||||
|
||||
def update_ptp_instances_config(self, context, host_uuid,
|
||||
ptp_instances_dict):
|
||||
"""Synchronously, have the conductor update PTP instance(s).
|
||||
|
||||
:param context: request context.
|
||||
:param host_uuid: uuid or id of the host
|
||||
:param ptp_instances_dict: a dictionary {name:service} of PTP instances
|
||||
"""
|
||||
return self.call(context,
|
||||
self.make_msg('update_ptp_instances_config',
|
||||
host_uuid=host_uuid,
|
||||
ptp_instances_dict=ptp_instances_dict))
|
||||
|
||||
def update_system_mode_config(self, context):
|
||||
"""Synchronously, have the conductor update the system mode
|
||||
configuration.
|
||||
|
@ -2106,6 +2106,22 @@ class Connection(object):
|
||||
:returns: A list of PTP associations (instances) for the interface.
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def ptp_interfaces_get_by_instance(self, ptp_instance_id, limit=None,
|
||||
marker=None, sort_key=None,
|
||||
sort_dir=None):
|
||||
"""Returns a list of the PTP associations for a given PTP instance.
|
||||
|
||||
:param ptp_instance_id: The id or uuid of a PTP instance.
|
||||
:param limit: Maximum number of PTP associations 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 associations (interfaces) for the PTP instance.
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def ptp_interface_destroy(self, ptp_interface_id):
|
||||
"""Destroys a PTP interface association.
|
||||
|
@ -3807,7 +3807,7 @@ class Connection(api.Connection):
|
||||
session.add(ptp_instance)
|
||||
session.flush()
|
||||
except db_exc.DBDuplicateEntry:
|
||||
raise exception.PtpInstanceAlreadyExists(uuid=values['uuid'])
|
||||
raise exception.PtpInstanceAlreadyExists(name=values['name'])
|
||||
return self._ptp_instance_get(values['uuid'])
|
||||
|
||||
@objects.objectify(objects.ptp_instance)
|
||||
@ -3926,6 +3926,18 @@ class Connection(api.Connection):
|
||||
return _paginate_query(models.PtpInterfaces, limit, marker,
|
||||
sort_key, sort_dir, query)
|
||||
|
||||
@objects.objectify(objects.ptp_interface)
|
||||
def ptp_interfaces_get_by_instance(self, ptp_instance_id, limit=None,
|
||||
marker=None, sort_key=None,
|
||||
sort_dir=None):
|
||||
# NOTE: ptp_instance_get() to raise an exception if the instance is
|
||||
# not found
|
||||
ptp_instance_obj = self.ptp_instance_get(ptp_instance_id)
|
||||
query = model_query(models.PtpInterfaces)
|
||||
query = query.filter_by(ptp_instance_id=ptp_instance_obj.id)
|
||||
return _paginate_query(models.PtpInterfaces, limit, marker,
|
||||
sort_key, sort_dir, query)
|
||||
|
||||
def ptp_interface_destroy(self, ptp_interface_id):
|
||||
with _session_for_write() as session:
|
||||
query = model_query(models.PtpInterfaces, session=session)
|
||||
|
243
sysinv/sysinv/sysinv/sysinv/tests/api/test_ptp_instance.py
Normal file
243
sysinv/sysinv/sysinv/sysinv/tests/api/test_ptp_instance.py
Normal file
@ -0,0 +1,243 @@
|
||||
# Copyright (c) 2021 Wind River Systems, Inc.
|
||||
#
|
||||
# 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
|
||||
|
||||
|
||||
class BasePtpInstanceTestCase(base.FunctionalTest, dbbase.BaseHostTestCase):
|
||||
# Generic header passed to most API calls
|
||||
API_HEADERS = {'User-Agent': 'sysinv-test'}
|
||||
|
||||
# Prefix for the URL
|
||||
API_PREFIX = '/ptp_instances'
|
||||
|
||||
# Python table key for the list of results
|
||||
RESULT_KEY = 'ptp_instances'
|
||||
|
||||
# Field that is known to exist for inputs and outputs
|
||||
COMMON_FIELD = 'name'
|
||||
|
||||
# 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)
|
||||
self.worker = self._create_test_host(constants.WORKER)
|
||||
|
||||
def get_single_url(self, ptp_instance_uuid):
|
||||
return '%s/%s' % (self.API_PREFIX, ptp_instance_uuid)
|
||||
|
||||
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)
|
||||
ptp_instance_db['host_uuid'] = host_uuid
|
||||
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'
|
||||
host_id = None
|
||||
host_uuid = 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)
|
||||
|
||||
def _create_ptp_instance_success(self, name, service, host_id, host_uuid):
|
||||
ptp_instance_db = self.get_post_object(name=name, service=service,
|
||||
host_id=host_id,
|
||||
host_uuid=host_uuid)
|
||||
response = self.post_json(self.API_PREFIX, ptp_instance_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_instance_db[self.COMMON_FIELD])
|
||||
|
||||
def _create_ptp_instance_failed(self, name, service, host_id, host_uuid,
|
||||
status_code, error_message):
|
||||
ptp_instance_db = self.get_post_object(name=name, service=service,
|
||||
host_id=host_id,
|
||||
host_uuid=host_uuid)
|
||||
response = self.post_json(self.API_PREFIX, ptp_instance_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_instance_ok(self):
|
||||
self._create_ptp_instance_success('test-instance', 'ptp4l',
|
||||
host_id=self.controller.id,
|
||||
host_uuid=self.controller.uuid)
|
||||
|
||||
def test_create_ptp_instance_invalid_service(self):
|
||||
self._create_ptp_instance_failed(
|
||||
'test-invalid',
|
||||
'invalid',
|
||||
host_id=self.controller.id,
|
||||
host_uuid=self.controller.uuid,
|
||||
status_code=http_client.BAD_REQUEST,
|
||||
error_message='Invalid input for field/attribute service')
|
||||
|
||||
def test_create_ptp_instance_duplicate_name(self):
|
||||
error_message = \
|
||||
"PTP instance with name '%s' already exists" % self.name
|
||||
self._create_ptp_instance_failed(
|
||||
name=self.name,
|
||||
service=self.service,
|
||||
host_id=self.host_id,
|
||||
host_uuid=self.host_uuid,
|
||||
status_code=http_client.CONFLICT,
|
||||
error_message=error_message)
|
||||
|
||||
def test_create_ptp_instance_invalid_host(self):
|
||||
bad_uuid = 'f4c56ddf-aef3-46ed-b9aa-126a1faafd40'
|
||||
error_message = '%s could not be found' % bad_uuid
|
||||
self._create_ptp_instance_failed(
|
||||
'test-invalid',
|
||||
'phc2sys',
|
||||
host_id=99,
|
||||
host_uuid='f4c56ddf-aef3-46ed-b9aa-126a1faafd40',
|
||||
status_code=http_client.NOT_FOUND,
|
||||
error_message=error_message)
|
||||
|
||||
|
||||
class TestGetPtpInstance(BasePtpInstanceTestCase):
|
||||
def setUp(self):
|
||||
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)
|
||||
uuid = ptp_instance['uuid']
|
||||
response = self.get_json(self.get_single_url(uuid))
|
||||
self.assertIn(self.COMMON_FIELD, response)
|
||||
|
||||
def test_get_ptp_instance_not_found(self):
|
||||
fake_uuid = 'f4c56ddf-aef3-46ed-b9aa-126a1faafd40'
|
||||
error_message = 'No PTP instance 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 TestListPtpInstance(BasePtpInstanceTestCase):
|
||||
def setUp(self):
|
||||
super(TestListPtpInstance, self).setUp()
|
||||
self._create_test_ptp_instances()
|
||||
|
||||
def _create_test_ptp_instances(self, name_prefix='test', host_id=None):
|
||||
services = ['ptp4l', 'phc2sys', '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(
|
||||
name=name, service=service, host_id=host_id)
|
||||
instances.append(instance)
|
||||
return instances
|
||||
|
||||
def test_list_ptp_instance_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_instance_empty(self):
|
||||
response = self.get_json(self.get_host_scoped_url(self.worker.uuid))
|
||||
self.assertEqual([], response[self.RESULT_KEY])
|
||||
|
||||
def test_list_ptp_instance_host(self):
|
||||
self._create_test_ptp_instances(name_prefix='fake',
|
||||
host_id=self.worker.id)
|
||||
response = self.get_json(self.get_host_scoped_url(self.worker.uuid))
|
||||
for result in response[self.RESULT_KEY]:
|
||||
self.assertEqual(self.worker.uuid, result['host_uuid'])
|
||||
|
||||
|
||||
class TestDeletePtpInstance(BasePtpInstanceTestCase):
|
||||
""" 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(TestDeletePtpInstance, self).setUp()
|
||||
|
||||
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),
|
||||
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),
|
||||
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
|
||||
|
||||
def test_delete_ptp_instance_with_interfaces_failed(self):
|
||||
# TODO: implement when PTP interfaces API is available
|
||||
pass
|
Loading…
x
Reference in New Issue
Block a user