1 # Copyright 2019 Telefonica
5 # Licensed under the Apache License, Version 2.0 (the "License"); you may
6 # not use this file except in compliance with the License. You may obtain
7 # a copy of the License at
9 # http://www.apache.org/licenses/LICENSE-2.0
11 # Unless required by applicable law or agreed to in writing, software
12 # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13 # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
14 # License for the specific language governing permissions and limitations
18 OSM API handling for the '--wait' option
21 from osmclient
.common
.exceptions
import ClientException
, NotFound
23 from time
import sleep
, time
24 from sys
import stderr
26 # Declare a constant for each module, to allow customizing each timeout in the future
27 TIMEOUT_GENERIC_OPERATION
= 600
28 TIMEOUT_NSI_OPERATION
= TIMEOUT_GENERIC_OPERATION
29 TIMEOUT_SDNC_OPERATION
= TIMEOUT_GENERIC_OPERATION
30 TIMEOUT_VIM_OPERATION
= TIMEOUT_GENERIC_OPERATION
31 TIMEOUT_K8S_OPERATION
= TIMEOUT_GENERIC_OPERATION
32 TIMEOUT_WIM_OPERATION
= TIMEOUT_GENERIC_OPERATION
33 TIMEOUT_NS_OPERATION
= 3600
34 POLLING_TIME_INTERVAL
= 5
35 MAX_DELETE_ATTEMPTS
= 3
38 def _show_detailed_status(old_detailed_status
, new_detailed_status
):
39 if new_detailed_status
is not None and new_detailed_status
!= old_detailed_status
:
40 stderr
.write("detailed-status: {}\n".format(new_detailed_status
))
41 return new_detailed_status
43 return old_detailed_status
46 def _get_finished_states(entity
):
48 Member name is either:
49 operationState' (NS, NSI)
50 '_admin.'operationalState' (VIM, WIM, SDN)
51 For NS and NSI, 'operationState' may be one of:
52 PROCESSING, COMPLETED,PARTIALLY_COMPLETED, FAILED_TEMP,FAILED,ROLLING_BACK,ROLLED_BACK
53 For VIM, WIM, SDN: '_admin.operationalState' may be one of:
54 ENABLED, DISABLED, ERROR, PROCESSING
56 :param entity: can be NS, NSI, or other
57 :return: two tuples with status completed strings, status failed string
59 if entity
== "NS" or entity
== "NSI":
60 return ("COMPLETED", "PARTIALLY_COMPLETED"), ("FAILED_TEMP", "FAILED")
61 elif entity
== "OPCANCEL":
62 return ("FAILED_TEMP"), ("COMPLETED",)
64 return ("ENABLED",), ("ERROR",)
67 def _get_operational_state(resp
, entity
):
69 The member name is either:
71 'operational-status' (NSI)
72 '_admin.'operationalState' (other)
73 :param resp: descriptor of the get response
74 :param entity: can be NS, NSI, or other
75 :return: status of the operation
77 if entity
in ["NS", "NSI", "OPCANCEL"]:
78 return resp
.get("operationState")
80 return resp
.get("_admin", {}).get("operationalState")
83 def _op_has_finished(resp
, entity
):
85 Indicates if operation has finished ok or is processing
86 :param resp: descriptor of the get response
87 :param entity: can be NS, NSI, or other
89 True on success (operation has finished)
90 False on pending (operation has not finished)
91 raise Exception if unexpected response, or ended with error
93 finished_states_ok
, finished_states_error
= _get_finished_states(entity
)
95 op_state
= _get_operational_state(resp
, entity
)
97 if op_state
in finished_states_ok
:
99 elif op_state
in finished_states_error
:
100 raise ClientException(
101 "Operation failed with status '{}'".format(op_state
)
104 raise ClientException("Unexpected response from server: {} ".format(resp
))
107 def _get_detailed_status(resp
, entity
):
109 For VIM, WIM, SDN, 'detailed-status' is either:
110 - a leaf node to '_admin' (operations NOT supported)
111 - a leaf node of the Nth element in the list '_admin.operations[]' (operations supported by LCM and NBI)
112 :param resp: content of the get response
113 :param entity: can be NS, NSI, or other
116 if entity
in ("NS", "NSI", "OPCANCEL"):
117 # For NS and NSI, 'detailed-status' is a JSON "root" member:
118 return resp
.get("detailed-status")
120 ops
= resp
.get("_admin", {}).get("operations")
121 current_op
= resp
.get("_admin", {}).get("current_operation")
122 if ops
and current_op
is not None:
123 # Operations are supported, verify operation index
124 if isinstance(ops
, dict) and current_op
in ops
:
125 return ops
[current_op
].get("detailed-status")
127 isinstance(ops
, list)
128 and isinstance(current_op
, int)
129 or current_op
.isdigit()
131 current_op
= int(current_op
)
134 and current_op
< len(ops
)
136 and ops
[current_op
]["detailed-status"]
138 return ops
[current_op
]["detailed-status"]
139 # operation index is either non-numeric or out-of-range
140 return "Unexpected error when getting detailed-status!"
142 # Operations are NOT supported
143 return resp
.get("_admin", {}).get("detailed-status")
147 entity_label
, entity_id
, timeout
, apiUrlStatus
, http_cmd
, deleteFlag
=False
150 Wait until operation ends, making polling every 5s. Prints detailed status when it changes
151 :param entity_label: String describing the entities using '--wait': 'NS', 'NSI', 'SDNC', 'VIM', 'WIM'
152 :param entity_id: The ID for an existing entity, the operation ID for an entity to create.
153 :param timeout: Timeout in seconds
154 :param apiUrlStatus: The endpoint to get the Response including 'detailed-status'
155 :param http_cmd: callback to HTTP command. (Normally the get method)
156 :param deleteFlag: If this is a delete operation
157 :return: None, exception if operation fails or timeout
160 # Loop here until the operation finishes, or a timeout occurs.
161 time_to_finish
= time() + timeout
162 detailed_status
= None
167 http_code
, resp_unicode
= http_cmd("{}/{}".format(apiUrlStatus
, entity_id
))
171 _show_detailed_status(detailed_status
, "Deleted")
174 except ClientException
:
175 if retries
>= max_retries
or time() < time_to_finish
:
178 sleep(POLLING_TIME_INTERVAL
)
183 resp
= json
.loads(resp_unicode
)
185 new_detailed_status
= _get_detailed_status(resp
, entity_label
)
186 # print('DETAILED-STATUS: {}'.format(new_detailed_status))
187 if not new_detailed_status
:
188 new_detailed_status
= "In progress"
189 detailed_status
= _show_detailed_status(detailed_status
, new_detailed_status
)
191 # Get operation status
192 if _op_has_finished(resp
, entity_label
):
195 if time() >= time_to_finish
:
196 # There was a timeout, so raise an exception
197 raise ClientException("operation timeout after {} seconds".format(timeout
))
198 sleep(POLLING_TIME_INTERVAL
)