minor changes to allow modern RO versions
[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 not isinstance(clean_indata['vnfd'], list) or len(clean_indata['vnfd']) != 1:
66 raise ROClientException("'vnfd' must be a list only one element")
67 clean_indata = clean_indata['vnfd'][0]
68 elif item == "nsd":
69 if clean_indata.get('nsd:nsd-catalog'):
70 clean_indata = clean_indata['nsd:nsd-catalog']
71 elif clean_indata.get('nsd-catalog'):
72 clean_indata = clean_indata['nsd-catalog']
73 if clean_indata.get('nsd'):
74 if not isinstance(clean_indata['nsd'], list) or len(clean_indata['nsd']) != 1:
75 raise ROClientException("'nsd' must be a list only one element")
76 clean_indata = clean_indata['nsd'][0]
77 elif item == "sdn":
78 if len(indata) == 1 and "sdn_controller" in indata:
79 clean_indata = indata["sdn_controller"]
80 elif item == "tenant":
81 if len(indata) == 1 and "tenant" in indata:
82 clean_indata = indata["tenant"]
83 elif item in ("vim", "vim_account", "datacenters"):
84 if len(indata) == 1 and "datacenter" in indata:
85 clean_indata = indata["datacenter"]
86 elif item == "wim":
87 if len(indata) == 1 and "wim" in indata:
88 clean_indata = indata["wim"]
89 elif item == "wim_account":
90 if len(indata) == 1 and "wim_account" in indata:
91 clean_indata = indata["wim_account"]
92 elif item == "ns" or item == "instances":
93 if len(indata) == 1 and "instance" in indata:
94 clean_indata = indata["instance"]
95 else:
96 assert False, "remove_envelop with unknown item {}".format(item)
97
98 return clean_indata
99
100
101 class ROClient:
102 headers_req = {'Accept': 'application/yaml', 'content-type': 'application/yaml'}
103 client_to_RO = {'tenant': 'tenants', 'vim': 'datacenters', 'vim_account': 'datacenters', 'sdn': 'sdn_controllers',
104 'vnfd': 'vnfs', 'nsd': 'scenarios', 'wim': 'wims', 'wim_account': 'wims',
105 'ns': 'instances'}
106 mandatory_for_create = {
107 'tenant': ("name", ),
108 'vnfd': ("name", "id"),
109 'nsd': ("name", "id"),
110 'ns': ("name", "scenario", "datacenter"),
111 'vim': ("name", "vim_url"),
112 'wim': ("name", "wim_url"),
113 'vim_account': (),
114 'wim_account': (),
115 'sdn': ("name", "port", 'ip', 'dpid', 'type'),
116 }
117 timeout_large = 120
118 timeout_short = 30
119
120 def __init__(self, loop, endpoint_url, **kwargs):
121 self.loop = loop
122 self.endpoint_url = endpoint_url
123
124 self.username = kwargs.get("username")
125 self.password = kwargs.get("password")
126 self.tenant_id_name = kwargs.get("tenant")
127 self.tenant = None
128 self.datacenter_id_name = kwargs.get("datacenter")
129 self.datacenter = None
130 logger_name = kwargs.get('logger_name', 'ROClient')
131 self.logger = logging.getLogger(logger_name)
132 if kwargs.get("loglevel"):
133 self.logger.setLevel(kwargs["loglevel"])
134 global requests
135 requests = kwargs.get("TODO remove")
136
137 def __getitem__(self, index):
138 if index == 'tenant':
139 return self.tenant_id_name
140 elif index == 'datacenter':
141 return self.datacenter_id_name
142 elif index == 'username':
143 return self.username
144 elif index == 'password':
145 return self.password
146 elif index == 'endpoint_url':
147 return self.endpoint_url
148 else:
149 raise KeyError("Invalid key '{}'".format(index))
150
151 def __setitem__(self, index, value):
152 if index == 'tenant':
153 self.tenant_id_name = value
154 elif index == 'datacenter' or index == 'vim':
155 self.datacenter_id_name = value
156 elif index == 'username':
157 self.username = value
158 elif index == 'password':
159 self.password = value
160 elif index == 'endpoint_url':
161 self.endpoint_url = value
162 else:
163 raise KeyError("Invalid key '{}'".format(index))
164 self.tenant = None # force to reload tenant with different credentials
165 self.datacenter = None # force to reload datacenter with different credentials
166
167 def _parse(self, descriptor, descriptor_format, response=False):
168 if descriptor_format and descriptor_format != "json" and descriptor_format != "yaml":
169 raise ROClientException("'descriptor_format' must be a 'json' or 'yaml' text")
170 if descriptor_format != "json":
171 try:
172 return yaml.load(descriptor)
173 except yaml.YAMLError as exc:
174 error_pos = ""
175 if hasattr(exc, 'problem_mark'):
176 mark = exc.problem_mark
177 error_pos = " at line:{} column:{}s".format(mark.line + 1, mark.column + 1)
178 error_text = "yaml format error" + error_pos
179 elif descriptor_format != "yaml":
180 try:
181 return json.loads(descriptor)
182 except Exception as e:
183 if response:
184 error_text = "json format error" + str(e)
185
186 if response:
187 raise ROClientException(error_text)
188 raise ROClientException(error_text)
189
190 def _parse_yaml(self, descriptor, response=False):
191 try:
192 return yaml.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(mark.line + 1, mark.column + 1)
198 error_text = "yaml format error" + error_pos
199 if response:
200 raise ROClientException(error_text)
201 raise ROClientException(error_text)
202
203 @staticmethod
204 def check_if_uuid(uuid_text):
205 """
206 Check if text correspond to an uuid foramt
207 :param uuid_text:
208 :return: True if it is an uuid False if not
209 """
210 try:
211 UUID(uuid_text)
212 return True
213 except Exception:
214 return False
215
216 @staticmethod
217 def _create_envelop(item, indata=None):
218 """
219 Returns a new dict that incledes indata with the expected envelop
220 :param item: can be 'tenant', 'vim', 'vnfd', 'nsd', 'ns'
221 :param indata: Content to be enveloped
222 :return: a new dic with {<envelop>: {indata} } where envelop can be e.g. tenant, datacenter, ...
223 """
224 if item == "vnfd":
225 return {'vnfd-catalog': {'vnfd': [indata]}}
226 elif item == "nsd":
227 return {'nsd-catalog': {'nsd': [indata]}}
228 elif item == "tenant":
229 return {'tenant': indata}
230 elif item in ("vim", "vim_account", "datacenter"):
231 return {'datacenter': indata}
232 elif item == "wim":
233 return {'wim': indata}
234 elif item == "wim_account":
235 return {'wim_account': indata}
236 elif item == "ns" or item == "instances":
237 return {'instance': indata}
238 elif item == "sdn":
239 return {'sdn_controller': indata}
240 else:
241 assert False, "_create_envelop with unknown item {}".format(item)
242
243 @staticmethod
244 def update_descriptor(desc, kwargs):
245 desc = deepcopy(desc) # do not modify original descriptor
246 try:
247 for k, v in kwargs.items():
248 update_content = desc
249 kitem_old = None
250 klist = k.split(".")
251 for kitem in klist:
252 if kitem_old is not None:
253 update_content = update_content[kitem_old]
254 if isinstance(update_content, dict):
255 kitem_old = kitem
256 elif isinstance(update_content, list):
257 kitem_old = int(kitem)
258 else:
259 raise ROClientException(
260 "Invalid query string '{}'. Descriptor is not a list nor dict at '{}'".format(k, kitem))
261 if v == "__DELETE__":
262 del update_content[kitem_old]
263 else:
264 update_content[kitem_old] = v
265 return desc
266 except KeyError:
267 raise ROClientException(
268 "Invalid query string '{}'. Descriptor does not contain '{}'".format(k, kitem_old))
269 except ValueError:
270 raise ROClientException("Invalid query string '{}'. Expected integer index list instead of '{}'".format(
271 k, kitem))
272 except IndexError:
273 raise ROClientException(
274 "Invalid query string '{}'. Index '{}' out of range".format(k, kitem_old))
275
276 @staticmethod
277 def check_ns_status(ns_descriptor):
278 """
279 Inspect RO instance descriptor and indicates the status
280 :param ns_descriptor: instance descriptor obtained with self.show("ns", )
281 :return: status, message: status can be BUILD,ACTIVE,ERROR, message is a text message
282 """
283 net_total = 0
284 vm_total = 0
285 net_done = 0
286 vm_done = 0
287
288 for net in ns_descriptor["nets"]:
289 net_total += 1
290 if net["status"] in ("ERROR", "VIM_ERROR"):
291 return "ERROR", net["error_msg"]
292 elif net["status"] == "ACTIVE":
293 net_done += 1
294 for vnf in ns_descriptor["vnfs"]:
295 for vm in vnf["vms"]:
296 vm_total += 1
297 if vm["status"] in ("ERROR", "VIM_ERROR"):
298 return "ERROR", vm["error_msg"]
299 elif vm["status"] == "ACTIVE":
300 vm_done += 1
301
302 if net_total == net_done and vm_total == vm_done:
303 return "ACTIVE", "VMs {}, networks: {}".format(vm_total, net_total)
304 else:
305 return "BUILD", "VMs: {}/{}, networks: {}/{}".format(vm_done, vm_total, net_done, net_total)
306
307 @staticmethod
308 def check_action_status(action_descriptor):
309 """
310 Inspect RO instance descriptor and indicates the status
311 :param action_descriptor: action instance descriptor obtained with self.show("ns", "action")
312 :return: status, message: status can be BUILD,ACTIVE,ERROR, message is a text message
313 """
314 net_total = 0
315 vm_total = 0
316 net_done = 0
317 vm_done = 0
318 other_total = 0
319 other_done = 0
320
321 for vim_action_set in action_descriptor["actions"]:
322 for vim_action in vim_action_set["vim_wim_actions"]:
323 if vim_action["item"] == "instance_vms":
324 vm_total += 1
325 elif vim_action["item"] == "instance_nets":
326 net_total += 1
327 else:
328 other_total += 1
329 if vim_action["status"] == "FAILED":
330 return "ERROR", vim_action["error_msg"]
331 elif vim_action["status"] in ("DONE", "SUPERSEDED", "FINISHED"):
332 if vim_action["item"] == "instance_vms":
333 vm_done += 1
334 elif vim_action["item"] == "instance_nets":
335 net_done += 1
336 else:
337 other_done += 1
338
339 if net_total == net_done and vm_total == vm_done and other_total == other_done:
340 return "ACTIVE", "VMs {}, networks: {}, other: {} ".format(vm_total, net_total, other_total)
341 else:
342 return "BUILD", "VMs: {}/{}, networks: {}/{}, other: {}/{}".format(vm_done, vm_total, net_done, net_total,
343 other_done, other_total)
344
345 @staticmethod
346 def get_ns_vnf_info(ns_descriptor):
347 """
348 Get a dict with the VIM_id, ip_addresses, mac_addresses of every vnf and vdu
349 :param ns_descriptor: instance descriptor obtained with self.show("ns", )
350 :return: dict with:
351 <member_vnf_index>:
352 ip_address: XXXX,
353 vdur:
354 <vdu_osm_id>:
355 ip_address: XXX
356 vim_id: XXXX
357 interfaces:
358 <name>:
359 ip_address: XXX
360 mac_address: XXX
361 """
362 ns_info = {}
363 for vnf in ns_descriptor["vnfs"]:
364 if not vnf.get("ip_address") and vnf.get("vms"):
365 raise ROClientException("ns member_vnf_index '{}' has no IP address".format(
366 vnf["member_vnf_index"]), http_code=409)
367 vnfr_info = {
368 "ip_address": vnf.get("ip_address"),
369 "vdur": {}
370 }
371 for vm in vnf["vms"]:
372 vdur = {
373 "vim_id": vm.get("vim_vm_id"),
374 "ip_address": vm.get("ip_address"),
375 "interfaces": {}
376 }
377 for iface in vm["interfaces"]:
378 if iface.get("type") == "mgmt" and not iface.get("ip_address"):
379 raise ROClientException("ns member_vnf_index '{}' vm '{}' management interface '{}' has no IP "
380 "address".format(vnf["member_vnf_index"], vm["vdu_osm_id"],
381 iface["external_name"]), http_code=409)
382 vdur["interfaces"][iface["internal_name"]] = {"ip_address": iface.get("ip_address"),
383 "mac_address": iface.get("mac_address"),
384 "vim_id": iface.get("vim_interface_id"),
385 }
386 vnfr_info["vdur"][vm["vdu_osm_id"]] = vdur
387 ns_info[str(vnf["member_vnf_index"])] = vnfr_info
388 return ns_info
389
390 async def _get_item_uuid(self, session, item, item_id_name, all_tenants=False):
391 if all_tenants:
392 tenant_text = "/any"
393 elif all_tenants is None:
394 tenant_text = ""
395 else:
396 if not self.tenant:
397 await self._get_tenant(session)
398 tenant_text = "/" + self.tenant
399
400 item_id = 0
401 url = "{}{}/{}".format(self.endpoint_url, tenant_text, item)
402 if self.check_if_uuid(item_id_name):
403 item_id = item_id_name
404 url += "/" + item_id_name
405 elif item_id_name and item_id_name.startswith("'") and item_id_name.endswith("'"):
406 item_id_name = item_id_name[1:-1]
407 self.logger.debug("RO GET %s", url)
408 with aiohttp.Timeout(self.timeout_short):
409 async with session.get(url, headers=self.headers_req) as response:
410 response_text = await response.read()
411 self.logger.debug("GET {} [{}] {}".format(url, response.status, response_text[:100]))
412 if response.status == 404: # NOT_FOUND
413 raise ROClientException("No {} found with id '{}'".format(item[:-1], item_id_name),
414 http_code=404)
415 if response.status >= 300:
416 raise ROClientException(response_text, http_code=response.status)
417 content = self._parse_yaml(response_text, response=True)
418
419 if item_id:
420 return item_id
421 desc = content[item]
422 assert isinstance(desc, list), "_get_item_uuid get a non dict with a list inside {}".format(type(desc))
423 uuid = None
424 for i in desc:
425 if item_id_name and i["name"] != item_id_name:
426 continue
427 if uuid: # found more than one
428 raise ROClientException(
429 "Found more than one {} with name '{}'. uuid must be used".format(item, item_id_name),
430 http_code=404)
431 uuid = i["uuid"]
432 if not uuid:
433 raise ROClientException("No {} found with name '{}'".format(item[:-1], item_id_name), http_code=404)
434 return uuid
435
436 async def _get_item(self, session, item, item_id_name, extra_item=None, extra_item_id=None, all_tenants=False):
437 if all_tenants:
438 tenant_text = "/any"
439 elif all_tenants is None:
440 tenant_text = ""
441 else:
442 if not self.tenant:
443 await self._get_tenant(session)
444 tenant_text = "/" + self.tenant
445
446 if self.check_if_uuid(item_id_name):
447 uuid = item_id_name
448 else:
449 # check that exist
450 uuid = await self._get_item_uuid(session, item, item_id_name, all_tenants)
451
452 url = "{}{}/{}/{}".format(self.endpoint_url, tenant_text, item, uuid)
453 if extra_item:
454 url += "/" + extra_item
455 if extra_item_id:
456 url += "/" + extra_item_id
457 self.logger.debug("GET %s", url)
458 with aiohttp.Timeout(self.timeout_short):
459 async with session.get(url, headers=self.headers_req) as response:
460 response_text = await response.read()
461 self.logger.debug("GET {} [{}] {}".format(url, response.status, response_text[:100]))
462 if response.status >= 300:
463 raise ROClientException(response_text, http_code=response.status)
464
465 return self._parse_yaml(response_text, response=True)
466
467 async def _get_tenant(self, session):
468 if not self.tenant:
469 self.tenant = await self._get_item_uuid(session, "tenants", self.tenant_id_name, None)
470 return self.tenant
471
472 async def _get_datacenter(self, session):
473 if not self.tenant:
474 await self._get_tenant(session)
475 if not self.datacenter:
476 self.datacenter = await self._get_item_uuid(session, "datacenters", self.datacenter_id_name, True)
477 return self.datacenter
478
479 async def _create_item(self, session, item, descriptor, item_id_name=None, action=None, all_tenants=False):
480 if all_tenants:
481 tenant_text = "/any"
482 elif all_tenants is None:
483 tenant_text = ""
484 else:
485 if not self.tenant:
486 await self._get_tenant(session)
487 tenant_text = "/" + self.tenant
488 payload_req = yaml.safe_dump(descriptor)
489 # print payload_req
490
491 api_version_text = ""
492 if item == "vnfs":
493 # assumes version v3 only
494 api_version_text = "/v3"
495 item = "vnfd"
496 elif item == "scenarios":
497 # assumes version v3 only
498 api_version_text = "/v3"
499 item = "nsd"
500
501 if not item_id_name:
502 uuid = ""
503 elif self.check_if_uuid(item_id_name):
504 uuid = "/{}".format(item_id_name)
505 else:
506 # check that exist
507 uuid = await self._get_item_uuid(session, item, item_id_name, all_tenants)
508 uuid = "/{}".format(uuid)
509 if not action:
510 action = ""
511 else:
512 action = "/{}".format(action)
513
514 url = "{}{apiver}{tenant}/{item}{id}{action}".format(self.endpoint_url, apiver=api_version_text,
515 tenant=tenant_text, item=item, id=uuid, action=action)
516 self.logger.debug("RO POST %s %s", url, payload_req)
517 with aiohttp.Timeout(self.timeout_large):
518 async with session.post(url, headers=self.headers_req, data=payload_req) as response:
519 response_text = await response.read()
520 self.logger.debug("POST {} [{}] {}".format(url, response.status, response_text[:100]))
521 if response.status >= 300:
522 raise ROClientException(response_text, http_code=response.status)
523
524 return self._parse_yaml(response_text, response=True)
525
526 async def _del_item(self, session, item, item_id_name, all_tenants=False):
527 if all_tenants:
528 tenant_text = "/any"
529 elif all_tenants is None:
530 tenant_text = ""
531 else:
532 if not self.tenant:
533 await self._get_tenant(session)
534 tenant_text = "/" + self.tenant
535 if not self.check_if_uuid(item_id_name):
536 # check that exist
537 _all_tenants = all_tenants
538 if item in ("datacenters", 'wims'):
539 _all_tenants = True
540 uuid = await self._get_item_uuid(session, item, item_id_name, all_tenants=_all_tenants)
541 else:
542 uuid = item_id_name
543
544 url = "{}{}/{}/{}".format(self.endpoint_url, tenant_text, item, uuid)
545 self.logger.debug("DELETE %s", url)
546 with aiohttp.Timeout(self.timeout_short):
547 async with session.delete(url, headers=self.headers_req) as response:
548 response_text = await response.read()
549 self.logger.debug("DELETE {} [{}] {}".format(url, response.status, response_text[:100]))
550 if response.status >= 300:
551 raise ROClientException(response_text, http_code=response.status)
552 return self._parse_yaml(response_text, response=True)
553
554 async def _list_item(self, session, item, all_tenants=False, filter_dict=None):
555 if all_tenants:
556 tenant_text = "/any"
557 elif all_tenants is None:
558 tenant_text = ""
559 else:
560 if not self.tenant:
561 await self._get_tenant(session)
562 tenant_text = "/" + self.tenant
563
564 url = "{}{}/{}".format(self.endpoint_url, tenant_text, item)
565 separator = "?"
566 if filter_dict:
567 for k in filter_dict:
568 url += separator + quote(str(k)) + "=" + quote(str(filter_dict[k]))
569 separator = "&"
570 self.logger.debug("RO GET %s", url)
571 with aiohttp.Timeout(self.timeout_short):
572 async with session.get(url, headers=self.headers_req) as response:
573 response_text = await response.read()
574 self.logger.debug("GET {} [{}] {}".format(url, response.status, response_text[:100]))
575 if response.status >= 300:
576 raise ROClientException(response_text, http_code=response.status)
577 return self._parse_yaml(response_text, response=True)
578
579 async def _edit_item(self, session, item, item_id, descriptor, all_tenants=False):
580 if all_tenants:
581 tenant_text = "/any"
582 elif all_tenants is None:
583 tenant_text = ""
584 else:
585 if not self.tenant:
586 await self._get_tenant(session)
587 tenant_text = "/" + self.tenant
588
589 payload_req = yaml.safe_dump(descriptor)
590
591 # print payload_req
592 url = "{}{}/{}/{}".format(self.endpoint_url, tenant_text, item, item_id)
593 self.logger.debug("RO PUT %s %s", url, payload_req)
594 with aiohttp.Timeout(self.timeout_large):
595 async with session.put(url, headers=self.headers_req, data=payload_req) as response:
596 response_text = await response.read()
597 self.logger.debug("PUT {} [{}] {}".format(url, response.status, response_text[:100]))
598 if response.status >= 300:
599 raise ROClientException(response_text, http_code=response.status)
600 return self._parse_yaml(response_text, response=True)
601
602 async def get_version(self):
603 """
604 Obtain RO server version.
605 :return: a list with integers ["major", "minor", "release"]. Raises ROClientException on Error,
606 """
607 try:
608 with aiohttp.ClientSession(loop=self.loop) as session:
609 url = "{}/version".format(self.endpoint_url)
610 self.logger.debug("RO GET %s", url)
611 with aiohttp.Timeout(self.timeout_short):
612 async with session.get(url, headers=self.headers_req) as response:
613 response_text = await response.read()
614 self.logger.debug("GET {} [{}] {}".format(url, response.status, response_text[:100]))
615 if response.status >= 300:
616 raise ROClientException(response_text, http_code=response.status)
617 for word in str(response_text).split(" "):
618 if "." in word:
619 version_text, _, _ = word.partition("-")
620 return list(map(int, version_text.split(".")))
621 raise ROClientException("Got invalid version text: '{}'".format(response_text), http_code=500)
622 except aiohttp.errors.ClientOSError as e:
623 raise ROClientException(e, http_code=504)
624 except asyncio.TimeoutError:
625 raise ROClientException("Timeout", http_code=504)
626 except Exception as e:
627 raise ROClientException("Got invalid version text: '{}'; causing exception {}".format(response_text, e),
628 http_code=500)
629
630 async def get_list(self, item, all_tenants=False, filter_by=None):
631 """
632 Obtain a list of items filtering by the specigy filter_by.
633 :param item: can be 'tenant', 'vim', 'vnfd', 'nsd', 'ns'
634 :param all_tenants: True if not filtering by tenant. Only allowed for admin
635 :param filter_by: dictionary with filtering
636 :return: a list of dict. It can be empty. Raises ROClientException on Error,
637 """
638 try:
639 if item not in self.client_to_RO:
640 raise ROClientException("Invalid item {}".format(item))
641 if item == 'tenant':
642 all_tenants = None
643 with aiohttp.ClientSession(loop=self.loop) as session:
644 content = await self._list_item(session, self.client_to_RO[item], all_tenants=all_tenants,
645 filter_dict=filter_by)
646 if isinstance(content, dict):
647 if len(content) == 1:
648 for _, v in content.items():
649 return v
650 return content.values()[0]
651 else:
652 raise ROClientException("Output not a list neither dict with len equal 1", http_code=500)
653 return content
654 except aiohttp.errors.ClientOSError as e:
655 raise ROClientException(e, http_code=504)
656 except asyncio.TimeoutError:
657 raise ROClientException("Timeout", http_code=504)
658
659 async def show(self, item, item_id_name=None, extra_item=None, extra_item_id=None, all_tenants=False):
660 """
661 Obtain the information of an item from its id or name
662 :param item: can be 'tenant', 'vim', 'vnfd', 'nsd', 'ns'
663 :param item_id_name: RO id or name of the item. Raise and exception if more than one found
664 :param extra_item: if supplied, it is used to add to the URL.
665 Can be 'action' if item='ns'; 'networks' or'images' if item='vim'
666 :param extra_item_id: if supplied, it is used get details of a concrete extra_item.
667 :param all_tenants: True if not filtering by tenant. Only allowed for admin
668 :return: dictionary with the information or raises ROClientException on Error, NotFound, found several
669 """
670 try:
671 if item not in self.client_to_RO:
672 raise ROClientException("Invalid item {}".format(item))
673 if item == 'tenant':
674 all_tenants = None
675 elif item == 'vim':
676 all_tenants = True
677 elif item == 'vim_account':
678 all_tenants = False
679
680 with aiohttp.ClientSession(loop=self.loop) as session:
681 content = await self._get_item(session, self.client_to_RO[item], item_id_name, extra_item=extra_item,
682 extra_item_id=extra_item_id, all_tenants=all_tenants)
683 return remove_envelop(item, content)
684 except aiohttp.errors.ClientOSError as e:
685 raise ROClientException(e, http_code=504)
686 except asyncio.TimeoutError:
687 raise ROClientException("Timeout", http_code=504)
688
689 async def delete(self, item, item_id_name=None, all_tenants=False):
690 """
691 Delete the information of an item from its id or name
692 :param item: can be 'tenant', 'vim', 'vnfd', 'nsd', 'ns'
693 :param item_id_name: RO id or name of the item. Raise and exception if more than one found
694 :param all_tenants: True if not filtering by tenant. Only allowed for admin
695 :return: dictionary with the information or raises ROClientException on Error, NotFound, found several
696 """
697 try:
698 if item not in self.client_to_RO:
699 raise ROClientException("Invalid item {}".format(item))
700 if item in ('tenant', 'vim', 'wim'):
701 all_tenants = None
702
703 with aiohttp.ClientSession(loop=self.loop) as session:
704 result = await self._del_item(session, self.client_to_RO[item], item_id_name, all_tenants=all_tenants)
705 # in case of ns delete, get the action_id embeded in text
706 if item == "ns" and result.get("result"):
707 _, _, action_id = result["result"].partition("action_id=")
708 action_id, _, _ = action_id.partition(" ")
709 if action_id:
710 result["action_id"] = action_id
711 return result
712 except aiohttp.errors.ClientOSError as e:
713 raise ROClientException(e, http_code=504)
714 except asyncio.TimeoutError:
715 raise ROClientException("Timeout", http_code=504)
716
717 async def edit(self, item, item_id_name, descriptor=None, descriptor_format=None, **kwargs):
718 """ Edit an item
719 :param item: can be 'tenant', 'vim', 'vnfd', 'nsd', 'ns', 'vim'
720 :param item_id_name: RO id or name of the item. Raise and exception if more than one found
721 :param descriptor: can be a dict, or a yaml/json text. Autodetect unless descriptor_format is provided
722 :param descriptor_format: Can be 'json' or 'yaml'
723 :param kwargs: Overrides descriptor with values as name, description, vim_url, vim_url_admin, vim_type
724 keys can be a dot separated list to specify elements inside dict
725 :return: dictionary with the information or raises ROClientException on Error
726 """
727 try:
728 if isinstance(descriptor, str):
729 descriptor = self._parse(descriptor, descriptor_format)
730 elif descriptor:
731 pass
732 else:
733 descriptor = {}
734
735 if item not in self.client_to_RO:
736 raise ROClientException("Invalid item {}".format(item))
737 desc = remove_envelop(item, descriptor)
738
739 # Override descriptor with kwargs
740 if kwargs:
741 desc = self.update_descriptor(desc, kwargs)
742 all_tenants = False
743 if item in ('tenant', 'vim'):
744 all_tenants = None
745
746 create_desc = self._create_envelop(item, desc)
747
748 with aiohttp.ClientSession(loop=self.loop) as session:
749 _all_tenants = all_tenants
750 if item == 'vim':
751 _all_tenants = True
752 item_id = await self._get_item_uuid(session, self.client_to_RO[item], item_id_name,
753 all_tenants=_all_tenants)
754 if item == 'vim':
755 _all_tenants = None
756 # await self._get_tenant(session)
757 outdata = await self._edit_item(session, self.client_to_RO[item], item_id, create_desc,
758 all_tenants=_all_tenants)
759 return remove_envelop(item, outdata)
760 except aiohttp.errors.ClientOSError as e:
761 raise ROClientException(e, http_code=504)
762 except asyncio.TimeoutError:
763 raise ROClientException("Timeout", http_code=504)
764
765 async def create(self, item, descriptor=None, descriptor_format=None, **kwargs):
766 """
767 Creates an item from its descriptor
768 :param item: can be 'tenant', 'vnfd', 'nsd', 'ns', 'vim', 'vim_account', 'sdn'
769 :param descriptor: can be a dict, or a yaml/json text. Autodetect unless descriptor_format is provided
770 :param descriptor_format: Can be 'json' or 'yaml'
771 :param kwargs: Overrides descriptor with values as name, description, vim_url, vim_url_admin, vim_type
772 keys can be a dot separated list to specify elements inside dict
773 :return: dictionary with the information or raises ROClientException on Error
774 """
775 try:
776 if isinstance(descriptor, str):
777 descriptor = self._parse(descriptor, descriptor_format)
778 elif descriptor:
779 pass
780 else:
781 descriptor = {}
782
783 if item not in self.client_to_RO:
784 raise ROClientException("Invalid item {}".format(item))
785 desc = remove_envelop(item, descriptor)
786
787 # Override descriptor with kwargs
788 if kwargs:
789 desc = self.update_descriptor(desc, kwargs)
790
791 for mandatory in self.mandatory_for_create[item]:
792 if mandatory not in desc:
793 raise ROClientException("'{}' is mandatory parameter for {}".format(mandatory, item))
794
795 all_tenants = False
796 if item in ('tenant', 'vim', 'wim'):
797 all_tenants = None
798
799 create_desc = self._create_envelop(item, desc)
800
801 with aiohttp.ClientSession(loop=self.loop) as session:
802 outdata = await self._create_item(session, self.client_to_RO[item], create_desc,
803 all_tenants=all_tenants)
804 return remove_envelop(item, outdata)
805 except aiohttp.errors.ClientOSError as e:
806 raise ROClientException(e, http_code=504)
807 except asyncio.TimeoutError:
808 raise ROClientException("Timeout", http_code=504)
809
810 async def create_action(self, item, item_id_name, descriptor=None, descriptor_format=None, **kwargs):
811 """
812 Performs an action over an item
813 :param item: can be 'tenant', 'vnfd', 'nsd', 'ns', 'vim', 'vim_account', 'sdn'
814 :param item_id_name: RO id or name of the item. Raise and exception if more than one found
815 :param descriptor: can be a dict, or a yaml/json text. Autodetect unless descriptor_format is provided
816 :param descriptor_format: Can be 'json' or 'yaml'
817 :param kwargs: Overrides descriptor with values as name, description, vim_url, vim_url_admin, vim_type
818 keys can be a dot separated list to specify elements inside dict
819 :return: dictionary with the information or raises ROClientException on Error
820 """
821 try:
822 if isinstance(descriptor, str):
823 descriptor = self._parse(descriptor, descriptor_format)
824 elif descriptor:
825 pass
826 else:
827 descriptor = {}
828
829 if item not in self.client_to_RO:
830 raise ROClientException("Invalid item {}".format(item))
831 desc = remove_envelop(item, descriptor)
832
833 # Override descriptor with kwargs
834 if kwargs:
835 desc = self.update_descriptor(desc, kwargs)
836
837 all_tenants = False
838 if item in ('tenant', 'vim'):
839 all_tenants = None
840
841 action = None
842 if item == "vims":
843 action = "sdn_mapping"
844 elif item in ("vim_account", "ns"):
845 action = "action"
846
847 # create_desc = self._create_envelop(item, desc)
848 create_desc = desc
849
850 with aiohttp.ClientSession(loop=self.loop) as session:
851 _all_tenants = all_tenants
852 if item == 'vim':
853 _all_tenants = True
854 # item_id = await self._get_item_uuid(session, self.client_to_RO[item], item_id_name,
855 # all_tenants=_all_tenants)
856 outdata = await self._create_item(session, self.client_to_RO[item], create_desc,
857 item_id_name=item_id_name, # item_id_name=item_id
858 action=action, all_tenants=_all_tenants)
859 return remove_envelop(item, outdata)
860 except aiohttp.errors.ClientOSError as e:
861 raise ROClientException(e, http_code=504)
862 except asyncio.TimeoutError:
863 raise ROClientException("Timeout", http_code=504)
864
865 async def attach(self, item, item_id_name=None, descriptor=None, descriptor_format=None, **kwargs):
866 """
867 Attach a datacenter or wim to a tenant, creating a vim_account, wim_account
868 :param item: can be vim_account or wim_account
869 :param item_id_name: id or name of the datacenter, wim
870 :param descriptor:
871 :param descriptor_format:
872 :param kwargs:
873 :return:
874 """
875 try:
876 if isinstance(descriptor, str):
877 descriptor = self._parse(descriptor, descriptor_format)
878 elif descriptor:
879 pass
880 else:
881 descriptor = {}
882
883 desc = remove_envelop(item, descriptor)
884
885 # # check that exist
886 # uuid = self._get_item_uuid(session, "datacenters", uuid_name, all_tenants=True)
887 # tenant_text = "/" + self._get_tenant()
888 if kwargs:
889 desc = self.update_descriptor(desc, kwargs)
890
891 if item == "vim_account":
892 if not desc.get("vim_tenant_name") and not desc.get("vim_tenant_id"):
893 raise ROClientException("Wrong descriptor. At least vim_tenant_name or vim_tenant_id must be "
894 "provided")
895 elif item != "wim_account":
896 raise ROClientException("Attach with unknown item {}. Must be 'vim_account' or 'wim_account'".
897 format(item))
898 create_desc = self._create_envelop(item, desc)
899 payload_req = yaml.safe_dump(create_desc)
900 with aiohttp.ClientSession(loop=self.loop) as session:
901 # check that exist
902 item_id = await self._get_item_uuid(session, self.client_to_RO[item], item_id_name, all_tenants=True)
903 await self._get_tenant(session)
904
905 url = "{}/{tenant}/{item}/{item_id}".format(self.endpoint_url, tenant=self.tenant,
906 item=self.client_to_RO[item], item_id=item_id)
907 self.logger.debug("RO POST %s %s", url, payload_req)
908 with aiohttp.Timeout(self.timeout_large):
909 async with session.post(url, headers=self.headers_req, data=payload_req) as response:
910 response_text = await response.read()
911 self.logger.debug("POST {} [{}] {}".format(url, response.status, response_text[:100]))
912 if response.status >= 300:
913 raise ROClientException(response_text, http_code=response.status)
914
915 response_desc = self._parse_yaml(response_text, response=True)
916 desc = remove_envelop(item, response_desc)
917 return desc
918 except aiohttp.errors.ClientOSError as e:
919 raise ROClientException(e, http_code=504)
920 except asyncio.TimeoutError:
921 raise ROClientException("Timeout", http_code=504)
922
923 async def detach(self, item, item_id_name=None):
924 # TODO replace the code with delete_item(vim_account,...)
925 try:
926 with aiohttp.ClientSession(loop=self.loop) as session:
927 # check that exist
928 item_id = await self._get_item_uuid(session, self.client_to_RO[item], item_id_name, all_tenants=False)
929 tenant = await self._get_tenant(session)
930
931 url = "{}/{tenant}/{item}/{datacenter}".format(self.endpoint_url, tenant=tenant,
932 item=self.client_to_RO[item], datacenter=item_id)
933 self.logger.debug("RO DELETE %s", url)
934 with aiohttp.Timeout(self.timeout_large):
935 async with session.delete(url, headers=self.headers_req) as response:
936 response_text = await response.read()
937 self.logger.debug("DELETE {} [{}] {}".format(url, response.status, response_text[:100]))
938 if response.status >= 300:
939 raise ROClientException(response_text, http_code=response.status)
940
941 response_desc = self._parse_yaml(response_text, response=True)
942 desc = remove_envelop(item, response_desc)
943 return desc
944 except aiohttp.errors.ClientOSError as e:
945 raise ROClientException(e, http_code=504)
946 except asyncio.TimeoutError:
947 raise ROClientException("Timeout", http_code=504)
948
949 # TODO convert to asyncio
950 # DATACENTERS
951
952 def edit_datacenter(self, uuid=None, name=None, descriptor=None, descriptor_format=None, all_tenants=False,
953 **kwargs):
954 """Edit the parameters of a datacenter
955 Params: must supply a descriptor or/and a parameter to change
956 uuid or/and name. If only name is supplied, there must be only one or an exception is raised
957 descriptor: with format {'datacenter':{params to change info}}
958 must be a dictionary or a json/yaml text.
959 parameters to change can be supplyied by the descriptor or as parameters:
960 new_name: the datacenter name
961 vim_url: the datacenter URL
962 vim_url_admin: the datacenter URL for administrative issues
963 vim_type: the datacenter type, can be openstack or openvim.
964 public: boolean, available to other tenants
965 description: datacenter description
966 Return: Raises an exception on error, not found or found several
967 Obtain a dictionary with format {'datacenter':{new_datacenter_info}}
968 """
969
970 if isinstance(descriptor, str):
971 descriptor = self.parse(descriptor, descriptor_format)
972 elif descriptor:
973 pass
974 elif kwargs:
975 descriptor = {"datacenter": {}}
976 else:
977 raise ROClientException("Missing descriptor")
978
979 if 'datacenter' not in descriptor or len(descriptor) != 1:
980 raise ROClientException("Descriptor must contain only one 'datacenter' field")
981 for param in kwargs:
982 if param == 'new_name':
983 descriptor['datacenter']['name'] = kwargs[param]
984 else:
985 descriptor['datacenter'][param] = kwargs[param]
986 return self._edit_item("datacenters", descriptor, uuid, name, all_tenants=None)
987
988 def edit_scenario(self, uuid=None, name=None, descriptor=None, descriptor_format=None, all_tenants=False, **kwargs):
989 """Edit the parameters of a scenario
990 Params: must supply a descriptor or/and a parameters to change
991 uuid or/and name. If only name is supplied, there must be only one or an exception is raised
992 descriptor: with format {'scenario':{params to change info}}
993 must be a dictionary or a json/yaml text.
994 parameters to change can be supplyied by the descriptor or as parameters:
995 new_name: the scenario name
996 public: boolean, available to other tenants
997 description: scenario description
998 tenant_id. Propietary tenant
999 Return: Raises an exception on error, not found or found several
1000 Obtain a dictionary with format {'scenario':{new_scenario_info}}
1001 """
1002
1003 if isinstance(descriptor, str):
1004 descriptor = self.parse(descriptor, descriptor_format)
1005 elif descriptor:
1006 pass
1007 elif kwargs:
1008 descriptor = {"scenario": {}}
1009 else:
1010 raise ROClientException("Missing descriptor")
1011
1012 if 'scenario' not in descriptor or len(descriptor) > 2:
1013 raise ROClientException("Descriptor must contain only one 'scenario' field")
1014 for param in kwargs:
1015 if param == 'new_name':
1016 descriptor['scenario']['name'] = kwargs[param]
1017 else:
1018 descriptor['scenario'][param] = kwargs[param]
1019 return self._edit_item("scenarios", descriptor, uuid, name, all_tenants=None)
1020
1021 # VIM ACTIONS
1022 def vim_action(self, action, item, uuid=None, all_tenants=False, **kwargs):
1023 """Perform an action over a vim
1024 Params:
1025 action: can be 'list', 'get'/'show', 'delete' or 'create'
1026 item: can be 'tenants' or 'networks'
1027 uuid: uuid of the tenant/net to show or to delete. Ignore otherwise
1028 other parameters:
1029 datacenter_name, datacenter_id: datacenters to act on, if missing uses classes store datacenter
1030 descriptor, descriptor_format: descriptor needed on creation, can be a dict or a yaml/json str
1031 must be a dictionary or a json/yaml text.
1032 name: for created tenant/net Overwrite descriptor name if any
1033 description: tenant descriptor. Overwrite descriptor description if any
1034
1035 Return: Raises an exception on error
1036 Obtain a dictionary with format {'tenant':{new_tenant_info}}
1037 """
1038 session = None # TODO remove when changed to asyncio
1039 if item not in ("tenants", "networks", "images"):
1040 raise ROClientException("Unknown value for item '{}', must be 'tenants', 'nets' or "
1041 "images".format(str(item)))
1042
1043 image_actions = ['list', 'get', 'show', 'delete']
1044 if item == "images" and action not in image_actions:
1045 raise ROClientException("Only available actions for item '{}' are {}\n"
1046 "Requested action was '{}'".format(item, ', '.join(image_actions), action))
1047 if all_tenants:
1048 tenant_text = "/any"
1049 else:
1050 tenant_text = "/" + self._get_tenant()
1051
1052 if "datacenter_id" in kwargs or "datacenter_name" in kwargs:
1053 datacenter = self._get_item_uuid(session, "datacenters", kwargs.get("datacenter"), all_tenants=all_tenants)
1054 else:
1055 datacenter = self.get_datacenter(session)
1056
1057 if action == "list":
1058 url = "{}{}/vim/{}/{}".format(self.endpoint_url, tenant_text, datacenter, item)
1059 self.logger.debug("GET %s", url)
1060 mano_response = requests.get(url, headers=self.headers_req)
1061 self.logger.debug("RO response: %s", mano_response.text)
1062 content = self._parse_yaml(mano_response.text, response=True)
1063 if mano_response.status_code == 200:
1064 return content
1065 else:
1066 raise ROClientException(str(content), http_code=mano_response.status)
1067 elif action == "get" or action == "show":
1068 url = "{}{}/vim/{}/{}/{}".format(self.endpoint_url, tenant_text, datacenter, item, uuid)
1069 self.logger.debug("GET %s", url)
1070 mano_response = requests.get(url, headers=self.headers_req)
1071 self.logger.debug("RO response: %s", mano_response.text)
1072 content = self._parse_yaml(mano_response.text, response=True)
1073 if mano_response.status_code == 200:
1074 return content
1075 else:
1076 raise ROClientException(str(content), http_code=mano_response.status)
1077 elif action == "delete":
1078 url = "{}{}/vim/{}/{}/{}".format(self.endpoint_url, tenant_text, datacenter, item, uuid)
1079 self.logger.debug("DELETE %s", url)
1080 mano_response = requests.delete(url, headers=self.headers_req)
1081 self.logger.debug("RO response: %s", mano_response.text)
1082 content = self._parse_yaml(mano_response.text, response=True)
1083 if mano_response.status_code == 200:
1084 return content
1085 else:
1086 raise ROClientException(str(content), http_code=mano_response.status)
1087 elif action == "create":
1088 if "descriptor" in kwargs:
1089 if isinstance(kwargs["descriptor"], str):
1090 descriptor = self._parse(kwargs["descriptor"], kwargs.get("descriptor_format"))
1091 else:
1092 descriptor = kwargs["descriptor"]
1093 elif "name" in kwargs:
1094 descriptor = {item[:-1]: {"name": kwargs["name"]}}
1095 else:
1096 raise ROClientException("Missing descriptor")
1097
1098 if item[:-1] not in descriptor or len(descriptor) != 1:
1099 raise ROClientException("Descriptor must contain only one 'tenant' field")
1100 if "name" in kwargs:
1101 descriptor[item[:-1]]['name'] = kwargs["name"]
1102 if "description" in kwargs:
1103 descriptor[item[:-1]]['description'] = kwargs["description"]
1104 payload_req = yaml.safe_dump(descriptor)
1105 # print payload_req
1106 url = "{}{}/vim/{}/{}".format(self.endpoint_url, tenant_text, datacenter, item)
1107 self.logger.debug("RO POST %s %s", url, payload_req)
1108 mano_response = requests.post(url, headers=self.headers_req, data=payload_req)
1109 self.logger.debug("RO response: %s", mano_response.text)
1110 content = self._parse_yaml(mano_response.text, response=True)
1111 if mano_response.status_code == 200:
1112 return content
1113 else:
1114 raise ROClientException(str(content), http_code=mano_response.status)
1115 else:
1116 raise ROClientException("Unknown value for action '{}".format(str(action)))
1117
1118
1119 if __name__ == '__main__':
1120 RO_URL = "http://localhost:9090/openmano"
1121 TEST_TENANT = "myTenant"
1122 TEST_VIM1 = "myvim"
1123 TEST_URL1 = "https://localhost:5000/v1"
1124 TEST_TYPE1 = "openstack"
1125 TEST_CONFIG1 = {"use_floating_ip": True}
1126 TEST_VIM2 = "myvim2"
1127 TEST_URL2 = "https://localhost:5000/v2"
1128 TEST_TYPE2 = "openvim"
1129 TEST_CONFIG2 = {"config2": "config2", "config3": True}
1130
1131 streamformat = "%(asctime)s %(name)s %(levelname)s: %(message)s"
1132 logging.basicConfig(format=streamformat)
1133 logger = logging.getLogger("ROClient")
1134
1135 tenant_id = None
1136 vim_id = False
1137 loop = asyncio.get_event_loop()
1138 myClient = ROClient(endpoint_url=RO_URL, loop=loop, loglevel="DEBUG")
1139 try:
1140 # test tenant
1141 content = loop.run_until_complete(myClient.get_list("tenant"))
1142 print("tenants", content)
1143 content = loop.run_until_complete(myClient.create("tenant", name=TEST_TENANT))
1144 tenant_id = True
1145 content = loop.run_until_complete(myClient.show("tenant", TEST_TENANT))
1146 print("tenant", TEST_TENANT, content)
1147 content = loop.run_until_complete(myClient.edit("tenant", TEST_TENANT, description="another description"))
1148 content = loop.run_until_complete(myClient.show("tenant", TEST_TENANT))
1149 print("tenant edited", TEST_TENANT, content)
1150 myClient["tenant"] = TEST_TENANT
1151
1152 # test VIM
1153 content = loop.run_until_complete(myClient.create("vim", name=TEST_VIM1, type=TEST_TYPE1, vim_url=TEST_URL1,
1154 config=TEST_CONFIG1))
1155 vim_id = True
1156 content = loop.run_until_complete(myClient.get_list("vim"))
1157 print("vim", content)
1158 content = loop.run_until_complete(myClient.show("vim", TEST_VIM1))
1159 print("vim", TEST_VIM1, content)
1160 content = loop.run_until_complete(myClient.edit("vim", TEST_VIM1, description="another description",
1161 name=TEST_VIM2, type=TEST_TYPE2, vim_url=TEST_URL2,
1162 config=TEST_CONFIG2))
1163 content = loop.run_until_complete(myClient.show("vim", TEST_VIM2))
1164 print("vim edited", TEST_VIM2, content)
1165
1166 # test VIM_ACCOUNT
1167 content = loop.run_until_complete(myClient.attach_datacenter(TEST_VIM2, vim_username='user',
1168 vim_password='pass', vim_tenant_name='vimtenant1',
1169 config=TEST_CONFIG1))
1170 vim_id = True
1171 content = loop.run_until_complete(myClient.get_list("vim_account"))
1172 print("vim_account", content)
1173 content = loop.run_until_complete(myClient.show("vim_account", TEST_VIM2))
1174 print("vim_account", TEST_VIM2, content)
1175 content = loop.run_until_complete(myClient.edit("vim_account", TEST_VIM2, vim_username='user2',
1176 vim_password='pass2', vim_tenant_name="vimtenant2",
1177 config=TEST_CONFIG2))
1178 content = loop.run_until_complete(myClient.show("vim_account", TEST_VIM2))
1179 print("vim_account edited", TEST_VIM2, content)
1180
1181 myClient["vim"] = TEST_VIM2
1182
1183 except Exception as e:
1184 logger.error("Error {}".format(e), exc_info=True)
1185
1186 for item in (("vim_account", TEST_VIM1), ("vim", TEST_VIM1),
1187 ("vim_account", TEST_VIM2), ("vim", TEST_VIM2),
1188 ("tenant", TEST_TENANT)):
1189 try:
1190 content = loop.run_until_complete(myClient.delete(item[0], item[1]))
1191 print("{} {} deleted; {}".format(item[0], item[1], content))
1192 except Exception as e:
1193 if e.http_code == 404:
1194 print("{} {} not present or already deleted".format(item[0], item[1]))
1195 else:
1196 logger.error("Error {}".format(e), exc_info=True)
1197
1198 loop.close()