fix wait option when operation fails
[osm/osmclient.git] / osmclient / sol005 / ns.py
1 # Copyright 2018 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 ns API handling
19 """
20
21 from osmclient.common import utils
22 from osmclient.common import wait as WaitForStatus
23 from osmclient.common.exceptions import ClientException
24 from osmclient.common.exceptions import NotFound
25 import yaml
26 import json
27 import logging
28
29
30 class Ns(object):
31
32 def __init__(self, http=None, client=None):
33 self._http = http
34 self._client = client
35 self._logger = logging.getLogger('osmclient')
36 self._apiName = '/nslcm'
37 self._apiVersion = '/v1'
38 self._apiResource = '/ns_instances_content'
39 self._apiBase = '{}{}{}'.format(self._apiName,
40 self._apiVersion, self._apiResource)
41
42 # NS '--wait' option
43 def _wait(self, id, wait_time, deleteFlag=False):
44 self._logger.debug("")
45 # Endpoint to get operation status
46 apiUrlStatus = '{}{}{}'.format(self._apiName, self._apiVersion, '/ns_lcm_op_occs')
47 # Wait for status for NS instance creation/update/deletion
48 if isinstance(wait_time, bool):
49 wait_time = WaitForStatus.TIMEOUT_NS_OPERATION
50 WaitForStatus.wait_for_status(
51 'NS',
52 str(id),
53 wait_time,
54 apiUrlStatus,
55 self._http.get2_cmd,
56 deleteFlag=deleteFlag)
57
58 def list(self, filter=None):
59 """Returns a list of NS
60 """
61 self._logger.debug("")
62 self._client.get_token()
63 filter_string = ''
64 if filter:
65 filter_string = '?{}'.format(filter)
66 _, resp = self._http.get2_cmd('{}{}'.format(self._apiBase,filter_string))
67 if resp:
68 return json.loads(resp)
69 return list()
70
71 def get(self, name):
72 """Returns an NS based on name or id
73 """
74 self._logger.debug("")
75 self._client.get_token()
76 if utils.validate_uuid4(name):
77 for ns in self.list():
78 if name == ns['_id']:
79 return ns
80 else:
81 for ns in self.list():
82 if name == ns['name']:
83 return ns
84 raise NotFound("ns '{}' not found".format(name))
85
86 def get_individual(self, name):
87 self._logger.debug("")
88 self._client.get_token()
89 ns_id = name
90 if not utils.validate_uuid4(name):
91 for ns in self.list():
92 if name == ns['name']:
93 ns_id = ns['_id']
94 break
95 try:
96 _, resp = self._http.get2_cmd('{}/{}'.format(self._apiBase, ns_id))
97 #resp = self._http.get_cmd('{}/{}/nsd_content'.format(self._apiBase, ns_id))
98 #print(yaml.safe_dump(resp))
99 if resp:
100 return json.loads(resp)
101 except NotFound:
102 raise NotFound("ns '{}' not found".format(name))
103 raise NotFound("ns '{}' not found".format(name))
104
105 def delete(self, name, force=False, config=None, wait=False):
106 """
107 Deletes a Network Service (NS)
108 :param name: name of network service
109 :param force: set force. Direct deletion without cleaning at VIM
110 :param config: parameters of deletion, as:
111 autoremove: Bool (default True)
112 timeout_ns_terminate: int
113 skip_terminate_primitives: Bool (default False) to not exec the terminate primitives
114 :param wait: Make synchronous. Wait until deletion is completed:
115 False to not wait (by default), True to wait a standard time, or int (time to wait)
116 :return: None. Exception if fail
117 """
118 self._logger.debug("")
119 ns = self.get(name)
120 querystring_list = []
121 querystring = ''
122 if config:
123 ns_config = yaml.safe_load(config)
124 querystring_list += ["{}={}".format(k, v) for k, v in ns_config.items()]
125 if force:
126 querystring_list.append('FORCE=True')
127 if querystring_list:
128 querystring = "?" + "&".join(querystring_list)
129 http_code, resp = self._http.delete_cmd('{}/{}{}'.format(self._apiBase,
130 ns['_id'], querystring))
131 # TODO change to use a POST self._http.post_cmd('{}/{}/terminate{}'.format(_apiBase, ns['_id'], querystring),
132 # postfields_dict=ns_config)
133 # seting autoremove as True by default
134 # print('HTTP CODE: {}'.format(http_code))
135 # print('RESP: {}'.format(resp))
136 if http_code == 202:
137 if wait and resp:
138 resp = json.loads(resp)
139 # For the 'delete' operation, '_id' is used
140 self._wait(resp.get('_id'), wait, deleteFlag=True)
141 else:
142 print('Deletion in progress')
143 elif http_code == 204:
144 print('Deleted')
145 else:
146 msg = resp or ""
147 # if resp:
148 # try:
149 # msg = json.loads(resp)
150 # except ValueError:
151 # msg = resp
152 raise ClientException("failed to delete ns {} - {}".format(name, msg))
153
154 def create(self, nsd_name, nsr_name, account, config=None,
155 ssh_keys=None, description='default description',
156 admin_status='ENABLED', wait=False):
157 self._logger.debug("")
158 self._client.get_token()
159 nsd = self._client.nsd.get(nsd_name)
160
161 vim_account_id = {}
162 wim_account_id = {}
163
164 def get_vim_account_id(vim_account):
165 self._logger.debug("")
166 if vim_account_id.get(vim_account):
167 return vim_account_id[vim_account]
168 vim = self._client.vim.get(vim_account)
169 if vim is None:
170 raise NotFound("cannot find vim account '{}'".format(vim_account))
171 vim_account_id[vim_account] = vim['_id']
172 return vim['_id']
173
174 def get_wim_account_id(wim_account):
175 self._logger.debug("")
176 # wim_account can be False (boolean) to indicate not use wim account
177 if not isinstance(wim_account, str):
178 return wim_account
179 if wim_account_id.get(wim_account):
180 return wim_account_id[wim_account]
181 wim = self._client.wim.get(wim_account)
182 if wim is None:
183 raise NotFound("cannot find wim account '{}'".format(wim_account))
184 wim_account_id[wim_account] = wim['_id']
185 return wim['_id']
186
187 ns = {}
188 ns['nsdId'] = nsd['_id']
189 ns['nsName'] = nsr_name
190 ns['nsDescription'] = description
191 ns['vimAccountId'] = get_vim_account_id(account)
192 #ns['userdata'] = {}
193 #ns['userdata']['key1']='value1'
194 #ns['userdata']['key2']='value2'
195
196 if ssh_keys is not None:
197 ns['ssh_keys'] = []
198 for pubkeyfile in ssh_keys.split(','):
199 with open(pubkeyfile, 'r') as f:
200 ns['ssh_keys'].append(f.read())
201 if config:
202 ns_config = yaml.safe_load(config)
203 if "vim-network-name" in ns_config:
204 ns_config["vld"] = ns_config.pop("vim-network-name")
205 if "vld" in ns_config:
206 if not isinstance(ns_config["vld"], list):
207 raise ValueError("Error at --config 'vld' must be a list of dictionaries")
208 for vld in ns_config["vld"]:
209 if not isinstance(vld, dict):
210 raise ValueError("Error at --config 'vld' must be a list of dictionaries")
211 if vld.get("vim-network-name"):
212 if isinstance(vld["vim-network-name"], dict):
213 vim_network_name_dict = {}
214 for vim_account, vim_net in vld["vim-network-name"].items():
215 vim_network_name_dict[get_vim_account_id(vim_account)] = vim_net
216 vld["vim-network-name"] = vim_network_name_dict
217 if "wim_account" in vld and vld["wim_account"] is not None:
218 vld["wimAccountId"] = get_wim_account_id(vld.pop("wim_account"))
219 if "vnf" in ns_config:
220 for vnf in ns_config["vnf"]:
221 if vnf.get("vim_account"):
222 vnf["vimAccountId"] = get_vim_account_id(vnf.pop("vim_account"))
223
224 if "additionalParamsForNs" in ns_config:
225 if not isinstance(ns_config["additionalParamsForNs"], dict):
226 raise ValueError("Error at --config 'additionalParamsForNs' must be a dictionary")
227 if "additionalParamsForVnf" in ns_config:
228 if not isinstance(ns_config["additionalParamsForVnf"], list):
229 raise ValueError("Error at --config 'additionalParamsForVnf' must be a list")
230 for additional_param_vnf in ns_config["additionalParamsForVnf"]:
231 if not isinstance(additional_param_vnf, dict):
232 raise ValueError("Error at --config 'additionalParamsForVnf' items must be dictionaries")
233 if not additional_param_vnf.get("member-vnf-index"):
234 raise ValueError("Error at --config 'additionalParamsForVnf' items must contain "
235 "'member-vnf-index'")
236 if "wim_account" in ns_config:
237 wim_account = ns_config.pop("wim_account")
238 if wim_account is not None:
239 ns['wimAccountId'] = get_wim_account_id(wim_account)
240 # rest of parameters without any transformation or checking
241 # "timeout_ns_deploy"
242 # "placement-engine"
243 ns.update(ns_config)
244
245 # print(yaml.safe_dump(ns))
246 try:
247 self._apiResource = '/ns_instances_content'
248 self._apiBase = '{}{}{}'.format(self._apiName,
249 self._apiVersion, self._apiResource)
250 headers = self._client._headers
251 headers['Content-Type'] = 'application/yaml'
252 http_header = ['{}: {}'.format(key,val)
253 for (key,val) in list(headers.items())]
254 self._http.set_http_header(http_header)
255 http_code, resp = self._http.post_cmd(endpoint=self._apiBase,
256 postfields_dict=ns)
257 # print('HTTP CODE: {}'.format(http_code))
258 # print('RESP: {}'.format(resp))
259 #if http_code in (200, 201, 202, 204):
260 if resp:
261 resp = json.loads(resp)
262 if not resp or 'id' not in resp:
263 raise ClientException('unexpected response from server - {} '.format(
264 resp))
265 if wait:
266 # Wait for status for NS instance creation
267 self._wait(resp.get('nslcmop_id'), wait)
268 print(resp['id'])
269 return resp['id']
270 #else:
271 # msg = ""
272 # if resp:
273 # try:
274 # msg = json.loads(resp)
275 # except ValueError:
276 # msg = resp
277 # raise ClientException(msg)
278 except ClientException as exc:
279 message="failed to create ns: {} nsd: {}\nerror:\n{}".format(
280 nsr_name,
281 nsd_name,
282 str(exc))
283 raise ClientException(message)
284
285 def list_op(self, name, filter=None):
286 """Returns the list of operations of a NS
287 """
288 self._logger.debug("")
289 ns = self.get(name)
290 try:
291 self._apiResource = '/ns_lcm_op_occs'
292 self._apiBase = '{}{}{}'.format(self._apiName,
293 self._apiVersion, self._apiResource)
294 filter_string = ''
295 if filter:
296 filter_string = '&{}'.format(filter)
297 http_code, resp = self._http.get2_cmd('{}?nsInstanceId={}'.format(
298 self._apiBase, ns['_id'],
299 filter_string) )
300 #print('HTTP CODE: {}'.format(http_code))
301 #print('RESP: {}'.format(resp))
302 if http_code == 200:
303 if resp:
304 resp = json.loads(resp)
305 return resp
306 else:
307 raise ClientException('unexpected response from server')
308 else:
309 msg = resp or ""
310 # if resp:
311 # try:
312 # resp = json.loads(resp)
313 # msg = resp['detail']
314 # except ValueError:
315 # msg = resp
316 raise ClientException(msg)
317 except ClientException as exc:
318 message="failed to get operation list of NS {}:\nerror:\n{}".format(
319 name,
320 str(exc))
321 raise ClientException(message)
322
323 def get_op(self, operationId):
324 """Returns the status of an operation
325 """
326 self._logger.debug("")
327 self._client.get_token()
328 try:
329 self._apiResource = '/ns_lcm_op_occs'
330 self._apiBase = '{}{}{}'.format(self._apiName,
331 self._apiVersion, self._apiResource)
332 http_code, resp = self._http.get2_cmd('{}/{}'.format(self._apiBase, operationId))
333 #print('HTTP CODE: {}'.format(http_code))
334 #print('RESP: {}'.format(resp))
335 if http_code == 200:
336 if resp:
337 resp = json.loads(resp)
338 return resp
339 else:
340 raise ClientException('unexpected response from server')
341 else:
342 msg = resp or ""
343 # if resp:
344 # try:
345 # resp = json.loads(resp)
346 # msg = resp['detail']
347 # except ValueError:
348 # msg = resp
349 raise ClientException(msg)
350 except ClientException as exc:
351 message="failed to get status of operation {}:\nerror:\n{}".format(
352 operationId,
353 str(exc))
354 raise ClientException(message)
355
356 def exec_op(self, name, op_name, op_data=None, wait=False, ):
357 """Executes an operation on a NS
358 """
359 self._logger.debug("")
360 ns = self.get(name)
361 try:
362 ns = self.get(name)
363 self._apiResource = '/ns_instances'
364 self._apiBase = '{}{}{}'.format(self._apiName,
365 self._apiVersion, self._apiResource)
366 endpoint = '{}/{}/{}'.format(self._apiBase, ns['_id'], op_name)
367 #print('OP_NAME: {}'.format(op_name))
368 #print('OP_DATA: {}'.format(json.dumps(op_data)))
369 http_code, resp = self._http.post_cmd(endpoint=endpoint, postfields_dict=op_data)
370 #print('HTTP CODE: {}'.format(http_code))
371 #print('RESP: {}'.format(resp))
372 #if http_code in (200, 201, 202, 204):
373 if resp:
374 resp = json.loads(resp)
375 if not resp or 'id' not in resp:
376 raise ClientException('unexpected response from server - {}'.format(
377 resp))
378 if wait:
379 # Wait for status for NS instance action
380 # For the 'action' operation, 'id' is used
381 self._wait(resp.get('id'), wait)
382 return resp['id']
383 #else:
384 # msg = ""
385 # if resp:
386 # try:
387 # msg = json.loads(resp)
388 # except ValueError:
389 # msg = resp
390 # raise ClientException(msg)
391 except ClientException as exc:
392 message="failed to exec operation {}:\nerror:\n{}".format(
393 name,
394 str(exc))
395 raise ClientException(message)
396
397 def scale_vnf(self, ns_name, vnf_name, scaling_group, scale_in, scale_out, wait=False):
398 """Scales a VNF by adding/removing VDUs
399 """
400 self._logger.debug("")
401 self._client.get_token()
402 try:
403 op_data={}
404 op_data["scaleType"] = "SCALE_VNF"
405 op_data["scaleVnfData"] = {}
406 if scale_in and not scale_out:
407 op_data["scaleVnfData"]["scaleVnfType"] = "SCALE_IN"
408 elif not scale_in and scale_out:
409 op_data["scaleVnfData"]["scaleVnfType"] = "SCALE_OUT"
410 else:
411 raise ClientException("you must set either 'scale_in' or 'scale_out'")
412 op_data["scaleVnfData"]["scaleByStepData"] = {
413 "member-vnf-index": vnf_name,
414 "scaling-group-descriptor": scaling_group,
415 }
416 op_id = self.exec_op(ns_name, op_name='scale', op_data=op_data, wait=wait)
417 print(str(op_id))
418 except ClientException as exc:
419 message="failed to scale vnf {} of ns {}:\nerror:\n{}".format(
420 vnf_name, ns_name, str(exc))
421 raise ClientException(message)
422
423 def create_alarm(self, alarm):
424 self._logger.debug("")
425 self._client.get_token()
426 data = {}
427 data["create_alarm_request"] = {}
428 data["create_alarm_request"]["alarm_create_request"] = alarm
429 try:
430 http_code, resp = self._http.post_cmd(endpoint='/test/message/alarm_request',
431 postfields_dict=data)
432 #print('HTTP CODE: {}'.format(http_code))
433 #print('RESP: {}'.format(resp))
434 # if http_code in (200, 201, 202, 204):
435 # resp = json.loads(resp)
436 print('Alarm created')
437 #else:
438 # msg = ""
439 # if resp:
440 # try:
441 # msg = json.loads(resp)
442 # except ValueError:
443 # msg = resp
444 # raise ClientException('error: code: {}, resp: {}'.format(
445 # http_code, msg))
446 except ClientException as exc:
447 message="failed to create alarm: alarm {}\n{}".format(
448 alarm,
449 str(exc))
450 raise ClientException(message)
451
452 def delete_alarm(self, name):
453 self._logger.debug("")
454 self._client.get_token()
455 data = {}
456 data["delete_alarm_request"] = {}
457 data["delete_alarm_request"]["alarm_delete_request"] = {}
458 data["delete_alarm_request"]["alarm_delete_request"]["alarm_uuid"] = name
459 try:
460 http_code, resp = self._http.post_cmd(endpoint='/test/message/alarm_request',
461 postfields_dict=data)
462 #print('HTTP CODE: {}'.format(http_code))
463 #print('RESP: {}'.format(resp))
464 # if http_code in (200, 201, 202, 204):
465 # resp = json.loads(resp)
466 print('Alarm deleted')
467 #else:
468 # msg = ""
469 # if resp:
470 # try:
471 # msg = json.loads(resp)
472 # except ValueError:
473 # msg = resp
474 # raise ClientException('error: code: {}, resp: {}'.format(
475 # http_code, msg))
476 except ClientException as exc:
477 message="failed to delete alarm: alarm {}\n{}".format(
478 name,
479 str(exc))
480 raise ClientException(message)
481
482 def export_metric(self, metric):
483 self._logger.debug("")
484 self._client.get_token()
485 data = {}
486 data["read_metric_data_request"] = metric
487 try:
488 http_code, resp = self._http.post_cmd(endpoint='/test/message/metric_request',
489 postfields_dict=data)
490 #print('HTTP CODE: {}'.format(http_code))
491 #print('RESP: {}'.format(resp))
492 # if http_code in (200, 201, 202, 204):
493 # resp = json.loads(resp)
494 return 'Metric exported'
495 #else:
496 # msg = ""
497 # if resp:
498 # try:
499 # msg = json.loads(resp)
500 # except ValueError:
501 # msg = resp
502 # raise ClientException('error: code: {}, resp: {}'.format(
503 # http_code, msg))
504 except ClientException as exc:
505 message="failed to export metric: metric {}\n{}".format(
506 metric,
507 str(exc))
508 raise ClientException(message)
509
510 def get_field(self, ns_name, field):
511 self._logger.debug("")
512 nsr = self.get(ns_name)
513 print(yaml.safe_dump(nsr))
514 if nsr is None:
515 raise NotFound("failed to retrieve ns {}".format(ns_name))
516
517 if field in nsr:
518 return nsr[field]
519
520 raise NotFound("failed to find {} in ns {}".format(field, ns_name))
521