e731ec1abe8b0952f5e52d265dea50aa0ac427cd
[osm/LCM.git] / osm_lcm / ROclient.py
1 #!/usr/bin/env python3
2 # -*- coding: utf-8 -*-
3
4 ##
5 # Copyright 2015 Telefónica Investigación y Desarrollo, S.A.U.
6 #
7 # Licensed under the Apache License, Version 2.0 (the "License"); you may
8 # not use this file except in compliance with the License. You may obtain
9 # a copy of the License at
10 #
11 # http://www.apache.org/licenses/LICENSE-2.0
12 #
13 # Unless required by applicable law or agreed to in writing, software
14 # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
15 # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
16 # License for the specific language governing permissions and limitations
17 # under the License.
18 #
19 ##
20
21 """
22 asyncio RO python client to interact with RO-server
23 """
24
25 import asyncio
26 import aiohttp
27 import json
28 import yaml
29 import logging
30 from urllib.parse import quote
31 from uuid import UUID
32 from copy import deepcopy
33
34 __author__ = "Alfonso Tierno"
35 __date__ = "$09-Jan-2018 09:09:48$"
36 __version__ = "0.1.2"
37 version_date = "2018-05-16"
38 requests = None
39
40
41 class ROClientException(Exception):
42 def __init__(self, message, http_code=400):
43 """Common Exception for all RO client exceptions"""
44 self.http_code = http_code
45 Exception.__init__(self, message)
46
47
48 def remove_envelop(item, indata=None):
49 """
50 Obtain the useful data removing the envelop. It goes through the vnfd or nsd catalog and returns the
51 vnfd or nsd content
52 :param item: can be 'tenant', 'vim', 'vnfd', 'nsd', 'ns'
53 :param indata: Content to be inspected
54 :return: the useful part of indata (a reference, not a new dictionay)
55 """
56 clean_indata = indata
57 if not indata:
58 return {}
59 if item == "vnfd":
60 if clean_indata.get("vnfd:vnfd-catalog"):
61 clean_indata = clean_indata["vnfd:vnfd-catalog"]
62 elif clean_indata.get("vnfd-catalog"):
63 clean_indata = clean_indata["vnfd-catalog"]
64 if clean_indata.get("vnfd"):
65 if (
66 not isinstance(clean_indata["vnfd"], list)
67 or len(clean_indata["vnfd"]) != 1
68 ):
69 raise ROClientException("'vnfd' must be a list only one element")
70 clean_indata = clean_indata["vnfd"][0]
71 elif item == "nsd":
72 if clean_indata.get("nsd:nsd-catalog"):
73 clean_indata = clean_indata["nsd:nsd-catalog"]
74 elif clean_indata.get("nsd-catalog"):
75 clean_indata = clean_indata["nsd-catalog"]
76 if clean_indata.get("nsd"):
77 if (
78 not isinstance(clean_indata["nsd"], list)
79 or len(clean_indata["nsd"]) != 1
80 ):
81 raise ROClientException("'nsd' must be a list only one element")
82 clean_indata = clean_indata["nsd"][0]
83 elif item == "sdn":
84 if len(indata) == 1 and "sdn_controller" in indata:
85 clean_indata = indata["sdn_controller"]
86 elif item == "tenant":
87 if len(indata) == 1 and "tenant" in indata:
88 clean_indata = indata["tenant"]
89 elif item in ("vim", "vim_account", "datacenters"):
90 if len(indata) == 1 and "datacenter" in indata:
91 clean_indata = indata["datacenter"]
92 elif item == "wim":
93 if len(indata) == 1 and "wim" in indata:
94 clean_indata = indata["wim"]
95 elif item == "wim_account":
96 if len(indata) == 1 and "wim_account" in indata:
97 clean_indata = indata["wim_account"]
98 elif item == "ns" or item == "instances":
99 if len(indata) == 1 and "instance" in indata:
100 clean_indata = indata["instance"]
101 else:
102 raise ROClientException("remove_envelop with unknown item {}".format(item))
103
104 return clean_indata
105
106
107 class ROClient:
108 headers_req = {"Accept": "application/yaml", "content-type": "application/yaml"}
109 client_to_RO = {
110 "tenant": "tenants",
111 "vim": "datacenters",
112 "vim_account": "datacenters",
113 "sdn": "sdn_controllers",
114 "vnfd": "vnfs",
115 "nsd": "scenarios",
116 "wim": "wims",
117 "wim_account": "wims",
118 "ns": "instances",
119 }
120 mandatory_for_create = {
121 "tenant": ("name",),
122 "vnfd": ("name", "id"),
123 "nsd": ("name", "id"),
124 "ns": ("name", "scenario", "datacenter"),
125 "vim": ("name", "vim_url"),
126 "wim": ("name", "wim_url"),
127 "vim_account": (),
128 "wim_account": (),
129 "sdn": ("name", "type"),
130 }
131 timeout_large = 120
132 timeout_short = 30
133
134 def __init__(self, uri, **kwargs):
135 self.uri = uri
136
137 self.username = kwargs.get("username")
138 self.password = kwargs.get("password")
139 self.tenant_id_name = kwargs.get("tenant")
140 self.tenant = None
141 self.datacenter_id_name = kwargs.get("datacenter")
142 self.datacenter = None
143 logger_name = kwargs.get("logger_name", "lcm.ro")
144 self.logger = logging.getLogger(logger_name)
145 if kwargs.get("loglevel"):
146 self.logger.setLevel(kwargs["loglevel"])
147 global requests
148 requests = kwargs.get("TODO remove")
149
150 def __getitem__(self, index):
151 if index == "tenant":
152 return self.tenant_id_name
153 elif index == "datacenter":
154 return self.datacenter_id_name
155 elif index == "username":
156 return self.username
157 elif index == "password":
158 return self.password
159 elif index == "uri":
160 return self.uri
161 else:
162 raise KeyError("Invalid key '{}'".format(index))
163
164 def __setitem__(self, index, value):
165 if index == "tenant":
166 self.tenant_id_name = value
167 elif index == "datacenter" or index == "vim":
168 self.datacenter_id_name = value
169 elif index == "username":
170 self.username = value
171 elif index == "password":
172 self.password = value
173 elif index == "uri":
174 self.uri = value
175 else:
176 raise KeyError("Invalid key '{}'".format(index))
177 self.tenant = None # force to reload tenant with different credentials
178 self.datacenter = None # force to reload datacenter with different credentials
179
180 @staticmethod
181 def _parse(descriptor, descriptor_format, response=False):
182 if (
183 descriptor_format
184 and descriptor_format != "json"
185 and descriptor_format != "yaml"
186 ):
187 raise ROClientException(
188 "'descriptor_format' must be a 'json' or 'yaml' text"
189 )
190 if descriptor_format != "json":
191 try:
192 return yaml.safe_load(descriptor)
193 except yaml.YAMLError as exc:
194 error_pos = ""
195 if hasattr(exc, "problem_mark"):
196 mark = exc.problem_mark
197 error_pos = " at line:{} column:{}s".format(
198 mark.line + 1, mark.column + 1
199 )
200 error_text = "yaml format error" + error_pos
201 elif descriptor_format != "yaml":
202 try:
203 return json.loads(descriptor)
204 except Exception as e:
205 if response:
206 error_text = "json format error" + str(e)
207
208 if response:
209 raise ROClientException(error_text)
210 raise ROClientException(error_text)
211
212 @staticmethod
213 def _parse_error_yaml(descriptor):
214 json_error = None
215 try:
216 json_error = yaml.safe_load(descriptor)
217 return json_error["error"]["description"]
218 except Exception:
219 return str(json_error or descriptor)
220
221 @staticmethod
222 def _parse_yaml(descriptor, response=False):
223 try:
224 return yaml.safe_load(descriptor)
225 except yaml.YAMLError as exc:
226 error_pos = ""
227 if hasattr(exc, "problem_mark"):
228 mark = exc.problem_mark
229 error_pos = " at line:{} column:{}s".format(
230 mark.line + 1, mark.column + 1
231 )
232 error_text = "yaml format error" + error_pos
233 if response:
234 raise ROClientException(error_text)
235 raise ROClientException(error_text)
236
237 @staticmethod
238 def check_if_uuid(uuid_text):
239 """
240 Check if text correspond to an uuid foramt
241 :param uuid_text:
242 :return: True if it is an uuid False if not
243 """
244 try:
245 UUID(uuid_text)
246 return True
247 except Exception:
248 return False
249
250 @staticmethod
251 def _create_envelop(item, indata=None):
252 """
253 Returns a new dict that incledes indata with the expected envelop
254 :param item: can be 'tenant', 'vim', 'vnfd', 'nsd', 'ns'
255 :param indata: Content to be enveloped
256 :return: a new dic with {<envelop>: {indata} } where envelop can be e.g. tenant, datacenter, ...
257 """
258 if item == "vnfd":
259 return {"vnfd-catalog": {"vnfd": [indata]}}
260 elif item == "nsd":
261 return {"nsd-catalog": {"nsd": [indata]}}
262 elif item == "tenant":
263 return {"tenant": indata}
264 elif item in ("vim", "vim_account", "datacenter"):
265 return {"datacenter": indata}
266 elif item == "wim":
267 return {"wim": indata}
268 elif item == "wim_account":
269 return {"wim_account": indata}
270 elif item == "ns" or item == "instances":
271 return {"instance": indata}
272 elif item == "sdn":
273 return {"sdn_controller": indata}
274 else:
275 raise ROClientException("remove_envelop with unknown item {}".format(item))
276
277 @staticmethod
278 def update_descriptor(desc, kwargs):
279 desc = deepcopy(desc) # do not modify original descriptor
280 try:
281 for k, v in kwargs.items():
282 update_content = desc
283 kitem_old = None
284 klist = k.split(".")
285 for kitem in klist:
286 if kitem_old is not None:
287 update_content = update_content[kitem_old]
288 if isinstance(update_content, dict):
289 kitem_old = kitem
290 elif isinstance(update_content, list):
291 kitem_old = int(kitem)
292 else:
293 raise ROClientException(
294 "Invalid query string '{}'. Descriptor is not a list nor dict at '{}'".format(
295 k, kitem
296 )
297 )
298 if v == "__DELETE__":
299 del update_content[kitem_old]
300 else:
301 update_content[kitem_old] = v
302 return desc
303 except KeyError:
304 raise ROClientException(
305 "Invalid query string '{}'. Descriptor does not contain '{}'".format(
306 k, kitem_old
307 )
308 )
309 except ValueError:
310 raise ROClientException(
311 "Invalid query string '{}'. Expected integer index list instead of '{}'".format(
312 k, kitem
313 )
314 )
315 except IndexError:
316 raise ROClientException(
317 "Invalid query string '{}'. Index '{}' out of range".format(
318 k, kitem_old
319 )
320 )
321
322 async def _get_item_uuid(self, session, item, item_id_name, all_tenants=False):
323 if all_tenants:
324 tenant_text = "/any"
325 elif all_tenants is None:
326 tenant_text = ""
327 else:
328 if not self.tenant:
329 await self._get_tenant(session)
330 tenant_text = "/" + self.tenant
331
332 item_id = 0
333 url = "{}{}/{}".format(self.uri, tenant_text, item)
334 if self.check_if_uuid(item_id_name):
335 item_id = item_id_name
336 url += "/" + item_id_name
337 elif (
338 item_id_name and item_id_name.startswith("'") and item_id_name.endswith("'")
339 ):
340 item_id_name = item_id_name[1:-1]
341 self.logger.debug("RO GET %s", url)
342 # timeout = aiohttp.ClientTimeout(total=self.timeout_short)
343 async with session.get(url, headers=self.headers_req) as response:
344 response_text = await response.read()
345 self.logger.debug(
346 "GET {} [{}] {}".format(url, response.status, response_text[:100])
347 )
348 if response.status == 404: # NOT_FOUND
349 raise ROClientException(
350 "No {} found with id '{}'".format(item[:-1], item_id_name),
351 http_code=404,
352 )
353 if response.status >= 300:
354 raise ROClientException(
355 self._parse_error_yaml(response_text), http_code=response.status
356 )
357 content = self._parse_yaml(response_text, response=True)
358
359 if item_id:
360 return item_id
361 desc = content[item]
362 if not isinstance(desc, list):
363 raise ROClientException(
364 "_get_item_uuid get a non dict with a list inside {}".format(type(desc))
365 )
366 uuid = None
367 for i in desc:
368 if item_id_name and i["name"] != item_id_name:
369 continue
370 if uuid: # found more than one
371 raise ROClientException(
372 "Found more than one {} with name '{}'. uuid must be used".format(
373 item, item_id_name
374 ),
375 http_code=404,
376 )
377 uuid = i["uuid"]
378 if not uuid:
379 raise ROClientException(
380 "No {} found with name '{}'".format(item[:-1], item_id_name),
381 http_code=404,
382 )
383 return uuid
384
385 async def _get_tenant(self, session):
386 if not self.tenant:
387 self.tenant = await self._get_item_uuid(
388 session, "tenants", self.tenant_id_name, None
389 )
390 return self.tenant
391
392 async def _get_datacenter(self, session):
393 if not self.tenant:
394 await self._get_tenant(session)
395 if not self.datacenter:
396 self.datacenter = await self._get_item_uuid(
397 session, "datacenters", self.datacenter_id_name, True
398 )
399 return self.datacenter
400
401 async def _create_item(
402 self,
403 session,
404 item,
405 descriptor,
406 item_id_name=None,
407 action=None,
408 all_tenants=False,
409 ):
410 if all_tenants:
411 tenant_text = "/any"
412 elif all_tenants is None:
413 tenant_text = ""
414 else:
415 if not self.tenant:
416 await self._get_tenant(session)
417 tenant_text = "/" + self.tenant
418 payload_req = yaml.safe_dump(descriptor)
419 # print payload_req
420
421 api_version_text = ""
422 if item == "vnfs":
423 # assumes version v3 only
424 api_version_text = "/v3"
425 item = "vnfd"
426 elif item == "scenarios":
427 # assumes version v3 only
428 api_version_text = "/v3"
429 item = "nsd"
430
431 if not item_id_name:
432 uuid = ""
433 elif self.check_if_uuid(item_id_name):
434 uuid = "/{}".format(item_id_name)
435 else:
436 # check that exist
437 uuid = await self._get_item_uuid(session, item, item_id_name, all_tenants)
438 uuid = "/{}".format(uuid)
439 if not action:
440 action = ""
441 else:
442 action = "/{}".format(action)
443
444 url = "{}{apiver}{tenant}/{item}{id}{action}".format(
445 self.uri,
446 apiver=api_version_text,
447 tenant=tenant_text,
448 item=item,
449 id=uuid,
450 action=action,
451 )
452 self.logger.debug("RO POST %s %s", url, payload_req)
453 # timeout = aiohttp.ClientTimeout(total=self.timeout_large)
454 async with session.post(
455 url, headers=self.headers_req, data=payload_req
456 ) as response:
457 response_text = await response.read()
458 self.logger.debug(
459 "POST {} [{}] {}".format(url, response.status, response_text[:100])
460 )
461 if response.status >= 300:
462 raise ROClientException(
463 self._parse_error_yaml(response_text), http_code=response.status
464 )
465
466 return self._parse_yaml(response_text, response=True)
467
468 async def _del_item(self, session, item, item_id_name, all_tenants=False):
469 if all_tenants:
470 tenant_text = "/any"
471 elif all_tenants is None:
472 tenant_text = ""
473 else:
474 if not self.tenant:
475 await self._get_tenant(session)
476 tenant_text = "/" + self.tenant
477 if not self.check_if_uuid(item_id_name):
478 # check that exist
479 _all_tenants = all_tenants
480 if item in ("datacenters", "wims"):
481 _all_tenants = True
482 uuid = await self._get_item_uuid(
483 session, item, item_id_name, all_tenants=_all_tenants
484 )
485 else:
486 uuid = item_id_name
487
488 url = "{}{}/{}/{}".format(self.uri, tenant_text, item, uuid)
489 self.logger.debug("DELETE %s", url)
490 # timeout = aiohttp.ClientTimeout(total=self.timeout_short)
491 async with session.delete(url, headers=self.headers_req) as response:
492 response_text = await response.read()
493 self.logger.debug(
494 "DELETE {} [{}] {}".format(url, response.status, response_text[:100])
495 )
496 if response.status >= 300:
497 raise ROClientException(
498 self._parse_error_yaml(response_text), http_code=response.status
499 )
500
501 return self._parse_yaml(response_text, response=True)
502
503 async def _list_item(self, session, item, all_tenants=False, filter_dict=None):
504 if all_tenants:
505 tenant_text = "/any"
506 elif all_tenants is None:
507 tenant_text = ""
508 else:
509 if not self.tenant:
510 await self._get_tenant(session)
511 tenant_text = "/" + self.tenant
512
513 url = "{}{}/{}".format(self.uri, tenant_text, item)
514 separator = "?"
515 if filter_dict:
516 for k in filter_dict:
517 url += separator + quote(str(k)) + "=" + quote(str(filter_dict[k]))
518 separator = "&"
519 self.logger.debug("RO GET %s", url)
520 # timeout = aiohttp.ClientTimeout(total=self.timeout_short)
521 async with session.get(url, headers=self.headers_req) as response:
522 response_text = await response.read()
523 self.logger.debug(
524 "GET {} [{}] {}".format(url, response.status, response_text[:100])
525 )
526 if response.status >= 300:
527 raise ROClientException(
528 self._parse_error_yaml(response_text), http_code=response.status
529 )
530
531 return self._parse_yaml(response_text, response=True)
532
533 async def _edit_item(self, session, item, item_id, descriptor, all_tenants=False):
534 if all_tenants:
535 tenant_text = "/any"
536 elif all_tenants is None:
537 tenant_text = ""
538 else:
539 if not self.tenant:
540 await self._get_tenant(session)
541 tenant_text = "/" + self.tenant
542
543 payload_req = yaml.safe_dump(descriptor)
544
545 # print payload_req
546 url = "{}{}/{}/{}".format(self.uri, tenant_text, item, item_id)
547 self.logger.debug("RO PUT %s %s", url, payload_req)
548 # timeout = aiohttp.ClientTimeout(total=self.timeout_large)
549 async with session.put(
550 url, headers=self.headers_req, data=payload_req
551 ) as response:
552 response_text = await response.read()
553 self.logger.debug(
554 "PUT {} [{}] {}".format(url, response.status, response_text[:100])
555 )
556 if response.status >= 300:
557 raise ROClientException(
558 self._parse_error_yaml(response_text), http_code=response.status
559 )
560
561 return self._parse_yaml(response_text, response=True)
562
563 async def get_version(self):
564 """
565 Obtain RO server version.
566 :return: a list with integers ["major", "minor", "release"]. Raises ROClientException on Error,
567 """
568 try:
569 response_text = ""
570 async with aiohttp.ClientSession() as session:
571 url = "{}/version".format(self.uri)
572 self.logger.debug("RO GET %s", url)
573 # timeout = aiohttp.ClientTimeout(total=self.timeout_short)
574 async with session.get(url, headers=self.headers_req) as response:
575 response_text = await response.read()
576 self.logger.debug(
577 "GET {} [{}] {}".format(
578 url, response.status, response_text[:100]
579 )
580 )
581 if response.status >= 300:
582 raise ROClientException(
583 self._parse_error_yaml(response_text),
584 http_code=response.status,
585 )
586
587 for word in str(response_text).split(" "):
588 if "." in word:
589 version_text, _, _ = word.partition("-")
590 return version_text
591 raise ROClientException(
592 "Got invalid version text: '{}'".format(response_text),
593 http_code=500,
594 )
595 except (aiohttp.ClientOSError, aiohttp.ClientError) as e:
596 raise ROClientException(e, http_code=504)
597 except asyncio.TimeoutError:
598 raise ROClientException("Timeout", http_code=504)
599 except Exception as e:
600 self.logger.critical(
601 "Got invalid version text: '{}'; causing exception {}".format(
602 response_text, str(e)
603 )
604 )
605 raise ROClientException(
606 "Got invalid version text: '{}'; causing exception {}".format(
607 response_text, e
608 ),
609 http_code=500,
610 )
611
612 async def get_list(self, item, all_tenants=False, filter_by=None):
613 """
614 List of items filtered by the contents in the dictionary "filter_by".
615 :param item: can be 'tenant', 'vim', 'vnfd', 'nsd', 'ns'
616 :param all_tenants: True if not filtering by tenant. Only allowed for admin
617 :param filter_by: dictionary with filtering
618 :return: a list of dict. It can be empty. Raises ROClientException on Error,
619 """
620 try:
621 if item not in self.client_to_RO:
622 raise ROClientException("Invalid item {}".format(item))
623 if item == "tenant":
624 all_tenants = None
625 async with aiohttp.ClientSession() as session:
626 content = await self._list_item(
627 session,
628 self.client_to_RO[item],
629 all_tenants=all_tenants,
630 filter_dict=filter_by,
631 )
632 if isinstance(content, dict):
633 if len(content) == 1:
634 for _, v in content.items():
635 return v
636 return content.values()[0]
637 else:
638 raise ROClientException(
639 "Output not a list neither dict with len equal 1", http_code=500
640 )
641 return content
642 except (aiohttp.ClientOSError, aiohttp.ClientError) as e:
643 raise ROClientException(e, http_code=504)
644 except asyncio.TimeoutError:
645 raise ROClientException("Timeout", http_code=504)
646
647 async def delete(self, item, item_id_name=None, all_tenants=False):
648 """
649 Delete the information of an item from its id or name
650 :param item: can be 'tenant', 'vim', 'vnfd', 'nsd', 'ns'
651 :param item_id_name: RO id or name of the item. Raise and exception if more than one found
652 :param all_tenants: True if not filtering by tenant. Only allowed for admin
653 :return: dictionary with the information or raises ROClientException on Error, NotFound, found several
654 """
655 try:
656 if item not in self.client_to_RO:
657 raise ROClientException("Invalid item {}".format(item))
658 if item in ("tenant", "vim", "wim"):
659 all_tenants = None
660
661 async with aiohttp.ClientSession() as session:
662 result = await self._del_item(
663 session,
664 self.client_to_RO[item],
665 item_id_name,
666 all_tenants=all_tenants,
667 )
668 # in case of ns delete, get the action_id embeded in text
669 if item == "ns" and result.get("result"):
670 _, _, action_id = result["result"].partition("action_id=")
671 action_id, _, _ = action_id.partition(" ")
672 if action_id:
673 result["action_id"] = action_id
674 return result
675 except (aiohttp.ClientOSError, aiohttp.ClientError) as e:
676 raise ROClientException(e, http_code=504)
677 except asyncio.TimeoutError:
678 raise ROClientException("Timeout", http_code=504)
679
680 async def edit(
681 self, item, item_id_name, descriptor=None, descriptor_format=None, **kwargs
682 ):
683 """Edit an item
684 :param item: can be 'tenant', 'vim', 'vnfd', 'nsd', 'ns', 'vim'
685 :param item_id_name: RO id or name of the item. Raise and exception if more than one found
686 :param descriptor: can be a dict, or a yaml/json text. Autodetect unless descriptor_format is provided
687 :param descriptor_format: Can be 'json' or 'yaml'
688 :param kwargs: Overrides descriptor with values as name, description, vim_url, vim_url_admin, vim_type
689 keys can be a dot separated list to specify elements inside dict
690 :return: dictionary with the information or raises ROClientException on Error
691 """
692 try:
693 if isinstance(descriptor, str):
694 descriptor = self._parse(descriptor, descriptor_format)
695 elif descriptor:
696 pass
697 else:
698 descriptor = {}
699
700 if item not in self.client_to_RO:
701 raise ROClientException("Invalid item {}".format(item))
702 desc = remove_envelop(item, descriptor)
703
704 # Override descriptor with kwargs
705 if kwargs:
706 desc = self.update_descriptor(desc, kwargs)
707 all_tenants = False
708 if item in ("tenant", "vim"):
709 all_tenants = None
710
711 create_desc = self._create_envelop(item, desc)
712
713 async with aiohttp.ClientSession() as session:
714 _all_tenants = all_tenants
715 if item == "vim":
716 _all_tenants = True
717 item_id = await self._get_item_uuid(
718 session,
719 self.client_to_RO[item],
720 item_id_name,
721 all_tenants=_all_tenants,
722 )
723 if item == "vim":
724 _all_tenants = None
725 # await self._get_tenant(session)
726 outdata = await self._edit_item(
727 session,
728 self.client_to_RO[item],
729 item_id,
730 create_desc,
731 all_tenants=_all_tenants,
732 )
733 return remove_envelop(item, outdata)
734 except (aiohttp.ClientOSError, aiohttp.ClientError) as e:
735 raise ROClientException(e, http_code=504)
736 except asyncio.TimeoutError:
737 raise ROClientException("Timeout", http_code=504)
738
739 async def create(self, item, descriptor=None, descriptor_format=None, **kwargs):
740 """
741 Creates an item from its descriptor
742 :param item: can be 'tenant', 'vnfd', 'nsd', 'ns', 'vim', 'vim_account', 'sdn'
743 :param descriptor: can be a dict, or a yaml/json text. Autodetect unless descriptor_format is provided
744 :param descriptor_format: Can be 'json' or 'yaml'
745 :param kwargs: Overrides descriptor with values as name, description, vim_url, vim_url_admin, vim_type
746 keys can be a dot separated list to specify elements inside dict
747 :return: dictionary with the information or raises ROClientException on Error
748 """
749 try:
750 if isinstance(descriptor, str):
751 descriptor = self._parse(descriptor, descriptor_format)
752 elif descriptor:
753 pass
754 else:
755 descriptor = {}
756
757 if item not in self.client_to_RO:
758 raise ROClientException("Invalid item {}".format(item))
759 desc = remove_envelop(item, descriptor)
760
761 # Override descriptor with kwargs
762 if kwargs:
763 desc = self.update_descriptor(desc, kwargs)
764
765 for mandatory in self.mandatory_for_create[item]:
766 if mandatory not in desc:
767 raise ROClientException(
768 "'{}' is mandatory parameter for {}".format(mandatory, item)
769 )
770
771 all_tenants = False
772 if item in ("tenant", "vim", "wim"):
773 all_tenants = None
774
775 create_desc = self._create_envelop(item, desc)
776
777 async with aiohttp.ClientSession() as session:
778 outdata = await self._create_item(
779 session,
780 self.client_to_RO[item],
781 create_desc,
782 all_tenants=all_tenants,
783 )
784 return remove_envelop(item, outdata)
785 except (aiohttp.ClientOSError, aiohttp.ClientError) as e:
786 raise ROClientException(e, http_code=504)
787 except asyncio.TimeoutError:
788 raise ROClientException("Timeout", http_code=504)
789
790 async def attach(
791 self, item, item_id_name=None, descriptor=None, descriptor_format=None, **kwargs
792 ):
793 """
794 Attach a datacenter or wim to a tenant, creating a vim_account, wim_account
795 :param item: can be vim_account or wim_account
796 :param item_id_name: id or name of the datacenter, wim
797 :param descriptor:
798 :param descriptor_format:
799 :param kwargs:
800 :return:
801 """
802 try:
803 if isinstance(descriptor, str):
804 descriptor = self._parse(descriptor, descriptor_format)
805 elif descriptor:
806 pass
807 else:
808 descriptor = {}
809
810 desc = remove_envelop(item, descriptor)
811
812 # # check that exist
813 # uuid = self._get_item_uuid(session, "datacenters", uuid_name, all_tenants=True)
814 # tenant_text = "/" + self._get_tenant()
815 if kwargs:
816 desc = self.update_descriptor(desc, kwargs)
817
818 if item == "vim_account":
819 if not desc.get("vim_tenant_name") and not desc.get("vim_tenant_id"):
820 raise ROClientException(
821 "Wrong descriptor. At least vim_tenant_name or vim_tenant_id must be "
822 "provided"
823 )
824 elif item != "wim_account":
825 raise ROClientException(
826 "Attach with unknown item {}. Must be 'vim_account' or 'wim_account'".format(
827 item
828 )
829 )
830 create_desc = self._create_envelop(item, desc)
831 payload_req = yaml.safe_dump(create_desc)
832 async with aiohttp.ClientSession() as session:
833 # check that exist
834 item_id = await self._get_item_uuid(
835 session, self.client_to_RO[item], item_id_name, all_tenants=True
836 )
837 await self._get_tenant(session)
838
839 url = "{}/{tenant}/{item}/{item_id}".format(
840 self.uri,
841 tenant=self.tenant,
842 item=self.client_to_RO[item],
843 item_id=item_id,
844 )
845 self.logger.debug("RO POST %s %s", url, payload_req)
846 # timeout = aiohttp.ClientTimeout(total=self.timeout_large)
847 async with session.post(
848 url, headers=self.headers_req, data=payload_req
849 ) as response:
850 response_text = await response.read()
851 self.logger.debug(
852 "POST {} [{}] {}".format(
853 url, response.status, response_text[:100]
854 )
855 )
856 if response.status >= 300:
857 raise ROClientException(
858 self._parse_error_yaml(response_text),
859 http_code=response.status,
860 )
861
862 response_desc = self._parse_yaml(response_text, response=True)
863 desc = remove_envelop(item, response_desc)
864 return desc
865 except (aiohttp.ClientOSError, aiohttp.ClientError) as e:
866 raise ROClientException(e, http_code=504)
867 except asyncio.TimeoutError:
868 raise ROClientException("Timeout", http_code=504)
869
870 async def detach(self, item, item_id_name=None):
871 # TODO replace the code with delete_item(vim_account,...)
872 try:
873 async with aiohttp.ClientSession() as session:
874 # check that exist
875 item_id = await self._get_item_uuid(
876 session, self.client_to_RO[item], item_id_name, all_tenants=False
877 )
878 tenant = await self._get_tenant(session)
879
880 url = "{}/{tenant}/{item}/{datacenter}".format(
881 self.uri,
882 tenant=tenant,
883 item=self.client_to_RO[item],
884 datacenter=item_id,
885 )
886 self.logger.debug("RO DELETE %s", url)
887
888 # timeout = aiohttp.ClientTimeout(total=self.timeout_large)
889 async with session.delete(url, headers=self.headers_req) as response:
890 response_text = await response.read()
891 self.logger.debug(
892 "DELETE {} [{}] {}".format(
893 url, response.status, response_text[:100]
894 )
895 )
896 if response.status >= 300:
897 raise ROClientException(
898 self._parse_error_yaml(response_text),
899 http_code=response.status,
900 )
901
902 response_desc = self._parse_yaml(response_text, response=True)
903 desc = remove_envelop(item, response_desc)
904 return desc
905 except (aiohttp.ClientOSError, aiohttp.ClientError) as e:
906 raise ROClientException(e, http_code=504)
907 except asyncio.TimeoutError:
908 raise ROClientException("Timeout", http_code=504)