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("Operation failed with status '{}'".format(op_state
))
100 raise ClientException('Unexpected response from server: {} '.format(resp
))
103 def _get_detailed_status(resp
, entity
):
105 For VIM, WIM, SDN, 'detailed-status' is either:
106 - a leaf node to '_admin' (operations NOT supported)
107 - a leaf node of the Nth element in the list '_admin.operations[]' (operations supported by LCM and NBI)
108 :param resp: content of the get response
109 :param entity: can be NS, NSI, or other
112 if entity
in ('NS', 'NSI'):
113 # For NS and NSI, 'detailed-status' is a JSON "root" member:
114 return resp
.get('detailed-status')
116 ops
= resp
.get('_admin', {}).get('operations')
117 current_op
= resp
.get('_admin', {}).get('current_operation')
118 if ops
and current_op
is not None:
119 # Operations are supported, verify operation index
120 if isinstance(ops
, dict) and current_op
in ops
:
121 return ops
[current_op
].get("detailed-status")
122 elif isinstance(ops
, list) and isinstance(current_op
, int) or current_op
.isdigit():
123 current_op
= int(current_op
)
124 if current_op
>= 0 and current_op
< len(ops
) and ops
[current_op
] and ops
[current_op
]["detailed-status"]:
125 return ops
[current_op
]["detailed-status"]
126 # operation index is either non-numeric or out-of-range
127 return 'Unexpected error when getting detailed-status!'
129 # Operations are NOT supported
130 return resp
.get('_admin', {}).get('detailed-status')
133 def wait_for_status(entity_label
, entity_id
, timeout
, apiUrlStatus
, http_cmd
, deleteFlag
=False):
135 Wait until operation ends, making polling every 5s. Prints detailed status when it changes
136 :param entity_label: String describing the entities using '--wait': 'NS', 'NSI', 'SDNC', 'VIM', 'WIM'
137 :param entity_id: The ID for an existing entity, the operation ID for an entity to create.
138 :param timeout: Timeout in seconds
139 :param apiUrlStatus: The endpoint to get the Response including 'detailed-status'
140 :param http_cmd: callback to HTTP command. (Normally the get method)
141 :param deleteFlag: If this is a delete operation
142 :return: None, exception if operation fails or timeout
145 # Loop here until the operation finishes, or a timeout occurs.
146 time_to_finish
= time() + timeout
147 detailed_status
= None
152 http_code
, resp_unicode
= http_cmd('{}/{}'.format(apiUrlStatus
, entity_id
))
156 _show_detailed_status(detailed_status
, 'Deleted')
159 except ClientException
:
160 if retries
>= max_retries
or time() < time_to_finish
:
163 sleep(POLLING_TIME_INTERVAL
)
168 resp
= json
.loads(resp_unicode
)
170 new_detailed_status
= _get_detailed_status(resp
, entity_label
)
171 # print('DETAILED-STATUS: {}'.format(new_detailed_status))
172 if not new_detailed_status
:
173 new_detailed_status
= 'In progress'
174 detailed_status
= _show_detailed_status(detailed_status
, new_detailed_status
)
176 # Get operation status
177 if _op_has_finished(resp
, entity_label
):
180 if time() >= time_to_finish
:
181 # There was a timeout, so raise an exception
182 raise ClientException('operation timeout after {} seconds'.format(timeout
))
183 sleep(POLLING_TIME_INTERVAL
)