blob: d3f673c64a285d2bb51d3c92e3e27c9ee92161ee [file] [log] [blame]
# Copyright 2019 Telefonica
#
# All Rights Reserved.
#
# 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.
"""
OSM API handling for the '--wait' option
"""
from osmclient.common.exceptions import ClientException, NotFound
import json
from time import sleep, time
from sys import stderr
# Declare a constant for each module, to allow customizing each timeout in the future
TIMEOUT_GENERIC_OPERATION = 600
TIMEOUT_NSI_OPERATION = TIMEOUT_GENERIC_OPERATION
TIMEOUT_SDNC_OPERATION = TIMEOUT_GENERIC_OPERATION
TIMEOUT_VIM_OPERATION = TIMEOUT_GENERIC_OPERATION
TIMEOUT_K8S_OPERATION = TIMEOUT_GENERIC_OPERATION
TIMEOUT_WIM_OPERATION = TIMEOUT_GENERIC_OPERATION
TIMEOUT_NS_OPERATION = 3600
POLLING_TIME_INTERVAL = 5
MAX_DELETE_ATTEMPTS = 3
def _show_detailed_status(old_detailed_status, new_detailed_status):
if new_detailed_status is not None and new_detailed_status != old_detailed_status:
stderr.write("detailed-status: {}\n".format(new_detailed_status))
return new_detailed_status
else:
return old_detailed_status
def _get_finished_states(entity):
"""
Member name is either:
operationState' (NS, NSI)
'_admin.'operationalState' (VIM, WIM, SDN)
For NS and NSI, 'operationState' may be one of:
PROCESSING, COMPLETED,PARTIALLY_COMPLETED, FAILED_TEMP,FAILED,ROLLING_BACK,ROLLED_BACK
For VIM, WIM, SDN: '_admin.operationalState' may be one of:
ENABLED, DISABLED, ERROR, PROCESSING
:param entity: can be NS, NSI, or other
:return: two tuples with status completed strings, status failed string
"""
if entity == "NS" or entity == "NSI":
return ("COMPLETED", "PARTIALLY_COMPLETED"), ("FAILED_TEMP", "FAILED")
else:
return ("ENABLED",), ("ERROR",)
def _get_operational_state(resp, entity):
"""
The member name is either:
'operationState' (NS)
'operational-status' (NSI)
'_admin.'operationalState' (other)
:param resp: descriptor of the get response
:param entity: can be NS, NSI, or other
:return: status of the operation
"""
if entity == "NS" or entity == "NSI":
return resp.get("operationState")
else:
return resp.get("_admin", {}).get("operationalState")
def _op_has_finished(resp, entity):
"""
Indicates if operation has finished ok or is processing
:param resp: descriptor of the get response
:param entity: can be NS, NSI, or other
:return:
True on success (operation has finished)
False on pending (operation has not finished)
raise Exception if unexpected response, or ended with error
"""
finished_states_ok, finished_states_error = _get_finished_states(entity)
if resp:
op_state = _get_operational_state(resp, entity)
if op_state:
if op_state in finished_states_ok:
return True
elif op_state in finished_states_error:
raise ClientException(
"Operation failed with status '{}'".format(op_state)
)
return False
raise ClientException("Unexpected response from server: {} ".format(resp))
def _get_detailed_status(resp, entity):
"""
For VIM, WIM, SDN, 'detailed-status' is either:
- a leaf node to '_admin' (operations NOT supported)
- a leaf node of the Nth element in the list '_admin.operations[]' (operations supported by LCM and NBI)
:param resp: content of the get response
:param entity: can be NS, NSI, or other
:return:
"""
if entity in ("NS", "NSI"):
# For NS and NSI, 'detailed-status' is a JSON "root" member:
return resp.get("detailed-status")
else:
ops = resp.get("_admin", {}).get("operations")
current_op = resp.get("_admin", {}).get("current_operation")
if ops and current_op is not None:
# Operations are supported, verify operation index
if isinstance(ops, dict) and current_op in ops:
return ops[current_op].get("detailed-status")
elif (
isinstance(ops, list)
and isinstance(current_op, int)
or current_op.isdigit()
):
current_op = int(current_op)
if (
current_op >= 0
and current_op < len(ops)
and ops[current_op]
and ops[current_op]["detailed-status"]
):
return ops[current_op]["detailed-status"]
# operation index is either non-numeric or out-of-range
return "Unexpected error when getting detailed-status!"
else:
# Operations are NOT supported
return resp.get("_admin", {}).get("detailed-status")
def wait_for_status(
entity_label, entity_id, timeout, apiUrlStatus, http_cmd, deleteFlag=False
):
"""
Wait until operation ends, making polling every 5s. Prints detailed status when it changes
:param entity_label: String describing the entities using '--wait': 'NS', 'NSI', 'SDNC', 'VIM', 'WIM'
:param entity_id: The ID for an existing entity, the operation ID for an entity to create.
:param timeout: Timeout in seconds
:param apiUrlStatus: The endpoint to get the Response including 'detailed-status'
:param http_cmd: callback to HTTP command. (Normally the get method)
:param deleteFlag: If this is a delete operation
:return: None, exception if operation fails or timeout
"""
# Loop here until the operation finishes, or a timeout occurs.
time_to_finish = time() + timeout
detailed_status = None
retries = 0
max_retries = 1
while True:
try:
http_code, resp_unicode = http_cmd("{}/{}".format(apiUrlStatus, entity_id))
retries = 0
except NotFound:
if deleteFlag:
_show_detailed_status(detailed_status, "Deleted")
return
raise
except ClientException:
if retries >= max_retries or time() < time_to_finish:
raise
retries += 1
sleep(POLLING_TIME_INTERVAL)
continue
resp = ""
if resp_unicode:
resp = json.loads(resp_unicode)
new_detailed_status = _get_detailed_status(resp, entity_label)
# print('DETAILED-STATUS: {}'.format(new_detailed_status))
if not new_detailed_status:
new_detailed_status = "In progress"
detailed_status = _show_detailed_status(detailed_status, new_detailed_status)
# Get operation status
if _op_has_finished(resp, entity_label):
return
if time() >= time_to_finish:
# There was a timeout, so raise an exception
raise ClientException("operation timeout after {} seconds".format(timeout))
sleep(POLLING_TIME_INTERVAL)