Feature 10339 - Enhanced Alarm Mgmt. (SOL005 FM Interface)
[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 ):
167 self._logger.debug("")
168 self._client.get_token()
169 nsd = self._client.nsd.get(nsd_name)
170
171 vim_account_id = {}
172 wim_account_id = {}
173
174 def get_vim_account_id(vim_account):
175 self._logger.debug("")
176 if vim_account_id.get(vim_account):
177 return vim_account_id[vim_account]
178 vim = self._client.vim.get(vim_account)
179 if vim is None:
180 raise NotFound("cannot find vim account '{}'".format(vim_account))
181 vim_account_id[vim_account] = vim["_id"]
182 return vim["_id"]
183
184 def get_vca_id(vim_id):
185 vim = self._client.vim.get(vim_id)
186 return vim.get("vca")
187
188 def get_wim_account_id(wim_account):
189 self._logger.debug("")
190 # wim_account can be False (boolean) to indicate not use wim account
191 if not isinstance(wim_account, str):
192 return wim_account
193 if wim_account_id.get(wim_account):
194 return wim_account_id[wim_account]
195 wim = self._client.wim.get(wim_account)
196 if wim is None:
197 raise NotFound("cannot find wim account '{}'".format(wim_account))
198 wim_account_id[wim_account] = wim["_id"]
199 return wim["_id"]
200
201 vim_id = get_vim_account_id(account)
202 vca_id = get_vca_id(vim_id)
203 ns = {}
204 ns["nsdId"] = nsd["_id"]
205 ns["nsName"] = nsr_name
206 ns["nsDescription"] = description
207 ns["vimAccountId"] = vim_id
208 if vca_id:
209 ns["vcaId"] = vca_id
210 # ns['userdata'] = {}
211 # ns['userdata']['key1']='value1'
212 # ns['userdata']['key2']='value2'
213
214 if ssh_keys is not None:
215 ns["ssh_keys"] = []
216 for pubkeyfile in ssh_keys.split(","):
217 with open(pubkeyfile, "r") as f:
218 ns["ssh_keys"].append(f.read())
219 if config:
220 ns_config = yaml.safe_load(config)
221 if "vim-network-name" in ns_config:
222 ns_config["vld"] = ns_config.pop("vim-network-name")
223 if "vld" in ns_config:
224 if not isinstance(ns_config["vld"], list):
225 raise ClientException(
226 "Error at --config 'vld' must be a list of dictionaries"
227 )
228 for vld in ns_config["vld"]:
229 if not isinstance(vld, dict):
230 raise ClientException(
231 "Error at --config 'vld' must be a list of dictionaries"
232 )
233 if vld.get("vim-network-name"):
234 if isinstance(vld["vim-network-name"], dict):
235 vim_network_name_dict = {}
236 for vim_account, vim_net in vld["vim-network-name"].items():
237 vim_network_name_dict[
238 get_vim_account_id(vim_account)
239 ] = vim_net
240 vld["vim-network-name"] = vim_network_name_dict
241 if "wim_account" in vld and vld["wim_account"] is not None:
242 vld["wimAccountId"] = get_wim_account_id(vld.pop("wim_account"))
243 if "vnf" in ns_config:
244 for vnf in ns_config["vnf"]:
245 if vnf.get("vim_account"):
246 vnf["vimAccountId"] = get_vim_account_id(vnf.pop("vim_account"))
247
248 if "additionalParamsForNs" in ns_config:
249 if not isinstance(ns_config["additionalParamsForNs"], dict):
250 raise ClientException(
251 "Error at --config 'additionalParamsForNs' must be a dictionary"
252 )
253 if "additionalParamsForVnf" in ns_config:
254 if not isinstance(ns_config["additionalParamsForVnf"], list):
255 raise ClientException(
256 "Error at --config 'additionalParamsForVnf' must be a list"
257 )
258 for additional_param_vnf in ns_config["additionalParamsForVnf"]:
259 if not isinstance(additional_param_vnf, dict):
260 raise ClientException(
261 "Error at --config 'additionalParamsForVnf' items must be dictionaries"
262 )
263 if not additional_param_vnf.get("member-vnf-index"):
264 raise ClientException(
265 "Error at --config 'additionalParamsForVnf' items must contain "
266 "'member-vnf-index'"
267 )
268 if "wim_account" in ns_config:
269 wim_account = ns_config.pop("wim_account")
270 if wim_account is not None:
271 ns["wimAccountId"] = get_wim_account_id(wim_account)
272 # rest of parameters without any transformation or checking
273 # "timeout_ns_deploy"
274 # "placement-engine"
275 ns.update(ns_config)
276
277 # print(yaml.safe_dump(ns))
278 try:
279 self._apiResource = "/ns_instances_content"
280 self._apiBase = "{}{}{}".format(
281 self._apiName, self._apiVersion, self._apiResource
282 )
283 headers = self._client._headers
284 headers["Content-Type"] = "application/yaml"
285 http_header = [
286 "{}: {}".format(key, val) for (key, val) in list(headers.items())
287 ]
288 self._http.set_http_header(http_header)
289 http_code, resp = self._http.post_cmd(
290 endpoint=self._apiBase, postfields_dict=ns
291 )
292 # print('HTTP CODE: {}'.format(http_code))
293 # print('RESP: {}'.format(resp))
294 # if http_code in (200, 201, 202, 204):
295 if resp:
296 resp = json.loads(resp)
297 if not resp or "id" not in resp:
298 raise ClientException(
299 "unexpected response from server - {} ".format(resp)
300 )
301 if wait:
302 # Wait for status for NS instance creation
303 self._wait(resp.get("nslcmop_id"), wait)
304 print(resp["id"])
305 return resp["id"]
306 # else:
307 # msg = ""
308 # if resp:
309 # try:
310 # msg = json.loads(resp)
311 # except ValueError:
312 # msg = resp
313 # raise ClientException(msg)
314 except ClientException as exc:
315 message = "failed to create ns: {} nsd: {}\nerror:\n{}".format(
316 nsr_name, nsd_name, str(exc)
317 )
318 raise ClientException(message)
319
320 def list_op(self, name, filter=None):
321 """Returns the list of operations of a NS"""
322 self._logger.debug("")
323 ns = self.get(name)
324 try:
325 self._apiResource = "/ns_lcm_op_occs"
326 self._apiBase = "{}{}{}".format(
327 self._apiName, self._apiVersion, self._apiResource
328 )
329 filter_string = ""
330 if filter:
331 filter_string = "&{}".format(filter)
332 http_code, resp = self._http.get2_cmd(
333 "{}?nsInstanceId={}{}".format(self._apiBase, ns["_id"], filter_string)
334 )
335 # print('HTTP CODE: {}'.format(http_code))
336 # print('RESP: {}'.format(resp))
337 if http_code == 200:
338 if resp:
339 resp = json.loads(resp)
340 return resp
341 else:
342 raise ClientException("unexpected response from server")
343 else:
344 msg = resp or ""
345 # if resp:
346 # try:
347 # resp = json.loads(resp)
348 # msg = resp['detail']
349 # except ValueError:
350 # msg = resp
351 raise ClientException(msg)
352 except ClientException as exc:
353 message = "failed to get operation list of NS {}:\nerror:\n{}".format(
354 name, str(exc)
355 )
356 raise ClientException(message)
357
358 def get_op(self, operationId):
359 """Returns the status of an operation"""
360 self._logger.debug("")
361 self._client.get_token()
362 try:
363 self._apiResource = "/ns_lcm_op_occs"
364 self._apiBase = "{}{}{}".format(
365 self._apiName, self._apiVersion, self._apiResource
366 )
367 http_code, resp = self._http.get2_cmd(
368 "{}/{}".format(self._apiBase, operationId)
369 )
370 # print('HTTP CODE: {}'.format(http_code))
371 # print('RESP: {}'.format(resp))
372 if http_code == 200:
373 if resp:
374 resp = json.loads(resp)
375 return resp
376 else:
377 raise ClientException("unexpected response from server")
378 else:
379 msg = resp or ""
380 # if resp:
381 # try:
382 # resp = json.loads(resp)
383 # msg = resp['detail']
384 # except ValueError:
385 # msg = resp
386 raise ClientException(msg)
387 except ClientException as exc:
388 message = "failed to get status of operation {}:\nerror:\n{}".format(
389 operationId, str(exc)
390 )
391 raise ClientException(message)
392
393 def exec_op(
394 self,
395 name,
396 op_name,
397 op_data=None,
398 wait=False,
399 ):
400 """Executes an operation on a NS"""
401 self._logger.debug("")
402 ns = self.get(name)
403 try:
404 ns = self.get(name)
405 self._apiResource = "/ns_instances"
406 self._apiBase = "{}{}{}".format(
407 self._apiName, self._apiVersion, self._apiResource
408 )
409 endpoint = "{}/{}/{}".format(self._apiBase, ns["_id"], op_name)
410 # print('OP_NAME: {}'.format(op_name))
411 # print('OP_DATA: {}'.format(json.dumps(op_data)))
412 http_code, resp = self._http.post_cmd(
413 endpoint=endpoint, postfields_dict=op_data
414 )
415 # print('HTTP CODE: {}'.format(http_code))
416 # print('RESP: {}'.format(resp))
417 # if http_code in (200, 201, 202, 204):
418 if resp:
419 resp = json.loads(resp)
420 if not resp or "id" not in resp:
421 raise ClientException(
422 "unexpected response from server - {}".format(resp)
423 )
424 if wait:
425 # Wait for status for NS instance action
426 # For the 'action' operation, 'id' is used
427 self._wait(resp.get("id"), wait)
428 return resp["id"]
429 # else:
430 # msg = ""
431 # if resp:
432 # try:
433 # msg = json.loads(resp)
434 # except ValueError:
435 # msg = resp
436 # raise ClientException(msg)
437 except ClientException as exc:
438 message = "failed to exec operation {}:\nerror:\n{}".format(name, str(exc))
439 raise ClientException(message)
440
441 def scale_vnf(
442 self,
443 ns_name,
444 vnf_name,
445 scaling_group,
446 scale_in,
447 scale_out,
448 wait=False,
449 timeout=None,
450 ):
451 """Scales a VNF by adding/removing VDUs"""
452 self._logger.debug("")
453 self._client.get_token()
454 try:
455 op_data = {}
456 op_data["scaleType"] = "SCALE_VNF"
457 op_data["scaleVnfData"] = {}
458 if scale_in and not scale_out:
459 op_data["scaleVnfData"]["scaleVnfType"] = "SCALE_IN"
460 elif not scale_in and scale_out:
461 op_data["scaleVnfData"]["scaleVnfType"] = "SCALE_OUT"
462 else:
463 raise ClientException("you must set either 'scale_in' or 'scale_out'")
464 op_data["scaleVnfData"]["scaleByStepData"] = {
465 "member-vnf-index": vnf_name,
466 "scaling-group-descriptor": scaling_group,
467 }
468 if timeout:
469 op_data["timeout_ns_scale"] = timeout
470 op_id = self.exec_op(ns_name, op_name="scale", op_data=op_data, wait=wait)
471 print(str(op_id))
472 except ClientException as exc:
473 message = "failed to scale vnf {} of ns {}:\nerror:\n{}".format(
474 vnf_name, ns_name, str(exc)
475 )
476 raise ClientException(message)
477
478 def create_alarm(self, alarm):
479 self._logger.debug("")
480 self._client.get_token()
481 data = {}
482 data["create_alarm_request"] = {}
483 data["create_alarm_request"]["alarm_create_request"] = alarm
484 try:
485 http_code, resp = self._http.post_cmd(
486 endpoint="/test/message/alarm_request", postfields_dict=data
487 )
488 # print('HTTP CODE: {}'.format(http_code))
489 # print('RESP: {}'.format(resp))
490 # if http_code in (200, 201, 202, 204):
491 # resp = json.loads(resp)
492 print("Alarm created")
493 # else:
494 # msg = ""
495 # if resp:
496 # try:
497 # msg = json.loads(resp)
498 # except ValueError:
499 # msg = resp
500 # raise ClientException('error: code: {}, resp: {}'.format(
501 # http_code, msg))
502 except ClientException as exc:
503 message = "failed to create alarm: alarm {}\n{}".format(alarm, str(exc))
504 raise ClientException(message)
505
506 def delete_alarm(self, name):
507 self._logger.debug("")
508 self._client.get_token()
509 data = {}
510 data["delete_alarm_request"] = {}
511 data["delete_alarm_request"]["alarm_delete_request"] = {}
512 data["delete_alarm_request"]["alarm_delete_request"]["alarm_uuid"] = name
513 try:
514 http_code, resp = self._http.post_cmd(
515 endpoint="/test/message/alarm_request", postfields_dict=data
516 )
517 # print('HTTP CODE: {}'.format(http_code))
518 # print('RESP: {}'.format(resp))
519 # if http_code in (200, 201, 202, 204):
520 # resp = json.loads(resp)
521 print("Alarm deleted")
522 # else:
523 # msg = ""
524 # if resp:
525 # try:
526 # msg = json.loads(resp)
527 # except ValueError:
528 # msg = resp
529 # raise ClientException('error: code: {}, resp: {}'.format(
530 # http_code, msg))
531 except ClientException as exc:
532 message = "failed to delete alarm: alarm {}\n{}".format(name, str(exc))
533 raise ClientException(message)
534
535 def get_alarm(self, project_name=None, ns_id=None, uuid=None):
536 self._client.get_token()
537 try:
538 self._apiName = "/nsfm"
539 self._apiResource = "/alarms"
540 self._apiBase = "{}{}{}".format(
541 self._apiName, self._apiVersion, self._apiResource
542 )
543 if uuid:
544 # if request is for any uuid
545 http_code, resp = self._http.get2_cmd(
546 "{}/{}".format(self._apiBase, uuid)
547 )
548 if not uuid:
549 http_code, resp = self._http.get2_cmd(
550 "{}/{}/{}/{}".format(self._apiBase, uuid, project_name, ns_id)
551 )
552 if http_code == 200:
553 if resp:
554 resp = json.loads(resp)
555 return resp
556 else:
557 raise ClientException("unexpected response from server")
558 else:
559 msg = resp or ""
560 raise ClientException(msg)
561 except ClientException as exc:
562 message = "failed to get alarm :\nerror:\n{}".format(str(exc))
563 raise ClientException(message)
564
565 def update_alarm(self, uuid, threshold=None, is_enable=None, wait=None):
566 self._client.get_token()
567 try:
568 op_data = {}
569 op_data["uuid"] = uuid
570 op_data["threshold"] = threshold
571 op_data["is_enable"] = is_enable
572 self._apiName = "/nsfm"
573 self._apiResource = "/alarms"
574 self._apiBase = "{}{}{}".format(
575 self._apiName, self._apiVersion, self._apiResource
576 )
577 http_code, resp = self._http.patch_cmd(
578 endpoint="{}".format(self._apiBase), postfields_dict=op_data
579 )
580 if resp:
581 resp = json.loads(resp)
582 print(resp)
583 return resp
584 except ClientException as exc:
585 message = "failed to update alarm :\nerror:\n{}".format(str(exc))
586 raise ClientException(message)
587
588 def export_metric(self, metric):
589 self._logger.debug("")
590 self._client.get_token()
591 data = {}
592 data["read_metric_data_request"] = metric
593 try:
594 http_code, resp = self._http.post_cmd(
595 endpoint="/test/message/metric_request", postfields_dict=data
596 )
597 # print('HTTP CODE: {}'.format(http_code))
598 # print('RESP: {}'.format(resp))
599 # if http_code in (200, 201, 202, 204):
600 # resp = json.loads(resp)
601 return "Metric exported"
602 # else:
603 # msg = ""
604 # if resp:
605 # try:
606 # msg = json.loads(resp)
607 # except ValueError:
608 # msg = resp
609 # raise ClientException('error: code: {}, resp: {}'.format(
610 # http_code, msg))
611 except ClientException as exc:
612 message = "failed to export metric: metric {}\n{}".format(metric, str(exc))
613 raise ClientException(message)
614
615 def get_field(self, ns_name, field):
616 self._logger.debug("")
617 nsr = self.get(ns_name)
618 print(yaml.safe_dump(nsr))
619 if nsr is None:
620 raise NotFound("failed to retrieve ns {}".format(ns_name))
621
622 if field in nsr:
623 return nsr[field]
624
625 raise NotFound("failed to find {} in ns {}".format(field, ns_name))