diff --git a/puppet-manifests/src/manifests/compute.pp b/puppet-manifests/src/manifests/compute.pp index 6a1620672a..6402489057 100644 --- a/puppet-manifests/src/manifests/compute.pp +++ b/puppet-manifests/src/manifests/compute.pp @@ -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 diff --git a/puppet-manifests/src/manifests/controller.pp b/puppet-manifests/src/manifests/controller.pp index ec0bfe1df2..1c7a41fa25 100644 --- a/puppet-manifests/src/manifests/controller.pp +++ b/puppet-manifests/src/manifests/controller.pp @@ -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 diff --git a/puppet-manifests/src/manifests/storage.pp b/puppet-manifests/src/manifests/storage.pp index b10452b3b7..9855560929 100644 --- a/puppet-manifests/src/manifests/storage.pp +++ b/puppet-manifests/src/manifests/storage.pp @@ -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 diff --git a/puppet-manifests/src/modules/platform/manifests/firewall.pp b/puppet-manifests/src/modules/platform/manifests/firewall.pp index 246cbc217f..d8901d827a 100644 --- a/puppet-manifests/src/modules/platform/manifests/firewall.pp +++ b/puppet-manifests/src/modules/platform/manifests/firewall.pp @@ -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}": diff --git a/puppet-manifests/src/modules/platform/manifests/ptp.pp b/puppet-manifests/src/modules/platform/manifests/ptp.pp new file mode 100644 index 0000000000..44eec77668 --- /dev/null +++ b/puppet-manifests/src/modules/platform/manifests/ptp.pp @@ -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', + } + } +} diff --git a/puppet-manifests/src/modules/platform/templates/ntp.conf.client.erb b/puppet-manifests/src/modules/platform/templates/ntp.conf.client.erb index 61b841f972..bf23b7393f 100644 --- a/puppet-manifests/src/modules/platform/templates/ntp.conf.client.erb +++ b/puppet-manifests/src/modules/platform/templates/ntp.conf.client.erb @@ -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 diff --git a/puppet-manifests/src/modules/platform/templates/ntp.conf.server.erb b/puppet-manifests/src/modules/platform/templates/ntp.conf.server.erb index 69f94f0b58..0aa2144ae4 100644 --- a/puppet-manifests/src/modules/platform/templates/ntp.conf.server.erb +++ b/puppet-manifests/src/modules/platform/templates/ntp.conf.server.erb @@ -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 diff --git a/puppet-manifests/src/modules/platform/templates/phc2sys.erb b/puppet-manifests/src/modules/platform/templates/phc2sys.erb new file mode 100644 index 0000000000..91d9bd7f64 --- /dev/null +++ b/puppet-manifests/src/modules/platform/templates/phc2sys.erb @@ -0,0 +1 @@ +OPTIONS="-a -r -E linreg -u 60" diff --git a/puppet-manifests/src/modules/platform/templates/phc2sys.pmon.conf.erb b/puppet-manifests/src/modules/platform/templates/phc2sys.pmon.conf.erb new file mode 100644 index 0000000000..dbab5deb3f --- /dev/null +++ b/puppet-manifests/src/modules/platform/templates/phc2sys.pmon.conf.erb @@ -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 diff --git a/puppet-manifests/src/modules/platform/templates/phc2sys.service.erb b/puppet-manifests/src/modules/platform/templates/phc2sys.service.erb new file mode 100644 index 0000000000..babfc30cdb --- /dev/null +++ b/puppet-manifests/src/modules/platform/templates/phc2sys.service.erb @@ -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 diff --git a/puppet-manifests/src/modules/platform/templates/ptp4l.conf.erb b/puppet-manifests/src/modules/platform/templates/ptp4l.conf.erb new file mode 100644 index 0000000000..61c56df480 --- /dev/null +++ b/puppet-manifests/src/modules/platform/templates/ptp4l.conf.erb @@ -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 -%> diff --git a/puppet-manifests/src/modules/platform/templates/ptp4l.erb b/puppet-manifests/src/modules/platform/templates/ptp4l.erb new file mode 100644 index 0000000000..98352570f4 --- /dev/null +++ b/puppet-manifests/src/modules/platform/templates/ptp4l.erb @@ -0,0 +1 @@ +OPTIONS="-f /etc/ptp4l.conf" diff --git a/puppet-manifests/src/modules/platform/templates/ptp4l.pmon.conf.erb b/puppet-manifests/src/modules/platform/templates/ptp4l.pmon.conf.erb new file mode 100644 index 0000000000..230b8e102b --- /dev/null +++ b/puppet-manifests/src/modules/platform/templates/ptp4l.pmon.conf.erb @@ -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 diff --git a/puppet-manifests/src/modules/platform/templates/ptp4l.service.erb b/puppet-manifests/src/modules/platform/templates/ptp4l.service.erb new file mode 100644 index 0000000000..450d4fdb56 --- /dev/null +++ b/puppet-manifests/src/modules/platform/templates/ptp4l.service.erb @@ -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 diff --git a/sysinv/cgts-client/centos/build_srpm.data b/sysinv/cgts-client/centos/build_srpm.data index 7c25370b11..9936e62623 100644 --- a/sysinv/cgts-client/centos/build_srpm.data +++ b/sysinv/cgts-client/centos/build_srpm.data @@ -1,2 +1,2 @@ SRC_DIR="cgts-client" -TIS_PATCH_VER=59 +TIS_PATCH_VER=60 diff --git a/sysinv/cgts-client/cgts-client/cgtsclient/common/utils.py b/sysinv/cgts-client/cgts-client/cgtsclient/common/utils.py index 70b996200e..14da7a866d 100644 --- a/sysinv/cgts-client/cgts-client/cgtsclient/common/utils.py +++ b/sysinv/cgts-client/cgts-client/cgtsclient/common/utils.py @@ -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', diff --git a/sysinv/cgts-client/cgts-client/cgtsclient/v1/client.py b/sysinv/cgts-client/cgts-client/cgtsclient/v1/client.py index be8d9c78ae..15ca3667bd 100644 --- a/sysinv/cgts-client/cgts-client/cgtsclient/v1/client.py +++ b/sysinv/cgts-client/cgts-client/cgtsclient/v1/client.py @@ -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) diff --git a/sysinv/cgts-client/cgts-client/cgtsclient/v1/ptp.py b/sysinv/cgts-client/cgts-client/cgtsclient/v1/ptp.py new file mode 100644 index 0000000000..d85b21206c --- /dev/null +++ b/sysinv/cgts-client/cgts-client/cgtsclient/v1/ptp.py @@ -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 "" % 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) diff --git a/sysinv/cgts-client/cgts-client/cgtsclient/v1/ptp_shell.py b/sysinv/cgts-client/cgts-client/cgtsclient/v1/ptp_shell.py new file mode 100644 index 0000000000..24451d05ee --- /dev/null +++ b/sysinv/cgts-client/cgts-client/cgtsclient/v1/ptp_shell.py @@ -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='', + help="PTP service enabled.") +@utils.arg('--mode', + metavar='', + default=None, + help="PTP time stamping mode.") +@utils.arg('--transport', + metavar='', + default=None, + help="PTP transport protocol.") +@utils.arg('--mechanism', + metavar='', + 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) diff --git a/sysinv/cgts-client/cgts-client/cgtsclient/v1/shell.py b/sysinv/cgts-client/cgts-client/cgtsclient/v1/shell.py index 46c7ab9860..ae77c3b214 100644 --- a/sysinv/cgts-client/cgts-client/cgtsclient/v1/shell.py +++ b/sysinv/cgts-client/cgts-client/cgtsclient/v1/shell.py @@ -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, diff --git a/sysinv/sysinv/centos/build_srpm.data b/sysinv/sysinv/centos/build_srpm.data index 03d4f1085d..a292c81413 100644 --- a/sysinv/sysinv/centos/build_srpm.data +++ b/sysinv/sysinv/centos/build_srpm.data @@ -1,2 +1,2 @@ SRC_DIR="sysinv" -TIS_PATCH_VER=278 +TIS_PATCH_VER=279 diff --git a/sysinv/sysinv/sysinv/sysinv/api/controllers/v1/__init__.py b/sysinv/sysinv/sysinv/sysinv/api/controllers/v1/__init__.py index 4f5ac60c95..6bfb5b1eb7 100644 --- a/sysinv/sysinv/sysinv/sysinv/api/controllers/v1/__init__.py +++ b/sysinv/sysinv/sysinv/sysinv/api/controllers/v1/__init__.py @@ -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() diff --git a/sysinv/sysinv/sysinv/sysinv/api/controllers/v1/ntp.py b/sysinv/sysinv/sysinv/sysinv/api/controllers/v1/ntp.py index 7a7a61ca06..48b896ed16 100644 --- a/sysinv/sysinv/sysinv/sysinv/api/controllers/v1/ntp.py +++ b/sysinv/sysinv/sysinv/sysinv/api/controllers/v1/ntp.py @@ -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: diff --git a/sysinv/sysinv/sysinv/sysinv/api/controllers/v1/ptp.py b/sysinv/sysinv/sysinv/sysinv/api/controllers/v1/ptp.py new file mode 100644 index 0000000000..91f082cd78 --- /dev/null +++ b/sysinv/sysinv/sysinv/sysinv/api/controllers/v1/ptp.py @@ -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 diff --git a/sysinv/sysinv/sysinv/sysinv/common/exception.py b/sysinv/sysinv/sysinv/sysinv/common/exception.py index 5654c9bdfd..74d20e5376 100644 --- a/sysinv/sysinv/sysinv/sysinv/common/exception.py +++ b/sysinv/sysinv/sysinv/sysinv/common/exception.py @@ -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") diff --git a/sysinv/sysinv/sysinv/sysinv/conductor/manager.py b/sysinv/sysinv/sysinv/sysinv/conductor/manager.py index e9abfa68e2..435cd669f3 100644 --- a/sysinv/sysinv/sysinv/sysinv/conductor/manager.py +++ b/sysinv/sysinv/sysinv/sysinv/conductor/manager.py @@ -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] diff --git a/sysinv/sysinv/sysinv/sysinv/conductor/rpcapi.py b/sysinv/sysinv/sysinv/sysinv/conductor/rpcapi.py index bf89796c0b..d8e7660ca9 100644 --- a/sysinv/sysinv/sysinv/sysinv/conductor/rpcapi.py +++ b/sysinv/sysinv/sysinv/sysinv/conductor/rpcapi.py @@ -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. diff --git a/sysinv/sysinv/sysinv/sysinv/db/api.py b/sysinv/sysinv/sysinv/sysinv/db/api.py index 3a25a08ac3..5c2f972ddf 100644 --- a/sysinv/sysinv/sysinv/sysinv/db/api.py +++ b/sysinv/sysinv/sysinv/sysinv/db/api.py @@ -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 diff --git a/sysinv/sysinv/sysinv/sysinv/db/sqlalchemy/api.py b/sysinv/sysinv/sysinv/sysinv/db/sqlalchemy/api.py index 2baded9fcb..d7c51f4137 100755 --- a/sysinv/sysinv/sysinv/sysinv/db/sqlalchemy/api.py +++ b/sysinv/sysinv/sysinv/sysinv/db/sqlalchemy/api.py @@ -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) diff --git a/sysinv/sysinv/sysinv/sysinv/db/sqlalchemy/migrate_repo/versions/075_ptp.py b/sysinv/sysinv/sysinv/sysinv/db/sqlalchemy/migrate_repo/versions/075_ptp.py new file mode 100644 index 0000000000..3e8618f9be --- /dev/null +++ b/sysinv/sysinv/sysinv/sysinv/db/sqlalchemy/migrate_repo/versions/075_ptp.py @@ -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() diff --git a/sysinv/sysinv/sysinv/sysinv/db/sqlalchemy/models.py b/sysinv/sysinv/sysinv/sysinv/db/sqlalchemy/models.py index f12d0fccee..1dcc265cd9 100755 --- a/sysinv/sysinv/sysinv/sysinv/db/sqlalchemy/models.py +++ b/sysinv/sysinv/sysinv/sysinv/db/sqlalchemy/models.py @@ -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' diff --git a/sysinv/sysinv/sysinv/sysinv/objects/__init__.py b/sysinv/sysinv/sysinv/sysinv/objects/__init__.py index b3e572509c..62ab9a83fc 100644 --- a/sysinv/sysinv/sysinv/sysinv/objects/__init__.py +++ b/sysinv/sysinv/sysinv/sysinv/objects/__init__.py @@ -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, diff --git a/sysinv/sysinv/sysinv/sysinv/objects/ptp.py b/sysinv/sysinv/sysinv/sysinv/objects/ptp.py new file mode 100644 index 0000000000..91e5f3da8e --- /dev/null +++ b/sysinv/sysinv/sysinv/sysinv/objects/ptp.py @@ -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) diff --git a/sysinv/sysinv/sysinv/sysinv/puppet/platform.py b/sysinv/sysinv/sysinv/sysinv/puppet/platform.py index 8f4c52540d..e7aa5581cb 100644 --- a/sysinv/sysinv/sysinv/sysinv/puppet/platform.py +++ b/sysinv/sysinv/sysinv/sysinv/puppet/platform.py @@ -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 = {} diff --git a/sysinv/sysinv/sysinv/sysinv/tests/db/sqlalchemy/test_migrations.py b/sysinv/sysinv/sysinv/sysinv/tests/db/sqlalchemy/test_migrations.py index dc83ee33d7..0328f51c5c 100644 --- a/sysinv/sysinv/sysinv/sysinv/tests/db/sqlalchemy/test_migrations.py +++ b/sysinv/sysinv/sysinv/sysinv/tests/db/sqlalchemy/test_migrations.py @@ -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)))