From 2d17e9849fbf7817e6d17849631dae98bff8cac4 Mon Sep 17 00:00:00 2001 From: Kristine Bujold Date: Fri, 13 Sep 2019 13:27:45 -0400 Subject: [PATCH] Support for NTP/PTP coexistence MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The NTP/PTP selection will now be done per host. NTP will the default selection. This commit; -Removes the enabled flag NTP and PTP API. Updates the CLI commands and the database. -Adds the parameter clock_synchronization to the host API. Valid values are ‘ntp’ and ‘ptp’. Updates the host CLI commands and the database. -Updates puppet to set NTP/PTP per host . -Updates the RestAPI documentation. Story: 2006499 Task: 36464 Change-Id: I37bbb30a014301f8786cb02e35f0a1bd39d2f4aa Signed-off-by: Kristine Bujold --- api-ref/source/api-ref-sysinv-v1-config.rst | 162 +++++++++++++++++- sysinv/cgts-client/centos/build_srpm.data | 2 +- .../cgts-client/cgtsclient/v1/iHost_shell.py | 11 +- .../cgts-client/cgtsclient/v1/ihost.py | 4 +- .../cgts-client/cgtsclient/v1/intp_shell.py | 15 +- .../cgts-client/cgtsclient/v1/ptp_shell.py | 14 +- sysinv/sysinv/centos/build_srpm.data | 2 +- .../sysinv/sysinv/api/controllers/v1/host.py | 22 ++- .../sysinv/sysinv/api/controllers/v1/ntp.py | 35 +--- .../sysinv/sysinv/api/controllers/v1/ptp.py | 28 +-- .../sysinv/sysinv/sysinv/common/constants.py | 9 + .../sysinv/sysinv/sysinv/conductor/manager.py | 19 +- .../sysinv/sysinv/sysinv/conductor/rpcapi.py | 16 +- .../versions/092_clock_synchronization.py | 43 +++++ .../sysinv/sysinv/db/sqlalchemy/models.py | 6 +- sysinv/sysinv/sysinv/sysinv/objects/host.py | 3 +- sysinv/sysinv/sysinv/sysinv/objects/ntp.py | 3 +- sysinv/sysinv/sysinv/sysinv/objects/ptp.py | 3 +- .../sysinv/sysinv/sysinv/puppet/platform.py | 14 +- .../sysinv/sysinv/tests/api/test_host.py | 35 +++- sysinv/sysinv/sysinv/sysinv/tests/db/utils.py | 3 +- 21 files changed, 342 insertions(+), 107 deletions(-) create mode 100644 sysinv/sysinv/sysinv/sysinv/db/sqlalchemy/migrate_repo/versions/092_clock_synchronization.py diff --git a/api-ref/source/api-ref-sysinv-v1-config.rst b/api-ref/source/api-ref-sysinv-v1-config.rst index 093fdaf24e..d3058066ac 100644 --- a/api-ref/source/api-ref-sysinv-v1-config.rst +++ b/api-ref/source/api-ref-sysinv-v1-config.rst @@ -4311,6 +4311,162 @@ badMediaType (415) "uuid":"81321749-5092-4faf-94ba-6a6853440725" } + +---- +PTP +---- + +The PTP is the Precision Time Protocol entity for the system. + +************************************ +Shows attributes of the PTP object +************************************ + +.. rest_method:: GET /v1/ptp + +**Normal response codes** + +200 + +**Error response codes** + +computeFault (400, 500, ...), serviceUnavailable (503), badRequest (400), +unauthorized (401), forbidden (403), badMethod (405), overLimit (413), +itemNotFound (404) + +**Response parameters** + +.. csv-table:: + :header: "Parameter", "Style", "Type", "Description" + :widths: 20, 20, 20, 60 + + "mode (Optional)", "plain", "xsd:string", "PTP time stamping mode." + "transport (Optional)", "plain", "xsd:string", "PTP transport protocol." + "mechanism (Optional)", "plain", "xsd:string", "PTP delay mechanism." + "isystem_uuid (Optional)", "plain", "csapi:UUID", "The System UUID which the PTP belongs to." + "uuid (Optional)", "plain", "csapi:UUID", "The universally unique identifier for this object." + "links (Optional)", "plain", "xsd:list", "For convenience, resources contain links to themselves. This allows a client to easily obtain rather than construct resource URIs. The following types of link relations are associated with resources: a self link containing a versioned link to the resource, and a bookmark link containing a permanent link to a resource that is appropriate for long term storage." + "created_at (Optional)", "plain", "xsd:dateTime", "The time when the object was created." + "updated_at (Optional)", "plain", "xsd:dateTime", "The time when the object was last updated." + +:: + + { + "ptp":[ + { + "links":[ + { + "href":"http://192.168.204.2:6385/v1/ptps/70649b44-b462-445a-9fa5-9233a1b5842d", + "rel":"self" + }, + { + "href":"http://192.168.204.2:6385/ptps/70649b44-b462-445a-9fa5-9233a1b5842d", + "rel":"bookmark" + } + ], + "created_at":"2019-09-30T14:42:16.693209+00:00", + "updated_at":"2019-10-01T17:33:43.169595+00:00", + "mechanism":"e2e", + "mode":"hardware", + "transport":"l2", + "isystem_uuid":"ce178041-2b2c-405d-bf87-f19334a35582", + "uuid":"70649b44-b462-445a-9fa5-9233a1b5842d" + } + ] + } + +This operation does not accept a request body. + +*************************************** +Modifies attributes of the PTP object +*************************************** + +.. rest_method:: PATCH /v1/ptp/​{ptp_id}​ + +The attributes of the PTP object that are configurable are: + +- mode +- transport +- mechanism + +**Normal response codes** + +200 + +**Error response codes** + +badMediaType (415) + +**Request parameters** + +.. csv-table:: + :header: "Parameter", "Style", "Type", "Description" + :widths: 20, 20, 20, 60 + + "ptp_id", "URI", "csapi:UUID", "The unique identifier of the PTP for this system." + "mode (Optional)", "plain", "xsd:string", "PTP time stamping mode. Valid values are (is): ``hardware``, ``software`` or ``legacy``" + "transport (Optional)", "plain", "xsd:string", "PTP transport protocol. Valid values are (is): ``udp`` or ``l2``" + "mechanism (Optional)", "plain", "xsd:string", "PTP delay mechanism. Valid values are (is): ``e2e`` or ``p2p``" + +**Response parameters** + +.. csv-table:: + :header: "Parameter", "Style", "Type", "Description" + :widths: 20, 20, 20, 60 + + "mode (Optional)", "plain", "xsd:string", "PTP time stamping mode." + "transport (Optional)", "plain", "xsd:string", "PTP transport protocol." + "mechanism (Optional)", "plain", "xsd:string", "PTP delay mechanism." + "isystem_uuid (Optional)", "plain", "csapi:UUID", "The System UUID which the NTP belongs to." + "uuid (Optional)", "plain", "csapi:UUID", "The universally unique identifier for this object." + "links (Optional)", "plain", "xsd:list", "For convenience, resources contain links to themselves. This allows a client to easily obtain rather than construct resource URIs. The following types of link relations are associated with resources: a self link containing a versioned link to the resource, and a bookmark link containing a permanent link to a resource that is appropriate for long term storage." + "created_at (Optional)", "plain", "xsd:dateTime", "The time when the object was created." + "updated_at (Optional)", "plain", "xsd:dateTime", "The time when the object was last updated." + +:: + + [ + { + "path": "/mode", + "value": "legacy", + "op": "replace" + }, + { + "path": "/transport", + "value": "udp", + "op": "replace" + }, + { + "path": "/mechanism", + "value": "p2p", + "op": "replace" + } + ] + + +:: + + { + "links":[ + { + "href":"http://192.168.204.2:6385/v1/ptps/70649b44-b462-445a-9fa5-9233a1b5842d", + "rel":"self" + }, + { + "href":"http://192.168.204.2:6385/ptps/70649b44-b462-445a-9fa5-9233a1b5842d", + "rel":"bookmark" + } + ], + "created_at":"2014-09-30T14:42:16.693209+00:00", + "updated_at":"2014-10-01T17:35:43.162472+00:00", + "mechanism": "p2p" + "mode": "legacy" + "transport": "udp" + "isystem_uuid":"ce178041-2b2c-405d-bf87-f19334a35582", + "forisystemid":1, + "uuid":"70649b44-b462-445a-9fa5-9233a1b5842d" + } + ------------- External OAM ------------- @@ -10896,7 +11052,7 @@ itemNotFound (404) "registry_images": [ { "tag": null, - "name": "docker.io/starlingx/ceph-config-helper" + "name": "docker.io/starlingx/ceph-config-helper" }, { "tag": null, @@ -10946,8 +11102,8 @@ itemNotFound (404) { "registry_images": [ { - "tag": "v1.15.0", - "name": "docker.io/starlingx/ceph-config-helper" + "tag": "v1.15.0", + "name": "docker.io/starlingx/ceph-config-helper" } ] } diff --git a/sysinv/cgts-client/centos/build_srpm.data b/sysinv/cgts-client/centos/build_srpm.data index ea8903d452..e0cfc61166 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=70 +TIS_PATCH_VER=71 diff --git a/sysinv/cgts-client/cgts-client/cgtsclient/v1/iHost_shell.py b/sysinv/cgts-client/cgts-client/cgtsclient/v1/iHost_shell.py index eb14e18165..6b53fe7c1b 100755 --- a/sysinv/cgts-client/cgts-client/cgtsclient/v1/iHost_shell.py +++ b/sysinv/cgts-client/cgts-client/cgtsclient/v1/iHost_shell.py @@ -1,6 +1,6 @@ #!/usr/bin/env python # -# Copyright (c) 2013-2017 Wind River Systems, Inc. +# Copyright (c) 2013-2019 Wind River Systems, Inc. # # SPDX-License-Identifier: Apache-2.0 # @@ -35,7 +35,7 @@ def _print_ihost_show(ihost): 'location', 'uptime', 'reserved', 'created_at', 'updated_at', 'boot_device', 'rootfs_device', 'install_output', 'console', 'tboot', 'vim_progress_status', 'software_load', 'install_state', - 'install_state_info', 'inv_state'] + 'install_state_info', 'inv_state', 'clock_synchronization'] optional_fields = ['vsc_controllers', 'ttys_dcd'] if ihost.subfunctions != ihost.personality: fields.append('subfunctions') @@ -141,13 +141,18 @@ def do_host_upgrade_list(cc, args): @utils.arg('-D', '--ttys_dcd', metavar='', help='Enable/disable serial console data carrier detection') +@utils.arg('-C', '--clock_synchronization', + metavar='', + choices=['ntp', 'ptp'], + help='Clock synchronization, ntp or ptp. Default: ntp') def do_host_add(cc, args): """Add a new host.""" field_list = ['hostname', 'personality', 'subfunctions', 'mgmt_mac', 'mgmt_ip', 'bm_ip', 'bm_type', 'bm_username', 'bm_password', 'boot_device', 'rootfs_device', 'install_output', 'console', - 'vsc_controllers', 'location', 'ttys_dcd'] + 'vsc_controllers', 'location', 'ttys_dcd', + 'clock_synchronization'] fields = dict((k, v) for (k, v) in vars(args).items() if k in field_list and not (v is None)) diff --git a/sysinv/cgts-client/cgts-client/cgtsclient/v1/ihost.py b/sysinv/cgts-client/cgts-client/cgtsclient/v1/ihost.py index 13920fc065..38a40f9e6c 100644 --- a/sysinv/cgts-client/cgts-client/cgtsclient/v1/ihost.py +++ b/sysinv/cgts-client/cgts-client/cgtsclient/v1/ihost.py @@ -1,5 +1,5 @@ # -# Copyright (c) 2013-2015 Wind River Systems, Inc. +# Copyright (c) 2013-2019 Wind River Systems, Inc. # # SPDX-License-Identifier: Apache-2.0 # @@ -20,7 +20,7 @@ CREATION_ATTRIBUTES = ['hostname', 'personality', 'subfunctions', 'boot_device', 'rootfs_device', 'install_output', 'console', 'tboot', 'vsc_controllers', 'ttys_dcd', 'administrative', 'operational', 'availability', - 'invprovision'] + 'invprovision', 'clock_synchronization'] class ihost(base.Resource): diff --git a/sysinv/cgts-client/cgts-client/cgtsclient/v1/intp_shell.py b/sysinv/cgts-client/cgts-client/cgtsclient/v1/intp_shell.py index f0928924d6..88b98e2ee5 100644 --- a/sysinv/cgts-client/cgts-client/cgtsclient/v1/intp_shell.py +++ b/sysinv/cgts-client/cgts-client/cgtsclient/v1/intp_shell.py @@ -1,6 +1,6 @@ #!/usr/bin/env python # -# Copyright (c) 2013-2017 Wind River Systems, Inc. +# Copyright (c) 2013-2019 Wind River Systems, Inc. # # SPDX-License-Identifier: Apache-2.0 # @@ -14,8 +14,7 @@ from cgtsclient import exc def _print_intp_show(intp): - fields = ['uuid', 'enabled', 'ntpservers', - 'isystem_uuid', 'created_at', 'updated_at'] + fields = ['uuid', 'ntpservers', 'isystem_uuid', 'created_at', 'updated_at'] data = [(f, getattr(intp, f, '')) for f in fields] utils.print_tuple_list(data) @@ -34,8 +33,8 @@ def donot_config_ntp_list(cc, args): intps = cc.intp.list() - field_labels = ['uuid', 'enabled', 'ntpservers'] - fields = ['uuid', 'enabled', 'ntpservers'] + field_labels = ['uuid', 'ntpservers'] + fields = ['uuid', 'ntpservers'] utils.print_list(intps, fields, field_labels, sortby=1) @@ -70,9 +69,6 @@ def donot_ntp_add(cc, args): _print_intp_show(intp) -@utils.arg('--enabled', - metavar='', - help="NTP service enabled.") @utils.arg('attributes', metavar='', nargs='*', @@ -86,9 +82,6 @@ def do_ntp_modify(cc, args): intp = intps[0] op = "replace" - if args.enabled is not None: - args.attributes[0].append('enabled=%s' % args.enabled) - for attribute in args.attributes: if 'ntpservers=' in attribute: ntpservers = attribute[0].split('=')[1] diff --git a/sysinv/cgts-client/cgts-client/cgtsclient/v1/ptp_shell.py b/sysinv/cgts-client/cgts-client/cgtsclient/v1/ptp_shell.py index d9d7c18ed9..a567fab7a1 100644 --- a/sysinv/cgts-client/cgts-client/cgtsclient/v1/ptp_shell.py +++ b/sysinv/cgts-client/cgts-client/cgtsclient/v1/ptp_shell.py @@ -1,6 +1,6 @@ ######################################################################## # -# Copyright (c) 2018 Wind River Systems, Inc. +# Copyright (c) 2018-2019 Wind River Systems, Inc. # # SPDX-License-Identifier: Apache-2.0 # @@ -11,7 +11,7 @@ from cgtsclient import exc def _print_ptp_show(ptp): - fields = ['uuid', 'enabled', 'mode', 'transport', 'mechanism', + fields = ['uuid', 'mode', 'transport', 'mechanism', 'isystem_uuid', 'created_at', 'updated_at'] data = [(f, getattr(ptp, f, '')) for f in fields] utils.print_tuple_list(data) @@ -30,14 +30,11 @@ def donot_config_ptp_list(cc, args): ptps = cc.ptp.list() - field_labels = ['uuid', 'enabled', 'mode', 'transport', 'mechanism'] - fields = ['uuid', 'enabled', 'mode', 'transport', 'mechanism'] + field_labels = ['uuid', 'mode', 'transport', 'mechanism'] + fields = ['uuid', '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, @@ -58,8 +55,7 @@ def do_ptp_modify(cc, args): 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: diff --git a/sysinv/sysinv/centos/build_srpm.data b/sysinv/sysinv/centos/build_srpm.data index c6050e4ee3..84c0634de1 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=331 +TIS_PATCH_VER=332 diff --git a/sysinv/sysinv/sysinv/sysinv/api/controllers/v1/host.py b/sysinv/sysinv/sysinv/sysinv/api/controllers/v1/host.py index 4b4fdca2f1..3ebb5e026f 100644 --- a/sysinv/sysinv/sysinv/sysinv/api/controllers/v1/host.py +++ b/sysinv/sysinv/sysinv/sysinv/api/controllers/v1/host.py @@ -422,6 +422,9 @@ class Host(base.APIBase): config_target = wtypes.text "Represent the configuration which needs to be applied to this ihost." + clock_synchronization = wtypes.text + "Represent the clock synchronization type of this ihost." + # Host uptime uptime = int @@ -563,7 +566,7 @@ class Host(base.APIBase): 'software_load', 'target_load', 'peers', 'peer_id', 'install_state', 'install_state_info', 'iscsi_initiator_name', - 'inv_state'] + 'inv_state', 'clock_synchronization'] fields = minimum_fields if not expand else None uhost = Host.from_rpc_object(rpc_ihost, fields) @@ -1867,6 +1870,14 @@ class HostController(rest.RestController): LOG.exception(e) raise wsme.exc.ClientSideError(_("Patching Error: %s") % e) + if patched_ihost['clock_synchronization'] not in \ + constants.CLOCK_SYNCHRONIZATION: + msg = _("Host update failed: clock_synchronization: " + "invalid choice: '%s', choose from %s" % + (patched_ihost['clock_synchronization'], + constants.CLOCK_SYNCHRONIZATION)) + raise wsme.exc.ClientSideError(msg) + self._validate_capabilities( ihost_dict['capabilities'], patched_ihost['capabilities']) @@ -2203,6 +2214,11 @@ class HostController(rest.RestController): self._handle_ttys_dcd_change(hostupdate.ihost_orig, hostupdate.ihost_patch['ttys_dcd']) + if 'clock_synchronization' in hostupdate.delta: + # perform rpc to conductor to perform config apply + pecan.request.rpcapi.update_clock_synchronization_config( + pecan.request.context, patched_ihost) + log_end = cutils.timestamped("ihost_patch_end") if uptime_update: LOG.debug("host %s %s patch" % (ihost_obj.hostname, @@ -2212,8 +2228,8 @@ class HostController(rest.RestController): log_end)) if ('administrative' in hostupdate.delta and - hostupdate.ihost_patch['administrative'] == - constants.ADMIN_LOCKED): + hostupdate.ihost_patch['administrative'] == + constants.ADMIN_LOCKED): LOG.info("Update host memory for (%s)" % ihost_obj['hostname']) pecan.request.rpcapi.update_host_memory(pecan.request.context, ihost_obj['uuid']) diff --git a/sysinv/sysinv/sysinv/sysinv/api/controllers/v1/ntp.py b/sysinv/sysinv/sysinv/sysinv/api/controllers/v1/ntp.py index b3c06aebdf..ec959b28ff 100644 --- a/sysinv/sysinv/sysinv/sysinv/api/controllers/v1/ntp.py +++ b/sysinv/sysinv/sysinv/sysinv/api/controllers/v1/ntp.py @@ -16,7 +16,7 @@ # License for the specific language governing permissions and limitations # under the License. # -# Copyright (c) 2013-2017 Wind River Systems, Inc. +# Copyright (c) 2013-2019 Wind River Systems, Inc. # @@ -66,9 +66,6 @@ class NTP(base.APIBase): uuid = types.uuid "Unique UUID for this ntp" - enabled = types.boolean - "Represent the status of the intp." - ntpservers = wtypes.text "Represent the ntpservers of the intp. csv list." @@ -105,7 +102,6 @@ class NTP(base.APIBase): ntp = NTP(**rpc_ntp.as_dict()) if not expand: ntp.unset_fields_except(['uuid', - 'enabled', 'ntpservers', 'isystem_uuid', 'created_at', @@ -152,23 +148,12 @@ class intpCollection(collection.Collection): ############## def _check_ntp_data(op, ntp): # Get data - enabled = ntp['enabled'] ntpservers = ntp['ntpservers'] intp_ntpservers_list = [] - ntp_ntpservers = "" idns_nameservers_list = [] 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: @@ -184,7 +169,8 @@ def _check_ntp_data(op, ntp): except (AddrFormatError, ValueError): if utils.is_valid_hostname(ntpserver): - # If server address in FQDN, and no DNS servers, raise error + # If server address in FQDN, and no DNS servers, + # raise error if len(idns_nameservers_list) == 0 and ntpserver != 'NC': raise wsme.exc.ClientSideError(_( "A DNS server must be configured prior to " @@ -200,9 +186,9 @@ def _check_ntp_data(op, ntp): raise wsme.exc.ClientSideError(_( "Invalid NTP server %s " "Please configure a valid NTP " - "IP address or hostname.") % (ntpserver)) + "IP address or hostname.") % ntpserver) - if len(intp_ntpservers_list) == 0 and enabled is None: + if len(intp_ntpservers_list) == 0: raise wsme.exc.ClientSideError(_("No NTP parameters provided.")) if len(intp_ntpservers_list) > MAX_S: @@ -270,7 +256,7 @@ class NTPController(rest.RestController): """Retrieve a list of ntps. Only one per system""" return self._get_ntps_collection(isystem_uuid, marker, limit, - sort_key, sort_dir) + sort_key, sort_dir) @wsme_pecan.wsexpose(intpCollection, types.uuid, types.uuid, int, wtypes.text, wtypes.text) @@ -359,18 +345,13 @@ class NTPController(rest.RestController): rpc_ntp[field] = ntp[field] delta = rpc_ntp.obj_what_changed() - delta_handle = list(delta) if delta: rpc_ntp.save() - if 'enabled' in delta_handle: - service_change = True - else: - service_change = False if action == constants.APPLY_ACTION: # perform rpc to conductor to perform config apply - pecan.request.rpcapi.update_ntp_config(pecan.request.context, - service_change) + pecan.request.rpcapi.update_ntp_config( + pecan.request.context) else: LOG.info("No NTP config changes") diff --git a/sysinv/sysinv/sysinv/sysinv/api/controllers/v1/ptp.py b/sysinv/sysinv/sysinv/sysinv/api/controllers/v1/ptp.py index 91f082cd78..16f2af7479 100644 --- a/sysinv/sysinv/sysinv/sysinv/api/controllers/v1/ptp.py +++ b/sysinv/sysinv/sysinv/sysinv/api/controllers/v1/ptp.py @@ -1,6 +1,6 @@ ######################################################################## # -# Copyright (c) 2018 Wind River Systems, Inc. +# Copyright (c) 2018-2019 Wind River Systems, Inc. # # SPDX-License-Identifier: Apache-2.0 # @@ -48,9 +48,6 @@ class PTP(base.APIBase): 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." @@ -80,7 +77,6 @@ class PTP(base.APIBase): ptp = PTP(**rpc_ptp.as_dict()) if not expand: ptp.unset_fields_except(['uuid', - 'enabled', 'mode', 'transport', 'mechanism', @@ -118,21 +114,6 @@ class ptpCollection(collection.Collection): 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' @@ -223,7 +204,7 @@ class PTPController(rest.RestController): except utils.JSONPATCH_EXCEPTIONS as e: raise exception.PatchError(patch=patch, reason=e) - ptp = _check_ptp_data("modify", ptp.as_dict()) + ptp = ptp.as_dict() try: # Update only the fields that have changed @@ -242,9 +223,8 @@ class PTPController(rest.RestController): 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)) + msg = _("PTP update failed: %s %s %s : patch %s" % + (ptp['mode'], ptp['transport'], ptp['mechanism'], patch)) raise wsme.exc.ClientSideError(msg) @wsme_pecan.wsexpose(None, types.uuid, status_code=204) diff --git a/sysinv/sysinv/sysinv/sysinv/common/constants.py b/sysinv/sysinv/sysinv/sysinv/common/constants.py index 02432355d0..ce7e3d2e11 100644 --- a/sysinv/sysinv/sysinv/sysinv/common/constants.py +++ b/sysinv/sysinv/sysinv/sysinv/common/constants.py @@ -1457,3 +1457,12 @@ DEFAULT_DNS_SERVICE_DOMAIN = 'cluster.local' ANSIBLE_BOOTSTRAP_FLAG = os.path.join(tsc.VOLATILE_PATH, ".ansible_bootstrap") UNLOCK_READY_FLAG = os.path.join(tsc.PLATFORM_CONF_PATH, ".unlock_ready") INVENTORY_WAIT_TIMEOUT_IN_SECS = 90 + +# Clock synchronization types +NTP = 'ntp' +PTP = 'ptp' + +CLOCK_SYNCHRONIZATION = [ + NTP, + PTP +] diff --git a/sysinv/sysinv/sysinv/sysinv/conductor/manager.py b/sysinv/sysinv/sysinv/sysinv/conductor/manager.py index f113c747df..dbd15c8012 100644 --- a/sysinv/sysinv/sysinv/sysinv/conductor/manager.py +++ b/sysinv/sysinv/sysinv/sysinv/conductor/manager.py @@ -1621,7 +1621,7 @@ class ConductorManager(service.PeriodicService): :param do_worker_apply: configure the worker subfunctions of the host. """ - LOG.debug("configure_ihost %s" % host.hostname) + LOG.info("configure_ihost %s" % host.hostname) # Generate system configuration files # TODO(mpeters): remove this once all system reconfigurations properly @@ -5385,14 +5385,17 @@ class ConductorManager(service.PeriodicService): config_uuid = self._config_update_hosts(context, personalities) self._update_resolv_file(context, config_uuid, personalities) - def update_ntp_config(self, context, service_change=False): + def update_clock_synchronization_config(self, context, host): + """Update clock_synchronization configuration of a host""" + personalities = [host.get('personality')] + self._config_update_hosts(context, personalities, [host.get('uuid')], + reboot=True) + + def update_ntp_config(self, context): """Update the NTP configuration""" - if service_change: - personalities = [constants.CONTROLLER, - constants.WORKER, - constants.STORAGE] - else: - personalities = [constants.CONTROLLER] + personalities = [constants.CONTROLLER, + constants.WORKER, + constants.STORAGE] self._config_update_hosts(context, personalities, reboot=True) def update_ptp_config(self, context): diff --git a/sysinv/sysinv/sysinv/sysinv/conductor/rpcapi.py b/sysinv/sysinv/sysinv/sysinv/conductor/rpcapi.py index e541c889bf..b71fe5aff2 100644 --- a/sysinv/sysinv/sysinv/sysinv/conductor/rpcapi.py +++ b/sysinv/sysinv/sysinv/sysinv/conductor/rpcapi.py @@ -726,13 +726,23 @@ class ConductorAPI(sysinv.openstack.common.rpc.proxy.RpcProxy): """ return self.call(context, self.make_msg('update_dns_config')) - def update_ntp_config(self, context, service_change=False): + def update_clock_synchronization_config(self, context, host): + """Synchronously, have the conductor update the + clock_synchronization configuration of a host. + + :param context: request context. + :param host: the host to be modified. + """ + return self.call(context, + self.make_msg('update_clock_synchronization_config', + host=host)) + + def update_ntp_config(self, context): """Synchronously, have the conductor update the NTP configuration. :param context: request context. """ - return self.call(context, self.make_msg('update_ntp_config', - service_change=service_change)) + return self.call(context, self.make_msg('update_ntp_config')) def update_ptp_config(self, context): """Synchronously, have the conductor update the PTP configuration. diff --git a/sysinv/sysinv/sysinv/sysinv/db/sqlalchemy/migrate_repo/versions/092_clock_synchronization.py b/sysinv/sysinv/sysinv/sysinv/db/sqlalchemy/migrate_repo/versions/092_clock_synchronization.py new file mode 100644 index 0000000000..9309719f23 --- /dev/null +++ b/sysinv/sysinv/sysinv/sysinv/db/sqlalchemy/migrate_repo/versions/092_clock_synchronization.py @@ -0,0 +1,43 @@ +# 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, Table +from sqlalchemy import String +from sysinv.common import constants + +ENGINE = 'InnoDB' +CHARSET = 'utf8' + + +def upgrade(migrate_engine): + """ + This database upgrade creates a new host clock_synchronization attribute + for storing the clock_synchronization type (ntp/ptp) for a host. + """ + + meta = MetaData() + meta.bind = migrate_engine + + host = Table('i_host', meta, autoload=True) + host.create_column(Column('clock_synchronization', String(32)), + default=constants.NTP) + + ntp = Table('i_ntp', meta, autoload=True) + ntp.drop_column(Column('enabled')) + + ptp = Table('ptp', meta, autoload=True) + ptp.drop_column(Column('enabled')) + + return True + + +def downgrade(migrate_engine): + meta = MetaData() + meta.bind = migrate_engine + + # Downgrade is unsupported. + raise NotImplementedError('SysInv database downgrade is unsupported.') diff --git a/sysinv/sysinv/sysinv/sysinv/db/sqlalchemy/models.py b/sysinv/sysinv/sysinv/sysinv/db/sqlalchemy/models.py index 075246e557..61e0b5f059 100644 --- a/sysinv/sysinv/sysinv/sysinv/db/sqlalchemy/models.py +++ b/sysinv/sysinv/sysinv/sysinv/db/sqlalchemy/models.py @@ -223,6 +223,8 @@ class ihost(Base): config_applied = Column(String(255)) config_target = Column(String(255)) + clock_synchronization = Column(String(32), default=constants.NTP) + boot_device = Column(String(255), default="/dev/sda") rootfs_device = Column(String(255), default="/dev/sda") install_output = Column(String(255), default="text") @@ -777,7 +779,6 @@ class intp(Base): id = Column(Integer, primary_key=True) uuid = Column(String(36)) - enabled = Column(Boolean, default=True) ntpservers = Column(String(255)) # csv list of ntp servers forisystemid = Column(Integer, @@ -792,13 +793,12 @@ class PTP(Base): 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')) + ForeignKey('i_system.id', ondelete='CASCADE')) system = relationship("isystem", lazy="joined", join_depth=1) diff --git a/sysinv/sysinv/sysinv/sysinv/objects/host.py b/sysinv/sysinv/sysinv/sysinv/objects/host.py index 8954096863..8d5d546442 100644 --- a/sysinv/sysinv/sysinv/sysinv/objects/host.py +++ b/sysinv/sysinv/sysinv/sysinv/objects/host.py @@ -1,5 +1,5 @@ # -# Copyright (c) 2013-2017 Wind River Systems, Inc. +# Copyright (c) 2013-2019 Wind River Systems, Inc. # # SPDX-License-Identifier: Apache-2.0 # @@ -75,6 +75,7 @@ class Host(base.SysinvObject): 'config_applied': utils.str_or_none, 'config_target': utils.str_or_none, 'capabilities': utils.dict_or_none, + 'clock_synchronization': utils.str_or_none, 'boot_device': utils.str_or_none, 'rootfs_device': utils.str_or_none, diff --git a/sysinv/sysinv/sysinv/sysinv/objects/ntp.py b/sysinv/sysinv/sysinv/sysinv/objects/ntp.py index fbba51a49f..b8d3c34ca2 100644 --- a/sysinv/sysinv/sysinv/sysinv/objects/ntp.py +++ b/sysinv/sysinv/sysinv/sysinv/objects/ntp.py @@ -1,5 +1,5 @@ # -# Copyright (c) 2013-2016 Wind River Systems, Inc. +# Copyright (c) 2013-2019 Wind River Systems, Inc. # # SPDX-License-Identifier: Apache-2.0 # @@ -21,7 +21,6 @@ class NTP(base.SysinvObject): 'id': int, 'uuid': utils.str_or_none, - 'enabled': utils.bool_or_none, 'ntpservers': utils.str_or_none, 'forisystemid': utils.int_or_none, diff --git a/sysinv/sysinv/sysinv/sysinv/objects/ptp.py b/sysinv/sysinv/sysinv/sysinv/objects/ptp.py index f0c8c51228..78d4568e4f 100644 --- a/sysinv/sysinv/sysinv/sysinv/objects/ptp.py +++ b/sysinv/sysinv/sysinv/sysinv/objects/ptp.py @@ -1,6 +1,6 @@ ######################################################################## # -# Copyright (c) 2018 Wind River Systems, Inc. +# Copyright (c) 2018-2019 Wind River Systems, Inc. # # SPDX-License-Identifier: Apache-2.0 # @@ -19,7 +19,6 @@ class PTP(base.SysinvObject): '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, diff --git a/sysinv/sysinv/sysinv/sysinv/puppet/platform.py b/sysinv/sysinv/sysinv/sysinv/puppet/platform.py index d0288b6b18..b803d2cec7 100644 --- a/sysinv/sysinv/sysinv/sysinv/puppet/platform.py +++ b/sysinv/sysinv/sysinv/sysinv/puppet/platform.py @@ -411,8 +411,13 @@ class PlatformPuppet(base.BasePuppet): else: ntpdate_timeout = "30" + if host.clock_synchronization == constants.NTP: + ntp_enabled = True + else: + ntp_enabled = False + return { - 'platform::ntp::enabled': ntp.enabled, + 'platform::ntp::enabled': ntp_enabled, 'platform::ntp::servers': servers, 'platform::ntp::ntpdate_timeout': ntpdate_timeout, } @@ -420,9 +425,14 @@ class PlatformPuppet(base.BasePuppet): def _get_host_ptp_config(self, host): ptp = self.dbapi.ptp_get_one() + if host.clock_synchronization == constants.PTP: + ptp_enabled = True + else: + ptp_enabled = False + return { 'platform::ptp::enabled': - ptp.enabled, + ptp_enabled, 'platform::ptp::mode': ptp.mode, 'platform::ptp::transport': diff --git a/sysinv/sysinv/sysinv/sysinv/tests/api/test_host.py b/sysinv/sysinv/sysinv/sysinv/tests/api/test_host.py index 6617b5d5ce..f4dd427245 100644 --- a/sysinv/sysinv/sysinv/sysinv/tests/api/test_host.py +++ b/sysinv/sysinv/sysinv/sysinv/tests/api/test_host.py @@ -33,6 +33,7 @@ class FakeConductorAPI(object): self.delete_barbican_secret = mock.MagicMock() self.iplatform_update_by_ihost = mock.MagicMock() self.evaluate_app_reapply = mock.MagicMock() + self.update_clock_synchronization_config = mock.MagicMock() def create_ihost(self, context, values): # Create the host in the DB as the code under test expects this @@ -691,6 +692,38 @@ class TestPatch(TestHost): result = self.get_json('/ihosts/%s' % ndict['hostname']) self.assertEqual(new_location, result['location']) + def test_update_clock_synchronization(self): + # Create controller-0 + ndict = dbutils.post_get_test_ihost(hostname='controller-0', + mgmt_ip=None, + serialid='serial1') + self.post_json('/ihosts', ndict, + headers={'User-Agent': 'sysinv-test'}) + + # Update clock_synchronization + + response = self.patch_json('/ihosts/%s' % ndict['hostname'], + [{'path': '/clock_synchronization', + 'value': constants.PTP, + 'op': 'replace'}], + headers={'User-Agent': 'sysinv-test'}) + self.assertEqual(response.content_type, 'application/json') + self.assertEqual(response.status_code, http_client.OK) + + # Verify that the host was configured + self.fake_conductor_api.configure_ihost.assert_called_once() + + # Verify that the app reapply was checked + self.fake_conductor_api.evaluate_app_reapply.assert_not_called() + + # Verify that update_clock_synchronization_config was called + self.fake_conductor_api.update_clock_synchronization_config.\ + assert_called_once() + + # Verify that the host was updated with the new clock_synchronization + result = self.get_json('/ihosts/%s' % ndict['hostname']) + self.assertEqual(constants.PTP, result['clock_synchronization']) + def test_unlock_action_controller(self): self._configure_networks() # Create controller-0 @@ -768,7 +801,7 @@ class TestPatch(TestHost): administrative=constants.ADMIN_LOCKED, operational=constants.OPERATIONAL_ENABLED, availability=constants.AVAILABILITY_ONLINE, - inv_state=None) + inv_state=None, clock_synchronization=constants.NTP) # Unlock host response = self._patch_host_action(c0_host['hostname'], diff --git a/sysinv/sysinv/sysinv/sysinv/tests/db/utils.py b/sysinv/sysinv/sysinv/sysinv/tests/db/utils.py index 904ecf1a21..a8fb722a48 100644 --- a/sysinv/sysinv/sysinv/sysinv/tests/db/utils.py +++ b/sysinv/sysinv/sysinv/sysinv/tests/db/utils.py @@ -15,7 +15,7 @@ # License for the specific language governing permissions and limitations # under the License. # -# Copyright (c) 2013-2018 Wind River Systems, Inc. +# Copyright (c) 2013-2019 Wind River Systems, Inc. # """Sysinv test utilities.""" @@ -162,6 +162,7 @@ def get_test_ihost(**kw): 'install_state_info': kw.get('install_state_info', None), 'iscsi_initiator_name': kw.get('iscsi_initiator_name', None), 'inv_state': kw.get('inv_state', 'inventoried'), + 'clock_synchronization': kw.get('clock_synchronization', constants.NTP), } return inv