cdbde9ce24a4ab61fe41c9a5da53d3b70f916e30
[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 assert False, "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 assert False, "_create_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 assert isinstance(
363 desc, list
364 ), "_get_item_uuid get a non dict with a list inside {}".format(type(desc))
365 uuid = None
366 for i in desc:
367 if item_id_name and i["name"] != item_id_name:
368 continue
369 if uuid: # found more than one
370 raise ROClientException(
371 "Found more than one {} with name '{}'. uuid must be used".format(
372 item, item_id_name
373 ),
374 http_code=404,
375 )
376 uuid = i["uuid"]
377 if not uuid:
378 raise ROClientException(
379 "No {} found with name '{}'".format(item[:-1], item_id_name),
380 http_code=404,
381 )
382 return uuid
383
384 async def _get_tenant(self, session):
385 if not self.tenant:
386 self.tenant = await self._get_item_uuid(
387 session, "tenants", self.tenant_id_name, None
388 )
389 return self.tenant
390
391 async def _get_datacenter(self, session):
392 if not self.tenant:
393 await self._get_tenant(session)
394 if not self.datacenter:
395 self.datacenter = await self._get_item_uuid(
396 session, "datacenters", self.datacenter_id_name, True
397 )
398 return self.datacenter
399
400 async def _create_item(
401 self,
402 session,
403 item,
404 descriptor,
405 item_id_name=None,
406 action=None,
407 all_tenants=False,
408 ):
409 if all_tenants:
410 tenant_text = "/any"
411 elif all_tenants is None:
412 tenant_text = ""
413 else:
414 if not self.tenant:
415 await self._get_tenant(session)
416 tenant_text = "/" + self.tenant
417 payload_req = yaml.safe_dump(descriptor)
418 # print payload_req
419
420 api_version_text = ""
421 if item == "vnfs":
422 # assumes version v3 only
423 api_version_text = "/v3"
424 item = "vnfd"
425 elif item == "scenarios":
426 # assumes version v3 only
427 api_version_text = "/v3"
428 item = "nsd"
429
430 if not item_id_name:
431 uuid = ""
432 elif self.check_if_uuid(item_id_name):
433 uuid = "/{}".format(item_id_name)
434 else:
435 # check that exist
436 uuid = await self._get_item_uuid(session, item, item_id_name, all_tenants)
437 uuid = "/{}".format(uuid)
438 if not action:
439 action = ""
440 else:
441 action = "/{}".format(action)
442
443 url = "{}{apiver}{tenant}/{item}{id}{action}".format(
444 self.uri,
445 apiver=api_version_text,
446 tenant=tenant_text,
447 item=item,
448 id=uuid,
449 action=action,
450 )
451 self.logger.debug("RO POST %s %s", url, payload_req)
452 # timeout = aiohttp.ClientTimeout(total=self.timeout_large)
453 async with session.post(
454 url, headers=self.headers_req, data=payload_req
455 ) as response:
456 response_text = await response.read()
457 self.logger.debug(
458 "POST {} [{}] {}".format(url, response.status, response_text[:100])
459 )
460 if response.status >= 300:
461 raise ROClientException(
462 self._parse_error_yaml(response_text), http_code=response.status
463 )
464
465 return self._parse_yaml(response_text, response=True)
466
467 async def _del_item(self, session, item, item_id_name, all_tenants=False):
468 if all_tenants:
469 tenant_text = "/any"
470 elif all_tenants is None:
471 tenant_text = ""
472 else:
473 if not self.tenant:
474 await self._get_tenant(session)
475 tenant_text = "/" + self.tenant
476 if not self.check_if_uuid(item_id_name):
477 # check that exist
478 _all_tenants = all_tenants
479 if item in ("datacenters", "wims"):
480 _all_tenants = True
481 uuid = await self._get_item_uuid(
482 session, item, item_id_name, all_tenants=_all_tenants
483 )
484 else:
485 uuid = item_id_name
486
487 url = "{}{}/{}/{}".format(self.uri, tenant_text, item, uuid)
488 self.logger.debug("DELETE %s", url)
489 # timeout = aiohttp.ClientTimeout(total=self.timeout_short)
490 async with session.delete(url, headers=self.headers_req) as response:
491 response_text = await response.read()
492 self.logger.debug(
493 "DELETE {} [{}] {}".format(url, response.status, response_text[:100])
494 )
495 if response.status >= 300:
496 raise ROClientException(
497 self._parse_error_yaml(response_text), http_code=response.status
498 )
499
500 return self._parse_yaml(response_text, response=True)
501
502 async def _list_item(self, session, item, all_tenants=False, filter_dict=None):
503 if all_tenants:
504 tenant_text = "/any"
505 elif all_tenants is None:
506 tenant_text = ""
507 else:
508 if not self.tenant:
509 await self._get_tenant(session)
510 tenant_text = "/" + self.tenant
511
512 url = "{}{}/{}".format(self.uri, tenant_text, item)
513 separator = "?"
514 if filter_dict:
515 for k in filter_dict:
516 url += separator + quote(str(k)) + "=" + quote(str(filter_dict[k]))
517 separator = "&"
518 self.logger.debug("RO GET %s", url)
519 # timeout = aiohttp.ClientTimeout(total=self.timeout_short)
520 async with session.get(url, headers=self.headers_req) as response:
521 response_text = await response.read()
522 self.logger.debug(
523 "GET {} [{}] {}".format(url, response.status, response_text[:100])
524 )
525 if response.status >= 300:
526 raise ROClientException(
527 self._parse_error_yaml(response_text), http_code=response.status
528 )
529
530 return self._parse_yaml(response_text, response=True)
531
532 async def _edit_item(self, session, item, item_id, descriptor, all_tenants=False):
533 if all_tenants:
534 tenant_text = "/any"
535 elif all_tenants is None:
536 tenant_text = ""
537 else:
538 if not self.tenant:
539 await self._get_tenant(session)
540 tenant_text = "/" + self.tenant
541
542 payload_req = yaml.safe_dump(descriptor)
543
544 # print payload_req
545 url = "{}{}/{}/{}".format(self.uri, tenant_text, item, item_id)
546 self.logger.debug("RO PUT %s %s", url, payload_req)
547 # timeout = aiohttp.ClientTimeout(total=self.timeout_large)
548 async with session.put(
549 url, headers=self.headers_req, data=payload_req
550 ) as response:
551 response_text = await response.read()
552 self.logger.debug(
553 "PUT {} [{}] {}".format(url, response.status, response_text[:100])
554 )
555 if response.status >= 300:
556 raise ROClientException(
557 self._parse_error_yaml(response_text), http_code=response.status
558 )
559
560 return self._parse_yaml(response_text, response=True)
561
562 async def get_version(self):
563 """
564 Obtain RO server version.
565 :return: a list with integers ["major", "minor", "release"]. Raises ROClientException on Error,
566 """
567 try:
568 response_text = ""
569 async with aiohttp.ClientSession() as session:
570 url = "{}/version".format(self.uri)
571 self.logger.debug("RO GET %s", url)
572 # timeout = aiohttp.ClientTimeout(total=self.timeout_short)
573 async with session.get(url, headers=self.headers_req) as response:
574 response_text = await response.read()
575 self.logger.debug(
576 "GET {} [{}] {}".format(
577 url, response.status, response_text[:100]
578 )
579 )
580 if response.status >= 300:
581 raise ROClientException(
582 self._parse_error_yaml(response_text),
583 http_code=response.status,
584 )
585
586 for word in str(response_text).split(" "):
587 if "." in word:
588 version_text, _, _ = word.partition("-")
589 return version_text
590 raise ROClientException(
591 "Got invalid version text: '{}'".format(response_text),
592 http_code=500,
593 )
594 except (aiohttp.ClientOSError, aiohttp.ClientError) as e:
595 raise ROClientException(e, http_code=504)
596 except asyncio.TimeoutError:
597 raise ROClientException("Timeout", http_code=504)
598 except Exception as e:
599 self.logger.critical(
600 "Got invalid version text: '{}'; causing exception {}".format(
601 response_text, str(e)
602 )
603 )
604 raise ROClientException(
605 "Got invalid version text: '{}'; causing exception {}".format(
606 response_text, e
607 ),
608 http_code=500,
609 )
610
611 async def get_list(self, item, all_tenants=False, filter_by=None):
612 """
613 List of items filtered by the contents in the dictionary "filter_by".
614 :param item: can be 'tenant', 'vim', 'vnfd', 'nsd', 'ns'
615 :param all_tenants: True if not filtering by tenant. Only allowed for admin
616 :param filter_by: dictionary with filtering
617 :return: a list of dict. It can be empty. Raises ROClientException on Error,
618 """
619 try:
620 if item not in self.client_to_RO:
621 raise ROClientException("Invalid item {}".format(item))
622 if item == "tenant":
623 all_tenants = None
624 async with aiohttp.ClientSession() as session:
625 content = await self._list_item(
626 session,
627 self.client_to_RO[item],
628 all_tenants=all_tenants,
629 filter_dict=filter_by,
630 )
631 if isinstance(content, dict):
632 if len(content) == 1:
633 for _, v in content.items():
634 return v
635 return content.values()[0]
636 else:
637 raise ROClientException(
638 "Output not a list neither dict with len equal 1", http_code=500
639 )
640 return content
641 except (aiohttp.ClientOSError, aiohttp.ClientError) as e:
642 raise ROClientException(e, http_code=504)
643 except asyncio.TimeoutError:
644 raise ROClientException("Timeout", http_code=504)
645
646 async def delete(self, item, item_id_name=None, all_tenants=False):
647 """
648 Delete the information of an item from its id or name
649 :param item: can be 'tenant', 'vim', 'vnfd', 'nsd', 'ns'
650 :param item_id_name: RO id or name of the item. Raise and exception if more than one found
651 :param all_tenants: True if not filtering by tenant. Only allowed for admin
652 :return: dictionary with the information or raises ROClientException on Error, NotFound, found several
653 """
654 try:
655 if item not in self.client_to_RO:
656 raise ROClientException("Invalid item {}".format(item))
657 if item in ("tenant", "vim", "wim"):
658 all_tenants = None
659
660 async with aiohttp.ClientSession() as session:
661 result = await self._del_item(
662 session,
663 self.client_to_RO[item],
664 item_id_name,
665 all_tenants=all_tenants,
666 )
667 # in case of ns delete, get the action_id embeded in text
668 if item == "ns" and result.get("result"):
669 _, _, action_id = result["result"].partition("action_id=")
670 action_id, _, _ = action_id.partition(" ")
671 if action_id:
672 result["action_id"] = action_id
673 return result
674 except (aiohttp.ClientOSError, aiohttp.ClientError) as e:
675 raise ROClientException(e, http_code=504)
676 except asyncio.TimeoutError:
677 raise ROClientException("Timeout", http_code=504)
678
679 async def edit(
680 self, item, item_id_name, descriptor=None, descriptor_format=None, **kwargs
681 ):
682 """Edit an item
683 :param item: can be 'tenant', 'vim', 'vnfd', 'nsd', 'ns', 'vim'
684 :param item_id_name: RO id or name of the item. Raise and exception if more than one found
685 :param descriptor: can be a dict, or a yaml/json text. Autodetect unless descriptor_format is provided
686 :param descriptor_format: Can be 'json' or 'yaml'
687 :param kwargs: Overrides descriptor with values as name, description, vim_url, vim_url_admin, vim_type
688 keys can be a dot separated list to specify elements inside dict
689 :return: dictionary with the information or raises ROClientException on Error
690 """
691 try:
692 if isinstance(descriptor, str):
693 descriptor = self._parse(descriptor, descriptor_format)
694 elif descriptor:
695 pass
696 else:
697 descriptor = {}
698
699 if item not in self.client_to_RO:
700 raise ROClientException("Invalid item {}".format(item))
701 desc = remove_envelop(item, descriptor)
702
703 # Override descriptor with kwargs
704 if kwargs:
705 desc = self.update_descriptor(desc, kwargs)
706 all_tenants = False
707 if item in ("tenant", "vim"):
708 all_tenants = None
709
710 create_desc = self._create_envelop(item, desc)
711
712 async with aiohttp.ClientSession() as session:
713 _all_tenants = all_tenants
714 if item == "vim":
715 _all_tenants = True
716 item_id = await self._get_item_uuid(
717 session,
718 self.client_to_RO[item],
719 item_id_name,
720 all_tenants=_all_tenants,
721 )
722 if item == "vim":
723 _all_tenants = None
724 # await self._get_tenant(session)
725 outdata = await self._edit_item(
726 session,
727 self.client_to_RO[item],
728 item_id,
729 create_desc,
730 all_tenants=_all_tenants,
731 )
732 return remove_envelop(item, outdata)
733 except (aiohttp.ClientOSError, aiohttp.ClientError) as e:
734 raise ROClientException(e, http_code=504)
735 except asyncio.TimeoutError:
736 raise ROClientException("Timeout", http_code=504)
737
738 async def create(self, item, descriptor=None, descriptor_format=None, **kwargs):
739 """
740 Creates an item from its descriptor
741 :param item: can be 'tenant', 'vnfd', 'nsd', 'ns', 'vim', 'vim_account', 'sdn'
742 :param descriptor: can be a dict, or a yaml/json text. Autodetect unless descriptor_format is provided
743 :param descriptor_format: Can be 'json' or 'yaml'
744 :param kwargs: Overrides descriptor with values as name, description, vim_url, vim_url_admin, vim_type
745 keys can be a dot separated list to specify elements inside dict
746 :return: dictionary with the information or raises ROClientException on Error
747 """
748 try:
749 if isinstance(descriptor, str):
750 descriptor = self._parse(descriptor, descriptor_format)
751 elif descriptor:
752 pass
753 else:
754 descriptor = {}
755
756 if item not in self.client_to_RO:
757 raise ROClientException("Invalid item {}".format(item))
758 desc = remove_envelop(item, descriptor)
759
760 # Override descriptor with kwargs
761 if kwargs:
762 desc = self.update_descriptor(desc, kwargs)
763
764 for mandatory in self.mandatory_for_create[item]:
765 if mandatory not in desc:
766 raise ROClientException(
767 "'{}' is mandatory parameter for {}".format(mandatory, item)
768 )
769
770 all_tenants = False
771 if item in ("tenant", "vim", "wim"):
772 all_tenants = None
773
774 create_desc = self._create_envelop(item, desc)
775
776 async with aiohttp.ClientSession() as session:
777 outdata = await self._create_item(
778 session,
779 self.client_to_RO[item],
780 create_desc,
781 all_tenants=all_tenants,
782 )
783 return remove_envelop(item, outdata)
784 except (aiohttp.ClientOSError, aiohttp.ClientError) as e:
785 raise ROClientException(e, http_code=504)
786 except asyncio.TimeoutError:
787 raise ROClientException("Timeout", http_code=504)
788
789 async def attach(
790 self, item, item_id_name=None, descriptor=None, descriptor_format=None, **kwargs
791 ):
792 """
793 Attach a datacenter or wim to a tenant, creating a vim_account, wim_account
794 :param item: can be vim_account or wim_account
795 :param item_id_name: id or name of the datacenter, wim
796 :param descriptor:
797 :param descriptor_format:
798 :param kwargs:
799 :return:
800 """
801 try:
802 if isinstance(descriptor, str):
803 descriptor = self._parse(descriptor, descriptor_format)
804 elif descriptor:
805 pass
806 else:
807 descriptor = {}
808
809 desc = remove_envelop(item, descriptor)
810
811 # # check that exist
812 # uuid = self._get_item_uuid(session, "datacenters", uuid_name, all_tenants=True)
813 # tenant_text = "/" + self._get_tenant()
814 if kwargs:
815 desc = self.update_descriptor(desc, kwargs)
816
817 if item == "vim_account":
818 if not desc.get("vim_tenant_name") and not desc.get("vim_tenant_id"):
819 raise ROClientException(
820 "Wrong descriptor. At least vim_tenant_name or vim_tenant_id must be "
821 "provided"
822 )
823 elif item != "wim_account":
824 raise ROClientException(
825 "Attach with unknown item {}. Must be 'vim_account' or 'wim_account'".format(
826 item
827 )
828 )
829 create_desc = self._create_envelop(item, desc)
830 payload_req = yaml.safe_dump(create_desc)
831 async with aiohttp.ClientSession() as session:
832 # check that exist
833 item_id = await self._get_item_uuid(
834 session, self.client_to_RO[item], item_id_name, all_tenants=True
835 )
836 await self._get_tenant(session)
837
838 url = "{}/{tenant}/{item}/{item_id}".format(
839 self.uri,
840 tenant=self.tenant,
841 item=self.client_to_RO[item],
842 item_id=item_id,
843 )
844 self.logger.debug("RO POST %s %s", url, payload_req)
845 # timeout = aiohttp.ClientTimeout(total=self.timeout_large)
846 async with session.post(
847 url, headers=self.headers_req, data=payload_req
848 ) as response:
849 response_text = await response.read()
850 self.logger.debug(
851 "POST {} [{}] {}".format(
852 url, response.status, response_text[:100]
853 )
854 )
855 if response.status >= 300:
856 raise ROClientException(
857 self._parse_error_yaml(response_text),
858 http_code=response.status,
859 )
860
861 response_desc = self._parse_yaml(response_text, response=True)
862 desc = remove_envelop(item, response_desc)
863 return desc
864 except (aiohttp.ClientOSError, aiohttp.ClientError) as e:
865 raise ROClientException(e, http_code=504)
866 except asyncio.TimeoutError:
867 raise ROClientException("Timeout", http_code=504)
868
869 async def detach(self, item, item_id_name=None):
870 # TODO replace the code with delete_item(vim_account,...)
871 try:
872 async with aiohttp.ClientSession() as session:
873 # check that exist
874 item_id = await self._get_item_uuid(
875 session, self.client_to_RO[item], item_id_name, all_tenants=False
876 )
877 tenant = await self._get_tenant(session)
878
879 url = "{}/{tenant}/{item}/{datacenter}".format(
880 self.uri,
881 tenant=tenant,
882 item=self.client_to_RO[item],
883 datacenter=item_id,
884 )
885 self.logger.debug("RO DELETE %s", url)
886
887 # timeout = aiohttp.ClientTimeout(total=self.timeout_large)
888 async with session.delete(url, headers=self.headers_req) as response:
889 response_text = await response.read()
890 self.logger.debug(
891 "DELETE {} [{}] {}".format(
892 url, response.status, response_text[:100]
893 )
894 )
895 if response.status >= 300:
896 raise ROClientException(
897 self._parse_error_yaml(response_text),
898 http_code=response.status,
899 )
900
901 response_desc = self._parse_yaml(response_text, response=True)
902 desc = remove_envelop(item, response_desc)
903 return desc
904 except (aiohttp.ClientOSError, aiohttp.ClientError) as e:
905 raise ROClientException(e, http_code=504)
906 except asyncio.TimeoutError:
907 raise ROClientException("Timeout", http_code=504)