Feature 11037 Change default NBI port
[osm/osmclient.git] / osmclient / common / wait.py
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 """
18 OSM API handling for the '--wait' option
19 """
20
21 from osmclient.common.exceptions import ClientException, NotFound
22 import json
23 from time import sleep, time
24 from sys import stderr
25
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
36
37
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
42 else:
43 return old_detailed_status
44
45
46 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 if entity == "NS" or entity == "NSI":
60 return ("COMPLETED", "PARTIALLY_COMPLETED"), ("FAILED_TEMP", "FAILED")
61 elif entity == "OPCANCEL":
62 return ("FAILED_TEMP"), ("COMPLETED",)
63 else:
64 return ("ENABLED",), ("ERROR",)
65
66
67 def _get_operational_state(resp, entity):
68 """
69 The member name is either:
70 'operationState' (NS)
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
76 """
77 if entity in ["NS", "NSI", "OPCANCEL"]:
78 return resp.get("operationState")
79 else:
80 return resp.get("_admin", {}).get("operationalState")
81
82
83 def _op_has_finished(resp, entity):
84 """
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
88 :return:
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
92 """
93 finished_states_ok, finished_states_error = _get_finished_states(entity)
94 if resp:
95 op_state = _get_operational_state(resp, entity)
96 if op_state:
97 if op_state in finished_states_ok:
98 return True
99 elif op_state in finished_states_error:
100 raise ClientException(
101 "Operation failed with status '{}'".format(op_state)
102 )
103 return False
104 raise ClientException("Unexpected response from server: {} ".format(resp))
105
106
107 def _get_detailed_status(resp, entity):
108 """
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
114 :return:
115 """
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")
119 else:
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")
126 elif (
127 isinstance(ops, list)
128 and isinstance(current_op, int)
129 or current_op.isdigit()
130 ):
131 current_op = int(current_op)
132 if (
133 current_op >= 0
134 and current_op < len(ops)
135 and ops[current_op]
136 and ops[current_op]["detailed-status"]
137 ):
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!"
141 else:
142 # Operations are NOT supported
143 return resp.get("_admin", {}).get("detailed-status")
144
145
146 def wait_for_status(
147 entity_label, entity_id, timeout, apiUrlStatus, http_cmd, deleteFlag=False
148 ):
149 """
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
158 """
159
160 # Loop here until the operation finishes, or a timeout occurs.
161 time_to_finish = time() + timeout
162 detailed_status = None
163 retries = 0
164 max_retries = 1
165 while True:
166 try:
167 http_code, resp_unicode = http_cmd("{}/{}".format(apiUrlStatus, entity_id))
168 retries = 0
169 except NotFound:
170 if deleteFlag:
171 _show_detailed_status(detailed_status, "Deleted")
172 return
173 raise
174 except ClientException:
175 if retries >= max_retries or time() < time_to_finish:
176 raise
177 retries += 1
178 sleep(POLLING_TIME_INTERVAL)
179 continue
180
181 resp = ""
182 if resp_unicode:
183 resp = json.loads(resp_unicode)
184
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)
190
191 # Get operation status
192 if _op_has_finished(resp, entity_label):
193 return
194
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)