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:
parent
638487f67b
commit
8ad91e1cbf
|
@ -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):
|
||||
|
|
|
@ -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']
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
||||
|
|
|
@ -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."""
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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.')
|
|
@ -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",
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 = {}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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")
|
|
@ -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)))
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
Loading…
Reference in New Issue