Code Coverage

Cobertura Coverage Report > osmclient.common >

wait.py

Trend

Classes100%
 
Lines24%
   
Conditionals100%
 

File Coverage summary

NameClassesLinesConditionals
wait.py
100%
1/1
24%
20/84
100%
0/0

Coverage Breakdown by Class

NameLinesConditionals
wait.py
24%
20/84
N/A

Source

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 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 0     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)