1 |
|
# Copyright 2019 Telefonica |
2 |
|
# |
3 |
|
# All Rights Reserved. |
4 |
|
# |
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 |
8 |
|
# |
9 |
|
# http://www.apache.org/licenses/LICENSE-2.0 |
10 |
|
# |
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 |
15 |
|
# under the License. |
16 |
|
|
17 |
1 |
""" |
18 |
|
OSM API handling for the '--wait' option |
19 |
|
""" |
20 |
|
|
21 |
1 |
from osmclient.common.exceptions import ClientException, NotFound |
22 |
1 |
import json |
23 |
1 |
from time import sleep, time |
24 |
1 |
from sys import stderr |
25 |
|
|
26 |
|
# Declare a constant for each module, to allow customizing each timeout in the future |
27 |
1 |
TIMEOUT_GENERIC_OPERATION = 600 |
28 |
1 |
TIMEOUT_NSI_OPERATION = TIMEOUT_GENERIC_OPERATION |
29 |
1 |
TIMEOUT_SDNC_OPERATION = TIMEOUT_GENERIC_OPERATION |
30 |
1 |
TIMEOUT_VIM_OPERATION = TIMEOUT_GENERIC_OPERATION |
31 |
1 |
TIMEOUT_K8S_OPERATION = TIMEOUT_GENERIC_OPERATION |
32 |
1 |
TIMEOUT_WIM_OPERATION = TIMEOUT_GENERIC_OPERATION |
33 |
1 |
TIMEOUT_NS_OPERATION = 3600 |
34 |
1 |
POLLING_TIME_INTERVAL = 5 |
35 |
1 |
MAX_DELETE_ATTEMPTS = 3 |
36 |
|
|
37 |
|
|
38 |
1 |
def _show_detailed_status(old_detailed_status, new_detailed_status): |
39 |
0 |
if new_detailed_status is not None and new_detailed_status != old_detailed_status: |
40 |
0 |
stderr.write("detailed-status: {}\n".format(new_detailed_status)) |
41 |
0 |
return new_detailed_status |
42 |
|
else: |
43 |
0 |
return old_detailed_status |
44 |
|
|
45 |
|
|
46 |
1 |
def _get_finished_states(entity): |
47 |
|
""" |
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 |
55 |
|
|
56 |
|
:param entity: can be NS, NSI, or other |
57 |
|
:return: two tuples with status completed strings, status failed string |
58 |
|
""" |
59 |
0 |
if entity == "NS" or entity == "NSI": |
60 |
0 |
return ("COMPLETED", "PARTIALLY_COMPLETED"), ("FAILED_TEMP", "FAILED") |
61 |
|
else: |
62 |
0 |
return ("ENABLED",), ("ERROR",) |
63 |
|
|
64 |
|
|
65 |
1 |
def _get_operational_state(resp, entity): |
66 |
|
""" |
67 |
|
The member name is either: |
68 |
|
'operationState' (NS) |
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 |
74 |
|
""" |
75 |
0 |
if entity == "NS" or entity == "NSI": |
76 |
0 |
return resp.get("operationState") |
77 |
|
else: |
78 |
0 |
return resp.get("_admin", {}).get("operationalState") |
79 |
|
|
80 |
|
|
81 |
1 |
def _op_has_finished(resp, entity): |
82 |
|
""" |
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 |
86 |
|
:return: |
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 |
90 |
|
""" |
91 |
0 |
finished_states_ok, finished_states_error = _get_finished_states(entity) |
92 |
0 |
if resp: |
93 |
0 |
op_state = _get_operational_state(resp, entity) |
94 |
0 |
if op_state: |
95 |
0 |
if op_state in finished_states_ok: |
96 |
0 |
return True |
97 |
0 |
elif op_state in finished_states_error: |
98 |
0 |
raise ClientException( |
99 |
|
"Operation failed with status '{}'".format(op_state) |
100 |
|
) |
101 |
0 |
return False |
102 |
0 |
raise ClientException("Unexpected response from server: {} ".format(resp)) |
103 |
|
|
104 |
|
|
105 |
1 |
def _get_detailed_status(resp, entity): |
106 |
|
""" |
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 |
112 |
|
:return: |
113 |
|
""" |
114 |
0 |
if entity in ("NS", "NSI"): |
115 |
|
# For NS and NSI, 'detailed-status' is a JSON "root" member: |
116 |
0 |
return resp.get("detailed-status") |
117 |
|
else: |
118 |
0 |
ops = resp.get("_admin", {}).get("operations") |
119 |
0 |
current_op = resp.get("_admin", {}).get("current_operation") |
120 |
0 |
if ops and current_op is not None: |
121 |
|
# Operations are supported, verify operation index |
122 |
0 |
if isinstance(ops, dict) and current_op in ops: |
123 |
0 |
return ops[current_op].get("detailed-status") |
124 |
0 |
elif ( |
125 |
|
isinstance(ops, list) |
126 |
|
and isinstance(current_op, int) |
127 |
|
or current_op.isdigit() |
128 |
|
): |
129 |
0 |
current_op = int(current_op) |
130 |
0 |
if ( |
131 |
|
current_op >= 0 |
132 |
|
and current_op < len(ops) |
133 |
|
and ops[current_op] |
134 |
|
and ops[current_op]["detailed-status"] |
135 |
|
): |
136 |
0 |
return ops[current_op]["detailed-status"] |
137 |
|
# operation index is either non-numeric or out-of-range |
138 |
0 |
return "Unexpected error when getting detailed-status!" |
139 |
|
else: |
140 |
|
# Operations are NOT supported |
141 |
0 |
return resp.get("_admin", {}).get("detailed-status") |
142 |
|
|
143 |
|
|
144 |
1 |
def wait_for_status( |
145 |
|
entity_label, entity_id, timeout, apiUrlStatus, http_cmd, deleteFlag=False |
146 |
|
): |
147 |
|
""" |
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 |
156 |
|
""" |
157 |
|
|
158 |
|
# Loop here until the operation finishes, or a timeout occurs. |
159 |
0 |
time_to_finish = time() + timeout |
160 |
0 |
detailed_status = None |
161 |
0 |
retries = 0 |
162 |
0 |
max_retries = 1 |
163 |
|
while True: |
164 |
0 |
try: |
165 |
0 |
http_code, resp_unicode = http_cmd("{}/{}".format(apiUrlStatus, entity_id)) |
166 |
0 |
retries = 0 |
167 |
0 |
except NotFound: |
168 |
0 |
if deleteFlag: |
169 |
0 |
_show_detailed_status(detailed_status, "Deleted") |
170 |
0 |
return |
171 |
0 |
raise |
172 |
0 |
except ClientException: |
173 |
0 |
if retries >= max_retries or time() < time_to_finish: |
174 |
0 |
raise |
175 |
0 |
retries += 1 |
176 |
0 |
sleep(POLLING_TIME_INTERVAL) |
177 |
0 |
continue |
178 |
|
|
179 |
0 |
resp = "" |
180 |
0 |
if resp_unicode: |
181 |
0 |
resp = json.loads(resp_unicode) |
182 |
|
|
183 |
0 |
new_detailed_status = _get_detailed_status(resp, entity_label) |
184 |
|
# print('DETAILED-STATUS: {}'.format(new_detailed_status)) |
185 |
0 |
if not new_detailed_status: |
186 |
0 |
new_detailed_status = "In progress" |
187 |
0 |
detailed_status = _show_detailed_status(detailed_status, new_detailed_status) |
188 |
|
|
189 |
|
# Get operation status |
190 |
0 |
if _op_has_finished(resp, entity_label): |
191 |
0 |
return |
192 |
|
|
193 |
0 |
if time() >= time_to_finish: |
194 |
|
# There was a timeout, so raise an exception |
195 |
0 |
raise ClientException("operation timeout after {} seconds".format(timeout)) |
196 |
0 |
sleep(POLLING_TIME_INTERVAL) |