Allow configuration of PTP master/slave interfaces

With this change a new field ptp_role will be added to the interfaces
table. This will allow administrators to configure which interfaces will
be used for PTP serivces. Only platform or SRIOV interfaces may be
specified for PTP use. If a host has clock_synchronization=ptp there
must be at least one host interface with a ptp_role value specified.

Change-Id: I86eddebf2f716f76d4eb9a5b5698a61ff3aec566
Story: 2006759
Task: 37690
Signed-off-by: David Sullivan <david.sullivan@windriver.com>
This commit is contained in:
David Sullivan 2019-12-01 23:56:20 -05:00
parent 638487f67b
commit 8ad91e1cbf
17 changed files with 562 additions and 8 deletions

View File

@ -17,7 +17,7 @@ CREATION_ATTRIBUTES = ['ifname', 'iftype', 'ihost_uuid', 'imtu', 'ifclass',
'providernetworks', 'datanetworks', 'ifcapabilities', 'ports', 'imac',
'vlan_id', 'uses', 'used_by',
'ipv4_mode', 'ipv6_mode', 'ipv4_pool', 'ipv6_pool',
'sriov_numvfs', 'sriov_vf_driver']
'sriov_numvfs', 'sriov_vf_driver', 'ptp_role']
class iinterface(base.Resource):

View File

@ -16,7 +16,7 @@ from cgtsclient.v1 import iinterface as iinterface_utils
def _print_iinterface_show(cc, iinterface):
fields = ['ifname', 'iftype', 'ports',
'imac', 'imtu', 'ifclass',
'imac', 'imtu', 'ifclass', 'ptp_role',
'aemode', 'schedpolicy', 'txhashpolicy',
'uuid', 'ihost_uuid',
'vlan_id', 'uses', 'used_by',
@ -164,11 +164,16 @@ def do_host_if_delete(cc, args):
metavar='<sriov vf driver>',
choices=['netdevice', 'vfio'],
help='The SR-IOV VF driver for this device')
@utils.arg('--ptp-role',
dest='ptp_role',
metavar='<ptp role>',
choices=['master', 'slave', 'none'],
help='The PTP role for this interface')
def do_host_if_add(cc, args):
"""Add an interface."""
field_list = ['ifname', 'iftype', 'imtu', 'ifclass', 'aemode',
'txhashpolicy', 'vlan_id',
'txhashpolicy', 'vlan_id', 'ptp_role',
'ipv4_mode', 'ipv6_mode', 'ipv4_pool', 'ipv6_pool',
'sriov_numvfs', 'sriov_vf_driver']
@ -254,11 +259,16 @@ def do_host_if_add(cc, args):
metavar='<sriov vf driver>',
choices=['netdevice', 'vfio'],
help='The SR-IOV VF driver for this device')
@utils.arg('--ptp-role',
dest='ptp_role',
metavar='<ptp role>',
choices=['master', 'slave', 'none'],
help='The PTP role for this interface')
def do_host_if_modify(cc, args):
"""Modify interface attributes."""
rwfields = ['iftype', 'ifname', 'imtu', 'aemode', 'txhashpolicy',
'ports', 'ifclass',
'ports', 'ifclass', 'ptp_role',
'ipv4_mode', 'ipv6_mode', 'ipv4_pool', 'ipv6_pool',
'sriov_numvfs', 'sriov_vf_driver']

View File

@ -6052,6 +6052,32 @@ class HostController(rest.RestController):
hostupdate.configure_required = True
host_uuid = ihost['uuid']
# Validate PTP interfaces if PTP is used on this host
if ihost['clock_synchronization'] == constants.PTP:
# Ensure we have at least one PTP interface
host_interfaces = pecan.request.dbapi.iinterface_get_by_ihost(host_uuid)
ptp_interfaces = []
for interface in host_interfaces:
if interface.ptp_role != constants.INTERFACE_PTP_ROLE_NONE:
ptp_interfaces.append(interface)
if not ptp_interfaces:
raise wsme.exc.ClientSideError(
_("Hosts with PTP clock synchronization must have at least one PTP interface configured"))
# Check that interfaces have addresses assigned if PTP transport is UDP
system_ptp = pecan.request.dbapi.ptp_get_one()
if system_ptp.transport == constants.PTP_TRANSPORT_UDP:
addresses = pecan.request.dbapi.addresses_get_by_host(host_uuid)
address_interfaces = set()
for address in addresses:
address_interfaces.add(address.ifname)
for ptp_interface in ptp_interfaces:
if ptp_interface.ifname not in address_interfaces:
raise wsme.exc.ClientSideError(
_("All PTP interfaces must have an associated address when PTP transport is UDP"))
def check_unlock_partitions(self, hostupdate):
"""Semantic check for interfaces on host-unlock."""
ihost = hostupdate.ihost_patch

View File

@ -189,6 +189,9 @@ class Interface(base.APIBase):
sriov_vf_driver = wtypes.text
"The driver of configured SR-IOV VFs"
ptp_role = wtypes.text
"The PTP role for this interface"
def __init__(self, **kwargs):
self.fields = list(objects.interface.fields.keys())
for k in self.fields:
@ -213,7 +216,7 @@ class Interface(base.APIBase):
'aemode', 'schedpolicy', 'txhashpolicy',
'vlan_id', 'uses', 'usesmodify', 'used_by',
'ipv4_mode', 'ipv6_mode', 'ipv4_pool', 'ipv6_pool',
'sriov_numvfs', 'sriov_vf_driver'])
'sriov_numvfs', 'sriov_vf_driver', 'ptp_role'])
# never expose the ihost_id attribute
interface.ihost_id = wtypes.Unset
@ -1000,6 +1003,33 @@ def _check_network_type_and_port(interface, ihost,
raise wsme.exc.ClientSideError(msg)
def _check_interface_ptp(interface):
# Ensure PTP settings are valid for this interface
# Validate PTP role value
ptp_role = interface['ptp_role']
if ptp_role is None:
return
supported_ptp_values = [constants.INTERFACE_PTP_ROLE_MASTER,
constants.INTERFACE_PTP_ROLE_SLAVE,
constants.INTERFACE_PTP_ROLE_NONE]
if ptp_role not in supported_ptp_values:
msg = (_("Interface ptp_role must be one of "
"{}").format(', '.join(supported_ptp_values)))
raise wsme.exc.ClientSideError(msg)
# Validate interface class for PTP
if ptp_role != constants.INTERFACE_PTP_ROLE_NONE:
ifclass = interface['ifclass']
supported_ptp_classes = [constants.INTERFACE_CLASS_PLATFORM,
constants.INTERFACE_CLASS_PCI_SRIOV,
constants.INTERFACE_TYPE_VF]
if not ifclass or ifclass not in supported_ptp_classes:
msg = (_("Invalid interface class for ptp_role: {0}. Device interface class must be one of "
"{1}").format(ptp_role, ', '.join(supported_ptp_classes)))
raise wsme.exc.ClientSideError(msg)
def _check_interface_data(op, interface, ihost, existing_interface,
datanetworks=None):
# Get data
@ -1258,6 +1288,8 @@ def _check_interface_data(op, interface, ihost, existing_interface,
"on the underlying interface (%s)" %
(interface['sriov_numvfs'], avail_vfs, lower_iface['ifname']))
raise wsme.exc.ClientSideError(msg)
_check_interface_ptp(interface)
return interface

View File

@ -20,6 +20,7 @@ from sysinv.api.controllers.v1 import collection
from sysinv.api.controllers.v1 import link
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
@ -210,6 +211,8 @@ class PTPController(rest.RestController):
rpc_ptp[field] = ptp[field]
delta = rpc_ptp.obj_what_changed()
if 'transport' in delta and rpc_ptp.transport == constants.PTP_TRANSPORT_UDP:
self._validate_ptp_udp_transport()
if delta:
rpc_ptp.save()
# perform rpc to conductor to perform config apply
@ -224,6 +227,32 @@ class PTPController(rest.RestController):
(ptp['mode'], ptp['transport'], ptp['mechanism'], patch))
raise wsme.exc.ClientSideError(msg)
def _validate_ptp_udp_transport(self):
# Ensure all hosts using ptp have addresses associated with their ptp interfaces
hosts = pecan.request.dbapi.ihost_get_list()
ptp_hosts = []
for host in hosts:
if host.clock_synchronization == constants.PTP and host.administrative == constants.ADMIN_UNLOCKED:
ptp_hosts.append(host)
for ptp_host in ptp_hosts:
host_interfaces = pecan.request.dbapi.iinterface_get_by_ihost(ptp_host.uuid)
ptp_interfaces = []
for interface in host_interfaces:
if interface.ptp_role != constants.INTERFACE_PTP_ROLE_NONE:
ptp_interfaces.append(interface)
addresses = pecan.request.dbapi.addresses_get_by_host(ptp_host.uuid)
address_interfaces = set()
for address in addresses:
address_interfaces.add(address.ifname)
for ptp_interface in ptp_interfaces:
if ptp_interface.ifname not in address_interfaces:
raise wsme.exc.ClientSideError(_("Invalid system configuration for UDP based PTP transport. All "
"hosts must have addresses specified for each PTP interface. "
"Interface %s on host %s does not have an address." %
(ptp_interface.ifname, ptp_host.hostname)))
@wsme_pecan.wsexpose(None, types.uuid, status_code=204)
def delete(self, ptp_uuid):
"""Delete a ptp."""

View File

@ -670,6 +670,10 @@ INTERFACE_CLASS_DATA = 'data'
INTERFACE_CLASS_PCI_PASSTHROUGH = 'pci-passthrough'
INTERFACE_CLASS_PCI_SRIOV = 'pci-sriov'
INTERFACE_PTP_ROLE_MASTER = 'master'
INTERFACE_PTP_ROLE_SLAVE = 'slave'
INTERFACE_PTP_ROLE_NONE = 'none'
AE_MODE_ACTIVE_STANDBY = 'active_standby'
AE_MODE_BALANCED = 'balanced'
AE_MODE_LACP = '802.3ad'
@ -1523,6 +1527,10 @@ CLOCK_SYNCHRONIZATION = [
PTP
]
# PTP transport modes
PTP_TRANSPORT_UDP = 'udp'
PTP_TRANSPORT_L2 = 'l2'
# Backup & Restore
FIX_INSTALL_UUID_INTERVAL_SECS = 30

View File

@ -0,0 +1,24 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
#
# Copyright (c) 2019 Wind River Systems, Inc.
#
# SPDX-License-Identifier: Apache-2.0
#
from sqlalchemy import Column, MetaData, String, Table
ENGINE = 'InnoDB'
CHARSET = 'utf8'
def upgrade(migrate_engine):
meta = MetaData()
meta.bind = migrate_engine
interface = Table('interfaces', meta, autoload=True)
interface.create_column(Column('ptp_role', String(255)), default='none')
def downgrade(migrate_engine):
# Downgrade is unsupported in this release.
raise NotImplementedError('SysInv database downgrade is unsupported.')

View File

@ -347,6 +347,7 @@ class Interfaces(Base):
farend = Column(JSONEncodedDict)
sriov_numvfs = Column(Integer)
sriov_vf_driver = Column(String(255))
ptp_role = Column(String(255), default='none')
used_by = relationship(
"Interfaces",

View File

@ -145,7 +145,8 @@ class Interface(base.SysinvObject):
'ipv4_pool': utils.uuid_or_none,
'ipv6_pool': utils.uuid_or_none,
'sriov_numvfs': utils.int_or_none,
'sriov_vf_driver': utils.str_or_none
'sriov_vf_driver': utils.str_or_none,
'ptp_role': utils.str_or_none
}
_foreign_fields = {'uses': _get_interface_name_list,

View File

@ -1230,6 +1230,17 @@ def find_sriov_interfaces_by_driver(context, driver):
return ifaces
def get_ptp_interfaces(context):
"""
Lookup interfaces with a ptp_role specified
"""
ifaces = []
for ifname, iface in six.iteritems(context['interfaces']):
if iface['ptp_role'] != constants.INTERFACE_PTP_ROLE_NONE:
ifaces.append(iface)
return ifaces
def interface_sort_key(iface):
"""
Sort interfaces by interface type placing ethernet interfaces ahead of

View File

@ -31,6 +31,7 @@ class NetworkingPuppet(base.BasePuppet):
config.update(self._get_mgmt_interface_config())
config.update(self._get_cluster_interface_config())
config.update(self._get_ironic_interface_config())
config.update(self._get_ptp_interface_config())
if host.personality == constants.CONTROLLER:
config.update(self._get_oam_interface_config())
return config
@ -174,6 +175,34 @@ class NetworkingPuppet(base.BasePuppet):
def _get_ironic_interface_config(self):
return self._get_interface_config(constants.NETWORK_TYPE_IRONIC)
def _get_ptp_interface_config(self):
config = {}
ptp_devices = {
constants.INTERFACE_PTP_ROLE_MASTER: [],
constants.INTERFACE_PTP_ROLE_SLAVE: []
}
ptp_interfaces = interface.get_ptp_interfaces(self.context)
ptp = self.dbapi.ptp_get_one()
is_udp = (ptp.transport == constants.PTP_TRANSPORT_UDP)
for network_interface in ptp_interfaces:
interface_devices = interface.get_interface_devices(self.context, network_interface)
address_family = None
if is_udp:
address = interface.get_interface_primary_address(self.context, network_interface)
if address:
address_family = netaddr.IPAddress(address['address']).version
for device in interface_devices:
ptp_devices[network_interface['ptp_role']].append({'device': device, 'family': address_family})
config.update({
'platform::ptp::master_devices': ptp_devices[constants.INTERFACE_PTP_ROLE_MASTER],
'platform::ptp::slave_devices': ptp_devices[constants.INTERFACE_PTP_ROLE_SLAVE]
})
return config
def _get_interface_config(self, networktype):
config = {}

View File

@ -120,6 +120,13 @@ class FunctionalTest(base.TestCase):
return self.post_json(path, expect_errors=expect_errors,
headers=headers, **newargs)
def patch_dict(self, path, data, expect_errors=False):
params = []
for key, value in data.items():
pathkey = '/' + key
params.append({'op': 'replace', 'path': pathkey, 'value': value})
return self.post_json(path, expect_errors=expect_errors, params=params, method='patch')
def delete(self, path, expect_errors=False, headers=None,
extra_environ=None, status=None, path_prefix=PATH_PREFIX):
full_path = path_prefix + path

View File

@ -2292,6 +2292,117 @@ class TestPatchStdDuplexControllerVIM(TestHost):
headers={'User-Agent': 'sysinv-test'})
class TestHostPTPValidation(TestHost):
def test_ptp_unlock_valid(self):
ptp = self.dbapi.ptp_get_one()
ptp_uuid = ptp.uuid
# Create controller-0
self._create_controller_0(
invprovision=constants.PROVISIONED,
administrative=constants.ADMIN_UNLOCKED,
operational=constants.OPERATIONAL_ENABLED,
availability=constants.AVAILABILITY_ONLINE)
# Create controller-1
self._create_controller_1(
invprovision=constants.PROVISIONED,
administrative=constants.ADMIN_UNLOCKED,
operational=constants.OPERATIONAL_ENABLED,
availability=constants.AVAILABILITY_ONLINE)
# Create worker-0
w0_host = self._create_worker(
mgmt_ip='192.168.204.5',
clock_synchronization=constants.PTP,
invprovision=constants.PROVISIONED,
administrative=constants.ADMIN_LOCKED,
operational=constants.OPERATIONAL_ENABLED,
availability=constants.AVAILABILITY_ONLINE)
self._create_test_host_platform_interface(w0_host)
self._create_test_host_cpus(w0_host, platform=1, vswitch=2, application=12)
# Host with PTP must have at least one ptp interface
interface = {
'forihostid': w0_host['id'],
'ifname': 'ptpif',
'iftype': constants.INTERFACE_TYPE_ETHERNET,
'imac': '02:11:22:33:44:11',
'ifclass': constants.INTERFACE_CLASS_PLATFORM,
'ptp_role': constants.INTERFACE_PTP_ROLE_MASTER
}
ptp_if = dbutils.create_test_interface(**interface)
response = self._patch_host_action(
w0_host['hostname'], constants.UNLOCK_ACTION, 'sysinv-test')
self.assertEqual(response.content_type, 'application/json')
self.assertEqual(response.status_code, http_client.OK)
# With UDP transport all host PTP interfaces must have an IP
self.dbapi.ptp_update(ptp_uuid, {'transport': constants.PTP_TRANSPORT_UDP})
address = {'interface_id': ptp_if.id,
'family': 4,
'prefix': 24,
'address': '192.168.1.2'}
dbutils.create_test_address(**address)
response = self._patch_host_action(
w0_host['hostname'], constants.UNLOCK_ACTION, 'sysinv-test')
self.assertEqual(response.content_type, 'application/json')
self.assertEqual(response.status_code, http_client.OK)
def test_ptp_unlock_invalid(self):
ptp = self.dbapi.ptp_get_one()
ptp_uuid = ptp.uuid
# Create controller-0
self._create_controller_0(
invprovision=constants.PROVISIONED,
administrative=constants.ADMIN_UNLOCKED,
operational=constants.OPERATIONAL_ENABLED,
availability=constants.AVAILABILITY_ONLINE)
# Create controller-1
self._create_controller_1(
invprovision=constants.PROVISIONED,
administrative=constants.ADMIN_UNLOCKED,
operational=constants.OPERATIONAL_ENABLED,
availability=constants.AVAILABILITY_ONLINE)
# Create worker-0
w0_host = self._create_worker(
mgmt_ip='192.168.204.5',
clock_synchronization=constants.PTP,
invprovision=constants.PROVISIONED,
administrative=constants.ADMIN_LOCKED,
operational=constants.OPERATIONAL_ENABLED,
availability=constants.AVAILABILITY_ONLINE)
self._create_test_host_platform_interface(w0_host)
self._create_test_host_cpus(w0_host, platform=1, vswitch=2, application=12)
# Host with PTP must have at least one ptp interface
response = self._patch_host_action(
w0_host['hostname'], constants.UNLOCK_ACTION, 'sysinv-test', expect_errors=True)
self.assertEqual(response.status_code, http_client.BAD_REQUEST)
self.assertTrue(response.json['error_message'])
self.assertIn("Hosts with PTP clock synchronization must have at least one", response.json['error_message'])
# With UDP transport all host PTP interfaces must have an IP
interface = {
'forihostid': w0_host['id'],
'ifname': 'ptpif',
'iftype': constants.INTERFACE_TYPE_ETHERNET,
'imac': '02:11:22:33:44:11',
'ifclass': constants.INTERFACE_CLASS_PLATFORM,
'ptp_role': constants.INTERFACE_PTP_ROLE_MASTER
}
dbutils.create_test_interface(**interface)
self.dbapi.ptp_update(ptp_uuid, {'transport': constants.PTP_TRANSPORT_UDP})
response = self._patch_host_action(
w0_host['hostname'], constants.UNLOCK_ACTION, 'sysinv-test', expect_errors=True)
self.assertEqual(response.status_code, http_client.BAD_REQUEST)
self.assertTrue(response.json['error_message'])
self.assertIn("All PTP interfaces must have an associated address", response.json['error_message'])
class PostControllerHostTestCase(TestPostControllerMixin, TestHost,
dbbase.ControllerHostTestCase):
pass

View File

@ -471,13 +471,27 @@ class InterfaceTestCase(base.FunctionalTest, dbbase.BaseHostTestCase):
self.assertEqual('application/json', response.content_type)
self.assertTrue(response.json['error_message'])
def _post_and_check(self, ndict, expect_errors=False):
def _post_and_check(self, ndict, expect_errors=False, error_message=None):
response = self.post_json('%s' % self._get_path(), ndict,
expect_errors)
if expect_errors:
self.assertEqual(http_client.BAD_REQUEST, response.status_int)
self.assertEqual('application/json', response.content_type)
self.assertTrue(response.json['error_message'])
if error_message:
self.assertIn(error_message, response.json['error_message'])
else:
self.assertEqual(http_client.OK, response.status_int)
return response
def _patch_and_check(self, data, path, expect_errors=False, error_message=None):
response = self.patch_dict('%s' % path, expect_errors=expect_errors, data=data)
if expect_errors:
self.assertEqual(http_client.BAD_REQUEST, response.status_int)
self.assertEqual('application/json', response.content_type)
self.assertTrue(response.json['error_message'])
if error_message:
self.assertIn(error_message, response.json['error_message'])
else:
self.assertEqual(http_client.OK, response.status_int)
return response
@ -1055,6 +1069,118 @@ class InterfaceAIOVlanOverDataEthernet(InterfaceTestCase):
super(InterfaceAIOVlanOverDataEthernet, self).setUp()
# Test PTP configs
class InterfacePTP(InterfaceTestCase):
def _setup_configuration(self):
# Setup a sample configuration with one controller and one worker
self._create_host(constants.CONTROLLER)
self._create_host(constants.WORKER, admin=constants.ADMIN_LOCKED)
self._create_datanetworks()
def setUp(self):
super(InterfacePTP, self).setUp()
def test_modify_ptp_interface_valid(self):
port0, if0 = self._create_ethernet('if0', host=self.worker)
sriovif = dbutils.create_test_interface(forihostid=self.worker.id, datanetworks='group0-data0')
dbutils.create_test_ethernet_port(
id=1, name='if1', host_id=self.worker.id, interface_id=sriovif.id, pciaddr='0000:00:00.11', dev_id=0,
sriov_totalvfs=1, sriov_numvfs=1, driver='i40e', sriov_vf_driver='i40evf'
)
if0_uuid = if0['uuid']
sriov_uuid = sriovif['uuid']
# Platform interface and master
data = {
'ifname': 'ptpif',
'ptp_role': constants.INTERFACE_PTP_ROLE_MASTER,
'ifclass': constants.INTERFACE_CLASS_PLATFORM
}
self._patch_and_check(data, self._get_path(if0_uuid))
# Slave role
data['ptp_role'] = constants.INTERFACE_PTP_ROLE_SLAVE
self._patch_and_check(data, self._get_path(if0_uuid))
# SRIOV and master
sriov_data = {
'ifname': 'sriovptp',
'sriov_numvfs': 1,
'ifclass': constants.INTERFACE_CLASS_PCI_SRIOV,
'ptp_role': constants.INTERFACE_PTP_ROLE_MASTER
}
self._patch_and_check(sriov_data, self._get_path(sriov_uuid))
# Back to none
self._patch_and_check({'ptp_role': constants.INTERFACE_PTP_ROLE_NONE}, self._get_path(sriov_uuid))
def test_modify_ptp_interface_invalid(self):
port0, if0 = self._create_ethernet('if0', ifclass=constants.INTERFACE_CLASS_PLATFORM, host=self.worker)
port1, if1 = self._create_ethernet('if1', host=self.worker)
if0_uuid = if0['uuid']
if1_uuid = if1['uuid']
# Invalid PTP role
data = {
'ifname': 'ptpif',
'ptp_role': 'invalid'
}
self._patch_and_check(data, self._get_path(if0_uuid), expect_errors=True,
error_message="Interface ptp_role must be one of")
# Valid role, incorrect class
data['ptp_role'] = constants.INTERFACE_PTP_ROLE_MASTER
self._patch_and_check(data, self._get_path(if1_uuid), expect_errors=True,
error_message="Invalid interface class for ptp_role")
def test_add_ptp_interface_valid(self):
self._create_ethernet('if0', host=self.worker)
self._create_ethernet('if1', host=self.worker)
self._create_ethernet('if2', host=self.worker)
# Add master vlan
vlan_data = {
'ihost_uuid': self.worker.uuid,
'ifname': 'vlanptp',
'iftype': constants.INTERFACE_TYPE_VLAN,
'ifclass': constants.INTERFACE_CLASS_PLATFORM,
'vlan_id': 100,
'uses': ['if0'],
'ptp_role': constants.INTERFACE_PTP_ROLE_MASTER
}
self._post_and_check(vlan_data)
# Add slave ae
ae_data = {
'ihost_uuid': self.worker.uuid,
'ifname': 'aeptp',
'iftype': constants.INTERFACE_TYPE_AE,
'ifclass': constants.INTERFACE_CLASS_PLATFORM,
'uses': ['if1', 'if2'],
'ptp_role': constants.INTERFACE_PTP_ROLE_SLAVE
}
self._post_and_check(ae_data)
def test_add_ptp_interface_invalid(self):
self._create_ethernet('if0', host=self.worker)
# Add vlan with invalid ptp_role data
vlan_data = {
'ihost_uuid': self.worker.uuid,
'ifname': 'vlanptp',
'iftype': constants.INTERFACE_TYPE_VLAN,
'ifclass': constants.INTERFACE_CLASS_PLATFORM,
'vlan_id': 100,
'uses': ['if0'],
'ptp_role': 'invalid'
}
error_message = "Interface ptp_role must be one of"
self._post_and_check(vlan_data, expect_errors=True, error_message=error_message)
vlan_data['ptp_role'] = ''
self._post_and_check(vlan_data, expect_errors=True, error_message=error_message)
class TestList(InterfaceTestCase):
def setUp(self):

View File

@ -0,0 +1,126 @@
# Copyright (c) 2019 Wind River Systems, Inc.
#
# SPDX-License-Identifier: Apache-2.0
#
import uuid
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 utils as dbutils
HEADER = {'User-Agent': 'sysinv'}
class PTPTestCase(base.FunctionalTest):
def setUp(self):
super(PTPTestCase, self).setUp()
self.dbapi = dbapi.get_instance()
self.system = dbutils.create_test_isystem()
self.load = dbutils.create_test_load()
self.controller = dbutils.create_test_ihost(
id='1',
uuid=None,
forisystemid=self.system.id,
hostname='controller-0',
personality=constants.CONTROLLER,
subfunctions=constants.CONTROLLER,
invprovision=constants.PROVISIONED,
)
self.worker = dbutils.create_test_ihost(
id='2',
uuid=None,
forisystemid=self.system.id,
hostname='worker-0',
personality=constants.WORKER,
subfunctions=constants.WORKER,
mgmt_mac='01:02.03.04.05.C0',
mgmt_ip='192.168.24.12',
invprovision=constants.PROVISIONED,
administrative=constants.ADMIN_LOCKED
)
self.dbapi.ptp_create({})
self.ptp = self.dbapi.ptp_get_one()
self.ptp_uuid = self.ptp.uuid
def _get_path(self, ptp_id):
if ptp_id:
path = '/ptp/' + ptp_id
else:
path = '/ptp'
return path
class PTPModifyTestCase(PTPTestCase):
def setUp(self):
super(PTPModifyTestCase, self).setUp()
transport_l2 = {'transport': constants.PTP_TRANSPORT_L2}
transport_udp = {'transport': constants.PTP_TRANSPORT_UDP}
def modify_ptp(self, input_data):
response = self.patch_dict('%s' % self._get_path(self.ptp_uuid), input_data)
self.assertEqual(http_client.OK, response.status_int)
return response
def modify_ptp_failure(self, input_data, error_message=None):
response = self.patch_dict('%s' % self._get_path(self.ptp_uuid), input_data, expect_errors=True)
self.assertEqual(response.content_type, 'application/json')
self.assertEqual(http_client.BAD_REQUEST, response.status_int)
self.assertTrue(response.json['error_message'])
if error_message:
self.assertIn(error_message, response.json['error_message'])
def test_modify_ptp_transport_valid(self):
# With no ptp hosts we should be able to modify ptp transport
self.modify_ptp(self.transport_udp)
self.modify_ptp(self.transport_l2)
# If the host is locked we can set the transport to UDP
self.dbapi.ihost_update(self.worker.id, {'clock_synchronization': constants.PTP})
self.modify_ptp(self.transport_udp)
self.modify_ptp(self.transport_l2)
# If the host is unlocked it must have a ptp interface with an IP to set to UDP
self.dbapi.ihost_update(self.worker.id, {'administrative': constants.ADMIN_UNLOCKED})
interface = {'id': 1,
'uuid': str(uuid.uuid4()),
'forihostid': self.worker.id,
'ifname': 'ptpif',
'iftype': constants.INTERFACE_TYPE_ETHERNET,
'imac': '02:11:22:33:44:11',
'uses': [],
'used_by': [],
'ifclass': constants.INTERFACE_CLASS_PLATFORM,
'ipv4_mode': 'static',
'ptp_role': constants.INTERFACE_PTP_ROLE_MASTER
}
dbutils.create_test_interface(**interface)
address = {'interface_id': '1',
'family': 4,
'prefix': 24,
'address': '192.168.1.2'}
dbutils.create_test_address(**address)
self.modify_ptp(self.transport_udp)
self.modify_ptp(self.transport_l2)
def test_modify_ptp_transport_invalid(self):
# If the host is unlocked it must have a ptp interface with an IP to set to UDP
self.dbapi.ihost_update(self.worker.id, {'clock_synchronization': constants.PTP})
self.dbapi.ihost_update(self.worker.id, {'administrative': constants.ADMIN_UNLOCKED})
interface = {'id': 1,
'uuid': str(uuid.uuid4()),
'forihostid': self.worker.id,
'ifname': 'ptpif',
'iftype': constants.INTERFACE_TYPE_ETHERNET,
'imac': '02:11:22:33:44:11',
'ifclass': constants.INTERFACE_CLASS_PLATFORM,
'ptp_role': constants.INTERFACE_PTP_ROLE_MASTER
}
dbutils.create_test_interface(**interface)
self.modify_ptp_failure(self.transport_udp, "Invalid system configuration for UDP based PTP transport")

View File

@ -1924,3 +1924,15 @@ class TestMigrations(BaseMigrationTestCase, WalkVersionsMixin):
for col, coltype in ports_col.items():
self.assertTrue(isinstance(ports.c[col].type,
getattr(sqlalchemy.types, coltype)))
def _check_096(self, engine, data):
# 096_ptp_interface.py
# Assert data types for new columns in table "interface"
interface = db_utils.get_table(engine, 'interfaces')
interface_col = {
'ptp_role': 'String',
}
for col, coltype in interface_col.items():
self.assertTrue(isinstance(interface.c[col].type,
getattr(sqlalchemy.types, coltype)))

View File

@ -938,7 +938,8 @@ def get_test_interface(**kw):
'ipv6_pool': kw.get('ipv6_pool'),
'sriov_numvfs': kw.get('sriov_numvfs', None),
'sriov_vf_driver': kw.get('sriov_vf_driver', None),
'sriov_vf_pdevice_id': kw.get('sriov_vf_pdevice_id', None)
'sriov_vf_pdevice_id': kw.get('sriov_vf_pdevice_id', None),
'ptp_role': kw.get('ptp_role', None)
}
return interface