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