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")
62 return ("ENABLED",), ("ERROR",)
65 def _get_operational_state(resp
, entity
):
67 The member name is either:
69 'operational-status' (NSI)
70 '_admin.'operationalState' (other)
71 :param resp: descriptor of the get response
72 :param entity: can be NS, NSI, or other
73 :return: status of the operation
75 if entity
== "NS" or entity
== "NSI":
76 return resp
.get("operationState")
78 return resp
.get("_admin", {}).get("operationalState")
81 def _op_has_finished(resp
, entity
):
83 Indicates if operation has finished ok or is processing
84 :param resp: descriptor of the get response
85 :param entity: can be NS, NSI, or other
87 True on success (operation has finished)
88 False on pending (operation has not finished)
89 raise Exception if unexpected response, or ended with error
91 finished_states_ok
, finished_states_error
= _get_finished_states(entity
)
93 op_state
= _get_operational_state(resp
, entity
)
95 if op_state
in finished_states_ok
:
97 elif op_state
in finished_states_error
:
98 raise ClientException(
99 "Operation failed with status '{}'".format(op_state
)
102 raise ClientException("Unexpected response from server: {} ".format(resp
))
105 def _get_detailed_status(resp
, entity
):
107 For VIM, WIM, SDN, 'detailed-status' is either:
108 - a leaf node to '_admin' (operations NOT supported)
109 - a leaf node of the Nth element in the list '_admin.operations[]' (operations supported by LCM and NBI)
110 :param resp: content of the get response
111 :param entity: can be NS, NSI, or other
114 if entity
in ("NS", "NSI"):
115 # For NS and NSI, 'detailed-status' is a JSON "root" member:
116 return resp
.get("detailed-status")
118 ops
= resp
.get("_admin", {}).get("operations")
119 current_op
= resp
.get("_admin", {}).get("current_operation")
120 if ops
and current_op
is not None:
121 # Operations are supported, verify operation index
122 if isinstance(ops
, dict) and current_op
in ops
:
123 return ops
[current_op
].get("detailed-status")
125 isinstance(ops
, list)
126 and isinstance(current_op
, int)
127 or current_op
.isdigit()
129 current_op
= int(current_op
)
132 and current_op
< len(ops
)
134 and ops
[current_op
]["detailed-status"]
136 return ops
[current_op
]["detailed-status"]
137 # operation index is either non-numeric or out-of-range
138 return "Unexpected error when getting detailed-status!"
140 # Operations are NOT supported
141 return resp
.get("_admin", {}).get("detailed-status")
145 entity_label
, entity_id
, timeout
, apiUrlStatus
, http_cmd
, deleteFlag
=False
148 Wait until operation ends, making polling every 5s. Prints detailed status when it changes
149 :param entity_label: String describing the entities using '--wait': 'NS', 'NSI', 'SDNC', 'VIM', 'WIM'
150 :param entity_id: The ID for an existing entity, the operation ID for an entity to create.
151 :param timeout: Timeout in seconds
152 :param apiUrlStatus: The endpoint to get the Response including 'detailed-status'
153 :param http_cmd: callback to HTTP command. (Normally the get method)
154 :param deleteFlag: If this is a delete operation
155 :return: None, exception if operation fails or timeout
158 # Loop here until the operation finishes, or a timeout occurs.
159 time_to_finish
= time() + timeout
160 detailed_status
= None
165 http_code
, resp_unicode
= http_cmd("{}/{}".format(apiUrlStatus
, entity_id
))
169 _show_detailed_status(detailed_status
, "Deleted")
172 except ClientException
:
173 if retries
>= max_retries
or time() < time_to_finish
:
176 sleep(POLLING_TIME_INTERVAL
)
181 resp
= json
.loads(resp_unicode
)
183 new_detailed_status
= _get_detailed_status(resp
, entity_label
)
184 # print('DETAILED-STATUS: {}'.format(new_detailed_status))
185 if not new_detailed_status
:
186 new_detailed_status
= "In progress"
187 detailed_status
= _show_detailed_status(detailed_status
, new_detailed_status
)
189 # Get operation status
190 if _op_has_finished(resp
, entity_label
):
193 if time() >= time_to_finish
:
194 # There was a timeout, so raise an exception
195 raise ClientException("operation timeout after {} seconds".format(timeout
))
196 sleep(POLLING_TIME_INTERVAL
)