Implementation of PTP support in SysInv and Puppet

Precision Time Protocol (PTP) support is added to StarlingX.
Controller Nodes act as a Boundary Clocks and synchronize its clock to a
Grand Master clock via OAM interface. Serve as a time source to other nodes.
Compute/Storage Nodes are Slave Clocks (Ordinary Clocks) and synchronize
its clock to a Controller Nodes via Management interface.
API is provided to enable and configure PTP as follows:
"system ptp-modify --enabled=<true/false>" is there to turn it on/off.
Note that NTP must be disabled first before turning PTP service on.
"system ptp-modify --mode=<hardware/software/legacy>" selects time stamping.
Hardware timestamping is the default option and achieves best time syncing.
"system ptp-modify --transport=<l2,udp>" switches between IEEE 802.3 or UDP
network transport for PTP messaging. L2 is the default transport.
"system ptp-modify --mechanism=<e2e,p2p>" sets the PTP delay mechanism.
Options: default delay request-response (E2E) mechanism and peer delay (P2P).
"system ptp-show" displays the current status of PTP service.

Change-Id: I6bb6162903c70a49b55f4fc02b44e5ca8a66d5a5
Story: 2002935
Task: 22923
Signed-off-by: Alex Kozyrev <alex.kozyrev@windriver.com>
This commit is contained in:
Alex Kozyrev 2018-08-21 22:06:07 -04:00
parent 5354843163
commit 3a42372b64
35 changed files with 1143 additions and 41 deletions

View File

@ -20,6 +20,7 @@ include ::platform::fstab
include ::platform::password
include ::platform::ldap::client
include ::platform::ntp::client
include ::platform::ptp
include ::platform::lldp
include ::platform::patching
include ::platform::remotelogging

View File

@ -25,6 +25,7 @@ include ::platform::ldap::server
include ::platform::ldap::client
include ::platform::password
include ::platform::ntp::server
include ::platform::ptp
include ::platform::lldp
include ::platform::amqp::rabbitmq
include ::platform::postgresql::server

View File

@ -18,6 +18,7 @@ include ::platform::fstab
include ::platform::password
include ::platform::ldap::client
include ::platform::ntp::client
include ::platform::ptp
include ::platform::lldp
include ::platform::patching
include ::platform::remotelogging

View File

@ -211,6 +211,13 @@ define platform::firewall::services (
provider => $provider,
}
firewall { "203 platform accept ptp ${version}":
proto => 'udp',
dport => [319, 320],
action => 'accept',
provider => $provider,
}
# allow IGMP Query traffic if IGMP Snooping is
# enabled on the TOR switch
firewall { "204 platform accept igmp ${version}":

View File

@ -0,0 +1,134 @@
class platform::ptp (
$enabled = false,
$mode = 'hardware',
$transport = 'l2',
$mechanism = 'e2e',
) {
if $::platform::params::personality == 'controller' {
include ::platform::network::oam::params
$oam_interface = $::platform::network::oam::params::interface_name
$slave_interface = split($oam_interface, '[.]')[0]
$slave_subnet = $::platform::network::oam::params::subnet_version
include ::platform::network::mgmt::params
$mgmt_interface = $::platform::network::mgmt::params::interface_name
$master_interface = split($mgmt_interface, '[.]')[0]
$master_subnet = $::platform::network::mgmt::params::subnet_version
if $::platform::params::system_type == 'All-in-one' {
$slave_only = true
} else {
$slave_only = false
}
} else {
include ::platform::network::mgmt::params
$mgmt_interface = $::platform::network::mgmt::params::interface_name
$slave_interface = split($mgmt_interface, '[.]')[0]
$slave_subnet = $::platform::network::mgmt::params::subnet_version
$slave_only = true
}
if $enabled {
$pmon_ensure = 'link'
} else {
$pmon_ensure = 'absent'
}
file { 'ptp4l_config':
ensure => file,
path => '/etc/ptp4l.conf',
mode => '0644',
content => template('platform/ptp4l.conf.erb'),
} ->
file { 'ptp4l_service':
ensure => file,
path => '/usr/lib/systemd/system/ptp4l.service',
mode => '0644',
content => template('platform/ptp4l.service.erb'),
} ->
file { 'ptp4l_sysconfig':
ensure => file,
path => '/etc/sysconfig/ptp4l',
mode => '0644',
content => template('platform/ptp4l.erb'),
} ->
file { 'phc2sys_service':
ensure => file,
path => '/usr/lib/systemd/system/phc2sys.service',
mode => '0644',
content => template('platform/phc2sys.service.erb'),
} ->
file { 'phc2sys_sysconfig':
ensure => file,
path => '/etc/sysconfig/phc2sys',
mode => '0644',
content => template('platform/phc2sys.erb'),
} ->
file { 'ptp4l_pmon':
ensure => file,
path => '/etc/ptp4l.pmon.conf',
mode => '0644',
content => template('platform/ptp4l.pmon.conf.erb'),
} ->
file { 'phc2sys_pmon':
ensure => file,
path => '/etc/phc2sys.pmon.conf',
mode => '0644',
content => template('platform/phc2sys.pmon.conf.erb'),
} ->
file { 'ptp4l_pmon_link':
ensure => $pmon_ensure,
path => '/etc/pmon.d/ptp4l.conf',
target => '/etc/ptp4l.pmon.conf',
owner => 'root',
group => 'root',
mode => '0600',
} ->
file { 'phc2sys_pmon_link':
ensure => $pmon_ensure,
path => '/etc/pmon.d/phc2sys.conf',
target => '/etc/phc2sys.pmon.conf',
owner => 'root',
group => 'root',
mode => '0600',
} ->
exec { 'systemctl-daemon-reload':
command => '/usr/bin/systemctl daemon-reload',
}
if $enabled {
exec { 'enable-ptp4l':
command => '/usr/bin/systemctl enable ptp4l.service',
require => Exec['systemctl-daemon-reload'],
} ->
exec { 'enable-phc2sys':
command => '/usr/bin/systemctl enable phc2sys.service',
} ->
service { 'ptp4l':
ensure => 'running',
enable => true,
name => 'ptp4l',
hasstatus => true,
hasrestart => true,
} ->
service { 'phc2sys':
ensure => 'running',
enable => true,
name => 'phc2sys',
hasstatus => true,
hasrestart => true,
}
} else {
exec { 'disable-ptp4l':
command => '/usr/bin/systemctl disable ptp4l.service',
require => Exec['systemctl-daemon-reload'],
} ->
exec { 'disable-phc2sys':
command => '/usr/bin/systemctl disable phc2sys.service',
}
exec { 'stop-ptp4l':
command => '/usr/bin/systemctl stop ptp4l.service',
} ->
exec { 'stop-phc2sys':
command => '/usr/bin/systemctl stop phc2sys.service',
}
}
}

View File

@ -11,7 +11,7 @@ restrict -6 default kod nomodify notrap nopeer noquery
restrict 127.0.0.1
restrict -6 ::1
<%- if @enabled == true -%>
<%- if scope['platform::ntp::enabled'] == true -%>
# Use orphan mode if external servers are unavailable (or not configured)
tos orphan 12

View File

@ -11,7 +11,7 @@ restrict -6 default kod nomodify notrap nopeer noquery
restrict 127.0.0.1
restrict -6 ::1
<%- if @enabled == true -%>
<%- if scope['platform::ntp::enabled'] == true -%>
# orphan - Use orphan mode if external servers are unavailable (or not configured).
# minclock - Prevent clustering algorithm from casting out any outlyers by setting
# minclock to the maximum number of ntp servers that can be configured

View File

@ -0,0 +1 @@
OPTIONS="-a -r -E linreg -u 60"

View File

@ -0,0 +1,19 @@
[process]
process = phc2sys
service = phc2sys
pidfile = /var/run/phc2sys.pid
style = lsb ; ocf or lsb
severity = minor ; minor, major, critical
restarts = 0 ; restart retries before error assertion
interval = 10 ; number of seconds to wait between restarts
debounce = 10 ; number of seconds that a process needs to remain
; running before degrade is removed and retry count
; is cleared.
; These settings will generate a log only without attempting to restart
; pmond will put the process into an ignore state after failure.
startuptime = 180 ; Seconds to wait after process start before starting the debounce monitor
mode = passive ; Monitoring mode: passive (default) or active
; passive: process death monitoring (default: always)
; active : heartbeat monitoring, i.e. request / response messaging
; ignore : do not monitor or stop monitoring

View File

@ -0,0 +1,13 @@
[Unit]
Description=Synchronize system clock or PTP hardware clock (PHC)
After=ntpdate.service
[Service]
Type=simple
EnvironmentFile=-/etc/sysconfig/phc2sys
ExecStart=/usr/sbin/phc2sys $OPTIONS
ExecStartPost=/bin/bash -c 'echo $MAINPID > /var/run/phc2sys.pid'
ExecStopPost=/bin/rm -f /var/run/phc2sys.pid
[Install]
WantedBy=multi-user.target

View File

@ -0,0 +1,107 @@
[global]
#
# Default Data Set
#
twoStepFlag 1
<%- if @slave_only == true -%>
slaveOnly 1
<%- else -%>
slaveOnly 0
<%- end -%>
priority1 128
priority2 128
domainNumber 0
#utc_offset 37
clockClass 248
clockAccuracy 0xFE
offsetScaledLogVariance 0xFFFF
free_running 0
freq_est_interval 1
dscp_event 0
dscp_general 0
#
# Port Data Set
#
logAnnounceInterval 1
logSyncInterval 0
logMinDelayReqInterval 0
logMinPdelayReqInterval 0
announceReceiptTimeout 3
syncReceiptTimeout 0
delayAsymmetry 0
fault_reset_interval 4
neighborPropDelayThresh 20000000
#
# Run time options
#
assume_two_step 0
logging_level 6
path_trace_enabled 0
follow_up_info 0
hybrid_e2e 0
tx_timestamp_timeout 1
use_syslog 1
verbose 0
summary_interval 6
kernel_leap 1
check_fup_sync 0
#
# Servo Options
#
pi_proportional_const 0.0
pi_integral_const 0.0
pi_proportional_scale 0.0
pi_proportional_exponent -0.3
pi_proportional_norm_max 0.7
pi_integral_scale 0.0
pi_integral_exponent 0.4
pi_integral_norm_max 0.3
step_threshold 0.0
first_step_threshold 0.00002
max_frequency 900000000
clock_servo linreg
sanity_freq_limit 200000000
ntpshm_segment 0
#
# Transport options
#
transportSpecific 0x0
ptp_dst_mac 01:1B:19:00:00:00
p2p_dst_mac 01:80:C2:00:00:0E
udp_ttl 1
udp6_scope 0x0E
uds_address /var/run/ptp4l
#
# Default interface options
#
network_transport L2
delay_mechanism <%= scope['platform::ptp::mechanism'].upcase %>
time_stamping <%= scope['platform::ptp::mode'].downcase %>
tsproc_mode filter
delay_filter moving_median
delay_filter_length 10
egressLatency 0
ingressLatency 0
<%- if @slave_only == true -%>
boundary_clock_jbod 0
<%- else -%>
boundary_clock_jbod 1
<%- end -%>
#
# Clock description
#
productDescription ;;
revisionData ;;
manufacturerIdentity 00:00:00
userDescription ;
timeSource 0xA0
<%- if scope['platform::ptp::transport'] == 'udp' -%>
[<%= @slave_interface %>]
network_transport UDPv<%= @slave_subnet %>
<%- if @slave_only == false -%>
[<%= @master_interface %>]
network_transport UDPv<%= @master_subnet %>
<%- end -%>
<%- end -%>

View File

@ -0,0 +1 @@
OPTIONS="-f /etc/ptp4l.conf"

View File

@ -0,0 +1,19 @@
[process]
process = ptp4l
service = ptp4l
pidfile = /var/run/ptp4l.pid
style = lsb ; ocf or lsb
severity = minor ; minor, major, critical
restarts = 0 ; restart retries before error assertion
interval = 10 ; number of seconds to wait between restarts
debounce = 10 ; number of seconds that a process needs to remain
; running before degrade is removed and retry count
; is cleared.
; These settings will generate a log only without attempting to restart
; pmond will put the process into an ignore state after failure.
startuptime = 180 ; Seconds to wait after process start before starting the debounce monitor
mode = passive ; Monitoring mode: passive (default) or active
; passive: process death monitoring (default: always)
; active : heartbeat monitoring, i.e. request / response messaging
; ignore : do not monitor or stop monitoring

View File

@ -0,0 +1,12 @@
[Unit]
Description=Precision Time Protocol (PTP) service
[Service]
Type=simple
EnvironmentFile=-/etc/sysconfig/ptp4l
ExecStart=/usr/sbin/ptp4l $OPTIONS
ExecStartPost=/bin/bash -c 'echo $MAINPID > /var/run/ptp4l.pid'
ExecStopPost=/bin/rm -f /var/run/ptp4l.pid
[Install]
WantedBy=multi-user.target

View File

@ -1,2 +1,2 @@
SRC_DIR="cgts-client"
TIS_PATCH_VER=59
TIS_PATCH_VER=60

View File

@ -96,6 +96,7 @@ def _does_command_need_no_wrap(callback):
if callback.__name__ in \
['donot_config_ntp_list',
'donot_config_ptp_list',
'do_host_apply_memprofile',
'do_host_apply_cpuprofile',
'do_host_apply_ifprofile',

View File

@ -57,6 +57,7 @@ from cgtsclient.v1 import network
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 remotelogging
from cgtsclient.v1 import route
from cgtsclient.v1 import sdn_controller
@ -102,6 +103,7 @@ class Client(http.HTTPClient):
self.iuser = iuser.iuserManager(self)
self.idns = idns.idnsManager(self)
self.intp = intp.intpManager(self)
self.ptp = ptp.ptpManager(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,50 @@
########################################################################
#
# Copyright (c) 2018 Wind River Systems, Inc.
#
# SPDX-License-Identifier: Apache-2.0
#
########################################################################
from cgtsclient.common import base
from cgtsclient import exc
CREATION_ATTRIBUTES = []
class ptp(base.Resource):
def __repr__(self):
return "<ptp %s>" % self._info
class ptpManager(base.Manager):
resource_class = ptp
@staticmethod
def _path(id=None):
return '/v1/ptp/%s' % id if id else '/v1/ptp'
def list(self):
return self._list(self._path(), "ptps")
def get(self, ptp_id):
try:
return self._list(self._path(ptp_id))[0]
except IndexError:
return None
def create(self, **kwargs):
new = {}
for (key, value) in kwargs.items():
if key in CREATION_ATTRIBUTES:
new[key] = value
else:
raise exc.InvalidAttribute('%s' % key)
return self._create(self._path(), new)
def delete(self, ptp_id):
return self._delete(self._path(ptp_id))
def update(self, ptp_id, patch):
return self._update(self._path(ptp_id), patch)

View File

@ -0,0 +1,79 @@
########################################################################
#
# Copyright (c) 2018 Wind River Systems, Inc.
#
# SPDX-License-Identifier: Apache-2.0
#
########################################################################
from cgtsclient.common import utils
from cgtsclient import exc
def _print_ptp_show(ptp):
fields = ['uuid', 'enabled', 'mode', 'transport', 'mechanism',
'isystem_uuid', 'created_at', 'updated_at']
data = [(f, getattr(ptp, f, '')) for f in fields]
utils.print_tuple_list(data)
def do_ptp_show(cc, args):
"""Show PTP (Precision Time Protocol) attributes."""
ptps = cc.ptp.list()
_print_ptp_show(ptps[0])
def donot_config_ptp_list(cc, args):
"""List ptps."""
ptps = cc.ptp.list()
field_labels = ['uuid', 'enabled', 'mode', 'transport', 'mechanism']
fields = ['uuid', 'enabled', 'mode', 'transport', 'mechanism']
utils.print_list(ptps, fields, field_labels, sortby=1)
@utils.arg('--enabled',
metavar='<true/false>',
help="PTP service enabled.")
@utils.arg('--mode',
metavar='<mode>',
default=None,
help="PTP time stamping mode.")
@utils.arg('--transport',
metavar='<transport>',
default=None,
help="PTP transport protocol.")
@utils.arg('--mechanism',
metavar='<mechanism>',
default=None,
help="PTP delay mechanism.")
def do_ptp_modify(cc, args):
"""Modify PTP attributes."""
ptps = cc.ptp.list()
ptp = ptps[0]
op = "replace"
attributes = []
if args.enabled is not None:
attributes.append('enabled=%s' % args.enabled)
if args.mode is not None:
attributes.append('mode=%s' % args.mode)
if args.transport is not None:
attributes.append('transport=%s' % args.transport)
if args.mechanism is not None:
attributes.append('mechanism=%s' % args.mechanism)
if len(attributes) == 0:
print "No options provided."
return
patch = utils.args_array_to_patch("replace", attributes)
try:
ptp = cc.ptp.update(ptp.uuid, patch)
except exc.HTTPNotFound:
raise exc.CommandError('PTP not found: %s' % ptp.uuid)
_print_ptp_show(ptp)

View File

@ -46,6 +46,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_shell
from cgtsclient.v1 import remotelogging_shell
from cgtsclient.v1 import route_shell
from cgtsclient.v1 import sdn_controller_shell
@ -63,6 +64,7 @@ COMMAND_MODULES = [
iuser_shell,
idns_shell,
intp_shell,
ptp_shell,
iextoam_shell,
controller_fs_shell,
storage_backend_shell,

View File

@ -1,2 +1,2 @@
SRC_DIR="sysinv"
TIS_PATCH_VER=278
TIS_PATCH_VER=279

View File

@ -53,6 +53,7 @@ from sysinv.api.controllers.v1 import partition
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 pv
from sysinv.api.controllers.v1 import remotelogging
from sysinv.api.controllers.v1 import route
@ -137,6 +138,9 @@ class V1(base.APIBase):
intp = [link.Link]
"Links to the intp resource"
ptp = [link.Link]
"Links to the ptp resource"
iextoam = [link.Link]
"Links to the iextoam resource"
@ -387,39 +391,47 @@ class V1(base.APIBase):
]
v1.idns = [link.Link.make_link('self', pecan.request.host_url,
'idns', ''),
'idns', ''),
link.Link.make_link('bookmark',
pecan.request.host_url,
'idns', '',
bookmark=True)
pecan.request.host_url,
'idns', '',
bookmark=True)
]
v1.intp = [link.Link.make_link('self', pecan.request.host_url,
'intp', ''),
link.Link.make_link('bookmark',
pecan.request.host_url,
'intp', '',
bookmark=True)
'intp', ''),
link.Link.make_link('bookmark',
pecan.request.host_url,
'intp', '',
bookmark=True)
]
v1.ptp = [link.Link.make_link('self', pecan.request.host_url,
'ptp', ''),
link.Link.make_link('bookmark',
pecan.request.host_url,
'ptp', '',
bookmark=True)
]
v1.iextoam = [link.Link.make_link('self', pecan.request.host_url,
'iextoam', ''),
link.Link.make_link('bookmark',
pecan.request.host_url,
'iextoam', '',
bookmark=True)
pecan.request.host_url,
'iextoam', '',
bookmark=True)
]
v1.controller_fs = [link.Link.make_link('self', pecan.request.host_url,
'controller_fs', ''),
link.Link.make_link('bookmark',
pecan.request.host_url,
'controller_fs', '',
bookmark=True)
pecan.request.host_url,
'controller_fs', '',
bookmark=True)
]
v1.storage_backend = [link.Link.make_link('self',
pecan.request.host_url,
pecan.request.host_url,
'storage_backend', ''),
link.Link.make_link('bookmark',
pecan.request.host_url,
@ -717,6 +729,7 @@ class Controller(rest.RestController):
iuser = user.UserController()
idns = dns.DNSController()
intp = ntp.NTPController()
ptp = ptp.PTPController()
iextoam = network_oam.OAMNetworkController()
controller_fs = controller_fs.ControllerFsController()
storage_backend = storage_backend.StorageBackendController()

View File

@ -159,6 +159,15 @@ def _check_ntp_data(op, ntp):
MAX_S = 3
ptp_list = pecan.request.dbapi.ptp_get_by_isystem(ntp['forisystemid'])
if ptp_list:
if hasattr(ptp_list[0], 'enabled'):
if ptp_list[0].enabled is True and enabled is True:
raise wsme.exc.ClientSideError(_(
"NTP cannot be configured alongside with PTP."
" Please disable PTP before enabling NTP."))
dns_list = pecan.request.dbapi.idns_get_by_isystem(ntp['forisystemid'])
if dns_list:

View File

@ -0,0 +1,253 @@
########################################################################
#
# Copyright (c) 2018 Wind River Systems, Inc.
#
# SPDX-License-Identifier: Apache-2.0
#
########################################################################
import jsonpatch
import pecan
from pecan import rest
import wsme
from wsme import types as wtypes
import wsmeext.pecan as wsme_pecan
from sysinv.api.controllers.v1 import base
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 exception
from sysinv.common import utils as cutils
from sysinv import objects
from sysinv.openstack.common.gettextutils import _
from sysinv.openstack.common import log
LOG = log.getLogger(__name__)
class PTPPatchType(types.JsonPatchType):
@staticmethod
def mandatory_attrs():
return []
class PTP(base.APIBase):
"""API representation of PTP configuration.
This class enforces type checking and value constraints, and converts
between the internal object model and the API representation of
an ptp.
"""
uuid = types.uuid
"Unique UUID for this ptp"
enabled = types.boolean
"Represent the status of the ptp."
mode = wtypes.Enum(str, 'hardware', 'software', 'legacy')
"Time stamping mode used by ptp."
transport = wtypes.Enum(str, 'l2', 'udp')
"Network transport used by ptp."
mechanism = wtypes.Enum(str, 'e2e', 'p2p')
"Messaging mechanism used by ptp."
links = [link.Link]
"A list containing a self link and associated ptp links"
isystem_uuid = types.uuid
"The UUID of the system this ptp belongs to"
created_at = wtypes.datetime.datetime
updated_at = wtypes.datetime.datetime
def __init__(self, **kwargs):
self.fields = objects.ptp.fields.keys()
for k in self.fields:
setattr(self, k, kwargs.get(k))
@classmethod
def convert_with_links(cls, rpc_ptp, expand=True):
ptp = PTP(**rpc_ptp.as_dict())
if not expand:
ptp.unset_fields_except(['uuid',
'enabled',
'mode',
'transport',
'mechanism',
'isystem_uuid',
'created_at',
'updated_at'])
ptp.links = [link.Link.make_link('self', pecan.request.host_url,
'ptps', ptp.uuid),
link.Link.make_link('bookmark',
pecan.request.host_url,
'ptps', ptp.uuid,
bookmark=True)
]
return ptp
class ptpCollection(collection.Collection):
"""API representation of a collection of ptps."""
ptps = [PTP]
"A list containing ptp objects"
def __init__(self, **kwargs):
self._type = 'ptps'
@classmethod
def convert_with_links(cls, rpc_ptps, limit, url=None,
expand=False, **kwargs):
collection = ptpCollection()
collection.ptps = [PTP.convert_with_links(p, expand)
for p in rpc_ptps]
collection.next = collection.get_next(limit, url=url, **kwargs)
return collection
##############
# UTILS
##############
def _check_ptp_data(op, ptp):
enabled = ptp['enabled']
ntp_list = pecan.request.dbapi.intp_get_by_isystem(ptp['isystem_uuid'])
if ntp_list:
if hasattr(ntp_list[0], 'enabled'):
if ntp_list[0].enabled is True and enabled is True:
raise wsme.exc.ClientSideError(_(
"PTP cannot be configured alongside with NTP."
" Please disable NTP before enabling PTP."))
return ptp
LOCK_NAME = 'PTPController'
class PTPController(rest.RestController):
"""REST controller for ptps."""
_custom_actions = {
'detail': ['GET'],
}
def _get_ptps_collection(self, marker, limit, sort_key, sort_dir,
expand=False, resource_url=None):
limit = utils.validate_limit(limit)
sort_dir = utils.validate_sort_dir(sort_dir)
marker_obj = None
if marker:
marker_obj = objects.ptp.get_by_uuid(pecan.request.context,
marker)
ptps = pecan.request.dbapi.ptp_get_list(limit, marker_obj,
sort_key=sort_key,
sort_dir=sort_dir)
return ptpCollection.convert_with_links(ptps, limit,
url=resource_url,
expand=expand,
sort_key=sort_key,
sort_dir=sort_dir)
@wsme_pecan.wsexpose(ptpCollection, types.uuid, int,
wtypes.text, wtypes.text)
def get_all(self, marker=None, limit=None,
sort_key='id', sort_dir='asc'):
"""Retrieve a list of ptps. Only one per system"""
return self._get_ptps_collection(marker, limit,
sort_key, sort_dir)
@wsme_pecan.wsexpose(ptpCollection, types.uuid, int,
wtypes.text, wtypes.text)
def detail(self, marker=None, limit=None,
sort_key='id', sort_dir='asc'):
"""Retrieve a list of ptps with detail."""
# NOTE(lucasagomes): /detail should only work agaist collections
parent = pecan.request.path.split('/')[:-1][-1]
if parent != "ptps":
raise exception.HTTPNotFound
expand = True
resource_url = '/'.join(['ptps', 'detail'])
return self._get_ptps_collection(marker, limit,
sort_key, sort_dir,
expand, resource_url)
@wsme_pecan.wsexpose(PTP, types.uuid)
def get_one(self, ptp_uuid):
"""Retrieve information about the given ptp."""
rpc_ptp = objects.ptp.get_by_uuid(pecan.request.context, ptp_uuid)
return PTP.convert_with_links(rpc_ptp)
@wsme_pecan.wsexpose(PTP, body=PTP)
def post(self, ptp):
"""Create a new ptp."""
raise exception.OperationNotPermitted
@cutils.synchronized(LOCK_NAME)
@wsme.validate(types.uuid, [PTPPatchType])
@wsme_pecan.wsexpose(PTP, types.uuid,
body=[PTPPatchType])
def patch(self, ptp_uuid, patch):
"""Update the current PTP configuration."""
rpc_ptp = objects.ptp.get_by_uuid(pecan.request.context, ptp_uuid)
patch_obj = jsonpatch.JsonPatch(patch)
state_rel_path = ['/uuid', '/id']
if any(p['path'] in state_rel_path for p in patch_obj):
raise wsme.exc.ClientSideError(_("The following fields can not be "
"modified: %s" %
state_rel_path))
try:
ptp = PTP(**jsonpatch.apply_patch(rpc_ptp.as_dict(),
patch_obj))
except utils.JSONPATCH_EXCEPTIONS as e:
raise exception.PatchError(patch=patch, reason=e)
ptp = _check_ptp_data("modify", ptp.as_dict())
try:
# Update only the fields that have changed
for field in objects.ptp.fields:
if rpc_ptp[field] != ptp[field]:
rpc_ptp[field] = ptp[field]
delta = rpc_ptp.obj_what_changed()
if delta:
rpc_ptp.save()
# perform rpc to conductor to perform config apply
pecan.request.rpcapi.update_ptp_config(pecan.request.context)
else:
LOG.info("No PTP config changes")
return PTP.convert_with_links(rpc_ptp)
except exception.HTTPNotFound:
msg = _("PTP update failed: enabled %s : %s %s %s : patch %s"
% (ptp['enabled'], ptp['mode'], ptp['transport'],
ptp['mechanism'], patch))
raise wsme.exc.ClientSideError(msg)
@wsme_pecan.wsexpose(None, types.uuid, status_code=204)
def delete(self, ptp_uuid):
"""Delete a ptp."""
raise exception.OperationNotPermitted

View File

@ -478,7 +478,11 @@ class DNSAlreadyExists(Conflict):
class NTPAlreadyExists(Conflict):
message = _("An NTP with UUID %(uuid)s already exists.")
message = _("A NTP with UUID %(uuid)s already exists.")
class PTPAlreadyExists(Conflict):
message = _("A PTP with UUID %(uuid)s already exists.")
class PMAlreadyExists(Conflict):
@ -614,6 +618,10 @@ class NTPNotFound(NotFound):
message = _("No NTP with id %(uuid)s found.")
class PTPNotFound(NotFound):
message = _("No PTP with id %(uuid)s found.")
class DiskNotFound(NotFound):
message = _("No disk with id %(disk_id)s")

View File

@ -227,6 +227,8 @@ class ConductorManager(service.PeriodicService):
# fill in empty remotelogging system_id fields
self.dbapi.remotelogging_fill_empty_system_id(system.id)
# fill in empty ptp system_id fields
self.dbapi.ptp_fill_empty_system_id(system.id)
return system # system already configured
except exception.NotFound:
@ -271,9 +273,10 @@ class ConductorManager(service.PeriodicService):
'rtt_ms': constants.DRBD_RTT_MS_DEFAULT
})
# remotelogging tables have attribute 'system_id' not 'forisystemid'
# remotelogging and ptp tables have attribute 'system_id' not 'forisystemid'
system_id_attribute_value = {'system_id': system.id}
self.dbapi.remotelogging_create(system_id_attribute_value)
self.dbapi.ptp_create(system_id_attribute_value)
# set default storage_backend
values.update({'backend': constants.SB_TYPE_FILE,
@ -5156,6 +5159,13 @@ class ConductorManager(service.PeriodicService):
personalities = [constants.CONTROLLER]
self._config_update_hosts(context, personalities, reboot=True)
def update_ptp_config(self, context):
"""Update the PTP configuration"""
personalities = [constants.CONTROLLER,
constants.COMPUTE,
constants.STORAGE]
self._config_update_hosts(context, personalities)
def update_system_mode_config(self, context):
"""Update the system mode configuration"""
personalities = [constants.CONTROLLER]

View File

@ -702,6 +702,13 @@ class ConductorAPI(sysinv.openstack.common.rpc.proxy.RpcProxy):
return self.call(context, self.make_msg('update_ntp_config',
service_change=service_change))
def update_ptp_config(self, context):
"""Synchronously, have the conductor update the PTP configuration.
:param context: request context.
"""
return self.call(context, self.make_msg('update_ptp_config'))
def update_system_mode_config(self, context):
"""Synchronously, have the conductor update the system mode
configuration.

View File

@ -1864,7 +1864,6 @@ class Connection(object):
def intp_create(self, values):
"""Create a new intp for an isystem.
:param forisystemid: intp belongs to this isystem
:param values: A dict containing several items used to identify
and track the ntp settings.
{
@ -1878,18 +1877,18 @@ class Connection(object):
"""
@abc.abstractmethod
def intp_get(self, server):
def intp_get(self, intp_id):
"""Return an intp.
:param isystem: The id or uuid of an intp.
:returns: A intp.
:param intp_id: The id or uuid of an intp.
:returns: An intp.
"""
@abc.abstractmethod
def intp_get_one(self):
"""Return exactly one intp.
:returns: A intp.
:returns: An intp.
"""
@abc.abstractmethod
@ -1910,7 +1909,7 @@ class Connection(object):
sort_key=None, sort_dir=None):
"""List all the intp for a given isystem.
:param isystem: The id or uuid of an isystem.
:param isystem_id: The id or uuid of an isystem.
:param limit: Maximum number of intp to return.
:param marker: the last item of the previous page; we return the next
result set.
@ -1921,7 +1920,7 @@ class Connection(object):
"""
@abc.abstractmethod
def intp_update(self, server, values):
def intp_update(self, intp_id, values):
"""Update properties of an intp.
:param intp_id: The id or uuid of an intp.
@ -1940,10 +1939,105 @@ class Connection(object):
"""
@abc.abstractmethod
def intp_destroy(self, server):
def intp_destroy(self, intp_id):
"""Destroy an intp.
:param id: The id or uuid of an intp.
:param intp_id: The id or uuid of an intp.
"""
@abc.abstractmethod
def ptp_create(self, values):
"""Create a new ptp for an isystem.
:param values: A dict containing several items used to identify
and track the ptp settings.
{
'enabled': 'True',
'mode': 'hardware',
'transport': 'l2',
'mechanism': 'e2e',
}
:returns: A ptp.
"""
@abc.abstractmethod
def ptp_get(self, ptp_id):
"""Return a ptp.
:param ptp_id: The id or uuid of a ptp.
:returns: A ptp.
"""
@abc.abstractmethod
def ptp_get_one(self):
"""Return exactly one ptp.
:returns: A ptp.
"""
@abc.abstractmethod
def ptp_get_list(self, limit=None, marker=None,
sort_key=None, sort_dir=None):
"""Return a list of ptp.
:param limit: Maximum number of ptp 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)
"""
@abc.abstractmethod
def ptp_get_by_isystem(self, isystem_id, limit=None, marker=None,
sort_key=None, sort_dir=None):
"""List all the ptp for a given isystem.
:param isystem_id: The id or uuid of an isystem.
:param limit: Maximum number of ptp 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.
"""
@abc.abstractmethod
def ptp_update(self, ptp_id, values):
"""Update properties of a ptp.
:param ptp_id: The id or uuid of a ptp.
:param values: Dict of values to update.
May be a partial list, eg. when setting the
properties for capabilities. For example:
{
'capabilities':
{
'my-field-1': val1,
'my-field-2': val2,
}
}
:returns: A ptp.
"""
@abc.abstractmethod
def ptp_destroy(self, ptp_id):
"""Destroy a ptp.
:param ptp_id: The id or uuid of a ptp.
"""
@abc.abstractmethod
def ptp_fill_empty_system_id(self, system_id):
"""fills all empty system_id in a ptp.
ptp did not always fill this entry in properly
so existing systems might still have no value in the
system_id field. This function fills in the system_id
in existing systems that were missing this value.
:param system_id: The value to fill system_id with
"""
@abc.abstractmethod

View File

@ -3508,14 +3508,14 @@ class Connection(api.Connection):
query.delete()
def _ntp_get(self, server):
def _ntp_get(self, intp_id):
query = model_query(models.intp)
query = add_identity_filter(query, server)
query = add_identity_filter(query, intp_id)
try:
return query.one()
except NoResultFound:
raise exception.NTPNotFound(server=server)
raise exception.NTPNotFound(intp_id=intp_id)
@objects.objectify(objects.ntp)
def intp_create(self, values):
@ -3564,25 +3564,25 @@ class Connection(api.Connection):
sort_key, sort_dir, query)
@objects.objectify(objects.ntp)
def intp_update(self, server, values):
def intp_update(self, intp_id, values):
with _session_for_write() as session:
query = model_query(models.intp, session=session)
query = add_identity_filter(query, server)
query = add_identity_filter(query, intp_id)
count = query.update(values, synchronize_session='fetch')
if count != 1:
raise exception.NTPNotFound(server=server)
raise exception.NTPNotFound(intp_id=intp_id)
return query.one()
def intp_destroy(self, server):
def intp_destroy(self, intp_id):
with _session_for_write() as session:
query = model_query(models.intp, session=session)
query = add_identity_filter(query, server)
query = add_identity_filter(query, intp_id)
try:
query.one()
except NoResultFound:
raise exception.NTPNotFound(server=server)
raise exception.NTPNotFound(intp_id=intp_id)
# if node_ref['reservation'] is not None:
# raise exception.NodeLocked(node=node)
@ -3595,6 +3595,92 @@ class Connection(api.Connection):
query.delete()
def _ptp_get(self, ptp_id):
query = model_query(models.PTP)
query = add_identity_filter(query, ptp_id)
try:
return query.one()
except NoResultFound:
raise exception.PTPNotFound(ptp_id=ptp_id)
@objects.objectify(objects.ptp)
def ptp_create(self, values):
if not values.get('uuid'):
values['uuid'] = uuidutils.generate_uuid()
ptp = models.PTP()
ptp.update(values)
with _session_for_write() as session:
try:
session.add(ptp)
session.flush()
except db_exc.DBDuplicateEntry:
raise exception.PTPAlreadyExists(uuid=values['uuid'])
return self._ptp_get(values['uuid'])
@objects.objectify(objects.ptp)
def ptp_get(self, ptp_id):
return self._ptp_get(ptp_id)
@objects.objectify(objects.ptp)
def ptp_get_one(self):
query = model_query(models.PTP)
try:
return query.one()
except NoResultFound:
raise exception.NotFound()
@objects.objectify(objects.ptp)
def ptp_get_list(self, limit=None, marker=None,
sort_key=None, sort_dir=None):
query = model_query(models.PTP)
return _paginate_query(models.PTP, limit, marker,
sort_key, sort_dir, query)
@objects.objectify(objects.ptp)
def ptp_get_by_isystem(self, isystem_id, limit=None, marker=None,
sort_key=None, sort_dir=None):
# isystem_get() to raise an exception if the isystem is not found
isystem_obj = self.isystem_get(isystem_id)
query = model_query(models.PTP)
query = query.filter_by(system_id=isystem_obj.id)
return _paginate_query(models.PTP, limit, marker,
sort_key, sort_dir, query)
@objects.objectify(objects.ptp)
def ptp_update(self, ptp_id, values):
with _session_for_write() as session:
query = model_query(models.PTP, session=session)
query = add_identity_filter(query, ptp_id)
count = query.update(values, synchronize_session='fetch')
if count != 1:
raise exception.PTPNotFound(ptp_id=ptp_id)
return query.one()
def ptp_destroy(self, ptp_id):
with _session_for_write() as session:
query = model_query(models.PTP, session=session)
query = add_identity_filter(query, ptp_id)
try:
query.one()
except NoResultFound:
raise exception.PTPNotFound(ptp_id=ptp_id)
query.delete()
def ptp_fill_empty_system_id(self, system_id):
values = {'system_id': system_id}
with _session_for_write() as session:
query = model_query(models.PTP,
session=session)
query = query.filter_by(system_id=None)
query.update(values, synchronize_session='fetch')
# NOTE: method is deprecated and provided for API compatibility.
# object class will convert Network entity to an iextoam object
@objects.objectify(objects.oam_network)

View File

@ -0,0 +1,78 @@
########################################################################
#
# Copyright (c) 2018 Wind River Systems, Inc.
#
# SPDX-License-Identifier: Apache-2.0
#
########################################################################
import uuid
from datetime import datetime
from sqlalchemy import Integer, String, Boolean, DateTime
from sqlalchemy import Column, MetaData, Table, ForeignKey
ENGINE = 'InnoDB'
CHARSET = 'utf8'
def _populate_ptp_table(migrate_engine, meta, ptp, i_system):
"""This function inserts all the initial data about journals,
into the ptp table.
"""
sys = list(i_system.select().where(i_system.c.uuid is not None).execute())
if len(sys) > 0:
ptp_insert = ptp.insert()
ptp_uuid = str(uuid.uuid4())
values = {'created_at': datetime.now(),
'updated_at': None,
'deleted_at': None,
'uuid': ptp_uuid,
'enabled': False,
'mode': 'hardware',
'transport': 'l2',
'mechanism': 'e2e',
'system_id': sys[0].id,
}
ptp_insert.execute(values)
def upgrade(migrate_engine):
meta = MetaData()
meta.bind = migrate_engine
i_system = Table('i_system', meta, autoload=True)
ptp = Table(
'ptp',
meta,
Column('created_at', DateTime),
Column('updated_at', DateTime),
Column('deleted_at', DateTime),
Column('id', Integer, primary_key=True, nullable=False),
Column('uuid', String(36), unique=True),
Column('enabled', Boolean, default=False),
Column('mode', String(16), default='hardware'),
Column('transport', String(4), default='l2'),
Column('mechanism', String(4), default='e2e'),
Column('system_id', Integer,
ForeignKey('i_system.id', ondelete="CASCADE"),
nullable=True),
mysql_engine=ENGINE,
mysql_charset=CHARSET,
)
ptp.create()
# Populate the new ptp table with the initial data
_populate_ptp_table(migrate_engine, meta, ptp, i_system)
def downgrade(migrate_engine):
meta = MetaData()
meta.bind = migrate_engine
ptp = Table('ptp', meta, autoload=True)
ptp.drop()

View File

@ -809,6 +809,23 @@ class intp(Base):
system = relationship("isystem", lazy="joined", join_depth=1)
class PTP(Base):
__tablename__ = 'ptp'
id = Column(Integer, primary_key=True)
uuid = Column(String(36))
enabled = Column(Boolean, default=False)
mode = Column(String(16), default='hardware')
transport = Column(String(4), default='l2')
mechanism = Column(String(4), default='e2e')
system_id = Column(Integer,
ForeignKey('i_system.id', ondelete='CASCADE'))
system = relationship("isystem", lazy="joined", join_depth=1)
class StorageTier(Base):
__tablename__ = 'storage_tiers'

View File

@ -50,13 +50,14 @@ from sysinv.objects import load
from sysinv.objects import lvg
from sysinv.objects import memory
from sysinv.objects import network
from sysinv.objects import network_oam
from sysinv.objects import node
from sysinv.objects import ntp
from sysinv.objects import network_oam
from sysinv.objects import pci_device
from sysinv.objects import peer
from sysinv.objects import port
from sysinv.objects import profile
from sysinv.objects import ptp
from sysinv.objects import pv
from sysinv.objects import remote_logging
from sysinv.objects import route
@ -137,6 +138,7 @@ community = community.Community
user = user.User
dns = dns.DNS
ntp = ntp.NTP
ptp = ptp.PTP
oam_network = network_oam.OAMNetwork
storage_backend = storage_backend.StorageBackend
storage_ceph = storage_ceph.StorageCeph
@ -201,6 +203,7 @@ __all__ = (system,
user,
dns,
ntp,
ptp,
oam_network,
storage_backend,
storage_ceph,

View File

@ -0,0 +1,40 @@
########################################################################
#
# Copyright (c) 2018 Wind River Systems, Inc.
#
# SPDX-License-Identifier: Apache-2.0
#
########################################################################
from sysinv.db import api as db_api
from sysinv.objects import base
from sysinv.objects import utils
class PTP(base.SysinvObject):
dbapi = db_api.get_instance()
fields = {
'id': int,
'uuid': utils.str_or_none,
'enabled': utils.bool_or_none,
'mode': utils.str_or_none,
'transport': utils.str_or_none,
'mechanism': utils.str_or_none,
'isystem_uuid': utils.str_or_none,
'system_id': utils.int_or_none
}
_foreign_fields = {
'isystem_uuid': 'system:uuid'
}
@base.remotable_classmethod
def get_by_uuid(cls, context, uuid):
return cls.dbapi.ptp_get(uuid)
def save_changes(self, context, updates):
self.dbapi.ptp_update(self.uuid, updates)

View File

@ -62,6 +62,7 @@ class PlatformPuppet(base.BasePuppet):
config = {}
config.update(self._get_host_platform_config(host, config_uuid))
config.update(self._get_host_ntp_config(host))
config.update(self._get_host_ptp_config(host))
config.update(self._get_host_sysctl_config(host))
config.update(self._get_host_drbd_config(host))
config.update(self._get_host_upgrade_config(host))
@ -430,6 +431,20 @@ class PlatformPuppet(base.BasePuppet):
'platform::ntp::ntpdate_timeout': ntpdate_timeout,
}
def _get_host_ptp_config(self, host):
ptp = self.dbapi.ptp_get_one()
return {
'platform::ptp::enabled':
ptp.enabled,
'platform::ptp::mode':
ptp.mode,
'platform::ptp::transport':
ptp.transport,
'platform::ptp::mechanism':
ptp.mechanism,
}
def _get_host_sysctl_config(self, host):
config = {}

View File

@ -1873,3 +1873,22 @@ class TestMigrations(BaseMigrationTestCase, WalkVersionsMixin):
for col, coltype in ntps_col.items():
self.assertTrue(isinstance(ntps.c[col].type,
getattr(sqlalchemy.types, coltype)))
def _check_075(self, engine, data):
# Assert data types for all columns in new table "ptp"
ptp = db_utils.get_table(engine, 'ptp')
ptp_cols = {
'created_at': 'DateTime',
'updated_at': 'DateTime',
'deleted_at': 'DateTime',
'id': 'Integer',
'uuid': 'String',
'enabled': 'Boolean',
'mode': 'String',
'transport': 'String',
'mechanism': 'String',
'system_id': 'Integer',
}
for col, coltype in ptp_cols.items():
self.assertTrue(isinstance(ptp.c[col].type,
getattr(sqlalchemy.types, coltype)))