diff --git a/api-ref/source/api-ref-nfv-vim-v1.rst b/api-ref/source/api-ref-nfv-vim-v1.rst index a4bfa1b3..a86cb03a 100644 --- a/api-ref/source/api-ref-nfv-vim-v1.rst +++ b/api-ref/source/api-ref-nfv-vim-v1.rst @@ -118,6 +118,10 @@ forbidden (403), badMethod (405), overLimit (413), itemNotFound (404) { "href": "http://192.168.204.2:4545/orchestration/fw-update/", "rel": "fw-update" + }, + { + "href": "http://192.168.204.2:4545/orchestration/system-config-update/", + "rel": "system-config-update" } ] } @@ -223,6 +227,39 @@ forbidden (403), badMethod (405), overLimit (413), itemNotFound (404) This operation does not accept a request body. +********************************************************************************* +Lists information about all NFV VIM API orchestration system-config-update links +********************************************************************************* + +.. rest_method:: GET /api/orchestration/system-config-update + +**Normal response codes** + +200 + +**Error response codes** + +serviceUnavailable (503), badRequest (400), unauthorized (401), +forbidden (403), badMethod (405), overLimit (413), itemNotFound (404) + +:: + + { + "id": "system-config-update", + "links": [ + { + "href": "http://192.168.204.2:4545/orchestration/system-config-update/", + "rel": "self" + }, + { + "href": "http://192.168.204.2:4545/orchestration/system-config-update/strategy/", + "rel": "strategy" + } + ] + } + +This operation does not accept a request body. + --------------- Patch Strategy --------------- @@ -3095,3 +3132,652 @@ forbidden (403), badMethod (405), overLimit (413) } } +------------------------------ +System Config Update Strategy +------------------------------ + +System config update orchestration is done with a system config update +orchestration strategy, or plan, for the automated update procedure which +contains a number of parameters for customizing the particular behavior of the +system config update orchestration. + +*************************************************************************** +Shows detailed information about the current system-config-update strategy +*************************************************************************** + +.. rest_method:: GET /api/orchestration/system-config-update/strategy + +**Normal response codes** + +200 + +**Error response codes** + +serviceUnavailable (503), badRequest (400), unauthorized (401), +forbidden (403), badMethod (405), overLimit (413), itemNotFound (404) + +:: + + { + "strategy": { + "controller-apply-type": "serial", + "swift-apply-type": "ignore", + "storage-apply-type": "serial", + "worker-apply-type": "parallel", + "state": "ready-to-apply", + "default-instance-action": "stop-start", + "max-parallel-worker-hosts": 4, + "alarm-restrictions": "strict", + "current-phase-completion-percentage": 100, + "uuid": "5dd16d94-dfc5-4029-bfcb-d815e7c2dc3d", + "name": "system-config-update", + "current-phase": "build", + "build-phase": { + "phase-name": "build", + "current-stage": 1, + "total-stages": 1, + "completion-percentage": 100, + "start-date-time": "", + "end-date-time": "", + "stop-at-stage": 1, + "result": "success", + "timeout": 182, + "reason": "", + "inprogress": false, + "stages": [ + { + "stage-id": 0, + "total-steps": 3, + "stage-name": "system-config-update-hosts-query", + "result": "success", + "timeout": 181, + "inprogress": false, + "start-date-time": "", + "end-date-time": "", + "reason": "", + "current-step" : 3, + "steps":[ + { + "step-id": 0, + "step-name": "query-alarms", + "entity-type": "", + "entity-names": [], + "entity-uuids": [], + "start-date-time": "", + "end-date-time": "", + "timeout": 60, + "result": "success", + "reason": "" + }, + { + "step-id": 1, + "step-name": "query-strategy-required", + "entity-type": "", + "entity-names": [], + "entity-uuids": [], + "start-date-time": "", + "end-date-time": "", + "timeout": 60, + "result": "success", + "reason": "" + }, + { + "step-id": 2, + "step-name": "query-in-sync", + "entity-type": "", + "entity-names": [], + "entity-uuids": [], + "start-date-time": "", + "end-date-time": "", + "timeout": 60, + "result": "success", + "reason": "" + } + ] + } + ] + }, + "apply-phase": { + "phase-name": "apply", + "current-stage": 0, + "completion-percentage": 100, + "total-stages": 2, + "stop-at-stage": 0, + "start-date-time": "", + "end-date-time": "", + "result": "initial", + "timeout": 0, + "reason": "", + "inprogress": false, + "stages": [ + { + "stage-id": 0, + "stage-name": "system-config-update-controllers", + "start-date-time": "", + "end-date-time": "", + "current-step": 0, + "result": "initial", + "timeout": 6436, + "inprogress": false, + "reason": "", + "total-steps": 6, + "steps": [ + { + "step-id": 0, + "step-name": "query-alarms", + "entity-type": "", + "entity-names": [], + "entity-uuids": [], + "start-date-time": "", + "end-date-time": "", + "timeout": 60, + "result": "initial", + "reason": "" + }, + { + "start-date-time": "", + "end-date-time": "", + "timeout": 900, + "entity-type": "hosts", + "step-id": 1, + "entity-uuids": [ + "523cbd2d-f7f8-4707-8617-d085386f8711" + ], + "step-name": "swact-hosts", + "result": "initial", + "entity-names": [ + "controller-1" + ], + "reason": "" + }, + { + "start-date-time": "", + "end-date-time": "", + "timeout": 900, + "entity-type": "hosts", + "step-id": 2, + "entity-uuids": [ + "523cbd2d-f7f8-4707-8617-d085386f8711" + ], + "step-name": "lock-hosts", + "result": "initial", + "entity-names": [ + "controller-1" + ], + "reason": "" + }, + { + "start-date-time": "", + "end-date-time": "", + "timeout": 1800, + "entity-type": "hosts", + "step-id": 3, + "entity-uuids": [ + "523cbd2d-f7f8-4707-8617-d085386f8711" + ], + "step-name": "config-disabled-host", + "result": "initial", + "entity-names": [ + "controller-1" + ], + "reason": "" + }, + { + "start-date-time": "", + "end-date-time": "", + "timeout": 900, + "entity-type": "hosts", + "step-id": 4, + "entity-uuids": [ + "523cbd2d-f7f8-4707-8617-d085386f8711" + ], + "step-name": "unlock-hosts", + "result": "initial", + "entity-names": [ + "controller-1" + ], + "reason": "" + }, + { + "step-id": 5, + "entity-type": "", + "step-name": "system-stabilize", + "entity-names": [], + "entity-uuids": [], + "start-date-time": "", + "end-date-time": "", + "timeout": 60, + "result": "initial", + "reason": "" + } + ], + }, + { + "stage-id": 1, + "total-steps": 6, + "stage-name": "system-config-update-controllers", + "inprogress": false, + "start-date-time": "", + "end-date-time": "", + "timeout": 6436, + "reason": "", + "result": "initial", + "current-step": 0, + "steps":[ + { + "step-id": 0, + "step-name": "query-alarms", + "entity-type": "", + "entity-names": [], + "entity-uuids": [], + "start-date-time": "", + "end-date-time": "", + "timeout": 60, + "result": "initial", + "reason": "" + }, + { + "start-date-time": "", + "end-date-time": "", + "timeout": 900, + "entity-type": "hosts", + "step-id": 1, + "entity-uuids": [ + "0f3715c0-fecd-46e0-9cd0-4fbb31810393" + ], + "step-name": "swact-hosts", + "result": "initial", + "entity-names": [ + "controller-0" + ], + "reason": "" + }, + { + "start-date-time": "", + "end-date-time": "", + "timeout": 900, + "entity-type": "hosts", + "step-id": 2, + "entity-uuids": [ + "0f3715c0-fecd-46e0-9cd0-4fbb31810393" + ], + "step-name": "lock-hosts", + "result": "initial", + "entity-names": [ + "controller-0" + ], + "reason": "" + }, + { + "start-date-time": "", + "end-date-time": "", + "timeout": 1800, + "entity-type": "hosts", + "step-id": 3, + "entity-uuids": [ + "0f3715c0-fecd-46e0-9cd0-4fbb31810393" + ], + "step-name": "config-disabled-host", + "result": "initial", + "entity-names": [ + "controller-0" + ], + "reason": "" + }, + { + "start-date-time": "", + "end-date-time": "", + "timeout": 900, + "entity-type": "hosts", + "step-id": 4, + "entity-uuids": [ + "0f3715c0-fecd-46e0-9cd0-4fbb31810393" + ], + "step-name": "unlock-hosts", + "result": "initial", + "entity-names": [ + "controller-0" + ], + "reason": "" + }, + { + "step-id": 5, + "entity-type": "", + "step-name": "system-stabilize", + "entity-names": [], + "entity-uuids": [], + "start-date-time": "", + "end-date-time": "", + "timeout": 60, + "result": "initial", + "reason": "" + } + ], + } + ], + }, + "abort-phase": { + "phase-name": "abort", + "total-stages": 0, + "completion-percentage": 100, + "start-date-time": "", + "end-date-time": "", + "stop-at-stage": 0, + "result": "initial", + "timeout": 0, + "reason": "", + "inprogress": false, + "stages": [], + "current-stage": 0 + } + } + } + +This operation does not accept a request body. + +**************************************** +Creates a system-config-update strategy +**************************************** + +.. rest_method:: POST /api/orchestration/system-config-update/strategy + +**Normal response codes** + +200 + +**Error response codes** + +serviceUnavailable (503), badRequest (400), unauthorized (401), +forbidden (403), badMethod (405), overLimit (413) + +**Request parameters** + +.. csv-table:: + :header: "Parameter", "Style", "Type", "Description" + :widths: 20, 20, 20, 60 + + "controller-apply-type", "plain", "xsd:string", "The apply type for controller hosts: ``serial`` or ``ignore``." + "storage-apply-type", "plain", "xsd:string", "The apply type for storage hosts: ``serial`` or ``ignore``." + "worker-apply-type", "plain", "xsd:string", "The apply type for worker hosts: ``serial``, ``parallel`` or ``ignore``." + "max-parallel-worker-hosts (Optional)", "plain", "xsd:integer", "The maximum number of worker hosts to patch in parallel; only applicable if ``worker-apply-type = parallel``. Default value is ``2``." + "default-instance-action", "plain", "xsd:string", "The default instance action: ``stop-start`` or ``migrate``." + "alarm-restrictions (Optional)", "plain", "xsd:string", "The strictness of alarm checks: ``strict`` or ``relaxed``." + +:: + + { + "controller-apply-type": "serial", + "storage-apply-type": "ignore", + "worker-apply-type": "serial", + "default-instance-action": "stop-start", + "alarm-restrictions": "strict", + } + +:: + + { + "strategy": { + "name": "system-config-update", + "worker-apply-type": "serial", + "controller-apply-type": "serial", + "swift-apply-type": "ignore", + "storage-apply-type": "ignore", + "current-phase-completion-percentage": 0, + "uuid": "447c4267-0ecb-48f4-9237-1d747a3e7cca", + "default-instance-action": "stop-start", + "max-parallel-worker-hosts": 2, + "alarm-restrictions": "strict", + "state": "building", + "build-phase": { + "phase-name": "build", + "current-stage": 0, + "start-date-time": "", + "end-date-time": "", + "completion-percentage": 0, + "stop-at-stage": 3, + "result": "inprogress", + "timeout": 182, + "reason": "", + "inprogress": true, + "total-stages": 3, + "stages": [ + { + "stage-id": 0, + "stage-name": "system-config-update-query", + "total-steps": 3, + "inprogress": true, + "start-date-time": "", + "end-date-time": "", + "reason": "", + "current-step": 0, + "result": "inprogress", + "timeout": 181, + "steps": [ + { + "step-id": 0, + "step-name": "query-alarms", + "entity-type": "", + "entity-names": [], + "entity-uuids": [], + "start-date-time": "", + "end-date-time": "", + "timeout": 60, + "result": "success", + "reason": "" + }, + { + "step-id": 1, + "step-name": "query-strategy-required", + "entity-type": "", + "entity-names": [], + "entity-uuids": [], + "start-date-time": "", + "end-date-time": "", + "timeout": 60, + "result": "success", + "reason": "" + }, + { + "step-id": 2, + "step-name": "query-in-sync", + "entity-type": "", + "entity-names": [], + "entity-uuids": [], + "start-date-time": "", + "end-date-time": "", + "timeout": 60, + "result": "success", + "reason": "" + } + ], + } + ], + }, + "apply-phase": { + "start-date-time": "", + "end-date-time": "", + "phase-name": "apply", + "completion-percentage": 100, + "total-stages": 0, + "stop-at-stage": 0, + "result": "initial", + "timeout": 0, + "reason": "", + "inprogress": false, + "stages": [], + "current-stage": 0 + }, + "abort-phase": { + "start-date-time": "", + "end-date-time": "", + "phase-name": "abort", + "completion-percentage": 100, + "total-stages": 0, + "stop-at-stage": 0, + "result": "initial", + "timeout": 0, + "reason": "", + "inprogress":false, + "stages": [], + "current-stage": 0 + } + } + } + +************************************************** +Deletes the current system-config-update strategy +************************************************** + +.. rest_method:: DELETE /api/orchestration/system-config-update/strategy + +**Normal response codes** + +204 + +:: + + { + } + +************************************************** +Applies or aborts a system-config-update strategy +************************************************** + +.. rest_method:: POST /api/orchestration/system-config-update/strategy/actions + +**Normal response codes** + +202 + +**Error response codes** + +serviceUnavailable (503), badRequest (400), unauthorized (401), +forbidden (403), badMethod (405), overLimit (413) + +**Request parameters** + +.. csv-table:: + :header: "Parameter", "Style", "Type", "Description" + :widths: 20, 20, 20, 60 + + "action", "plain", "xsd:string", "The action to take: ``apply-all``, ``apply-stage``, ``abort`` or ``abort-stage``." + "stage-id (Optional)", "plain", "xsd:string", "The stage-id to apply or abort. Only used with ``apply-stage`` or ``abort-stage`` actions." + +:: + + { + "action": "apply-all" + } + +:: + + { + "strategy":{ + "controller-apply-type": "serial", + "swift-apply-type": "ignore", + "current-phase-completion-percentage": 0, + "uuid": "447c4267-0ecb-48f4-9237-1d747a3e7cca", + "name": "system-config-update", + "current-phase": "build", + "storage-apply-type": "ignore", + "state":"building", + "worker-apply-type": "serial", + "default-instance-action": "stop-start", + "max-parallel-worker-hosts": 2, + "alarm-restrictions": "strict", + "build-phase": { + "phase-name": "build", + "current-stage": 0, + "start-date-time": "", + "end-date-time": "", + "completion-percentage": 0, + "stop-at-stage": 3, + "result": "inprogress", + "timeout": 182, + "reason": "", + "inprogress": true, + "total-stages": 3, + "stages": [ + { + "stage-id": 0, + "stage-name": "system-config-update-query", + "total-steps": 3, + "inprogress": true, + "start-date-time": "", + "end-date-time": "", + "reason": "", + "current-step": 0, + "result": "inprogress", + "timeout": 181, + "steps": [ + { + "step-id": 0, + "step-name": "query-alarms", + "entity-type": "", + "entity-names": [], + "entity-uuids": [], + "start-date-time": "", + "end-date-time": "", + "timeout": 60, + "result": "success", + "reason": "" + }, + { + "step-id": 1, + "step-name": "query-strategy-required", + "entity-type": "", + "entity-names": [], + "entity-uuids": [], + "start-date-time": "", + "end-date-time": "", + "timeout": 60, + "result": "success", + "reason": "" + }, + { + "step-id": 2, + "step-name": "query-in-sync", + "entity-type": "", + "entity-names": [], + "entity-uuids": [], + "start-date-time": "", + "end-date-time": "", + "timeout": 60, + "result": "success", + "reason": "" + } + ], + } + ], + }, + "apply-phase": { + "start-date-time": "", + "end-date-time": "", + "phase-name": "apply", + "completion-percentage": 100, + "total-stages": 0, + "stop-at-stage": 0, + "result": "initial", + "timeout": 0, + "reason": "", + "inprogress": false, + "stages": [], + "current-stage": 0 + }, + "abort-phase": { + "start-date-time": "", + "end-date-time": "", + "phase-name": "abort", + "completion-percentage": 100, + "total-stages": 0, + "stop-at-stage": 0, + "result": "initial", + "timeout": 0, + "reason": "", + "inprogress": false, + "stages": [], + "current-stage": 0 + } + } + } + diff --git a/nfv/nfv-client/nfv_client/openstack/sw_update.py b/nfv/nfv-client/nfv_client/openstack/sw_update.py index 83f23e85..0f912975 100755 --- a/nfv/nfv-client/nfv_client/openstack/sw_update.py +++ b/nfv/nfv-client/nfv_client/openstack/sw_update.py @@ -1,5 +1,5 @@ # -# Copyright (c) 2016-2021 Wind River Systems, Inc. +# Copyright (c) 2016-2023 Wind River Systems, Inc. # # SPDX-License-Identifier: Apache-2.0 # @@ -279,6 +279,9 @@ def create_strategy(token_id, # required: 'to_version' passed to strategy as 'to-version' api_cmd_payload['to-version'] = kwargs['to_version'] api_cmd_payload['default-instance-action'] = default_instance_action + elif 'system-config-update' == strategy_name: + api_cmd_payload['controller-apply-type'] = controller_apply_type + api_cmd_payload['default-instance-action'] = default_instance_action elif 'sw-upgrade' == strategy_name: # for upgrade: default-instance-action is hardcoded to MIGRATE if 'start_upgrade' in kwargs and kwargs['start_upgrade']: diff --git a/nfv/nfv-client/nfv_client/shell.py b/nfv/nfv-client/nfv_client/shell.py index 9cf3d4e2..281aca23 100755 --- a/nfv/nfv-client/nfv_client/shell.py +++ b/nfv/nfv-client/nfv_client/shell.py @@ -1,5 +1,5 @@ # -# Copyright (c) 2016-2021 Wind River Systems, Inc. +# Copyright (c) 2016-2023 Wind River Systems, Inc. # # SPDX-License-Identifier: Apache-2.0 # @@ -56,6 +56,9 @@ def get_extra_create_args(cmd_area, args): elif 'fw-update-strategy' == cmd_area: # no additional kwargs for firmware update return {} + elif 'system-config-update-strategy' == cmd_area: + # no additional kwargs for system config update + return {} elif 'kube-rootca-update-strategy' == cmd_area: # kube rootca update supports expiry_date, subject and cert_file return { @@ -384,6 +387,48 @@ def setup_patch_parser(commands): _ = setup_show_cmd(sub_cmds) +def setup_system_config_update_parser(commands): + """System config update Strategy Commands""" + + cmd_area = 'system-config-update-strategy' + register_strategy(cmd_area, sw_update.STRATEGY_NAME_SYSTEM_CONFIG_UPDATE) + cmd_parser = commands.add_parser(cmd_area, + help='system config update Strategy') + cmd_parser.set_defaults(cmd_area=cmd_area) + + sub_cmds = cmd_parser.add_subparsers(title='Sytem Config Update Commands', + metavar='') + sub_cmds.required = True + + # define the create command + # alarm restrictions, defaults to strict + _ = setup_create_cmd( + sub_cmds, + [sw_update.APPLY_TYPE_SERIAL, # controller supports serial only + sw_update.APPLY_TYPE_IGNORE], + [sw_update.APPLY_TYPE_SERIAL, # storage supports serial only + sw_update.APPLY_TYPE_IGNORE], + [sw_update.APPLY_TYPE_SERIAL, # worker supports serial and parallel + sw_update.APPLY_TYPE_PARALLEL, + sw_update.APPLY_TYPE_IGNORE], + [sw_update.INSTANCE_ACTION_STOP_START, # instance actions + sw_update.INSTANCE_ACTION_MIGRATE], + [sw_update.ALARM_RESTRICTIONS_STRICT, # alarm restrictions + sw_update.ALARM_RESTRICTIONS_RELAXED], + min_parallel=2, + max_parallel=100 # config update supports 2..100 workers in parallel + ) + + # define the delete command + _ = setup_delete_cmd(sub_cmds) + # define the apply command + _ = setup_apply_cmd(sub_cmds) + # define the abort command + _ = setup_abort_cmd(sub_cmds) + # define the show command + _ = setup_show_cmd(sub_cmds) + + def setup_upgrade_parser(commands): """Upgrade Strategy Commands""" @@ -468,6 +513,9 @@ def process_main(argv=sys.argv[1:]): # pylint: disable=dangerous-default-value # Add software patch strategy commands setup_patch_parser(commands) + # Add system config update strategy commands + setup_system_config_update_parser(commands) + # Add software upgrade strategy commands setup_upgrade_parser(commands) diff --git a/nfv/nfv-client/nfv_client/sw_update/__init__.py b/nfv/nfv-client/nfv_client/sw_update/__init__.py index 5a3cba2d..44c06c3e 100755 --- a/nfv/nfv-client/nfv_client/sw_update/__init__.py +++ b/nfv/nfv-client/nfv_client/sw_update/__init__.py @@ -1,4 +1,4 @@ -# Copyright (c) 2016-2021 Wind River Systems, Inc. +# Copyright (c) 2016-2023 Wind River Systems, Inc. # # SPDX-License-Identifier: Apache-2.0 # @@ -19,3 +19,4 @@ from nfv_client.sw_update._sw_update import STRATEGY_NAME_KUBE_ROOTCA_UPDATE # from nfv_client.sw_update._sw_update import STRATEGY_NAME_KUBE_UPGRADE # noqa: F401 from nfv_client.sw_update._sw_update import STRATEGY_NAME_SW_PATCH # noqa: F401 from nfv_client.sw_update._sw_update import STRATEGY_NAME_SW_UPGRADE # noqa: F401 +from nfv_client.sw_update._sw_update import STRATEGY_NAME_SYSTEM_CONFIG_UPDATE # noqa: F401 diff --git a/nfv/nfv-client/nfv_client/sw_update/_sw_update.py b/nfv/nfv-client/nfv_client/sw_update/_sw_update.py index 400bea12..398d9757 100755 --- a/nfv/nfv-client/nfv_client/sw_update/_sw_update.py +++ b/nfv/nfv-client/nfv_client/sw_update/_sw_update.py @@ -1,5 +1,5 @@ # -# Copyright (c) 2016-2022 Wind River Systems, Inc. +# Copyright (c) 2016-2023 Wind River Systems, Inc. # # SPDX-License-Identifier: Apache-2.0 # @@ -11,6 +11,7 @@ STRATEGY_NAME_SW_UPGRADE = 'sw-upgrade' STRATEGY_NAME_FW_UPDATE = 'fw-update' STRATEGY_NAME_KUBE_ROOTCA_UPDATE = 'kube-rootca-update' STRATEGY_NAME_KUBE_UPGRADE = 'kube-upgrade' +STRATEGY_NAME_SYSTEM_CONFIG_UPDATE = 'system-config-update' APPLY_TYPE_SERIAL = 'serial' APPLY_TYPE_PARALLEL = 'parallel' @@ -130,6 +131,8 @@ def _display_strategy(strategy, details=False, active=False): print("Strategy Kubernetes RootCA Update Strategy:") elif strategy.name == STRATEGY_NAME_KUBE_UPGRADE: print("Strategy Kubernetes Upgrade Strategy:") + elif strategy.name == STRATEGY_NAME_SYSTEM_CONFIG_UPDATE: + print("Strategy System Config Upgrade Strategy:") else: print("Strategy Unknown Strategy:") diff --git a/nfv/nfv-client/scripts/sw-manager.completion b/nfv/nfv-client/scripts/sw-manager.completion index 9a71a8d3..60add5a6 100755 --- a/nfv/nfv-client/scripts/sw-manager.completion +++ b/nfv/nfv-client/scripts/sw-manager.completion @@ -1,5 +1,5 @@ # -# Copyright (c) 2016-2021 Wind River Systems, Inc. +# Copyright (c) 2016-2023 Wind River Systems, Inc. # # SPDX-License-Identifier: Apache-2.0 # @@ -24,6 +24,7 @@ function _swmanager() fw-update-strategy kube-rootca-update-strategy kube-upgrade-strategy + system-config-update-strategy " if [ $COMP_CWORD -gt 1 ]; then @@ -252,6 +253,83 @@ function _swmanager() esac fi + # Provide actions for completion + COMPREPLY=($(compgen -W "${actions}" -- ${cur})) + return 0 + ;; + system-config-update-strategy) + local actions=" + create + delete + apply + abort + show + " + if [ $COMP_CWORD -gt 2 ]; then + local action=${COMP_WORDS[2]} + # + # Complete the arguments for each action + # + case "$action" in + create) + local createopts=" + --controller-apply-type + --storage-apply-type + --worker-apply-type + --max-parallel-worker-hosts + --instance-action + --alarm-restrictions + " + local createopt=${prev} + case "$createopt" in + --controller-apply-type|--storage-apply-type) + COMPREPLY=($(compgen -W "serial ignore" -- ${cur})) + return 0 + ;; + --worker-apply-type) + COMPREPLY=($(compgen -W "serial parallel ignore" -- ${cur})) + return 0 + ;; + --max-parallel-worker-hosts) + COMPREPLY=( $(compgen -- ${cur})) + return 0 + ;; + --instance-action) + COMPREPLY=($(compgen -W "migrate stop-start" -- ${cur})) + return 0 + ;; + --alarm-restrictions) + COMPREPLY=($(compgen -W "strict relaxed" -- ${cur})) + return 0 + ;; + *) + ;; + esac + COMPREPLY=($(compgen -W "${createopts}" -- ${cur})) + return 0 + ;; + apply|abort) + if [ "${prev}" = "${action}" ]; then + COMPREPLY=($(compgen -W "--stage-id" -- ${cur})) + fi + return 0 + ;; + show) + if [ "${prev}" = "${action}" ]; then + COMPREPLY=($(compgen -W "--details --active" -- ${cur})) + fi + return 0 + ;; + delete) + # These subcommands have no options/arguments + COMPREPLY=( $(compgen -- ${cur}) ) + return 0 + ;; + *) + ;; + esac + fi + # Provide actions for completion COMPREPLY=($(compgen -W "${actions}" -- ${cur})) return 0 diff --git a/nfv/nfv-common/nfv_common/alarm/objects/v1/_alarm_defs.py b/nfv/nfv-common/nfv_common/alarm/objects/v1/_alarm_defs.py index 2375f413..35e06ea5 100755 --- a/nfv/nfv-common/nfv_common/alarm/objects/v1/_alarm_defs.py +++ b/nfv/nfv-common/nfv_common/alarm/objects/v1/_alarm_defs.py @@ -1,5 +1,5 @@ # -# Copyright (c) 2015-2021 Wind River Systems, Inc. +# Copyright (c) 2015-2023 Wind River Systems, Inc. # # SPDX-License-Identifier: Apache-2.0 # @@ -50,6 +50,9 @@ class _AlarmType(Constants): KUBE_UPGRADE_AUTO_APPLY_INPROGRESS = Constant('kube-upgrade-auto-apply-inprogress') KUBE_UPGRADE_AUTO_APPLY_ABORTING = Constant('kube-upgrade-auto-apply-aborting') KUBE_UPGRADE_AUTO_APPLY_FAILED = Constant('kube-upgrade-auto-apply-failed') + SYSTEM_CONFIG_UPDATE_AUTO_APPLY_INPROGRESS = Constant('system-config-update-auto-apply-inprogress') + SYSTEM_CONFIG_UPDATE_AUTO_APPLY_ABORTING = Constant('system-config-update-auto-apply-aborting') + SYSTEM_CONFIG_UPDATE_AUTO_APPLY_FAILED = Constant('system-config-update-auto-apply-failed') @six.add_metaclass(Singleton) diff --git a/nfv/nfv-common/nfv_common/event_log/objects/v1/_event_log_defs.py b/nfv/nfv-common/nfv_common/event_log/objects/v1/_event_log_defs.py index ad310f1f..d60a0be5 100755 --- a/nfv/nfv-common/nfv_common/event_log/objects/v1/_event_log_defs.py +++ b/nfv/nfv-common/nfv_common/event_log/objects/v1/_event_log_defs.py @@ -1,5 +1,5 @@ # -# Copyright (c) 2015-2021 Wind River Systems, Inc. +# Copyright (c) 2015-2023 Wind River Systems, Inc. # # SPDX-License-Identifier: Apache-2.0 # @@ -196,6 +196,18 @@ class _EventId(Constants): KUBE_UPGRADE_AUTO_APPLY_ABORT_FAILED = Constant('kube-upgrade-auto-apply-abort-failed') KUBE_UPGRADE_AUTO_APPLY_ABORTED = Constant('kube-upgrade-auto-apply-aborted') + SYSTEM_CONFIG_UPDATE_AUTO_APPLY_START = Constant('system-config-update-auto-apply-started') + SYSTEM_CONFIG_UPDATE_AUTO_APPLY_INPROGRESS = Constant('system-config-update-auto-apply-inprogress') + SYSTEM_CONFIG_UPDATE_AUTO_APPLY_REJECTED = Constant('system-config-update-auto-apply-rejected') + SYSTEM_CONFIG_UPDATE_AUTO_APPLY_CANCELLED = Constant('system-config-update-auto-apply-cancelled') + SYSTEM_CONFIG_UPDATE_AUTO_APPLY_FAILED = Constant('system-config-update-auto-apply-failed') + SYSTEM_CONFIG_UPDATE_AUTO_APPLY_COMPLETED = Constant('system-config-update-auto-apply-completed') + SYSTEM_CONFIG_UPDATE_AUTO_APPLY_ABORT = Constant('system-config-update-auto-apply-abort') + SYSTEM_CONFIG_UPDATE_AUTO_APPLY_ABORTING = Constant('system-config-update-auto-apply-aborting') + SYSTEM_CONFIG_UPDATE_AUTO_APPLY_ABORT_REJECTED = Constant('system-config-update-auto-apply-abort-rejected') + SYSTEM_CONFIG_UPDATE_AUTO_APPLY_ABORT_FAILED = Constant('system-config-update-auto-apply-abort-failed') + SYSTEM_CONFIG_UPDATE_AUTO_APPLY_ABORTED = Constant('system-config-update-auto-apply-aborted') + @six.add_metaclass(Singleton) class _EventType(Constants): diff --git a/nfv/nfv-plugins/nfv_plugins/alarm_handlers/fm.py b/nfv/nfv-plugins/nfv_plugins/alarm_handlers/fm.py index 63227008..61ea3ee8 100755 --- a/nfv/nfv-plugins/nfv_plugins/alarm_handlers/fm.py +++ b/nfv/nfv-plugins/nfv_plugins/alarm_handlers/fm.py @@ -97,6 +97,18 @@ _fm_kube_rootca_update_alarm_id_mapping = dict([ # Merge the kube rootca update mapping with the entire mapping _fm_alarm_id_mapping.update(_fm_kube_rootca_update_alarm_id_mapping) +_fm_system_config_update_alarm_id_mapping = dict([ + (alarm_objects_v1.ALARM_TYPE.SYSTEM_CONFIG_UPDATE_AUTO_APPLY_INPROGRESS, + fm_constants.FM_ALARM_ID_SYSTEM_CONFIG_UPDATE_AUTO_APPLY_INPROGRESS), + (alarm_objects_v1.ALARM_TYPE.SYSTEM_CONFIG_UPDATE_AUTO_APPLY_ABORTING, + fm_constants.FM_ALARM_ID_SYSTEM_CONFIG_UPDATE_AUTO_APPLY_ABORTING), + (alarm_objects_v1.ALARM_TYPE.SYSTEM_CONFIG_UPDATE_AUTO_APPLY_FAILED, + fm_constants.FM_ALARM_ID_SYSTEM_CONFIG_UPDATE_AUTO_APPLY_FAILED), +]) + +# Merge the system config update mapping with the entire mapping +_fm_alarm_id_mapping.update(_fm_system_config_update_alarm_id_mapping) + _fm_alarm_type_mapping = dict([ (alarm_objects_v1.ALARM_EVENT_TYPE.COMMUNICATIONS_ALARM, fm_constants.FM_ALARM_TYPE_1), diff --git a/nfv/nfv-plugins/nfv_plugins/event_log_handlers/fm.py b/nfv/nfv-plugins/nfv_plugins/event_log_handlers/fm.py index 27241f67..c043ce9e 100755 --- a/nfv/nfv-plugins/nfv_plugins/event_log_handlers/fm.py +++ b/nfv/nfv-plugins/nfv_plugins/event_log_handlers/fm.py @@ -386,6 +386,35 @@ _fm_kube_rootca_update_event_id_mapping = dict([ # Merge the kube rootca update mapping with the entire mapping _fm_event_id_mapping.update(_fm_kube_rootca_update_event_id_mapping) +# define system config update event mapping +_fm_system_config_update_event_id_mapping = dict([ + (event_log_objects_v1.EVENT_ID.SYSTEM_CONFIG_UPDATE_AUTO_APPLY_START, + fm_constants.FM_LOG_ID_SYSTEM_CONFIG_UPDATE_AUTO_APPLY_START), + (event_log_objects_v1.EVENT_ID.SYSTEM_CONFIG_UPDATE_AUTO_APPLY_INPROGRESS, + fm_constants.FM_LOG_ID_SYSTEM_CONFIG_UPDATE_AUTO_APPLY_INPROGRESS), + (event_log_objects_v1.EVENT_ID.SYSTEM_CONFIG_UPDATE_AUTO_APPLY_REJECTED, + fm_constants.FM_LOG_ID_SYSTEM_CONFIG_UPDATE_AUTO_APPLY_REJECTED), + (event_log_objects_v1.EVENT_ID.SYSTEM_CONFIG_UPDATE_AUTO_APPLY_CANCELLED, + fm_constants.FM_LOG_ID_SYSTEM_CONFIG_UPDATE_AUTO_APPLY_CANCELLED), + (event_log_objects_v1.EVENT_ID.SYSTEM_CONFIG_UPDATE_AUTO_APPLY_FAILED, + fm_constants.FM_LOG_ID_SYSTEM_CONFIG_UPDATE_AUTO_APPLY_FAILED), + (event_log_objects_v1.EVENT_ID.SYSTEM_CONFIG_UPDATE_AUTO_APPLY_COMPLETED, + fm_constants.FM_LOG_ID_SYSTEM_CONFIG_UPDATE_AUTO_APPLY_COMPLETED), + (event_log_objects_v1.EVENT_ID.SYSTEM_CONFIG_UPDATE_AUTO_APPLY_ABORT, + fm_constants.FM_LOG_ID_SYSTEM_CONFIG_UPDATE_AUTO_APPLY_ABORT), + (event_log_objects_v1.EVENT_ID.SYSTEM_CONFIG_UPDATE_AUTO_APPLY_ABORTING, + fm_constants.FM_LOG_ID_SYSTEM_CONFIG_UPDATE_AUTO_APPLY_ABORTING), + (event_log_objects_v1.EVENT_ID.SYSTEM_CONFIG_UPDATE_AUTO_APPLY_ABORT_REJECTED, + fm_constants.FM_LOG_ID_SYSTEM_CONFIG_UPDATE_AUTO_APPLY_ABORT_REJECTED), + (event_log_objects_v1.EVENT_ID.SYSTEM_CONFIG_UPDATE_AUTO_APPLY_ABORT_FAILED, + fm_constants.FM_LOG_ID_SYSTEM_CONFIG_UPDATE_AUTO_APPLY_ABORT_FAILED), + (event_log_objects_v1.EVENT_ID.SYSTEM_CONFIG_UPDATE_AUTO_APPLY_ABORTED, + fm_constants.FM_LOG_ID_SYSTEM_CONFIG_UPDATE_AUTO_APPLY_ABORTED), +]) + +# Merge the system config update mapping with the entire mapping +_fm_event_id_mapping.update(_fm_system_config_update_event_id_mapping) + _fm_event_type_mapping = dict([ (event_log_objects_v1.EVENT_TYPE.STATE_EVENT, fm_constants.FM_ALARM_TYPE_4), diff --git a/nfv/nfv-plugins/nfv_plugins/nfvi_plugins/clients/kubernetes_client.py b/nfv/nfv-plugins/nfv_plugins/nfvi_plugins/clients/kubernetes_client.py index f6b8927b..5e568874 100644 --- a/nfv/nfv-plugins/nfv_plugins/nfvi_plugins/clients/kubernetes_client.py +++ b/nfv/nfv-plugins/nfv_plugins/nfvi_plugins/clients/kubernetes_client.py @@ -1,5 +1,5 @@ # -# Copyright (c) 2018-2022 Wind River Systems, Inc. +# Copyright (c) 2018-2023 Wind River Systems, Inc. # # SPDX-License-Identifier: Apache-2.0 # @@ -55,6 +55,28 @@ def get_client(): return kubernetes.client.CoreV1Api() +def get_kubertnetes_https_client(): + """ + Get Kubernetes client with HTTPS enabled + """ + kubernetes.config.load_kube_config('/etc/kubernetes/admin.conf') + + if K8S_MODULE_MAJOR_VERSION < 12: + c = kubernetes.client.Configuration() + else: + c = kubernetes.client.Configuration().get_default_copy() + kubernetes.client.Configuration.set_default(c) + return kubernetes.client + + +def get_customobjects_api_instance(): + """ + Get a custom objects API instance + """ + client = get_kubertnetes_https_client() + return client.CustomObjectsApi() + + def taint_node(node_name, effect, key, value): """ Apply a taint to a node @@ -231,3 +253,132 @@ def get_terminating_pods(node_name): terminating_pods.append(pod.metadata.name) return Result(','.join(terminating_pods)) + + +def get_namespaced_custom_object(name, plural, group, version, namespace): + """ + Get a custom resource object in a namespace + """ + # Get a CustomObjectsApi instance + api_instance = get_customobjects_api_instance() + + try: + resource = api_instance.get_namespaced_custom_object( + group=group, + version=version, + name=name, + namespace=namespace, + plural=plural + ) + return Result(resource) + except ApiException as e: + DLOG.exception( + "Failed to get object %s from namespace %s, " + "reason: %s" % (name, namespace, e.reason)) + return None + + +def get_deployment_host(name): + """ + Get a host in the deployment namespace + """ + # Get a CustomObjectsApi instance + api_instance = get_customobjects_api_instance() + + try: + resource = api_instance.get_namespaced_custom_object( + group='starlingx.windriver.com', + version='v1', + name=name, + namespace='deployment', + plural='hosts' + ) + unlock_request = resource.get('status').get('strategyRequired') + result = {'name': name, 'unlock_request': unlock_request} + return Result(result) + except ApiException as e: + DLOG.exception( + "Failed to get object %s from namespace deployment, " + "reason: %s" % (name, e.reason)) + return None + + +def list_namespaced_custom_objects(plural, group, version, namespace): + """ + List custom resource objects in a namespace + """ + # Get a CustomObjectsApi instance + api_instance = get_customobjects_api_instance() + + try: + resources = api_instance.list_namespaced_custom_object( + group=group, + version=version, + namespace=namespace, + plural=plural + ) + return Result(resources) + except ApiException as e: + DLOG.exception( + "Failed to list objects %s from namespace %s, " + "reason: %s" % (plural, namespace, e.reason)) + return None + + +def list_deployment_hosts(): + """ + List hosts in a deployment namespace + """ + # Get a CustomObjectsApi instance + api_instance = get_customobjects_api_instance() + + try: + resources = api_instance.list_namespaced_custom_object( + group='starlingx.windriver.com', + version='v1', + namespace='deployment', + plural='hosts' + ) + + if not resources: + return None + + results = list() + for resource in resources.get('items'): + name = resource.get('metadata').get('name') + unlock_request = resource.get('status').get('strategyRequired') + results.append({'name': name, + 'unlock_request': unlock_request}) + + return Result(results) + except ApiException as e: + DLOG.exception( + "Failed to list hosts from deployment namespace, " + "reason: %s" % e.reason) + return None + + +def get_namespaced_running_pods(namespace, name): + """ + Get running pods in a namespace + """ + api_instance = get_client() + + try: + response = api_instance.list_namespaced_pod( + namespace=namespace, + field_selector="status.phase=Running",) + except ApiException as e: + DLOG.exception( + "Failed to list pods from namespace %s, " + "reason: %s" % (namespace, e.reason)) + return None + + pods = response.items + found = list() + if pods is not None: + for pod in pods: + if name in pod.metadata.name: + found.append(pod.metadata.name) + + return Result(','.join(found)) diff --git a/nfv/nfv-plugins/nfv_plugins/nfvi_plugins/nfvi_infrastructure_api.py b/nfv/nfv-plugins/nfv_plugins/nfvi_plugins/nfvi_infrastructure_api.py index b1313954..2ba51e8c 100755 --- a/nfv/nfv-plugins/nfv_plugins/nfvi_plugins/nfvi_infrastructure_api.py +++ b/nfv/nfv-plugins/nfv_plugins/nfvi_plugins/nfvi_infrastructure_api.py @@ -3806,6 +3806,118 @@ class NFVIInfrastructureAPI(nfvi.api.v1.NFVIInfrastructureAPI): callback.send(response) callback.close() + def get_deployment_host(self, future, host_name, callback): + """ + Get a host resource from the deployment namespace space + """ + response = dict() + response['completed'] = False + response['reason'] = '' + + try: + future.set_timeouts(config.CONF.get('nfvi-timeouts', None)) + + future.work(kubernetes_client.get_deployment_host, host_name) + future.result = (yield) + + if not future.result.is_complete(): + DLOG.error("Kubernetes get_deployment_host failed, operation " + "did not complete, host_name=%s" % host_name) + self.set_response_error(response, "Kubernetes get-deployment-host") + return + + response['result-data'] = future.result.data + response['completed'] = True + + except Exception as e: + DLOG.exception("Caught exception while trying to get " + "deployment hosts: %s, error=%s." % (host_name, e)) + + finally: + callback.send(response) + callback.close() + + def list_deployment_hosts(self, future, callback): + """ + List a hosts resource from the deployment namespace space + """ + response = dict() + response['completed'] = False + response['reason'] = '' + + try: + future.set_timeouts(config.CONF.get('nfvi-timeouts', None)) + + future.work(kubernetes_client.list_deployment_hosts) + future.result = (yield) + + if not future.result.is_complete() or future.result.data is None: + DLOG.error("Kubernetes list_deployment_hosts failed, operation " + "did not complete.") + self.set_response_error(response, "Kubernetes list-deployment-hosts") + return + + hosts = list() + for host_data in future.result.data: + host = nfvi.objects.v1.HostSystemConfigUpdate( + host_data['name'], host_data['unlock_request']) + hosts.append(host) + + response['result-data'] = hosts + response['completed'] = True + + except Exception as e: + DLOG.exception("Caught exception while trying to list " + "deployment hosts, error=%s." % (e)) + + finally: + callback.send(response) + callback.close() + + def get_system_config_unlock_request(self, future, host_names, callback): + """ + Get unlock request from host resource status + """ + response = dict() + response['completed'] = False + response['reason'] = '' + + try: + future.set_timeouts(config.CONF.get('nfvi-timeouts', None)) + result = list() + for host_name in host_names: + future.work(kubernetes_client.get_deployment_host, host_name) + future.result = (yield) + + if not future.result.is_complete(): + DLOG.error("Cannot get resource of host: %s from deployment " + "namespace." % host_name) + self.set_response_error(response, "Kubernetes get-deployment-host") + return + + if future.result.data['unlock_request'] != 'unlock_required': + # The host is not ready for unlock, do not update the reason + # as the the transitional status is expected. + DLOG.debug("Host: %s is not ready for unlock." % host_name) + return + + host_resource = nfvi.objects.v1.HostSystemConfigUpdate( + future.result.data['name'], + future.result.data['unlock_request'] + ) + result.append(host_resource) + + response['completed'] = True + response['result-data'] = result + + except Exception as e: + DLOG.exception("Caught exception while trying to check the unlock " + "requst from kubernetes deployment namespace %s, " + "error=%s." % (host_name, e)) + finally: + callback.send(response) + callback.close() + def sw_update_rest_api_get_handler(self, request_dispatch): """ Software update Rest-API GET handler callback diff --git a/nfv/nfv-tests/nfv_unit_tests/tests/test_nfv_client.py b/nfv/nfv-tests/nfv_unit_tests/tests/test_nfv_client.py index 6dec1eaa..8a96ae99 100755 --- a/nfv/nfv-tests/nfv_unit_tests/tests/test_nfv_client.py +++ b/nfv/nfv-tests/nfv_unit_tests/tests/test_nfv_client.py @@ -282,3 +282,10 @@ class TestCLIKubeUpgradeStrategy(TestNFVClientShell, def required_create_fields(self): """Kube Upgrade requires a to-version for create""" return ['--to-version=1.2.3'] + + +class TestSystemConfigUpdateStrategy(TestNFVClientShell, + StrategyMixin): + def setUp(self): + super(TestSystemConfigUpdateStrategy, self).setUp() + self.set_strategy('system-config-update-strategy') diff --git a/nfv/nfv-tests/nfv_unit_tests/tests/test_plugin_kubernetes_client.py b/nfv/nfv-tests/nfv_unit_tests/tests/test_plugin_kubernetes_client.py index 93624fc7..a98f3409 100755 --- a/nfv/nfv-tests/nfv_unit_tests/tests/test_plugin_kubernetes_client.py +++ b/nfv/nfv-tests/nfv_unit_tests/tests/test_plugin_kubernetes_client.py @@ -434,3 +434,67 @@ class TestNFVPluginsK8SGetTerminatingPods(testcase.NFVTestCase): assert result.result_data == \ 'test-pod-terminating,test-pod-terminating-2' + + +@mock.patch('kubernetes.config.load_kube_config', mock_load_kube_config) +class TestNFVPluginsK8SGetNamespacedRunningPods(testcase.NFVTestCase): + + list_namespaced_pod_result = kubernetes.client.V1PodList( + api_version="v1", + items=[ + kubernetes.client.V1Pod( + api_version="v1", + kind="Pod", + metadata=kubernetes.client.V1ObjectMeta( + name="test-pod-not-found-1", + namespace="test-namespace-1"), + ), + kubernetes.client.V1Pod( + api_version="v1", + kind="Pod", + metadata=kubernetes.client.V1ObjectMeta( + name="test-pod-not-found-2", + namespace="test-namespace-1"), + ), + kubernetes.client.V1Pod( + api_version="v1", + kind="Pod", + metadata=kubernetes.client.V1ObjectMeta( + name="test-pod-found-1", + namespace="test-namespace-1"), + ), + kubernetes.client.V1Pod( + api_version="v1", + kind="Pod", + metadata=kubernetes.client.V1ObjectMeta( + name="test-pod-found-2", + namespace="test-namespace-1"), + ), + ] + ) + + def setUp(self): + super(TestNFVPluginsK8SGetNamespacedRunningPods, self).setUp() + + def mock_list_namespaced_pod(obj, namespace, field_selector=""): + return self.list_namespaced_pod_result + + self.mocked_list_namespaced_pod = mock.patch( + 'kubernetes.client.CoreV1Api.list_namespaced_pod', + mock_list_namespaced_pod) + self.mocked_list_namespaced_pod.start() + + def tearDown(self): + super(TestNFVPluginsK8SGetNamespacedRunningPods, self).tearDown() + + self.mocked_list_namespaced_pod.stop() + + def test_get_terminating_with_two_terminating(self): + + result = kubernetes_client.get_namespaced_running_pods( + namespace="test-namespace-1", + name="test-pod-found" + ) + + assert result.result_data == \ + 'test-pod-found-1,test-pod-found-2' diff --git a/nfv/nfv-tests/nfv_unit_tests/tests/test_system_config_update_strategy.py b/nfv/nfv-tests/nfv_unit_tests/tests/test_system_config_update_strategy.py new file mode 100644 index 00000000..8abdae61 --- /dev/null +++ b/nfv/nfv-tests/nfv_unit_tests/tests/test_system_config_update_strategy.py @@ -0,0 +1,790 @@ +# +# Copyright (c) 2023 Wind River Systems, Inc. +# +# SPDX-License-Identifier: Apache-2.0 +# +from unittest import mock +import uuid + +from nfv_common import strategy as common_strategy +from nfv_vim import nfvi + +from nfv_vim.objects import SW_UPDATE_ALARM_RESTRICTION +from nfv_vim.objects import SW_UPDATE_APPLY_TYPE +from nfv_vim.objects import SW_UPDATE_INSTANCE_ACTION +from nfv_vim.objects import SystemConfigUpdate +from nfv_vim.strategy._strategy import SystemConfigUpdateStrategy + +from nfv_unit_tests.tests import sw_update_testcase + + +@mock.patch('nfv_vim.event_log._instance._event_issue', + sw_update_testcase.fake_event_issue) +@mock.patch('nfv_vim.objects._sw_update.SwUpdate.save', + sw_update_testcase.fake_save) +@mock.patch('nfv_vim.objects._sw_update.timers.timers_create_timer', + sw_update_testcase.fake_timer) +@mock.patch('nfv_vim.nfvi.nfvi_compute_plugin_disabled', + sw_update_testcase.fake_nfvi_compute_plugin_disabled) +class TestSystemConfigUpdateStrategy(sw_update_testcase.SwUpdateStrategyTestCase): + + def _create_system_config_update_strategy(self, + sw_update_obj, + controller_apply_type=SW_UPDATE_APPLY_TYPE.SERIAL, + storage_apply_type=SW_UPDATE_APPLY_TYPE.SERIAL, + worker_apply_type=SW_UPDATE_APPLY_TYPE.SERIAL, + max_parallel_worker_hosts=10, + default_instance_action=SW_UPDATE_INSTANCE_ACTION.STOP_START, + alarm_restrictions=SW_UPDATE_ALARM_RESTRICTION.STRICT, + single_controller=False, + nfvi_system_config_update_hosts=None): + """ + Create a system config update strategy + """ + strategy = SystemConfigUpdateStrategy( + uuid=str(uuid.uuid4()), + controller_apply_type=controller_apply_type, + storage_apply_type=storage_apply_type, + worker_apply_type=worker_apply_type, + max_parallel_worker_hosts=max_parallel_worker_hosts, + default_instance_action=default_instance_action, + alarm_restrictions=alarm_restrictions, + single_controller=single_controller, + ignore_alarms=[]) + + strategy.sw_update_obj = sw_update_obj + strategy.nfvi_system_config_update_hosts = nfvi_system_config_update_hosts + return strategy + + @mock.patch('nfv_common.strategy._strategy.Strategy._build') + def test_system_config_update_strategy_build_steps(self, fake_build): + """ + Verify build phases, etc.. for system config update strategy creation. + """ + # setup a minimal host environment + self.create_host('controller-0', aio=True) + + # construct the strategy. the update_obj MUST be declared here and not + # in the create method, because it is a weakref and will be cleaned up + # when it goes out of scope. + update_obj = SystemConfigUpdate() + strategy = self._create_system_config_update_strategy(update_obj) + # The 'build' constructs a strategy that includes multiple queries + # the results of those queries are not used until build_complete + # mock away '_build', which invokes the build steps and their api calls + fake_build.return_value = None + strategy.build() + + # verify the build phase and steps + build_phase = strategy.build_phase.as_dict() + + query_steps = [ + {'name': 'query-alarms'}, + {'name': 'query-system-config-update-hosts'}, + ] + expected_results = { + 'total_stages': 1, + 'stages': [ + {'name': 'system-config-update-query', + 'total_steps': len(query_steps), + 'steps': query_steps, + }, + ], + } + sw_update_testcase.validate_phase(build_phase, expected_results) + + def temp_log(self, txt): + with open('./tbd.txt', 'a') as f: + f.write(txt + "\n") + + @mock.patch('nfv_common.strategy._strategy.Strategy._build') + def test_apply_system_config_update_strategy_simplex(self, fake_build): + + self.create_host('controller-0', aio=True) + + update_obj = SystemConfigUpdate() + nfvi_system_config_update_hosts = list() + controller_0_resource = nfvi.objects.v1.HostSystemConfigUpdate( + 'controller-0', 'lock_required') + nfvi_system_config_update_hosts.append(controller_0_resource) + + strategy = self._create_system_config_update_strategy( + update_obj, + single_controller=True, + nfvi_system_config_update_hosts=nfvi_system_config_update_hosts) + + strategy.build_complete(common_strategy.STRATEGY_RESULT.SUCCESS, "") + self.assertFalse(strategy.is_build_failed()) + self.assertEqual(strategy.build_phase.result_reason, "") + + apply_phase = strategy.apply_phase.as_dict() + + expected_results = { + 'total_stages': 1, + 'stages': [ + { + 'name': "system-config-update-worker-hosts", + 'total_steps': 6, + 'steps': [ + {'name': 'query-alarms'}, + {'name': 'lock-hosts', + 'entity_names': ['controller-0']}, + {'name': 'system-config-update-hosts', + 'entity_names': ['controller-0']}, + {'name': 'system-stabilize', + 'timeout': 15}, + {'name': 'unlock-hosts', + 'entity_names': ['controller-0']}, + {'name': 'wait-alarms-clear', + 'timeout': 1800}, + ] + }, + ] + } + sw_update_testcase.validate_strategy_persists(strategy) + sw_update_testcase.validate_phase(apply_phase, expected_results) + + @mock.patch('nfv_common.strategy._strategy.Strategy._build') + def test_apply_system_config_update_strategy_duplex(self, fake_build): + + self.create_host('controller-0', aio=True) + self.create_host('controller-1', aio=True) + + update_obj = SystemConfigUpdate() + nfvi_system_config_update_hosts = list() + controller_0_resource = nfvi.objects.v1.HostSystemConfigUpdate( + 'controller-0', 'lock_required') + controller_1_resource = nfvi.objects.v1.HostSystemConfigUpdate( + 'controller-1', 'lock_required') + nfvi_system_config_update_hosts.append(controller_0_resource) + nfvi_system_config_update_hosts.append(controller_1_resource) + + strategy = self._create_system_config_update_strategy( + update_obj, + nfvi_system_config_update_hosts=nfvi_system_config_update_hosts) + + strategy.build_complete(common_strategy.STRATEGY_RESULT.SUCCESS, "") + self.assertFalse(strategy.is_build_failed()) + self.assertEqual(strategy.build_phase.result_reason, "") + + apply_phase = strategy.apply_phase.as_dict() + + expected_results = { + 'total_stages': 2, + 'stages': [ + { + 'name': "system-config-update-worker-hosts", + 'total_steps': 7, + 'steps': [ + {'name': 'query-alarms'}, + {'name': 'swact-hosts', + 'entity_names': ['controller-0']}, + {'name': 'lock-hosts', + 'entity_names': ['controller-0']}, + {'name': 'system-config-update-hosts', + 'entity_names': ['controller-0']}, + {'name': 'system-stabilize', + 'timeout': 15}, + {'name': 'unlock-hosts', + 'entity_names': ['controller-0']}, + {'name': 'wait-alarms-clear', + 'timeout': 1800}, + ] + }, + { + 'name': "system-config-update-worker-hosts", + 'total_steps': 7, + 'steps': [ + {'name': 'query-alarms'}, + {'name': 'swact-hosts', + 'entity_names': ['controller-1']}, + {'name': 'lock-hosts', + 'entity_names': ['controller-1']}, + {'name': 'system-config-update-hosts', + 'entity_names': ['controller-1']}, + {'name': 'system-stabilize', + 'timeout': 15}, + {'name': 'unlock-hosts', + 'entity_names': ['controller-1']}, + {'name': 'wait-alarms-clear', + 'timeout': 1800}, + ] + }, + ] + } + + sw_update_testcase.validate_strategy_persists(strategy) + sw_update_testcase.validate_phase(apply_phase, expected_results) + + @mock.patch('nfv_common.strategy._strategy.Strategy._build') + def test_apply_system_config_update_strategy_standard_serial(self, fake_build): + + self.create_host('controller-0') + self.create_host('controller-1') + self.create_host('storage-0') + self.create_host('storage-1') + self.create_host('compute-0') + self.create_host('compute-1') + + update_obj = SystemConfigUpdate() + nfvi_system_config_update_hosts = list() + controller_0_resource = nfvi.objects.v1.HostSystemConfigUpdate( + 'controller-0', 'lock_required') + controller_1_resource = nfvi.objects.v1.HostSystemConfigUpdate( + 'controller-1', 'lock_required') + compute_0_resource = nfvi.objects.v1.HostSystemConfigUpdate( + 'compute-0', 'lock_required') + compute_1_resource = nfvi.objects.v1.HostSystemConfigUpdate( + 'compute-1', 'lock_required') + storage_0_resource = nfvi.objects.v1.HostSystemConfigUpdate( + 'storage-0', 'lock_required') + storage_1_resource = nfvi.objects.v1.HostSystemConfigUpdate( + 'storage-1', 'lock_required') + nfvi_system_config_update_hosts.append(controller_0_resource) + nfvi_system_config_update_hosts.append(controller_1_resource) + nfvi_system_config_update_hosts.append(compute_0_resource) + nfvi_system_config_update_hosts.append(compute_1_resource) + nfvi_system_config_update_hosts.append(storage_0_resource) + nfvi_system_config_update_hosts.append(storage_1_resource) + + strategy = self._create_system_config_update_strategy( + update_obj, + nfvi_system_config_update_hosts=nfvi_system_config_update_hosts) + + strategy.build_complete(common_strategy.STRATEGY_RESULT.SUCCESS, "") + self.assertFalse(strategy.is_build_failed()) + self.assertEqual(strategy.build_phase.result_reason, "") + + apply_phase = strategy.apply_phase.as_dict() + + expected_results = { + 'total_stages': 6, + 'stages': [ + { + 'name': "system-config-update-controllers", + 'total_steps': 7, + 'steps': [ + {'name': 'query-alarms'}, + {'name': 'swact-hosts', + 'entity_names': ['controller-0']}, + {'name': 'lock-hosts', + 'entity_names': ['controller-0']}, + {'name': 'system-config-update-hosts', + 'entity_names': ['controller-0']}, + {'name': 'system-stabilize', + 'timeout': 15}, + {'name': 'unlock-hosts', + 'entity_names': ['controller-0']}, + {'name': 'wait-alarms-clear', + 'timeout': 1800}, + ] + }, + { + 'name': "system-config-update-controllers", + 'total_steps': 7, + 'steps': [ + {'name': 'query-alarms'}, + {'name': 'swact-hosts', + 'entity_names': ['controller-1']}, + {'name': 'lock-hosts', + 'entity_names': ['controller-1']}, + {'name': 'system-config-update-hosts', + 'entity_names': ['controller-1']}, + {'name': 'system-stabilize', + 'timeout': 15}, + {'name': 'unlock-hosts', + 'entity_names': ['controller-1']}, + {'name': 'wait-alarms-clear', + 'timeout': 1800}, + ] + }, + { + 'name': "system-config-update-storage-hosts", + 'total_steps': 6, + 'steps': [ + {'name': 'query-alarms'}, + {'name': 'lock-hosts', + 'entity_names': ['storage-0']}, + {'name': 'system-config-update-hosts', + 'entity_names': ['storage-0']}, + {'name': 'system-stabilize', + 'timeout': 15}, + {'name': 'unlock-hosts', + 'entity_names': ['storage-0']}, + {'name': 'wait-data-sync', + 'timeout': 1800}, + ] + }, + { + 'name': "system-config-update-storage-hosts", + 'total_steps': 6, + 'steps': [ + {'name': 'query-alarms'}, + {'name': 'lock-hosts', + 'entity_names': ['storage-1']}, + {'name': 'system-config-update-hosts', + 'entity_names': ['storage-1']}, + {'name': 'system-stabilize', + 'timeout': 15}, + {'name': 'unlock-hosts', + 'entity_names': ['storage-1']}, + {'name': 'wait-data-sync', + 'timeout': 1800}, + ] + }, + { + 'name': "system-config-update-worker-hosts", + 'total_steps': 6, + 'steps': [ + {'name': 'query-alarms'}, + {'name': 'lock-hosts', + 'entity_names': ['compute-0']}, + {'name': 'system-config-update-hosts', + 'entity_names': ['compute-0']}, + {'name': 'system-stabilize', + 'timeout': 15}, + {'name': 'unlock-hosts', + 'entity_names': ['compute-0']}, + {'name': 'wait-alarms-clear', + 'timeout': 600}, + ] + }, + { + 'name': "system-config-update-worker-hosts", + 'total_steps': 6, + 'steps': [ + {'name': 'query-alarms'}, + {'name': 'lock-hosts', + 'entity_names': ['compute-1']}, + {'name': 'system-config-update-hosts', + 'entity_names': ['compute-1']}, + {'name': 'system-stabilize', + 'timeout': 15}, + {'name': 'unlock-hosts', + 'entity_names': ['compute-1']}, + {'name': 'wait-alarms-clear', + 'timeout': 600}, + ] + }, + ] + } + + sw_update_testcase.validate_strategy_persists(strategy) + sw_update_testcase.validate_phase(apply_phase, expected_results) + + @mock.patch('nfv_common.strategy._strategy.Strategy._build') + def test_apply_system_config_update_strategy_standard_parallel_worker(self, fake_build): + + self.create_host('controller-0') + self.create_host('controller-1') + self.create_host('storage-0') + self.create_host('storage-1') + self.create_host('compute-0') + self.create_host('compute-1') + + update_obj = SystemConfigUpdate() + nfvi_system_config_update_hosts = list() + controller_0_resource = nfvi.objects.v1.HostSystemConfigUpdate( + 'controller-0', 'lock_required') + controller_1_resource = nfvi.objects.v1.HostSystemConfigUpdate( + 'controller-1', 'lock_required') + compute_0_resource = nfvi.objects.v1.HostSystemConfigUpdate( + 'compute-0', 'lock_required') + compute_1_resource = nfvi.objects.v1.HostSystemConfigUpdate( + 'compute-1', 'lock_required') + storage_0_resource = nfvi.objects.v1.HostSystemConfigUpdate( + 'storage-0', 'lock_required') + storage_1_resource = nfvi.objects.v1.HostSystemConfigUpdate( + 'storage-1', 'lock_required') + nfvi_system_config_update_hosts.append(controller_0_resource) + nfvi_system_config_update_hosts.append(controller_1_resource) + nfvi_system_config_update_hosts.append(compute_0_resource) + nfvi_system_config_update_hosts.append(compute_1_resource) + nfvi_system_config_update_hosts.append(storage_0_resource) + nfvi_system_config_update_hosts.append(storage_1_resource) + + strategy = self._create_system_config_update_strategy( + update_obj, + worker_apply_type=SW_UPDATE_APPLY_TYPE.PARALLEL, + nfvi_system_config_update_hosts=nfvi_system_config_update_hosts) + + strategy.build_complete(common_strategy.STRATEGY_RESULT.SUCCESS, "") + self.assertFalse(strategy.is_build_failed()) + self.assertEqual(strategy.build_phase.result_reason, "") + + apply_phase = strategy.apply_phase.as_dict() + + expected_results = { + 'total_stages': 5, + 'stages': [ + { + 'name': "system-config-update-controllers", + 'total_steps': 7, + 'steps': [ + {'name': 'query-alarms'}, + {'name': 'swact-hosts', + 'entity_names': ['controller-0']}, + {'name': 'lock-hosts', + 'entity_names': ['controller-0']}, + {'name': 'system-config-update-hosts', + 'entity_names': ['controller-0']}, + {'name': 'system-stabilize', + 'timeout': 15}, + {'name': 'unlock-hosts', + 'entity_names': ['controller-0']}, + {'name': 'wait-alarms-clear', + 'timeout': 1800}, + ] + }, + { + 'name': "system-config-update-controllers", + 'total_steps': 7, + 'steps': [ + {'name': 'query-alarms'}, + {'name': 'swact-hosts', + 'entity_names': ['controller-1']}, + {'name': 'lock-hosts', + 'entity_names': ['controller-1']}, + {'name': 'system-config-update-hosts', + 'entity_names': ['controller-1']}, + {'name': 'system-stabilize', + 'timeout': 15}, + {'name': 'unlock-hosts', + 'entity_names': ['controller-1']}, + {'name': 'wait-alarms-clear', + 'timeout': 1800}, + ] + }, + { + 'name': "system-config-update-storage-hosts", + 'total_steps': 6, + 'steps': [ + {'name': 'query-alarms'}, + {'name': 'lock-hosts', + 'entity_names': ['storage-0']}, + {'name': 'system-config-update-hosts', + 'entity_names': ['storage-0']}, + {'name': 'system-stabilize', + 'timeout': 15}, + {'name': 'unlock-hosts', + 'entity_names': ['storage-0']}, + {'name': 'wait-data-sync', + 'timeout': 1800}, + ] + }, + { + 'name': "system-config-update-storage-hosts", + 'total_steps': 6, + 'steps': [ + {'name': 'query-alarms'}, + {'name': 'lock-hosts', + 'entity_names': ['storage-1']}, + {'name': 'system-config-update-hosts', + 'entity_names': ['storage-1']}, + {'name': 'system-stabilize', + 'timeout': 15}, + {'name': 'unlock-hosts', + 'entity_names': ['storage-1']}, + {'name': 'wait-data-sync', + 'timeout': 1800}, + ] + }, + { + 'name': "system-config-update-worker-hosts", + 'total_steps': 6, + 'steps': [ + {'name': 'query-alarms'}, + {'name': 'lock-hosts', + 'entity_names': ['compute-0', 'compute-1']}, + {'name': 'system-config-update-hosts', + 'entity_names': ['compute-0', 'compute-1']}, + {'name': 'system-stabilize', + 'timeout': 15}, + {'name': 'unlock-hosts', + 'entity_names': ['compute-0', 'compute-1']}, + {'name': 'wait-alarms-clear', + 'timeout': 600}, + ] + }, + ] + } + + sw_update_testcase.validate_strategy_persists(strategy) + sw_update_testcase.validate_phase(apply_phase, expected_results) + + @mock.patch('nfv_common.strategy._strategy.Strategy._build') + def test_apply_system_config_update_strategy_host_not_required(self, fake_build): + + self.create_host('controller-0') + self.create_host('controller-1') + self.create_host('storage-0') + self.create_host('storage-1') + self.create_host('compute-0') + self.create_host('compute-1') + + update_obj = SystemConfigUpdate() + nfvi_system_config_update_hosts = list() + controller_0_resource = nfvi.objects.v1.HostSystemConfigUpdate( + 'controller-0', 'lock_required') + controller_1_resource = nfvi.objects.v1.HostSystemConfigUpdate( + 'controller-1', 'not_required') + compute_0_resource = nfvi.objects.v1.HostSystemConfigUpdate( + 'compute-0', 'lock_required') + compute_1_resource = nfvi.objects.v1.HostSystemConfigUpdate( + 'compute-1', 'not_required') + storage_0_resource = nfvi.objects.v1.HostSystemConfigUpdate( + 'storage-0', 'not_required') + storage_1_resource = nfvi.objects.v1.HostSystemConfigUpdate( + 'storage-1', 'lock_required') + nfvi_system_config_update_hosts.append(controller_0_resource) + nfvi_system_config_update_hosts.append(controller_1_resource) + nfvi_system_config_update_hosts.append(compute_0_resource) + nfvi_system_config_update_hosts.append(compute_1_resource) + nfvi_system_config_update_hosts.append(storage_0_resource) + nfvi_system_config_update_hosts.append(storage_1_resource) + + strategy = self._create_system_config_update_strategy( + update_obj, + worker_apply_type=SW_UPDATE_APPLY_TYPE.PARALLEL, + nfvi_system_config_update_hosts=nfvi_system_config_update_hosts) + + strategy.build_complete(common_strategy.STRATEGY_RESULT.SUCCESS, "") + self.assertFalse(strategy.is_build_failed()) + self.assertEqual(strategy.build_phase.result_reason, "") + + apply_phase = strategy.apply_phase.as_dict() + + expected_results = { + 'total_stages': 3, + 'stages': [ + { + 'name': "system-config-update-controllers", + 'total_steps': 7, + 'steps': [ + {'name': 'query-alarms'}, + {'name': 'swact-hosts', + 'entity_names': ['controller-0']}, + {'name': 'lock-hosts', + 'entity_names': ['controller-0']}, + {'name': 'system-config-update-hosts', + 'entity_names': ['controller-0']}, + {'name': 'system-stabilize', + 'timeout': 15}, + {'name': 'unlock-hosts', + 'entity_names': ['controller-0']}, + {'name': 'wait-alarms-clear', + 'timeout': 1800}, + ] + }, + { + 'name': "system-config-update-storage-hosts", + 'total_steps': 6, + 'steps': [ + {'name': 'query-alarms'}, + {'name': 'lock-hosts', + 'entity_names': ['storage-1']}, + {'name': 'system-config-update-hosts', + 'entity_names': ['storage-1']}, + {'name': 'system-stabilize', + 'timeout': 15}, + {'name': 'unlock-hosts', + 'entity_names': ['storage-1']}, + {'name': 'wait-data-sync', + 'timeout': 1800}, + ] + }, + { + 'name': "system-config-update-worker-hosts", + 'total_steps': 6, + 'steps': [ + {'name': 'query-alarms'}, + {'name': 'lock-hosts', + 'entity_names': ['compute-0']}, + {'name': 'system-config-update-hosts', + 'entity_names': ['compute-0']}, + {'name': 'system-stabilize', + 'timeout': 15}, + {'name': 'unlock-hosts', + 'entity_names': ['compute-0']}, + {'name': 'wait-alarms-clear', + 'timeout': 600}, + ] + }, + ] + } + + sw_update_testcase.validate_strategy_persists(strategy) + sw_update_testcase.validate_phase(apply_phase, expected_results) + + @mock.patch('nfv_common.strategy._strategy.Strategy._build') + def test_apply_system_config_update_strategy_controller_ignore(self, fake_build): + + self.create_host('controller-0') + self.create_host('controller-1') + self.create_host('storage-0') + self.create_host('storage-1') + self.create_host('compute-0') + self.create_host('compute-1') + + update_obj = SystemConfigUpdate() + nfvi_system_config_update_hosts = list() + controller_0_resource = nfvi.objects.v1.HostSystemConfigUpdate( + 'controller-0', 'lock_required') + controller_1_resource = nfvi.objects.v1.HostSystemConfigUpdate( + 'controller-1', 'not_required') + compute_0_resource = nfvi.objects.v1.HostSystemConfigUpdate( + 'compute-0', 'lock_required') + compute_1_resource = nfvi.objects.v1.HostSystemConfigUpdate( + 'compute-1', 'not_required') + storage_0_resource = nfvi.objects.v1.HostSystemConfigUpdate( + 'storage-0', 'not_required') + storage_1_resource = nfvi.objects.v1.HostSystemConfigUpdate( + 'storage-1', 'lock_required') + nfvi_system_config_update_hosts.append(controller_0_resource) + nfvi_system_config_update_hosts.append(controller_1_resource) + nfvi_system_config_update_hosts.append(compute_0_resource) + nfvi_system_config_update_hosts.append(compute_1_resource) + nfvi_system_config_update_hosts.append(storage_0_resource) + nfvi_system_config_update_hosts.append(storage_1_resource) + + strategy = self._create_system_config_update_strategy( + update_obj, + controller_apply_type=SW_UPDATE_APPLY_TYPE.IGNORE, + worker_apply_type=SW_UPDATE_APPLY_TYPE.PARALLEL, + nfvi_system_config_update_hosts=nfvi_system_config_update_hosts) + + strategy.build_complete(common_strategy.STRATEGY_RESULT.SUCCESS, "") + self.assertFalse(strategy.is_build_failed()) + self.assertEqual(strategy.build_phase.result_reason, "") + + apply_phase = strategy.apply_phase.as_dict() + + expected_results = { + 'total_stages': 2, + 'stages': [ + { + 'name': "system-config-update-storage-hosts", + 'total_steps': 6, + 'steps': [ + {'name': 'query-alarms'}, + {'name': 'lock-hosts', + 'entity_names': ['storage-1']}, + {'name': 'system-config-update-hosts', + 'entity_names': ['storage-1']}, + {'name': 'system-stabilize', + 'timeout': 15}, + {'name': 'unlock-hosts', + 'entity_names': ['storage-1']}, + {'name': 'wait-data-sync', + 'timeout': 1800}, + ] + }, + { + 'name': "system-config-update-worker-hosts", + 'total_steps': 6, + 'steps': [ + {'name': 'query-alarms'}, + {'name': 'lock-hosts', + 'entity_names': ['compute-0']}, + {'name': 'system-config-update-hosts', + 'entity_names': ['compute-0']}, + {'name': 'system-stabilize', + 'timeout': 15}, + {'name': 'unlock-hosts', + 'entity_names': ['compute-0']}, + {'name': 'wait-alarms-clear', + 'timeout': 600}, + ] + }, + ] + } + + sw_update_testcase.validate_strategy_persists(strategy) + sw_update_testcase.validate_phase(apply_phase, expected_results) + + @mock.patch('nfv_common.strategy._strategy.Strategy._build') + def test_apply_system_config_update_strategy_worker_ignore(self, fake_build): + + self.create_host('controller-0') + self.create_host('controller-1') + self.create_host('storage-0') + self.create_host('storage-1') + self.create_host('compute-0') + self.create_host('compute-1') + + update_obj = SystemConfigUpdate() + nfvi_system_config_update_hosts = list() + controller_0_resource = nfvi.objects.v1.HostSystemConfigUpdate( + 'controller-0', 'lock_required') + controller_1_resource = nfvi.objects.v1.HostSystemConfigUpdate( + 'controller-1', 'not_required') + compute_0_resource = nfvi.objects.v1.HostSystemConfigUpdate( + 'compute-0', 'lock_required') + compute_1_resource = nfvi.objects.v1.HostSystemConfigUpdate( + 'compute-1', 'not_required') + storage_0_resource = nfvi.objects.v1.HostSystemConfigUpdate( + 'storage-0', 'not_required') + storage_1_resource = nfvi.objects.v1.HostSystemConfigUpdate( + 'storage-1', 'lock_required') + nfvi_system_config_update_hosts.append(controller_0_resource) + nfvi_system_config_update_hosts.append(controller_1_resource) + nfvi_system_config_update_hosts.append(compute_0_resource) + nfvi_system_config_update_hosts.append(compute_1_resource) + nfvi_system_config_update_hosts.append(storage_0_resource) + nfvi_system_config_update_hosts.append(storage_1_resource) + + strategy = self._create_system_config_update_strategy( + update_obj, + worker_apply_type=SW_UPDATE_APPLY_TYPE.IGNORE, + nfvi_system_config_update_hosts=nfvi_system_config_update_hosts) + + strategy.build_complete(common_strategy.STRATEGY_RESULT.SUCCESS, "") + self.assertFalse(strategy.is_build_failed()) + self.assertEqual(strategy.build_phase.result_reason, "") + + apply_phase = strategy.apply_phase.as_dict() + + expected_results = { + 'total_stages': 2, + 'stages': [ + { + 'name': "system-config-update-controllers", + 'total_steps': 7, + 'steps': [ + {'name': 'query-alarms'}, + {'name': 'swact-hosts', + 'entity_names': ['controller-0']}, + {'name': 'lock-hosts', + 'entity_names': ['controller-0']}, + {'name': 'system-config-update-hosts', + 'entity_names': ['controller-0']}, + {'name': 'system-stabilize', + 'timeout': 15}, + {'name': 'unlock-hosts', + 'entity_names': ['controller-0']}, + {'name': 'wait-alarms-clear', + 'timeout': 1800}, + ] + }, + { + 'name': "system-config-update-storage-hosts", + 'total_steps': 6, + 'steps': [ + {'name': 'query-alarms'}, + {'name': 'lock-hosts', + 'entity_names': ['storage-1']}, + {'name': 'system-config-update-hosts', + 'entity_names': ['storage-1']}, + {'name': 'system-stabilize', + 'timeout': 15}, + {'name': 'unlock-hosts', + 'entity_names': ['storage-1']}, + {'name': 'wait-data-sync', + 'timeout': 1800}, + ] + }, + ] + } + + sw_update_testcase.validate_strategy_persists(strategy) + sw_update_testcase.validate_phase(apply_phase, expected_results) diff --git a/nfv/nfv-vim/nfv_vim/alarm/_sw_update.py b/nfv/nfv-vim/nfv_vim/alarm/_sw_update.py index 87c2e239..49debb0a 100755 --- a/nfv/nfv-vim/nfv_vim/alarm/_sw_update.py +++ b/nfv/nfv-vim/nfv_vim/alarm/_sw_update.py @@ -1,5 +1,5 @@ # -# Copyright (c) 2016-2021 Wind River Systems, Inc. +# Copyright (c) 2016-2023 Wind River Systems, Inc. # # SPDX-License-Identifier: Apache-2.0 # @@ -188,6 +188,42 @@ _alarm_templates = { "problem persists contact next level of support"), 'exclude_alarm_context': [alarm.ALARM_CONTEXT.TENANT], }, + + alarm.ALARM_TYPE.SYSTEM_CONFIG_UPDATE_AUTO_APPLY_INPROGRESS: { + 'entity_type': "orchestration", + 'entity': "orchestration=system-config-update", + 'event_type': alarm.ALARM_EVENT_TYPE.EQUIPMENT_ALARM, + 'severity': alarm.ALARM_SEVERITY.MAJOR, + 'probable_cause': alarm.ALARM_PROBABLE_CAUSE.UNKNOWN, + 'reason_text': "System config update auto-apply inprogress", + 'repair_action': ("Wait for system config update auto-apply to " + "complete; if problem persists contact next " + "level of support"), + 'exclude_alarm_context': [alarm.ALARM_CONTEXT.TENANT], + }, + alarm.ALARM_TYPE.SYSTEM_CONFIG_UPDATE_AUTO_APPLY_ABORTING: { + 'entity_type': "orchestration", + 'entity': "orchestration=system-config-update", + 'event_type': alarm.ALARM_EVENT_TYPE.EQUIPMENT_ALARM, + 'severity': alarm.ALARM_SEVERITY.MAJOR, + 'probable_cause': alarm.ALARM_PROBABLE_CAUSE.UNKNOWN, + 'reason_text': "System config update auto-apply aborting", + 'repair_action': ("Wait for system config update auto-apply abort " + "to complete; if problem persists contact next " + "level of support"), + 'exclude_alarm_context': [alarm.ALARM_CONTEXT.TENANT], + }, + alarm.ALARM_TYPE.SYSTEM_CONFIG_UPDATE_AUTO_APPLY_FAILED: { + 'entity_type': "orchestration", + 'entity': "orchestration=system-config-update", + 'event_type': alarm.ALARM_EVENT_TYPE.EQUIPMENT_ALARM, + 'severity': alarm.ALARM_SEVERITY.CRITICAL, + 'probable_cause': alarm.ALARM_PROBABLE_CAUSE.UNKNOWN, + 'reason_text': "System config update auto-apply failed", + 'repair_action': ("Attempt to apply system config update manually; " + "if problem persists contact next level of support"), + 'exclude_alarm_context': [alarm.ALARM_CONTEXT.TENANT], + }, } diff --git a/nfv/nfv-vim/nfv_vim/api/acl/_application.py b/nfv/nfv-vim/nfv_vim/api/acl/_application.py index 53b75243..e253eb07 100755 --- a/nfv/nfv-vim/nfv_vim/api/acl/_application.py +++ b/nfv/nfv-vim/nfv_vim/api/acl/_application.py @@ -1,5 +1,5 @@ # -# Copyright (c) 2016-2022 Wind River Systems, Inc. +# Copyright (c) 2016-2023 Wind River Systems, Inc. # # SPDX-License-Identifier: Apache-2.0 # @@ -13,6 +13,7 @@ from nfv_vim.api.acl.policies import kube_upgrade_strategy_policy from nfv_vim.api.acl.policies import sw_patch_strategy_policy from nfv_vim.api.acl.policies import sw_update_strategy_policy from nfv_vim.api.acl.policies import sw_upgrade_strategy_policy +from nfv_vim.api.acl.policies import system_config_update_strategy_policy from nfv_vim.api.acl import policy from nfv_vim.api import openstack @@ -46,7 +47,8 @@ class AuthenticationApplication(object): kube_rootca_update_strategy_policy.list_rules(), kube_upgrade_strategy_policy.list_rules(), sw_patch_strategy_policy.list_rules(), - sw_upgrade_strategy_policy.list_rules() + sw_upgrade_strategy_policy.list_rules(), + system_config_update_strategy_policy.list_rules() ) rules = policy.Rules.load_rules(policy_file_contents, default_rule, diff --git a/nfv/nfv-vim/nfv_vim/api/acl/policies/system_config_update_strategy_policy.py b/nfv/nfv-vim/nfv_vim/api/acl/policies/system_config_update_strategy_policy.py new file mode 100644 index 00000000..02b29f6a --- /dev/null +++ b/nfv/nfv-vim/nfv_vim/api/acl/policies/system_config_update_strategy_policy.py @@ -0,0 +1,42 @@ +# Copyright (c) 2023 Wind River Systems, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +# SPDX-License-Identifier: Apache-2.0 + +from nfv_vim.api.acl.policies import base + +POLICY_ROOT = 'nfv_api:system_config_update_strategy:%s' + + +system_config_update_strategy_rules = [ + base.RuleDefault( + name=POLICY_ROOT % 'add', + check_str='rule:' + base.ADMIN_IN_SYSTEM_PROJECTS, + description="Add a system_config_update_strategy", + ), + base.RuleDefault( + name=POLICY_ROOT % 'delete', + check_str='rule:' + base.ADMIN_IN_SYSTEM_PROJECTS, + description="Delete a system_config_update_strategy", + ), + base.RuleDefault( + name=POLICY_ROOT % 'get', + check_str='rule:' + base.READER_IN_SYSTEM_PROJECTS, + description="Get a system_config_update_strategy", + ) +] + + +def list_rules(): + return system_config_update_strategy_rules diff --git a/nfv/nfv-vim/nfv_vim/api/controllers/v1/orchestration/_controller.py b/nfv/nfv-vim/nfv_vim/api/controllers/v1/orchestration/_controller.py index 8ef2ac10..255394d3 100755 --- a/nfv/nfv-vim/nfv_vim/api/controllers/v1/orchestration/_controller.py +++ b/nfv/nfv-vim/nfv_vim/api/controllers/v1/orchestration/_controller.py @@ -1,5 +1,5 @@ # -# Copyright (c) 2015-2021 Wind River Systems, Inc. +# Copyright (c) 2015-2023 Wind River Systems, Inc. # # SPDX-License-Identifier: Apache-2.0 # @@ -15,6 +15,7 @@ from nfv_vim.api.controllers.v1.orchestration.sw_update import KubeRootcaUpdateA from nfv_vim.api.controllers.v1.orchestration.sw_update import KubeUpgradeAPI from nfv_vim.api.controllers.v1.orchestration.sw_update import SwPatchAPI from nfv_vim.api.controllers.v1.orchestration.sw_update import SwUpgradeAPI +from nfv_vim.api.controllers.v1.orchestration.sw_update import SystemConfigUpdateAPI class OrchestrationDescription(wsme_types.Base): @@ -34,6 +35,8 @@ class OrchestrationDescription(wsme_types.Base): Link.make_link('self', url, 'orchestration'), Link.make_link('sw-patch', url, 'orchestration/sw-patch', ''), Link.make_link('sw-upgrade', url, 'orchestration/sw-upgrade', ''), + Link.make_link('system-config-update', + url, 'orchestration/system-config-update', ''), Link.make_link('kube-rootca-update', url, 'orchestration/kube-rootca-update', ''), Link.make_link('kube-upgrade', @@ -52,6 +55,8 @@ class OrchestrationAPI(rest.RestController): return SwPatchAPI(), remainder elif 'sw-upgrade' == key: return SwUpgradeAPI(), remainder + elif 'system-config-update' == key: + return SystemConfigUpdateAPI(), remainder elif 'fw-update' == key: return FwUpdateAPI(), remainder elif 'kube-rootca-update' == key: diff --git a/nfv/nfv-vim/nfv_vim/api/controllers/v1/orchestration/sw_update/__init__.py b/nfv/nfv-vim/nfv_vim/api/controllers/v1/orchestration/sw_update/__init__.py index c875c9f3..4cb570c0 100755 --- a/nfv/nfv-vim/nfv_vim/api/controllers/v1/orchestration/sw_update/__init__.py +++ b/nfv/nfv-vim/nfv_vim/api/controllers/v1/orchestration/sw_update/__init__.py @@ -1,5 +1,5 @@ # -# Copyright (c) 2015-2021 Wind River Systems, Inc. +# Copyright (c) 2015-2023 Wind River Systems, Inc. # # SPDX-License-Identifier: Apache-2.0 # @@ -8,3 +8,4 @@ from nfv_vim.api.controllers.v1.orchestration.sw_update._kube_rootca_update impo from nfv_vim.api.controllers.v1.orchestration.sw_update._kube_upgrade import KubeUpgradeAPI # noqa: F401 from nfv_vim.api.controllers.v1.orchestration.sw_update._sw_patch import SwPatchAPI # noqa: F401 from nfv_vim.api.controllers.v1.orchestration.sw_update._sw_upgrade import SwUpgradeAPI # noqa: F401 +from nfv_vim.api.controllers.v1.orchestration.sw_update._system_config_update import SystemConfigUpdateAPI # noqa: F401 diff --git a/nfv/nfv-vim/nfv_vim/api/controllers/v1/orchestration/sw_update/_sw_update_defs.py b/nfv/nfv-vim/nfv_vim/api/controllers/v1/orchestration/sw_update/_sw_update_defs.py index 6966fb4b..127c8fbd 100755 --- a/nfv/nfv-vim/nfv_vim/api/controllers/v1/orchestration/sw_update/_sw_update_defs.py +++ b/nfv/nfv-vim/nfv_vim/api/controllers/v1/orchestration/sw_update/_sw_update_defs.py @@ -1,4 +1,4 @@ -# Copyright (c) 2015-2021 Wind River Systems, Inc. +# Copyright (c) 2015-2023 Wind River Systems, Inc. # # SPDX-License-Identifier: Apache-2.0 # @@ -21,6 +21,7 @@ class SwUpdateNames(Constants): KUBE_UPGRADE = Constant('kube-upgrade') SW_PATCH = Constant('sw-patch') SW_UPGRADE = Constant('sw-upgrade') + SYSTEM_CONFIG_UPDATE = Constant('system-config-update') @six.add_metaclass(Singleton) @@ -76,7 +77,8 @@ SwUpdateNames = wsme_types.Enum(str, SW_UPDATE_NAME.KUBE_ROOTCA_UPDATE, SW_UPDATE_NAME.KUBE_UPGRADE, SW_UPDATE_NAME.SW_PATCH, - SW_UPDATE_NAME.SW_UPGRADE) + SW_UPDATE_NAME.SW_UPGRADE, + SW_UPDATE_NAME.SYSTEM_CONFIG_UPDATE) SwUpdateApplyTypes = wsme_types.Enum(str, SW_UPDATE_APPLY_TYPE.SERIAL, SW_UPDATE_APPLY_TYPE.PARALLEL, diff --git a/nfv/nfv-vim/nfv_vim/api/controllers/v1/orchestration/sw_update/_sw_update_strategy.py b/nfv/nfv-vim/nfv_vim/api/controllers/v1/orchestration/sw_update/_sw_update_strategy.py index 301d7fff..9642e798 100755 --- a/nfv/nfv-vim/nfv_vim/api/controllers/v1/orchestration/sw_update/_sw_update_strategy.py +++ b/nfv/nfv-vim/nfv_vim/api/controllers/v1/orchestration/sw_update/_sw_update_strategy.py @@ -1,5 +1,5 @@ # -# Copyright (c) 2015-2022 Wind River Systems, Inc. +# Copyright (c) 2015-2023 Wind River Systems, Inc. # # SPDX-License-Identifier: Apache-2.0 # @@ -19,6 +19,7 @@ from nfv_vim.api.acl.policies import kube_upgrade_strategy_policy from nfv_vim.api.acl.policies import sw_patch_strategy_policy from nfv_vim.api.acl.policies import sw_update_strategy_policy from nfv_vim.api.acl.policies import sw_upgrade_strategy_policy +from nfv_vim.api.acl.policies import system_config_update_strategy_policy from nfv_vim.api.acl import policy from nfv_vim import rpc @@ -41,6 +42,7 @@ MAX_PARALLEL_FW_UPDATE_HOSTS = 5 MAX_PARALLEL_KUBE_ROOTCA_UPDATE_HOSTS = 10 MAX_PARALLEL_KUBE_UPGRADE_HOSTS = 10 MAX_PARALLEL_PATCH_HOSTS = 100 +MAX_PARALLEL_SYSTEM_CONFIG_UPDATE_HOSTS = 100 MAX_PARALLEL_UPGRADE_HOSTS = 10 @@ -50,6 +52,8 @@ def _get_sw_update_type_from_path(path): return SW_UPDATE_NAME.SW_PATCH elif 'sw-upgrade' in split_path: return SW_UPDATE_NAME.SW_UPGRADE + elif 'system-config-update' in split_path: + return SW_UPDATE_NAME.SYSTEM_CONFIG_UPDATE elif 'fw-update' in split_path: return SW_UPDATE_NAME.FW_UPDATE elif 'kube-rootca-update' in split_path: @@ -193,6 +197,27 @@ class SwUpdateStrategyDeleteData(wsme_types.Base): default=False) +class SystemConfigUpdateStrategyCreateData(wsme_types.Base): + """ + System Config Update Strategy - Create Data + """ + controller_apply_type = wsme_types.wsattr(SwUpdateApplyTypes, mandatory=True, + name='controller-apply-type') + storage_apply_type = wsme_types.wsattr(SwUpdateApplyTypes, mandatory=True, + name='storage-apply-type') + worker_apply_type = wsme_types.wsattr(SwUpdateApplyTypes, mandatory=True, + name='worker-apply-type') + max_parallel_worker_hosts = wsme_types.wsattr( + int, mandatory=False, name='max-parallel-worker-hosts') + default_instance_action = wsme_types.wsattr(SwUpdateInstanceActionTypes, + mandatory=True, + name='default-instance-action') + alarm_restrictions = wsme_types.wsattr( + SwUpdateAlarmRestrictionTypes, mandatory=False, + default=SW_UPDATE_ALARM_RESTRICTION_TYPES.STRICT, + name='alarm-restrictions') + + class FwUpdateStrategyCreateData(wsme_types.Base): """ Firmware Update Strategy - Create Data @@ -697,6 +722,70 @@ class SwUpgradeStrategyAPI(SwUpdateStrategyAPI): policy.check('admin_in_system_projects', {}, auth_context_dict) +class SystemConfigUpdateStrategyAPI(SwUpdateStrategyAPI): + """ + System Config Update Strategy Rest API + """ + @wsme_pecan.wsexpose(SwUpdateStrategyQueryData, + body=SystemConfigUpdateStrategyCreateData, + status_code=httplib.OK) + def post(self, request_data): + rpc_request = rpc.APIRequestCreateSwUpdateStrategy() + rpc_request.sw_update_type = _get_sw_update_type_from_path( + pecan.request.path) + rpc_request.controller_apply_type = request_data.controller_apply_type + rpc_request.storage_apply_type = request_data.storage_apply_type + rpc_request.worker_apply_type = request_data.worker_apply_type + if wsme_types.Unset != request_data.max_parallel_worker_hosts: + if request_data.max_parallel_worker_hosts < MIN_PARALLEL_HOSTS \ + or request_data.max_parallel_worker_hosts > \ + MAX_PARALLEL_SYSTEM_CONFIG_UPDATE_HOSTS: + return pecan.abort( + httplib.BAD_REQUEST, + "Invalid value for max-parallel-worker-hosts") + rpc_request.max_parallel_worker_hosts = \ + request_data.max_parallel_worker_hosts + rpc_request.default_instance_action = request_data.default_instance_action + rpc_request.alarm_restrictions = request_data.alarm_restrictions + vim_connection = pecan.request.vim.open_connection() + vim_connection.send(rpc_request.serialize()) + msg = vim_connection.receive(timeout_in_secs=30) + if msg is None: + DLOG.error("No response received.") + return pecan.abort(httplib.INTERNAL_SERVER_ERROR) + + response = rpc.RPCMessage.deserialize(msg) + if rpc.RPC_MSG_TYPE.CREATE_SW_UPDATE_STRATEGY_RESPONSE != response.type: + DLOG.error("Unexpected message type received, msg_type=%s." + % response.type) + return pecan.abort(httplib.INTERNAL_SERVER_ERROR) + + if rpc.RPC_MSG_RESULT.SUCCESS == response.result: + strategy = json.loads(response.strategy) + query_data = SwUpdateStrategyQueryData() + query_data.convert_strategy(strategy) + return query_data + elif rpc.RPC_MSG_RESULT.CONFLICT == response.result: + return pecan.abort(httplib.CONFLICT, response.error_string) + + DLOG.error("Unexpected result received, result=%s." % response.result) + return pecan.abort(httplib.INTERNAL_SERVER_ERROR) + + def enforce_policy(self, method_name, auth_context_dict): + """Check policy rules for each action of this controller.""" + if method_name == "delete": + policy.check(system_config_update_strategy_policy.POLICY_ROOT % "delete", + {}, auth_context_dict, exc=policy.PolicyForbidden) + elif method_name in ["get_all", "get_one"]: + policy.check(system_config_update_strategy_policy.POLICY_ROOT % "get", + {}, auth_context_dict, exc=policy.PolicyForbidden) + elif method_name == "post": + policy.check(system_config_update_strategy_policy.POLICY_ROOT % "add", + {}, auth_context_dict, exc=policy.PolicyForbidden) + else: + policy.check('admin_in_system_projects', {}, auth_context_dict) + + class FwUpdateStrategyAPI(SwUpdateStrategyAPI): """ Firmware Update Strategy Rest API diff --git a/nfv/nfv-vim/nfv_vim/api/controllers/v1/orchestration/sw_update/_system_config_update.py b/nfv/nfv-vim/nfv_vim/api/controllers/v1/orchestration/sw_update/_system_config_update.py new file mode 100644 index 00000000..803ae245 --- /dev/null +++ b/nfv/nfv-vim/nfv_vim/api/controllers/v1/orchestration/sw_update/_system_config_update.py @@ -0,0 +1,59 @@ +# +# Copyright (c) 2023 Wind River Systems, Inc. +# +# SPDX-License-Identifier: Apache-2.0 +# +import pecan +from pecan import rest +from six.moves import http_client as httplib +from wsme import types as wsme_types +import wsmeext.pecan as wsme_pecan + +from nfv_common import debug +from nfv_vim.api._link import Link +from nfv_vim.api.controllers.v1.orchestration.sw_update._sw_update_strategy \ + import SystemConfigUpdateStrategyAPI + +DLOG = debug.debug_get_logger('nfv_vim.api.system_config_update') + + +class SystemConfigUpdateDescription(wsme_types.Base): + """ + System Config Update Description + """ + id = wsme_types.text + links = wsme_types.wsattr([Link], name='links') + + @classmethod + def convert(cls): + url = pecan.request.host_url + + description = SystemConfigUpdateDescription() + description.id = "system-config-update" + description.links = [ + Link.make_link('self', + url, + 'orchestration/system-config-update'), + Link.make_link('strategy', + url, + 'orchestration/system-config-update/strategy')] + return description + + +class SystemConfigUpdateAPI(rest.RestController): + """ + SystemConfigUpdateRest API + """ + @pecan.expose() + def _lookup(self, key, *remainder): + if 'strategy' == key: + return SystemConfigUpdateStrategyAPI(), remainder + else: + pecan.abort(httplib.NOT_FOUND) + + @wsme_pecan.wsexpose(SystemConfigUpdateDescription) + def get(self): + # NOTE: The reason why convert() is being called for every + # request is because we need to get the host url from + # the request object to make the links. + return SystemConfigUpdateDescription.convert() diff --git a/nfv/nfv-vim/nfv_vim/database/_database_sw_update.py b/nfv/nfv-vim/nfv_vim/database/_database_sw_update.py index 823da5c4..d9c01cd7 100755 --- a/nfv/nfv-vim/nfv_vim/database/_database_sw_update.py +++ b/nfv/nfv-vim/nfv_vim/database/_database_sw_update.py @@ -1,5 +1,5 @@ # -# Copyright (c) 2016-2021 Wind River Systems, Inc. +# Copyright (c) 2016-2023 Wind River Systems, Inc. # # SPDX-License-Identifier: Apache-2.0 # @@ -77,4 +77,8 @@ def database_sw_update_get_list(): kube_upgrade_obj = objects.KubeUpgrade(sw_update.uuid, strategy_data) sw_update_objs.append(kube_upgrade_obj) + elif objects.SW_UPDATE_TYPE.SYSTEM_CONFIG_UPDATE == sw_update.sw_update_type: + system_config_update_obj = objects.SystemConfigUpdate(sw_update.uuid, + strategy_data) + sw_update_objs.append(system_config_update_obj) return sw_update_objs diff --git a/nfv/nfv-vim/nfv_vim/debug.ini b/nfv/nfv-vim/nfv_vim/debug.ini index e46795fc..279a91e0 100644 --- a/nfv/nfv-vim/nfv_vim/debug.ini +++ b/nfv/nfv-vim/nfv_vim/debug.ini @@ -1,5 +1,5 @@ # -# Copyright (c) 2015-2020 Wind River Systems, Inc. +# Copyright (c) 2015-2023 Wind River Systems, Inc. # # SPDX-License-Identifier: Apache-2.0 # @@ -127,6 +127,7 @@ nfv_vim.objects.instance_group: debug.level.verbose nfv_vim.objects.fw_update: debug.level.info nfv_vim.objects.kube_rootca_update: debug.level.info nfv_vim.objects.kube_upgrade: debug.level.info +nfv_vim.objects.system_config_update: debug.level.info nfv_vim.objects.sw_update: debug.level.verbose nfv_vim.objects.sw_patch: debug.level.verbose nfv_vim.objects.sw_upgrade: debug.level.verbose @@ -171,6 +172,7 @@ nfv_vim.api.kube_upgrade: debug.level.verbose nfv_vim.api.sw_patch: debug.level.verbose nfv_vim.api.sw_upgrade: debug.level.verbose nfv_vim.api.sw_update.strategy: debug.level.verbose +nfv_vim.api.system_config_update: debug.level.verbose nfv_vim.api.image: debug.level.verbose nfv_vim.api.volume: debug.level.verbose nfv_vim.api.virtualised_resources: debug.level.verbose diff --git a/nfv/nfv-vim/nfv_vim/directors/_sw_mgmt_director.py b/nfv/nfv-vim/nfv_vim/directors/_sw_mgmt_director.py index cba96732..f91af669 100755 --- a/nfv/nfv-vim/nfv_vim/directors/_sw_mgmt_director.py +++ b/nfv/nfv-vim/nfv_vim/directors/_sw_mgmt_director.py @@ -102,6 +102,35 @@ class SwMgmtDirector(object): self._sw_update.strategy) return strategy_uuid, '' + def create_system_config_update_strategy(self, controller_apply_type, + storage_apply_type, worker_apply_type, + max_parallel_worker_hosts, + default_instance_action, + alarm_restrictions, callback): + """ + Create System Config Update Strategy + """ + strategy_uuid = str(uuid.uuid4()) + + if self._sw_update is not None: + # Do not schedule the callback - if creation failed because a + # strategy already exists, the callback will attempt to operate + # on the old strategy, which is not what we want. + reason = "strategy already exists of type:%s" % self._sw_update._sw_update_type + return None, reason + + self._sw_update = objects.SystemConfigUpdate() + success, reason = self._sw_update.strategy_build( + strategy_uuid, controller_apply_type, storage_apply_type, + worker_apply_type, max_parallel_worker_hosts, + default_instance_action, + alarm_restrictions, self._ignore_alarms, + self._single_controller) + + schedule.schedule_function_call(callback, success, reason, + self._sw_update.strategy) + return strategy_uuid, '' + def create_fw_update_strategy(self, controller_apply_type, storage_apply_type, @@ -345,6 +374,15 @@ class SwMgmtDirector(object): self._sw_update.handle_event( strategy.STRATEGY_EVENT.HOST_FW_UPDATE_FAILED, host) + def system_config_update_failed(self, host): + """ + Called when a system config update for a host phase fails + """ + if self._sw_update is not None: + self._sw_update.handle_event( + strategy.STRATEGY_EVENT.SYSTEM_CONFIG_UPDATE_HOST_FAILED, + host) + def kube_host_rootca_update_failed(self, host): """ Called when a kube footca update for a host phase fails diff --git a/nfv/nfv-vim/nfv_vim/event_log/_sw_update.py b/nfv/nfv-vim/nfv_vim/event_log/_sw_update.py index b87f12ed..91cd7301 100755 --- a/nfv/nfv-vim/nfv_vim/event_log/_sw_update.py +++ b/nfv/nfv-vim/nfv_vim/event_log/_sw_update.py @@ -1,5 +1,5 @@ # -# Copyright (c) 2015-2021 Wind River Systems, Inc. +# Copyright (c) 2015-2023 Wind River Systems, Inc. # # SPDX-License-Identifier: Apache-2.0 # @@ -342,6 +342,175 @@ _event_templates = { } } }, + event_log.EVENT_ID.SYSTEM_CONFIG_UPDATE_AUTO_APPLY_START: { + 'entity_type': "orchestration", + 'entity': "orchestration=system-config-update", + 'event_type': event_log.EVENT_TYPE.ACTION_EVENT, + 'importance': event_log.EVENT_IMPORTANCE.HIGH, + 'reason_text': "System config update auto-apply start", + 'exclude_event_context': [], + 'event_context_data': { + event_log.EVENT_CONTEXT.ADMIN: { + 'entity_type': "orchestration", + 'entity': "orchestration=system-config-update", + 'reason_text': "System config update auto-apply start", + } + } + }, + event_log.EVENT_ID.SYSTEM_CONFIG_UPDATE_AUTO_APPLY_INPROGRESS: { + 'entity_type': "orchestration", + 'entity': "orchestration=system-config-update", + 'event_type': event_log.EVENT_TYPE.ACTION_EVENT, + 'importance': event_log.EVENT_IMPORTANCE.HIGH, + 'reason_text': "System config update auto-apply inprogress", + 'exclude_event_context': [], + 'event_context_data': { + event_log.EVENT_CONTEXT.ADMIN: { + 'entity_type': "orchestration", + 'entity': "orchestration=system-config-update", + 'reason_text': "System config update auto-apply inprogress", + } + } + }, + event_log.EVENT_ID.SYSTEM_CONFIG_UPDATE_AUTO_APPLY_REJECTED: { + 'entity_type': "orchestration", + 'entity': "orchestration=system-config-update", + 'event_type': event_log.EVENT_TYPE.ACTION_EVENT, + 'importance': event_log.EVENT_IMPORTANCE.HIGH, + 'reason_text': "System config update auto-apply rejected", + 'exclude_event_context': [], + 'event_context_data': { + event_log.EVENT_CONTEXT.ADMIN: { + 'entity_type': "orchestration", + 'entity': "orchestration=system-config-update", + 'reason_text': "System config update auto-apply rejected%(reason)s", + } + } + }, + event_log.EVENT_ID.SYSTEM_CONFIG_UPDATE_AUTO_APPLY_CANCELLED: { + 'entity_type': "orchestration", + 'entity': "orchestration=system-config-update", + 'event_type': event_log.EVENT_TYPE.ACTION_EVENT, + 'importance': event_log.EVENT_IMPORTANCE.HIGH, + 'reason_text': "System config update auto-apply cancelled", + 'exclude_event_context': [], + 'event_context_data': { + event_log.EVENT_CONTEXT.ADMIN: { + 'entity_type': "orchestration", + 'entity': "orchestration=system-config-update", + 'reason_text': "System config update auto-apply " + "cancelled%(reason)s", + } + } + }, + event_log.EVENT_ID.SYSTEM_CONFIG_UPDATE_AUTO_APPLY_FAILED: { + 'entity_type': "orchestration", + 'entity': "orchestration=system-config-update", + 'event_type': event_log.EVENT_TYPE.ACTION_EVENT, + 'importance': event_log.EVENT_IMPORTANCE.HIGH, + 'reason_text': "System config update auto-apply failed", + 'exclude_event_context': [], + 'event_context_data': { + event_log.EVENT_CONTEXT.ADMIN: { + 'entity_type': "orchestration", + 'entity': "orchestration=system-config-update", + 'reason_text': "System config update auto-apply " + "failed%(reason)s", + } + } + }, + event_log.EVENT_ID.SYSTEM_CONFIG_UPDATE_AUTO_APPLY_COMPLETED: { + 'entity_type': "orchestration", + 'entity': "orchestration=system-config-update", + 'event_type': event_log.EVENT_TYPE.ACTION_EVENT, + 'importance': event_log.EVENT_IMPORTANCE.HIGH, + 'reason_text': "System config update auto-apply completed", + 'exclude_event_context': [], + 'event_context_data': { + event_log.EVENT_CONTEXT.ADMIN: { + 'entity_type': "orchestration", + 'entity': "orchestration=system-config-update", + 'reason_text': "System config update auto-apply completed", + } + } + }, + event_log.EVENT_ID.SYSTEM_CONFIG_UPDATE_AUTO_APPLY_ABORT: { + 'entity_type': "orchestration", + 'entity': "orchestration=system-config-update", + 'event_type': event_log.EVENT_TYPE.ACTION_EVENT, + 'importance': event_log.EVENT_IMPORTANCE.HIGH, + 'reason_text': "System config update auto-apply abort", + 'exclude_event_context': [], + 'event_context_data': { + event_log.EVENT_CONTEXT.ADMIN: { + 'entity_type': "orchestration", + 'entity': "orchestration=system-config-update", + 'reason_text': "System config update auto-apply abort", + } + } + }, + event_log.EVENT_ID.SYSTEM_CONFIG_UPDATE_AUTO_APPLY_ABORTING: { + 'entity_type': "orchestration", + 'entity': "orchestration=system-config-update", + 'event_type': event_log.EVENT_TYPE.ACTION_EVENT, + 'importance': event_log.EVENT_IMPORTANCE.HIGH, + 'reason_text': "System config update auto-apply aborting", + 'exclude_event_context': [], + 'event_context_data': { + event_log.EVENT_CONTEXT.ADMIN: { + 'entity_type': "orchestration", + 'entity': "orchestration=system-config-update", + 'reason_text': "System config update auto-apply aborting", + } + } + }, + event_log.EVENT_ID.SYSTEM_CONFIG_UPDATE_AUTO_APPLY_ABORT_REJECTED: { + 'entity_type': "orchestration", + 'entity': "orchestration=system-config-update", + 'event_type': event_log.EVENT_TYPE.ACTION_EVENT, + 'importance': event_log.EVENT_IMPORTANCE.HIGH, + 'reason_text': "System config update auto-apply abort rejected", + 'exclude_event_context': [], + 'event_context_data': { + event_log.EVENT_CONTEXT.ADMIN: { + 'entity_type': "orchestration", + 'entity': "orchestration=system-config-update", + 'reason_text': "System config update auto-apply abort " + "rejected%(reason)s", + } + } + }, + event_log.EVENT_ID.SYSTEM_CONFIG_UPDATE_AUTO_APPLY_ABORT_FAILED: { + 'entity_type': "orchestration", + 'entity': "orchestration=system-config-update", + 'event_type': event_log.EVENT_TYPE.ACTION_EVENT, + 'importance': event_log.EVENT_IMPORTANCE.HIGH, + 'reason_text': "System config update auto-apply abort failed", + 'exclude_event_context': [], + 'event_context_data': { + event_log.EVENT_CONTEXT.ADMIN: { + 'entity_type': "orchestration", + 'entity': "orchestration=system-config-update", + 'reason_text': "System config update auto-apply abort " + "failed%(reason)s", + } + } + }, + event_log.EVENT_ID.SYSTEM_CONFIG_UPDATE_AUTO_APPLY_ABORTED: { + 'entity_type': "orchestration", + 'entity': "orchestration=system-config-update", + 'event_type': event_log.EVENT_TYPE.ACTION_EVENT, + 'importance': event_log.EVENT_IMPORTANCE.HIGH, + 'reason_text': "System config update auto-apply aborted", + 'exclude_event_context': [], + 'event_context_data': { + event_log.EVENT_CONTEXT.ADMIN: { + 'entity_type': "orchestration", + 'entity': "orchestration=system-config-update", + 'reason_text': "System config update auto-apply aborted", + } + } + }, event_log.EVENT_ID.KUBE_ROOTCA_UPDATE_AUTO_APPLY_START: { 'entity_type': "orchestration", 'entity': "orchestration=kube-rootca update", diff --git a/nfv/nfv-vim/nfv_vim/events/_vim_api_events.py b/nfv/nfv-vim/nfv_vim/events/_vim_api_events.py index b29d7033..b1bb0c35 100755 --- a/nfv/nfv-vim/nfv_vim/events/_vim_api_events.py +++ b/nfv/nfv-vim/nfv_vim/events/_vim_api_events.py @@ -1,5 +1,5 @@ # -# Copyright (c) 2015-2021 Wind River Systems, Inc. +# Copyright (c) 2015-2023 Wind River Systems, Inc. # # SPDX-License-Identifier: Apache-2.0 # @@ -196,6 +196,9 @@ def _vim_api_message_handler(connection, msg): elif rpc.RPC_MSG_TYPE.CREATE_SW_UPGRADE_STRATEGY_REQUEST == msg.type: vim_sw_update_api_create_strategy(connection, msg) + elif rpc.RPC_MSG_TYPE.CREATE_SYSTEM_CONFIG_UPDATE_STRATEGY_REQUEST == msg.type: + vim_sw_update_api_create_strategy(connection, msg) + elif rpc.RPC_MSG_TYPE.APPLY_SW_UPDATE_STRATEGY_REQUEST == msg.type: vim_sw_update_api_apply_strategy(connection, msg) diff --git a/nfv/nfv-vim/nfv_vim/events/_vim_nfvi_events.py b/nfv/nfv-vim/nfv_vim/events/_vim_nfvi_events.py index 3af45760..2fa1c51b 100755 --- a/nfv/nfv-vim/nfv_vim/events/_vim_nfvi_events.py +++ b/nfv/nfv-vim/nfv_vim/events/_vim_nfvi_events.py @@ -1,5 +1,5 @@ # -# Copyright (c) 2015-2021 Wind River Systems, Inc. +# Copyright (c) 2015-2023 Wind River Systems, Inc. # # SPDX-License-Identifier: Apache-2.0 # @@ -122,6 +122,8 @@ def _nfvi_sw_update_get_callback(): sw_update_type = 'sw-patch' elif sw_update.sw_update_type == objects.SW_UPDATE_TYPE.SW_UPGRADE: sw_update_type = 'sw-upgrade' + elif sw_update.sw_update_type == objects.SW_UPDATE_TYPE.SYSTEM_CONFIG_UPDATE: + sw_update_type = 'system-config-update' elif sw_update.sw_update_type == objects.SW_UPDATE_TYPE.FW_UPDATE: sw_update_type = 'fw-update' elif sw_update.sw_update_type == objects.SW_UPDATE_TYPE.KUBE_ROOTCA_UPDATE: diff --git a/nfv/nfv-vim/nfv_vim/events/_vim_sw_update_api_events.py b/nfv/nfv-vim/nfv_vim/events/_vim_sw_update_api_events.py index c8769106..a5aa0a95 100755 --- a/nfv/nfv-vim/nfv_vim/events/_vim_sw_update_api_events.py +++ b/nfv/nfv-vim/nfv_vim/events/_vim_sw_update_api_events.py @@ -1,5 +1,5 @@ # -# Copyright (c) 2015-2021 Wind River Systems, Inc. +# Copyright (c) 2015-2023 Wind River Systems, Inc. # # SPDX-License-Identifier: Apache-2.0 # @@ -139,6 +139,13 @@ def vim_sw_update_api_create_strategy(connection, msg): alarm_restrictions, to_version, _vim_sw_update_api_create_strategy_callback) + elif 'system-config-update' == msg.sw_update_type: + uuid, reason = sw_mgmt_director.create_system_config_update_strategy( + controller_apply_type, storage_apply_type, + worker_apply_type, max_parallel_worker_hosts, + default_instance_action, + alarm_restrictions, + _vim_sw_update_api_create_strategy_callback) else: DLOG.error("Invalid message name: %s" % msg.sw_update_type) response = rpc.APIResponseCreateSwUpdateStrategy() @@ -205,6 +212,8 @@ def vim_sw_update_api_apply_strategy(connection, msg): sw_update_type = objects.SW_UPDATE_TYPE.KUBE_ROOTCA_UPDATE elif 'kube-upgrade' == msg.sw_update_type: sw_update_type = objects.SW_UPDATE_TYPE.KUBE_UPGRADE + elif 'system-config-update' == msg.sw_update_type: + sw_update_type = objects.SW_UPDATE_TYPE.SYSTEM_CONFIG_UPDATE else: DLOG.error("Invalid message name: %s" % msg.sw_update_type) sw_update_type = 'unknown' @@ -264,6 +273,8 @@ def vim_sw_update_api_abort_strategy(connection, msg): sw_update_type = objects.SW_UPDATE_TYPE.KUBE_ROOTCA_UPDATE elif 'kube-upgrade' == msg.sw_update_type: sw_update_type = objects.SW_UPDATE_TYPE.KUBE_UPGRADE + elif 'system-config-update' == msg.sw_update_type: + sw_update_type = objects.SW_UPDATE_TYPE.SYSTEM_CONFIG_UPDATE else: DLOG.error("Invalid message name: %s" % msg.sw_update_type) sw_update_type = 'unknown' @@ -320,6 +331,8 @@ def vim_sw_update_api_delete_strategy(connection, msg): sw_update_type = objects.SW_UPDATE_TYPE.KUBE_ROOTCA_UPDATE elif 'kube-upgrade' == msg.sw_update_type: sw_update_type = objects.SW_UPDATE_TYPE.KUBE_UPGRADE + elif 'system-config-update' == msg.sw_update_type: + sw_update_type = objects.SW_UPDATE_TYPE.SYSTEM_CONFIG_UPDATE else: DLOG.error("Invalid message name: %s" % msg.sw_update_type) sw_update_type = 'unknown' @@ -355,6 +368,8 @@ def vim_sw_update_api_get_strategy(connection, msg): sw_update_type = objects.SW_UPDATE_TYPE.KUBE_ROOTCA_UPDATE elif 'kube-upgrade' == msg.sw_update_type: sw_update_type = objects.SW_UPDATE_TYPE.KUBE_UPGRADE + elif 'system-config-update' == msg.sw_update_type: + sw_update_type = objects.SW_UPDATE_TYPE.SYSTEM_CONFIG_UPDATE else: DLOG.error("Invalid message name: %s" % msg.sw_update_type) sw_update_type = 'unknown' diff --git a/nfv/nfv-vim/nfv_vim/nfvi/__init__.py b/nfv/nfv-vim/nfv_vim/nfvi/__init__.py index abb1d022..1171a520 100755 --- a/nfv/nfv-vim/nfv_vim/nfvi/__init__.py +++ b/nfv/nfv-vim/nfv_vim/nfvi/__init__.py @@ -106,6 +106,7 @@ from nfv_vim.nfvi._nfvi_infrastructure_module import nfvi_get_kube_rootca_update from nfv_vim.nfvi._nfvi_infrastructure_module import nfvi_get_kube_upgrade # noqa: F401 from nfv_vim.nfvi._nfvi_infrastructure_module import nfvi_get_kube_version_list # noqa: F401 from nfv_vim.nfvi._nfvi_infrastructure_module import nfvi_get_logs # noqa: F401 +from nfv_vim.nfvi._nfvi_infrastructure_module import nfvi_get_system_config_unlock_request # noqa: F401 from nfv_vim.nfvi._nfvi_infrastructure_module import nfvi_get_system_info # noqa: F401 from nfv_vim.nfvi._nfvi_infrastructure_module import nfvi_get_system_state # noqa: F401 from nfv_vim.nfvi._nfvi_infrastructure_module import nfvi_get_terminating_pods # noqa: F401 @@ -129,6 +130,7 @@ from nfv_vim.nfvi._nfvi_infrastructure_module import nfvi_kube_upgrade_complete from nfv_vim.nfvi._nfvi_infrastructure_module import nfvi_kube_upgrade_download_images # noqa: F401 from nfv_vim.nfvi._nfvi_infrastructure_module import nfvi_kube_upgrade_networking # noqa: F401 from nfv_vim.nfvi._nfvi_infrastructure_module import nfvi_kube_upgrade_start # noqa: F401 +from nfv_vim.nfvi._nfvi_infrastructure_module import nfvi_list_deployment_hosts # noqa: F401 from nfv_vim.nfvi._nfvi_infrastructure_module import nfvi_lock_host # noqa: F401 from nfv_vim.nfvi._nfvi_infrastructure_module import nfvi_notify_host_failed # noqa: F401 from nfv_vim.nfvi._nfvi_infrastructure_module import nfvi_notify_host_services_delete_failed # noqa: F401 diff --git a/nfv/nfv-vim/nfv_vim/nfvi/_nfvi_infrastructure_module.py b/nfv/nfv-vim/nfv_vim/nfvi/_nfvi_infrastructure_module.py index 668f7676..ed5bd6d4 100755 --- a/nfv/nfv-vim/nfv_vim/nfvi/_nfvi_infrastructure_module.py +++ b/nfv/nfv-vim/nfv_vim/nfvi/_nfvi_infrastructure_module.py @@ -59,6 +59,35 @@ def nfvi_get_host(host_uuid, host_name, callback): return cmd_id +def nfvi_get_deployment_host(host_name, callback): + """ + Get host resource from deployment namespace + """ + cmd_id = _infrastructure_plugin.invoke_plugin('get_deployment_host', + host_name, + callback=callback) + return cmd_id + + +def nfvi_list_deployment_hosts(callback): + """ + Get host resource from deployment namespace + """ + cmd_id = _infrastructure_plugin.invoke_plugin('list_deployment_hosts', + callback=callback) + return cmd_id + + +def nfvi_get_system_config_unlock_request(host_names, callback): + """ + Get host unlock request from deployment namespace + """ + cmd_id = _infrastructure_plugin.invoke_plugin('get_system_config_unlock_request', + host_names, + callback=callback) + return cmd_id + + def nfvi_get_host_devices(host_uuid, host_name, callback): """ Get host device list details diff --git a/nfv/nfv-vim/nfv_vim/nfvi/objects/v1/__init__.py b/nfv/nfv-vim/nfv_vim/nfvi/objects/v1/__init__.py index f0dd696d..2a0e1139 100755 --- a/nfv/nfv-vim/nfv_vim/nfvi/objects/v1/__init__.py +++ b/nfv/nfv-vim/nfv_vim/nfvi/objects/v1/__init__.py @@ -1,5 +1,5 @@ # -# Copyright (c) 2015-2021 Wind River Systems, Inc. +# Copyright (c) 2015-2023 Wind River Systems, Inc. # # SPDX-License-Identifier: Apache-2.0 # @@ -22,6 +22,7 @@ from nfv_vim.nfvi.objects.v1._host_fw_update import HostFwUpdate # noqa: F401 from nfv_vim.nfvi.objects.v1._host_group import HOST_GROUP_POLICY # noqa: F401 from nfv_vim.nfvi.objects.v1._host_group import HostGroup # noqa: F401 from nfv_vim.nfvi.objects.v1._host_sw_patch import HostSwPatch # noqa: F401 +from nfv_vim.nfvi.objects.v1._host_system_config_update import HostSystemConfigUpdate # noqa: F401 from nfv_vim.nfvi.objects.v1._hypervisor import Hypervisor # noqa: F401 from nfv_vim.nfvi.objects.v1._hypervisor import HYPERVISOR_ADMIN_STATE # noqa: F401 from nfv_vim.nfvi.objects.v1._hypervisor import HYPERVISOR_OPER_STATE # noqa: F401 diff --git a/nfv/nfv-vim/nfv_vim/nfvi/objects/v1/_host_system_config_update.py b/nfv/nfv-vim/nfv_vim/nfvi/objects/v1/_host_system_config_update.py new file mode 100644 index 00000000..c95a6ee1 --- /dev/null +++ b/nfv/nfv-vim/nfv_vim/nfvi/objects/v1/_host_system_config_update.py @@ -0,0 +1,16 @@ +# +# Copyright (c) 2023 Wind River Systems, Inc. +# +# SPDX-License-Identifier: Apache-2.0 +# +from nfv_vim.nfvi.objects.v1._object import ObjectData + + +class HostSystemConfigUpdate(ObjectData): + """ + NFVI Host System Config Update Object + """ + def __init__(self, name, unlock_request): + super(HostSystemConfigUpdate, self).__init__('1.0.0') + self.update(dict(name=name, + unlock_request=unlock_request)) diff --git a/nfv/nfv-vim/nfv_vim/objects/__init__.py b/nfv/nfv-vim/nfv_vim/objects/__init__.py index 5b3edf8e..ebdc7db0 100755 --- a/nfv/nfv-vim/nfv_vim/objects/__init__.py +++ b/nfv/nfv-vim/nfv_vim/objects/__init__.py @@ -1,5 +1,5 @@ # -# Copyright (c) 2015-2021 Wind River Systems, Inc. +# Copyright (c) 2015-2023 Wind River Systems, Inc. # # SPDX-License-Identifier: Apache-2.0 # @@ -40,6 +40,7 @@ from nfv_vim.objects._sw_update import SW_UPDATE_TYPE # noqa: F401 from nfv_vim.objects._sw_update import SwUpdate # noqa: F401 from nfv_vim.objects._sw_upgrade import SwUpgrade # noqa: F401 from nfv_vim.objects._system import System # noqa: F401 +from nfv_vim.objects._system_config_update import SystemConfigUpdate # noqa: F401 from nfv_vim.objects._tenant import Tenant # noqa: F401 from nfv_vim.objects._volume import Volume # noqa: F401 from nfv_vim.objects._volume_snapshot import VolumeSnapshot # noqa: F401 diff --git a/nfv/nfv-vim/nfv_vim/objects/_sw_update.py b/nfv/nfv-vim/nfv_vim/objects/_sw_update.py index 0e8d8be3..ceb501f7 100755 --- a/nfv/nfv-vim/nfv_vim/objects/_sw_update.py +++ b/nfv/nfv-vim/nfv_vim/objects/_sw_update.py @@ -1,5 +1,5 @@ # -# Copyright (c) 2016-2021 Wind River Systems, Inc. +# Copyright (c) 2016-2023 Wind River Systems, Inc. # # SPDX-License-Identifier: Apache-2.0 # @@ -29,6 +29,7 @@ class SwUpdateTypes(Constants): """ SW_PATCH = Constant('sw-patch') SW_UPGRADE = Constant('sw-upgrade') + SYSTEM_CONFIG_UPDATE = Constant('system-config-update') FW_UPDATE = Constant('fw-update') KUBE_ROOTCA_UPDATE = Constant('kube-rootca-update') KUBE_UPGRADE = Constant('kube-upgrade') diff --git a/nfv/nfv-vim/nfv_vim/objects/_system_config_update.py b/nfv/nfv-vim/nfv_vim/objects/_system_config_update.py new file mode 100644 index 00000000..5bdac7a2 --- /dev/null +++ b/nfv/nfv-vim/nfv_vim/objects/_system_config_update.py @@ -0,0 +1,197 @@ +# +# Copyright (c) 2023 Wind River Systems, Inc. +# +# SPDX-License-Identifier: Apache-2.0 +# +from nfv_common import debug +from nfv_common import timers + +from nfv_common.helpers import coroutine + +from nfv_vim import alarm +from nfv_vim import event_log +from nfv_vim import nfvi + +from nfv_vim.objects._sw_update import SW_UPDATE_ALARM_TYPES +from nfv_vim.objects._sw_update import SW_UPDATE_EVENT_IDS +from nfv_vim.objects._sw_update import SW_UPDATE_TYPE +from nfv_vim.objects._sw_update import SwUpdate + +DLOG = debug.debug_get_logger('nfv_vim.objects.system_config_update') + + +class SystemConfigUpdate(SwUpdate): + """ + System config update Object + """ + def __init__(self, sw_update_uuid=None, strategy_data=None): + super(SystemConfigUpdate, self).__init__( + sw_update_type=SW_UPDATE_TYPE.SYSTEM_CONFIG_UPDATE, + sw_update_uuid=sw_update_uuid, + strategy_data=strategy_data) + + self._system_config_update_hosts = list() + + def strategy_build(self, + strategy_uuid, + controller_apply_type, + storage_apply_type, + worker_apply_type, + max_parallel_worker_hosts, + default_instance_action, + alarm_restrictions, + ignore_alarms, + single_controller): + """ + Create a system config update strategy + """ + from nfv_vim import strategy + + if self._strategy: + reason = "strategy already exists of type:%s" % self._sw_update_type + return False, reason + + self._strategy = \ + strategy.SystemConfigUpdateStrategy(strategy_uuid, + controller_apply_type, + storage_apply_type, + worker_apply_type, + max_parallel_worker_hosts, + default_instance_action, + alarm_restrictions, + ignore_alarms, + single_controller) + self._strategy.sw_update_obj = self + self._strategy.build() + self._persist() + return True, '' + + def strategy_build_complete(self, success, reason): + """ + Creation of a system config update strategy complete + """ + DLOG.info("System config update strategy build complete.") + pass + + @staticmethod + def alarm_type(alarm_type): + """ + Returns ALARM_TYPE corresponding to SW_UPDATE_ALARM_TYPES + """ + ALARM_TYPE_MAPPING = { + SW_UPDATE_ALARM_TYPES.APPLY_INPROGRESS: + alarm.ALARM_TYPE.SYSTEM_CONFIG_UPDATE_AUTO_APPLY_INPROGRESS, + SW_UPDATE_ALARM_TYPES.APPLY_ABORTING: + alarm.ALARM_TYPE.SYSTEM_CONFIG_UPDATE_AUTO_APPLY_ABORTING, + SW_UPDATE_ALARM_TYPES.APPLY_FAILED: + alarm.ALARM_TYPE.SYSTEM_CONFIG_UPDATE_AUTO_APPLY_FAILED, + } + return ALARM_TYPE_MAPPING[alarm_type] + + @staticmethod + def event_id(event_id): + """ + Returns EVENT_ID corresponding to SW_UPDATE_EVENT_IDS + """ + EVENT_ID_MAPPING = { + SW_UPDATE_EVENT_IDS.APPLY_START: + event_log.EVENT_ID.SYSTEM_CONFIG_UPDATE_AUTO_APPLY_START, + SW_UPDATE_EVENT_IDS.APPLY_INPROGRESS: + event_log.EVENT_ID.SYSTEM_CONFIG_UPDATE_AUTO_APPLY_INPROGRESS, + SW_UPDATE_EVENT_IDS.APPLY_REJECTED: + event_log.EVENT_ID.SYSTEM_CONFIG_UPDATE_AUTO_APPLY_REJECTED, + SW_UPDATE_EVENT_IDS.APPLY_CANCELLED: + event_log.EVENT_ID.SYSTEM_CONFIG_UPDATE_AUTO_APPLY_CANCELLED, + SW_UPDATE_EVENT_IDS.APPLY_FAILED: + event_log.EVENT_ID.SYSTEM_CONFIG_UPDATE_AUTO_APPLY_FAILED, + SW_UPDATE_EVENT_IDS.APPLY_COMPLETED: + event_log.EVENT_ID.SYSTEM_CONFIG_UPDATE_AUTO_APPLY_COMPLETED, + SW_UPDATE_EVENT_IDS.APPLY_ABORT: + event_log.EVENT_ID.SYSTEM_CONFIG_UPDATE_AUTO_APPLY_ABORT, + SW_UPDATE_EVENT_IDS.APPLY_ABORTING: + event_log.EVENT_ID.SYSTEM_CONFIG_UPDATE_AUTO_APPLY_ABORTING, + SW_UPDATE_EVENT_IDS.APPLY_ABORT_REJECTED: + event_log.EVENT_ID.SYSTEM_CONFIG_UPDATE_AUTO_APPLY_ABORT_REJECTED, + SW_UPDATE_EVENT_IDS.APPLY_ABORT_FAILED: + event_log.EVENT_ID.SYSTEM_CONFIG_UPDATE_AUTO_APPLY_ABORT_FAILED, + SW_UPDATE_EVENT_IDS.APPLY_ABORTED: + event_log.EVENT_ID.SYSTEM_CONFIG_UPDATE_AUTO_APPLY_ABORTED, + } + return EVENT_ID_MAPPING[event_id] + + def nfvi_update(self): + """ + NFVI Update + """ + if self._strategy is None: + if self._alarms: + alarm.clear_sw_update_alarm(self._alarms) + return False + + if self.strategy.is_applying(): + if not self._alarms: + self._alarms = alarm.raise_sw_update_alarm( + self.alarm_type(SW_UPDATE_ALARM_TYPES.APPLY_INPROGRESS)) + event_log.sw_update_issue_log( + self.event_id(SW_UPDATE_EVENT_IDS.APPLY_INPROGRESS)) + + elif (self.strategy.is_apply_failed() or + self.strategy.is_apply_timed_out()): + for system_config_update_host in self._system_config_update_hosts: + if not self._alarms: + self._alarms = \ + alarm.raise_sw_update_alarm( + self.alarm_type(SW_UPDATE_ALARM_TYPES.APPLY_FAILED)) + + event_log.sw_update_issue_log( + self.event_id(SW_UPDATE_EVENT_IDS.APPLY_FAILED)) + break + + else: + if self._alarms: + alarm.clear_sw_update_alarm(self._alarms) + return False + + elif self.strategy.is_aborting(): + if not self._alarms: + self._alarms = alarm.raise_sw_update_alarm( + self.alarm_type(SW_UPDATE_ALARM_TYPES.APPLY_ABORTING)) + event_log.sw_update_issue_log( + self.event_id(SW_UPDATE_EVENT_IDS.APPLY_ABORTING)) + + else: + if self._alarms: + alarm.clear_sw_update_alarm(self._alarms) + return False + + return True + + @coroutine + def nfvi_audit(self): + """ + Audit NFVI layer + """ + while True: + timer_id = (yield) + + DLOG.debug("Audit alarms, timer_id=%s." % timer_id) + self.nfvi_alarms_clear() + nfvi.nfvi_get_alarms(self.nfvi_alarms_callback(timer_id)) + if not nfvi.nfvi_fault_mgmt_plugin_disabled(): + nfvi.nfvi_get_openstack_alarms( + self.nfvi_alarms_callback(timer_id)) + self._nfvi_audit_inprogress = True + while self._nfvi_audit_inprogress: + timer_id = (yield) + + # nfvi_alarms_callback sets timer to 2 seconds. reset back to 30 + timers.timers_reschedule_timer(timer_id, 30) + + if not self.nfvi_update(): + DLOG.info("Audit no longer needed.") + break + + DLOG.verbose("Audit system config update still running, timer_id=%s." + % timer_id) + + self._nfvi_timer_id = None diff --git a/nfv/nfv-vim/nfv_vim/rpc/__init__.py b/nfv/nfv-vim/nfv_vim/rpc/__init__.py index 77906598..b42cb96b 100755 --- a/nfv/nfv-vim/nfv_vim/rpc/__init__.py +++ b/nfv/nfv-vim/nfv_vim/rpc/__init__.py @@ -1,5 +1,5 @@ # -# Copyright (c) 2015-2021 Wind River Systems, Inc. +# Copyright (c) 2015-2023 Wind River Systems, Inc. # # SPDX-License-Identifier: Apache-2.0 # @@ -77,6 +77,7 @@ from nfv_vim.rpc._rpc_message_sw_update import APIRequestCreateKubeRootcaUpdateS from nfv_vim.rpc._rpc_message_sw_update import APIRequestCreateKubeUpgradeStrategy # noqa: F401 from nfv_vim.rpc._rpc_message_sw_update import APIRequestCreateSwUpdateStrategy # noqa: F401 from nfv_vim.rpc._rpc_message_sw_update import APIRequestCreateSwUpgradeStrategy # noqa: F401 +from nfv_vim.rpc._rpc_message_sw_update import APIRequestCreateSystemConfigUpdateStrategy # noqa: F401 from nfv_vim.rpc._rpc_message_sw_update import APIRequestDeleteSwUpdateStrategy # noqa: F401 from nfv_vim.rpc._rpc_message_sw_update import APIRequestGetSwUpdateStrategy # noqa: F401 from nfv_vim.rpc._rpc_message_sw_update import APIResponseAbortSwUpdateStrategy # noqa: F401 diff --git a/nfv/nfv-vim/nfv_vim/rpc/_rpc_defs.py b/nfv/nfv-vim/nfv_vim/rpc/_rpc_defs.py index b6f3bead..ef674014 100755 --- a/nfv/nfv-vim/nfv_vim/rpc/_rpc_defs.py +++ b/nfv/nfv-vim/nfv_vim/rpc/_rpc_defs.py @@ -1,5 +1,5 @@ # -# Copyright (c) 2015-2021 Wind River Systems, Inc. +# Copyright (c) 2015-2023 Wind River Systems, Inc. # # SPDX-License-Identifier: Apache-2.0 # @@ -99,6 +99,7 @@ class _RPCMessageType(Constants): CREATE_KUBE_ROOTCA_UPDATE_STRATEGY_REQUEST = Constant('create-kube-rootca-update-strategy-request') CREATE_KUBE_UPGRADE_STRATEGY_REQUEST = Constant('create-kube-upgrade-strategy-request') CREATE_SW_UPGRADE_STRATEGY_REQUEST = Constant('create-sw-upgrade-strategy-request') + CREATE_SYSTEM_CONFIG_UPDATE_STRATEGY_REQUEST = Constant('create-system-config-update-strategy-request') CREATE_SW_UPDATE_STRATEGY_RESPONSE = Constant('create-sw-update-strategy-response') APPLY_SW_UPDATE_STRATEGY_REQUEST = Constant('apply-sw-update-strategy-request') APPLY_SW_UPDATE_STRATEGY_RESPONSE = Constant('apply-sw-update-strategy-response') diff --git a/nfv/nfv-vim/nfv_vim/rpc/_rpc_message.py b/nfv/nfv-vim/nfv_vim/rpc/_rpc_message.py index b0503636..f0cf5d6c 100755 --- a/nfv/nfv-vim/nfv_vim/rpc/_rpc_message.py +++ b/nfv/nfv-vim/nfv_vim/rpc/_rpc_message.py @@ -1,5 +1,5 @@ # -# Copyright (c) 2015-2021 Wind River Systems, Inc. +# Copyright (c) 2015-2023 Wind River Systems, Inc. # # SPDX-License-Identifier: Apache-2.0 # @@ -147,6 +147,7 @@ class RPCMessageFactory(object): from nfv_vim.rpc._rpc_message_sw_update import APIRequestCreateKubeUpgradeStrategy from nfv_vim.rpc._rpc_message_sw_update import APIRequestCreateSwUpdateStrategy from nfv_vim.rpc._rpc_message_sw_update import APIRequestCreateSwUpgradeStrategy + from nfv_vim.rpc._rpc_message_sw_update import APIRequestCreateSystemConfigUpdateStrategy from nfv_vim.rpc._rpc_message_sw_update import APIRequestDeleteSwUpdateStrategy from nfv_vim.rpc._rpc_message_sw_update import APIRequestGetSwUpdateStrategy from nfv_vim.rpc._rpc_message_sw_update import APIResponseAbortSwUpdateStrategy @@ -230,6 +231,7 @@ class RPCMessageFactory(object): RPC_MSG_TYPE.CREATE_KUBE_UPGRADE_STRATEGY_REQUEST: APIRequestCreateKubeUpgradeStrategy, RPC_MSG_TYPE.CREATE_SW_UPGRADE_STRATEGY_REQUEST: APIRequestCreateSwUpgradeStrategy, RPC_MSG_TYPE.CREATE_SW_UPDATE_STRATEGY_RESPONSE: APIResponseCreateSwUpdateStrategy, + RPC_MSG_TYPE.CREATE_SYSTEM_CONFIG_UPDATE_STRATEGY_REQUEST: APIRequestCreateSystemConfigUpdateStrategy, RPC_MSG_TYPE.APPLY_SW_UPDATE_STRATEGY_REQUEST: APIRequestApplySwUpdateStrategy, RPC_MSG_TYPE.APPLY_SW_UPDATE_STRATEGY_RESPONSE: APIResponseApplySwUpdateStrategy, RPC_MSG_TYPE.ABORT_SW_UPDATE_STRATEGY_REQUEST: APIRequestAbortSwUpdateStrategy, diff --git a/nfv/nfv-vim/nfv_vim/rpc/_rpc_message_sw_update.py b/nfv/nfv-vim/nfv_vim/rpc/_rpc_message_sw_update.py index 36f83e05..b9c15004 100755 --- a/nfv/nfv-vim/nfv_vim/rpc/_rpc_message_sw_update.py +++ b/nfv/nfv-vim/nfv_vim/rpc/_rpc_message_sw_update.py @@ -1,5 +1,5 @@ # -# Copyright (c) 2015-2021 Wind River Systems, Inc. +# Copyright (c) 2015-2023 Wind River Systems, Inc. # # SPDX-License-Identifier: Apache-2.0 # @@ -138,6 +138,21 @@ class APIRequestCreateKubeUpgradeStrategy(APIRequestCreateSwUpdateStrategy): return "create-kube-upgrade-strategy request: %s" % self.deserialize_payload +class APIRequestCreateSystemConfigUpdateStrategy(APIRequestCreateSwUpdateStrategy): + """ + RPC API Request Message - Create System Config Update Strategy + """ + def __init__(self, msg_version=RPC_MSG_VERSION.VERSION_1_0, + msg_type=RPC_MSG_TYPE.CREATE_SYSTEM_CONFIG_UPDATE_STRATEGY_REQUEST, + msg_result=RPC_MSG_RESULT.SUCCESS): + super(APIRequestCreateSystemConfigUpdateStrategy, self).__init__( + msg_version, msg_type, msg_result) + + def __str__(self): + return "create-system-config-update-strategy request: %s" % \ + self.deserialize_payload + + class APIResponseCreateSwUpdateStrategy(RPCMessage): """ RPC API Response Message - Create Software Update Strategy diff --git a/nfv/nfv-vim/nfv_vim/strategy/__init__.py b/nfv/nfv-vim/nfv_vim/strategy/__init__.py index fac2dc25..21e5af77 100755 --- a/nfv/nfv-vim/nfv_vim/strategy/__init__.py +++ b/nfv/nfv-vim/nfv_vim/strategy/__init__.py @@ -10,6 +10,7 @@ from nfv_vim.strategy._strategy import KubeUpgradeStrategy # noqa: F401 from nfv_vim.strategy._strategy import strategy_rebuild_from_dict # noqa: F401 from nfv_vim.strategy._strategy import SwPatchStrategy # noqa: F401 from nfv_vim.strategy._strategy import SwUpgradeStrategy # noqa: F401 +from nfv_vim.strategy._strategy import SystemConfigUpdateStrategy # noqa: F401 from nfv_vim.strategy._strategy_defs import STRATEGY_EVENT # noqa: F401 from nfv_vim.strategy._strategy_stages import STRATEGY_STAGE_NAME # noqa: F401 from nfv_vim.strategy._strategy_steps import ApplySwPatchesStep # noqa: F401 @@ -47,6 +48,7 @@ from nfv_vim.strategy._strategy_steps import QueryKubeUpgradeStep # noqa: F401 from nfv_vim.strategy._strategy_steps import QueryKubeVersionsStep # noqa: F401 from nfv_vim.strategy._strategy_steps import QuerySwPatchesStep # noqa: F401 from nfv_vim.strategy._strategy_steps import QuerySwPatchHostsStep # noqa: F401 +from nfv_vim.strategy._strategy_steps import QuerySystemConfigUpdateHostsStep # noqa: F401 from nfv_vim.strategy._strategy_steps import QueryUpgradeStep # noqa: F401 from nfv_vim.strategy._strategy_steps import RebootHostsStep # noqa: F401 from nfv_vim.strategy._strategy_steps import StartInstancesStep # noqa: F401 @@ -54,6 +56,7 @@ from nfv_vim.strategy._strategy_steps import StopInstancesStep # noqa: F401 from nfv_vim.strategy._strategy_steps import STRATEGY_STEP_NAME # noqa: F401 from nfv_vim.strategy._strategy_steps import SwactHostsStep # noqa: F401 from nfv_vim.strategy._strategy_steps import SwPatchHostsStep # noqa: F401 +from nfv_vim.strategy._strategy_steps import SystemConfigUpdateHostsStep # noqa: F401 from nfv_vim.strategy._strategy_steps import SystemStabilizeStep # noqa: F401 from nfv_vim.strategy._strategy_steps import UnlockHostsStep # noqa: F401 from nfv_vim.strategy._strategy_steps import UpgradeActivateStep # noqa: F401 diff --git a/nfv/nfv-vim/nfv_vim/strategy/_strategy.py b/nfv/nfv-vim/nfv_vim/strategy/_strategy.py index f377798c..b10e8f2d 100755 --- a/nfv/nfv-vim/nfv_vim/strategy/_strategy.py +++ b/nfv/nfv-vim/nfv_vim/strategy/_strategy.py @@ -36,6 +36,7 @@ class StrategyNames(Constants): FW_UPDATE = Constant('fw-update') KUBE_ROOTCA_UPDATE = Constant('kube-rootca-update') KUBE_UPGRADE = Constant('kube-upgrade') + SYSYTEM_CONFIG_UPDATE = Constant('system-config-update') # Constant Instantiation @@ -433,6 +434,21 @@ class SwUpdateStrategy(strategy.Strategy): self.sw_update_obj.strategy_abort_complete( False, self.abort_phase.result_reason) + def report_build_failure(self, reason): + """ + Report a build failure for the strategy + + todo(yuxing): report all build failure use this method + """ + DLOG.warn("Strategy Build Failed: %s" % reason) + self._state = strategy.STRATEGY_STATE.BUILD_FAILED + self.build_phase.result = strategy.STRATEGY_PHASE_RESULT.FAILED + self.build_phase.result_reason = reason + self.sw_update_obj.strategy_build_complete( + False, + self.build_phase.result_reason) + self.save() + def from_dict(self, data, build_phase=None, apply_phase=None, abort_phase=None): """ Initializes a software update strategy object using the given dictionary @@ -863,6 +879,54 @@ class QueryKubeVersionsMixin(QueryMixinBase): data['nfvi_kube_versions_list_data'] = mixin_data +class QuerySystemConfigUpdateHostsMixin(QueryMixinBase): + """This mixin is used through the QuerySystemConfigUpdateHostsMixin class""" + + def initialize_mixin(self): + super(QuerySystemConfigUpdateHostsMixin, self).initialize_mixin() + self._nfvi_system_config_update_hosts = list() + + @property + def nfvi_system_config_update_hosts(self): + """ + Returns the System Config Update hosts from the NFVI layer + """ + return self._nfvi_system_config_update_hosts + + @nfvi_system_config_update_hosts.setter + def nfvi_system_config_update_hosts(self, nfvi_system_config_update_hosts): + """ + Save the System Config Update hosts from the NFVI Layer + """ + self._nfvi_system_config_update_hosts = nfvi_system_config_update_hosts + + def mixin_from_dict(self, data): + """ + Extracts this mixin data from a dictionary + """ + super(QuerySystemConfigUpdateHostsMixin, self).mixin_from_dict(data) + + from nfv_vim import nfvi + + mixin_data = list() + for host_data in data['nfvi_system_config_update_hosts_data']: + host = nfvi.objects.v1.HostSystemConfigUpdate( + host_data['name'], + host_data['unlock_request']) + mixin_data.append(host) + self._nfvi_system_config_update_hosts = mixin_data + + def mixin_as_dict(self, data): + """ + Updates the dictionary with this mixin data + """ + super(QuerySystemConfigUpdateHostsMixin, self).mixin_as_dict(data) + mixin_data = list() + for host in self._nfvi_system_config_update_hosts: + mixin_data.append(host.as_dict()) + data['nfvi_system_config_update_hosts_data'] = mixin_data + + class UpdateControllerHostsMixin(object): def _add_update_controller_strategy_stages(self, @@ -990,6 +1054,19 @@ class PatchControllerHostsMixin(UpdateControllerHostsMixin): strategy.SwPatchHostsStep) +class UpdateSystemConfigControllerHostsMixin(UpdateControllerHostsMixin): + def _add_system_config_controller_strategy_stages(self, controllers): + """ + Add controller system config update stages to a strategy + """ + from nfv_vim import strategy + return self._add_update_controller_strategy_stages( + controllers, + True, + strategy.STRATEGY_STAGE_NAME.SYSTEM_CONFIG_UPDATE_CONTROLLERS, + strategy.SystemConfigUpdateHostsStep) + + class UpgradeKubeletControllerHostsMixin(UpdateControllerHostsMixin): def _add_kubelet_controller_strategy_stages(self, controllers, to_version, reboot, stage_name): from nfv_vim import strategy @@ -1064,6 +1141,19 @@ class PatchStorageHostsMixin(UpdateStorageHostsMixin): strategy.SwPatchHostsStep) +class UpdateSystemConfigStorageHostsMixin(UpdateStorageHostsMixin): + def _add_system_config_storage_strategy_stages(self, storage_hosts): + """ + Add storage system config update stages to a strategy + """ + from nfv_vim import strategy + return self._add_update_storage_strategy_stages( + storage_hosts, + True, + strategy.STRATEGY_STAGE_NAME.SYSTEM_CONFIG_UPDATE_STORAGE_HOSTS, + strategy.SystemConfigUpdateHostsStep) + + class UpdateWorkerHostsMixin(object): """ Adds the ability to add update steps for worker hosts to a strategy. @@ -1248,6 +1338,19 @@ class UpgradeKubeletWorkerHostsMixin(UpdateWorkerHostsMixin): extra_args=to_version) +class UpdateSystemConfigWorkerHostsMixin(UpdateWorkerHostsMixin): + def _add_system_config_worker_strategy_stages(self, worker_hosts): + """ + Add worker system config update stages to a strategy + """ + from nfv_vim import strategy + return self._add_update_worker_strategy_stages( + worker_hosts, + True, + strategy.STRATEGY_STAGE_NAME.SYSTEM_CONFIG_UPDATE_WORKER_HOSTS, + strategy.SystemConfigUpdateHostsStep) + + ################################################################### # # The Software Patch Strategy @@ -2198,6 +2301,199 @@ class SwUpgradeStrategy(SwUpdateStrategy): return data +################################################################### +# +# The System Config Update Strategy +# +################################################################### +class SystemConfigUpdateStrategy(SwUpdateStrategy, + QuerySystemConfigUpdateHostsMixin, + UpdateSystemConfigControllerHostsMixin, + UpdateSystemConfigStorageHostsMixin, + UpdateSystemConfigWorkerHostsMixin): + """ + System Config Update - Strategy + """ + def __init__(self, uuid, controller_apply_type, storage_apply_type, + worker_apply_type, max_parallel_worker_hosts, + default_instance_action, alarm_restrictions, + ignore_alarms, single_controller): + super(SystemConfigUpdateStrategy, self).__init__( + uuid, + STRATEGY_NAME.SYSYTEM_CONFIG_UPDATE, + controller_apply_type, + storage_apply_type, + SW_UPDATE_APPLY_TYPE.IGNORE, + worker_apply_type, + max_parallel_worker_hosts, + default_instance_action, + alarm_restrictions, + ignore_alarms) + + # The following alarms will not prevent a system config update operation + IGNORE_ALARMS = ['100.103', # Memory threshold exceeded + '200.001', # Locked Host + '250.001', # System Config out of date + '260.001', # Unreconciled resource + '260.002', # Unsynchronized resource + '280.001', # Subcloud resource off-line + '280.002', # Subcloud resource out-of-sync + '280.003', # Subcloud backup failed + '500.200', # Certificate expiring soon + '700.004', # VM stopped + '750.006', # Configuration change requires reapply of an application + '900.010', # System Config Update in progress + '900.601', # System Config Update Auto Apply in progress + ] + self._ignore_alarms += IGNORE_ALARMS + self._single_controller = single_controller + + # initialize the variables required by the mixins + self.initialize_mixin() + + def build(self): + """ + Build the strategy + """ + from nfv_vim import strategy + + stage = strategy.StrategyStage( + strategy.STRATEGY_STAGE_NAME.SYSTEM_CONFIG_UPDATE_QUERY) + stage.add_step(strategy.QueryAlarmsStep( + ignore_alarms=self._ignore_alarms)) + stage.add_step(strategy.QuerySystemConfigUpdateHostsStep()) + self.build_phase.add_stage(stage) + super(SystemConfigUpdateStrategy, self).build() + + def build_complete(self, result, result_reason): + """ + Strategy Build Complete + """ + from nfv_vim import strategy + from nfv_vim import tables + + result, result_reason = \ + super(SystemConfigUpdateStrategy, self).build_complete(result, result_reason) + + DLOG.info("Build Complete Callback, result=%s, reason=%s." + % (result, result_reason)) + + if result in [strategy.STRATEGY_RESULT.SUCCESS, + strategy.STRATEGY_RESULT.DEGRADED]: + + if self._nfvi_alarms: + alarm_id_set = set() + for alarm_data in self._nfvi_alarms: + alarm_id_set.add(alarm_data['alarm_id']) + alarm_id_list = ", ".join(sorted(alarm_id_set)) + DLOG.warn("System config update: Active alarms present [ %s ]" + % alarm_id_list) + self.report_build_failure("active alarms present [ %s ]" + % alarm_id_list) + + return + + host_table = tables.tables_get_host_table() + for host in list(host_table.values()): + if HOST_PERSONALITY.WORKER in host.personality and \ + HOST_PERSONALITY.CONTROLLER not in host.personality: + # Allow system config update orchestration when worker + # hosts are available, locked or powered down. + if not ((host.is_unlocked() and host.is_enabled() and + host.is_available()) or + (host.is_locked() and host.is_disabled() and + host.is_offline()) or + (host.is_locked() and host.is_disabled() and + host.is_online())): + self.report_build_failure( + "all worker hosts must be unlocked-enabled-available, " + "locked-disabled-online or locked-disabled-offline") + return + else: + # Only allow system config update orchestration when all + # controller and storage hosts are available. The config + # update wil be blocked when we do not have full redundancy. + if not (host.is_unlocked() and host.is_enabled() and + host.is_available()): + self.report_build_failure( + "all %s hosts must be unlocked-enabled-available, " + % host.personality) + return + + controller_hosts = list() + storage_hosts = list() + worker_hosts = list() + host_list = list(host_table.values()) + + for host_resource in self.nfvi_system_config_update_hosts: + for host in host_list: + if host_resource.name == host.name: + if HOST_PERSONALITY.CONTROLLER in host.personality and \ + host_resource.unlock_request != 'not_required': + controller_hosts.append(host) + + elif HOST_PERSONALITY.STORAGE in host.personality and \ + host_resource.unlock_request != 'not_required': + storage_hosts.append(host) + + if HOST_PERSONALITY.WORKER in host.personality and \ + host_resource.unlock_request != 'not_required': + worker_hosts.append(host) + + host_list.remove(host) + break + + STRATEGY_CREATION_COMMANDS = [ + (self._add_system_config_controller_strategy_stages, + controller_hosts), + (self._add_system_config_storage_strategy_stages, + storage_hosts), + (self._add_system_config_worker_strategy_stages, + worker_hosts) + ] + + for add_strategy_stages_function, host_list in \ + STRATEGY_CREATION_COMMANDS: + if host_list: + success, reason = add_strategy_stages_function(host_list) + if not success: + self.report_build_failure(reason) + return + + if 0 == len(self.apply_phase.stages): + self.report_build_failure( + "no system config updates need to be applied") + return + else: + self.sw_update_obj.strategy_build_complete( # pylint: disable=no-member + False, self.build_phase.result_reason) + + self.sw_update_obj.strategy_build_complete(True, '') # pylint: disable=no-member + self.save() + + def from_dict(self, data, build_phase=None, apply_phase=None, abort_phase=None): + """ + Initializes a system config update strategy object using the given + dictionary + """ + super(SystemConfigUpdateStrategy, self).from_dict( + data, build_phase, apply_phase, abort_phase) + self._single_controller = data['single_controller'] + + self.mixin_from_dict(data) + return self + + def as_dict(self): + """ + Represent the software upgrade strategy as a dictionary + """ + data = super(SystemConfigUpdateStrategy, self).as_dict() + data['single_controller'] = self._single_controller + + self.mixin_as_dict(data) + return data + + ################################################################### # # The Firmware Update Strategy @@ -2610,21 +2906,6 @@ class KubeRootcaUpdateStrategy(SwUpdateStrategy, # initialize the variables required by the mixins self.initialize_mixin() - def report_build_failure(self, reason): - """ - Report a build failure for the strategy - - todo(abailey): this should be in the superclass - """ - DLOG.warn("Strategy Build Failed: %s" % reason) - self._state = strategy.STRATEGY_STATE.BUILD_FAILED - self.build_phase.result = strategy.STRATEGY_PHASE_RESULT.FAILED - self.build_phase.result_reason = reason - self.sw_update_obj.strategy_build_complete( - False, - self.build_phase.result_reason) - self.save() - def build(self): """Build the strategy""" from nfv_vim import strategy @@ -3444,16 +3725,6 @@ class KubeUpgradeStrategy(SwUpdateStrategy, stage.add_step(strategy.KubeUpgradeCleanupStep()) self.apply_phase.add_stage(stage) - def report_build_failure(self, reason): - DLOG.warn("Strategy Build Failed: %s" % reason) - self._state = strategy.STRATEGY_STATE.BUILD_FAILED - self.build_phase.result = strategy.STRATEGY_PHASE_RESULT.FAILED - self.build_phase.result_reason = reason - self.sw_update_obj.strategy_build_complete( - False, - self.build_phase.result_reason) - self.save() - def get_first_host(self): """ This corresponds to the first host that should be updated. @@ -3685,6 +3956,8 @@ def strategy_rebuild_from_dict(data): strategy_obj = object.__new__(SwPatchStrategy) elif STRATEGY_NAME.SW_UPGRADE == data['name']: strategy_obj = object.__new__(SwUpgradeStrategy) + elif STRATEGY_NAME.SYSYTEM_CONFIG_UPDATE == data['name']: + strategy_obj = object.__new__(SystemConfigUpdateStrategy) elif STRATEGY_NAME.FW_UPDATE == data['name']: strategy_obj = object.__new__(FwUpdateStrategy) elif STRATEGY_NAME.KUBE_ROOTCA_UPDATE == data['name']: diff --git a/nfv/nfv-vim/nfv_vim/strategy/_strategy_stages.py b/nfv/nfv-vim/nfv_vim/strategy/_strategy_stages.py index e4dbdb23..a061ae90 100755 --- a/nfv/nfv-vim/nfv_vim/strategy/_strategy_stages.py +++ b/nfv/nfv-vim/nfv_vim/strategy/_strategy_stages.py @@ -37,6 +37,11 @@ class StrategyStageNames(Constants): FW_UPDATE_HOSTS_QUERY = Constant('fw-update-hosts-query') FW_UPDATE_HOST_QUERY = Constant('fw-update-host-query') FW_UPDATE_WORKER_HOSTS = Constant('fw-update-worker-hosts') + # system config update stages + SYSTEM_CONFIG_UPDATE_QUERY = Constant('system-config-update-query') + SYSTEM_CONFIG_UPDATE_CONTROLLERS = Constant('system-config-update-controllers') + SYSTEM_CONFIG_UPDATE_STORAGE_HOSTS = Constant('system-config-update-storage-hosts') + SYSTEM_CONFIG_UPDATE_WORKER_HOSTS = Constant('system-config-update-worker-hosts') # kube root ca update stages KUBE_ROOTCA_UPDATE_CERT = Constant('kube-rootca-update-cert') KUBE_ROOTCA_UPDATE_COMPLETE = Constant('kube-rootca-update-complete') diff --git a/nfv/nfv-vim/nfv_vim/strategy/_strategy_steps.py b/nfv/nfv-vim/nfv_vim/strategy/_strategy_steps.py index 3cebfe75..3d6a6534 100755 --- a/nfv/nfv-vim/nfv_vim/strategy/_strategy_steps.py +++ b/nfv/nfv-vim/nfv_vim/strategy/_strategy_steps.py @@ -86,6 +86,9 @@ class StrategyStepNames(Constants): KUBE_HOST_UPGRADE_CONTROL_PLANE = \ Constant('kube-host-upgrade-control-plane') KUBE_HOST_UPGRADE_KUBELET = Constant('kube-host-upgrade-kubelet') + # system config update specific steps + QUERY_SYSTEM_CONFIG_UPDATE_HOSTS = Constant('query-system-config-update-hosts') + SYSTEM_CONFIG_UPDATE_HOSTS = Constant('system-config-update-hosts') # Constant Instantiation @@ -809,6 +812,108 @@ class SwPatchHostsStep(strategy.StrategyStep): return data +class SystemConfigUpdateHostsStep(strategy.StrategyStep): + """ + System Config Update Hosts - Strategy Step + """ + def __init__(self, hosts): + super(SystemConfigUpdateHostsStep, self).__init__( + STRATEGY_STEP_NAME.SYSTEM_CONFIG_UPDATE_HOSTS, timeout_in_secs=1800) + self._hosts = hosts + self._wait_time = 0 + self._host_names = list() + self._host_uuids = list() + self._query_inprogress = False + + for host in hosts: + self._host_names.append(host.name) + self._host_uuids.append(host.uuid) + + @coroutine + def _query_hosts_callback(self): + """ + Query System Config Update Hosts Callback + """ + response = (yield) + DLOG.debug("Query-Hosts callback response=%s." % response) + + self._query_inprogress = False + + if response['completed']: + for host in response['result-data']: + if host.unlock_request != 'unlock_required': + # Keep waiting for the hosts to reach the unlock required + # status + pass + + # Ready to unlock the hosts + result = strategy.STRATEGY_STEP_RESULT.SUCCESS + self.stage.step_complete(result, "") + + def apply(self): + """ + System Config Update Hosts + """ + DLOG.info("Step (%s) apply for hosts %s." % (self._name, + self._host_names)) + # Do nothing, wait for the callback to check the unlock request status + return strategy.STRATEGY_STEP_RESULT.WAIT, "" + + def handle_event(self, event, event_data=None): + """ + Handle Host events + """ + from nfv_vim import nfvi + + DLOG.debug("Step (%s) handle event (%s)." % (self._name, event)) + + if event == STRATEGY_EVENT.HOST_AUDIT: + if 0 == self._wait_time: + self._wait_time = timers.get_monotonic_timestamp_in_ms() + + now_ms = timers.get_monotonic_timestamp_in_ms() + secs_expired = (now_ms - self._wait_time) // 1000 + # Wait at least 30 seconds before checking the unlock status for + # first time + if 30 <= secs_expired and not self._query_inprogress: + self._query_inprogress = True + nfvi.nfvi_get_system_config_unlock_request( + self._host_names, self._query_hosts_callback()) + return True + + return False + + def from_dict(self, data): + """ + Returns the system config update hosts step object initialized using + the given dictionary + """ + super(SystemConfigUpdateHostsStep, self).from_dict(data) + self._hosts = list() + self._wait_time = 0 + self._host_uuids = list() + self._query_inprogress = False + + self._host_names = data['entity_names'] + host_table = tables.tables_get_host_table() + for host_name in self._host_names: + host = host_table.get(host_name, None) + if host is not None: + self._hosts.append(host) + self._host_uuids.append(host.uuid) + return self + + def as_dict(self): + """ + Represent the system config update hosts step as a dictionary + """ + data = super(SystemConfigUpdateHostsStep, self).as_dict() + data['entity_type'] = 'hosts' + data['entity_names'] = self._host_names + data['entity_uuids'] = self._host_uuids + return data + + class UpgradeHostsStep(strategy.StrategyStep): """ Upgrade Hosts - Strategy Step @@ -2197,6 +2302,45 @@ class QuerySwPatchHostsStep(strategy.StrategyStep): return data +class QuerySystemConfigUpdateHostsStep(AbstractStrategyStep): + """ + Query System Config Update Hosts - Strategy Step + """ + def __init__(self): + super(QuerySystemConfigUpdateHostsStep, self).__init__( + STRATEGY_STEP_NAME.QUERY_SYSTEM_CONFIG_UPDATE_HOSTS, + timeout_in_secs=60) + + @coroutine + def _query_hosts_callback(self): + """ + Query System Config Update Hosts Callback + """ + response = (yield) + DLOG.debug("Query-Hosts callback response=%s." % response) + + if response['completed']: + if self.strategy is not None: + self.strategy.nfvi_system_config_update_hosts = \ + response['result-data'] + + result = strategy.STRATEGY_STEP_RESULT.SUCCESS + self.stage.step_complete(result, "") + else: + result = strategy.STRATEGY_STEP_RESULT.FAILED + self.stage.step_complete(result, "") + + def apply(self): + """ + Query System Config Update Hosts + """ + from nfv_vim import nfvi + + DLOG.info("Step (%s) apply." % self._name) + nfvi.nfvi_list_deployment_hosts(self._query_hosts_callback()) + return strategy.STRATEGY_STEP_RESULT.WAIT, "" + + class QueryFwUpdateHostStep(strategy.StrategyStep): """ Query Host @@ -4711,6 +4855,13 @@ def strategy_step_rebuild_from_dict(data): STRATEGY_STEP_NAME.QUERY_KUBE_HOST_UPGRADE: QueryKubeHostUpgradeStep, STRATEGY_STEP_NAME.QUERY_KUBE_UPGRADE: QueryKubeUpgradeStep, STRATEGY_STEP_NAME.QUERY_KUBE_VERSIONS: QueryKubeVersionsStep, + # + # system config update steps + # + STRATEGY_STEP_NAME.QUERY_SYSTEM_CONFIG_UPDATE_HOSTS: + QuerySystemConfigUpdateHostsStep, + STRATEGY_STEP_NAME.SYSTEM_CONFIG_UPDATE_HOSTS: + SystemConfigUpdateHostsStep, } obj_type = rebuild_map.get(data['name']) if obj_type is not None: