[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:
Douglas Henrique Koerich 2021-10-07 13:12:23 -03:00
parent 488b4e717c
commit 1a46426e56
13 changed files with 708 additions and 2 deletions

View File

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

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

View File

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

View File

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

View File

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

View File

@ -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'],

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

View File

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

View File

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

View File

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

View File

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

View File

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

View 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