2 # -*- coding: utf-8 -*-
5 # Copyright 2015 Telefónica Investigación y Desarrollo, S.A.U.
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
11 # http://www.apache.org/licenses/LICENSE-2.0
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
22 asyncio RO python client to interact with RO-server
30 from urllib
.parse
import quote
32 from copy
import deepcopy
34 __author__
= "Alfonso Tierno"
35 __date__
= "$09-Jan-2018 09:09:48$"
37 version_date
= "2018-05-16"
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
)
48 def remove_envelop(item
, indata
=None):
50 Obtain the useful data removing the envelop. It goes through the vnfd or nsd catalog and returns the
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)
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]
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]
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"]
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"]
96 assert False, "remove_envelop with unknown item {}".format(item
)
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',
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"),
115 'sdn': ("name", 'type'),
120 def __init__(self
, loop
, uri
, **kwargs
):
124 self
.username
= kwargs
.get("username")
125 self
.password
= kwargs
.get("password")
126 self
.tenant_id_name
= kwargs
.get("tenant")
128 self
.datacenter_id_name
= kwargs
.get("datacenter")
129 self
.datacenter
= None
130 logger_name
= kwargs
.get('logger_name', 'lcm.ro')
131 self
.logger
= logging
.getLogger(logger_name
)
132 if kwargs
.get("loglevel"):
133 self
.logger
.setLevel(kwargs
["loglevel"])
135 requests
= kwargs
.get("TODO remove")
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':
144 elif index
== 'password':
149 raise KeyError("Invalid key '{}'".format(index
))
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
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
168 def _parse(descriptor
, descriptor_format
, response
=False):
169 if descriptor_format
and descriptor_format
!= "json" and descriptor_format
!= "yaml":
170 raise ROClientException("'descriptor_format' must be a 'json' or 'yaml' text")
171 if descriptor_format
!= "json":
173 return yaml
.load(descriptor
)
174 except yaml
.YAMLError
as exc
:
176 if hasattr(exc
, 'problem_mark'):
177 mark
= exc
.problem_mark
178 error_pos
= " at line:{} column:{}s".format(mark
.line
+ 1, mark
.column
+ 1)
179 error_text
= "yaml format error" + error_pos
180 elif descriptor_format
!= "yaml":
182 return json
.loads(descriptor
)
183 except Exception as e
:
185 error_text
= "json format error" + str(e
)
188 raise ROClientException(error_text
)
189 raise ROClientException(error_text
)
192 def _parse_error_yaml(descriptor
):
195 json_error
= yaml
.load(descriptor
, Loader
=yaml
.Loader
)
196 return json_error
["error"]["description"]
198 return str(json_error
or descriptor
)
201 def _parse_yaml(descriptor
, response
=False):
203 return yaml
.load(descriptor
, Loader
=yaml
.Loader
)
204 except yaml
.YAMLError
as exc
:
206 if hasattr(exc
, 'problem_mark'):
207 mark
= exc
.problem_mark
208 error_pos
= " at line:{} column:{}s".format(mark
.line
+ 1, mark
.column
+ 1)
209 error_text
= "yaml format error" + error_pos
211 raise ROClientException(error_text
)
212 raise ROClientException(error_text
)
215 def check_if_uuid(uuid_text
):
217 Check if text correspond to an uuid foramt
219 :return: True if it is an uuid False if not
228 def _create_envelop(item
, indata
=None):
230 Returns a new dict that incledes indata with the expected envelop
231 :param item: can be 'tenant', 'vim', 'vnfd', 'nsd', 'ns'
232 :param indata: Content to be enveloped
233 :return: a new dic with {<envelop>: {indata} } where envelop can be e.g. tenant, datacenter, ...
236 return {'vnfd-catalog': {'vnfd': [indata
]}}
238 return {'nsd-catalog': {'nsd': [indata
]}}
239 elif item
== "tenant":
240 return {'tenant': indata
}
241 elif item
in ("vim", "vim_account", "datacenter"):
242 return {'datacenter': indata
}
244 return {'wim': indata
}
245 elif item
== "wim_account":
246 return {'wim_account': indata
}
247 elif item
== "ns" or item
== "instances":
248 return {'instance': indata
}
250 return {'sdn_controller': indata
}
252 assert False, "_create_envelop with unknown item {}".format(item
)
255 def update_descriptor(desc
, kwargs
):
256 desc
= deepcopy(desc
) # do not modify original descriptor
258 for k
, v
in kwargs
.items():
259 update_content
= desc
263 if kitem_old
is not None:
264 update_content
= update_content
[kitem_old
]
265 if isinstance(update_content
, dict):
267 elif isinstance(update_content
, list):
268 kitem_old
= int(kitem
)
270 raise ROClientException(
271 "Invalid query string '{}'. Descriptor is not a list nor dict at '{}'".format(k
, kitem
))
272 if v
== "__DELETE__":
273 del update_content
[kitem_old
]
275 update_content
[kitem_old
] = v
278 raise ROClientException(
279 "Invalid query string '{}'. Descriptor does not contain '{}'".format(k
, kitem_old
))
281 raise ROClientException("Invalid query string '{}'. Expected integer index list instead of '{}'".format(
284 raise ROClientException(
285 "Invalid query string '{}'. Index '{}' out of range".format(k
, kitem_old
))
288 def check_ns_status(ns_descriptor
):
290 Inspect RO instance descriptor and indicates the status
291 :param ns_descriptor: instance descriptor obtained with self.show("ns", )
292 :return: status, message: status can be BUILD,ACTIVE,ERROR, message is a text message
295 total
= {"VMs": 0, "networks": 0, "SDN_networks": 0}
296 done
= {"VMs": 0, "networks": 0, "SDN_networks": 0}
299 # return an identification for the network or vm. Try vim_id if exist, if not descriptor id for net
300 if desc
.get("vim_net_id"):
301 return "'vim-id={}'".format(desc
["vim_net_id"])
302 elif desc
.get("ns_net_osm_id"):
303 return "'nsd-vld-id={}'".format(desc
["ns_net_osm_id"])
304 elif desc
.get("vnf_net_osm_id"):
305 return "'vnfd-vld-id={}'".format(desc
["vnf_net_osm_id"])
307 elif desc
.get("vim_vm_id"):
308 return "'vim-id={}'".format(desc
["vim_vm_id"])
309 elif desc
.get("vdu_osm_id"):
310 return "'vnfd-vdu-id={}'".format(desc
["vdu_osm_id"])
314 def _get_sdn_ref(sce_net_id
):
315 # look for the network associated to the SDN network and obtain the identification
316 net
= next((x
for x
in ns_descriptor
["nets"] if x
.get("sce_net_id") == sce_net_id
), None)
317 if not sce_net_id
or not net
:
322 total
["networks"] = len(ns_descriptor
["nets"])
323 for net
in ns_descriptor
["nets"]:
324 if net
["status"] in ("ERROR", "VIM_ERROR"):
325 error_list
.append("Error at VIM network {}: {}".format(_get_ref(net
), net
["error_msg"]))
326 elif net
["status"] == "ACTIVE":
327 done
["networks"] += 1
329 total
["SDN_networks"] = len(ns_descriptor
["sdn_nets"])
330 for sdn_net
in ns_descriptor
["sdn_nets"]:
331 if sdn_net
["status"] in ("ERROR", "VIM_ERROR", "WIM_ERROR"):
332 error_list
.append("Error at SDN network {}: {}".format(_get_sdn_ref(sdn_net
.get("sce_net_id")),
333 sdn_net
["error_msg"]))
334 elif sdn_net
["status"] == "ACTIVE":
335 done
["SDN_networks"] += 1
337 for vnf
in ns_descriptor
["vnfs"]:
338 for vm
in vnf
["vms"]:
340 if vm
["status"] in ("ERROR", "VIM_ERROR"):
341 error_list
.append("Error at VIM VM {}: {}".format(_get_ref(vm
), vm
["error_msg"]))
342 elif vm
["status"] == "ACTIVE":
345 # skip errors caused because other dependendent task is on error
346 return "ERROR", "; ".join([el
for el
in error_list
if "because depends on failed ACTION" not in el
])
347 if all(total
[x
] == done
[x
] for x
in total
): # DONE == TOTAL for all items
348 return "ACTIVE", str({x
: total
[x
] for x
in total
if total
[x
]}) # print only those which value is not 0
350 return "BUILD", str({x
: "{}/{}".format(done
[x
], total
[x
]) for x
in total
if total
[x
]})
351 # print done/total for each item if total is not 0
352 except Exception as e
:
353 raise ROClientException("Unexpected RO ns descriptor. Wrong version? {}".format(e
)) from e
356 def check_action_status(action_descriptor
):
358 Inspect RO instance descriptor and indicates the status
359 :param action_descriptor: action instance descriptor obtained with self.show("ns", "action")
360 :return: status, message: status can be BUILD,ACTIVE,ERROR, message is a text message
369 for vim_action_set
in action_descriptor
["actions"]:
370 for vim_action
in vim_action_set
["vim_wim_actions"]:
371 if vim_action
["item"] == "instance_vms":
373 elif vim_action
["item"] == "instance_nets":
377 if vim_action
["status"] == "FAILED":
378 return "ERROR", vim_action
["error_msg"]
379 elif vim_action
["status"] in ("DONE", "SUPERSEDED", "FINISHED"):
380 if vim_action
["item"] == "instance_vms":
382 elif vim_action
["item"] == "instance_nets":
387 if net_total
== net_done
and vm_total
== vm_done
and other_total
== other_done
:
388 return "ACTIVE", "VMs {}, networks: {}, other: {} ".format(vm_total
, net_total
, other_total
)
390 return "BUILD", "VMs: {}/{}, networks: {}/{}, other: {}/{}".format(vm_done
, vm_total
, net_done
, net_total
,
391 other_done
, other_total
)
394 def get_ns_vnf_info(ns_descriptor
):
396 Get a dict with the VIM_id, ip_addresses, mac_addresses of every vnf and vdu
397 :param ns_descriptor: instance descriptor obtained with self.show("ns", )
411 for vnf
in ns_descriptor
["vnfs"]:
412 if not vnf
.get("ip_address") and vnf
.get("vms"):
413 raise ROClientException("ns member_vnf_index '{}' has no IP address".format(
414 vnf
["member_vnf_index"]), http_code
=409)
416 "ip_address": vnf
.get("ip_address"),
419 for vm
in vnf
["vms"]:
421 "vim_id": vm
.get("vim_vm_id"),
422 "ip_address": vm
.get("ip_address"),
425 for iface
in vm
["interfaces"]:
426 if iface
.get("type") == "mgmt" and not iface
.get("ip_address"):
427 raise ROClientException("ns member_vnf_index '{}' vm '{}' management interface '{}' has no IP "
428 "address".format(vnf
["member_vnf_index"], vm
["vdu_osm_id"],
429 iface
["external_name"]), http_code
=409)
430 vdur
["interfaces"][iface
["internal_name"]] = {"ip_address": iface
.get("ip_address"),
431 "mac_address": iface
.get("mac_address"),
432 "vim_id": iface
.get("vim_interface_id"),
434 vnfr_info
["vdur"][vm
["vdu_osm_id"]] = vdur
435 ns_info
[str(vnf
["member_vnf_index"])] = vnfr_info
438 async def _get_item_uuid(self
, session
, item
, item_id_name
, all_tenants
=False):
441 elif all_tenants
is None:
445 await self
._get
_tenant
(session
)
446 tenant_text
= "/" + self
.tenant
449 url
= "{}{}/{}".format(self
.uri
, tenant_text
, item
)
450 if self
.check_if_uuid(item_id_name
):
451 item_id
= item_id_name
452 url
+= "/" + item_id_name
453 elif item_id_name
and item_id_name
.startswith("'") and item_id_name
.endswith("'"):
454 item_id_name
= item_id_name
[1:-1]
455 self
.logger
.debug("RO GET %s", url
)
456 # timeout = aiohttp.ClientTimeout(total=self.timeout_short)
457 async with session
.get(url
, headers
=self
.headers_req
) as response
:
458 response_text
= await response
.read()
459 self
.logger
.debug("GET {} [{}] {}".format(url
, response
.status
, response_text
[:100]))
460 if response
.status
== 404: # NOT_FOUND
461 raise ROClientException("No {} found with id '{}'".format(item
[:-1], item_id_name
),
463 if response
.status
>= 300:
464 raise ROClientException(self
._parse
_error
_yaml
(response_text
), http_code
=response
.status
)
465 content
= self
._parse
_yaml
(response_text
, response
=True)
470 assert isinstance(desc
, list), "_get_item_uuid get a non dict with a list inside {}".format(type(desc
))
473 if item_id_name
and i
["name"] != item_id_name
:
475 if uuid
: # found more than one
476 raise ROClientException(
477 "Found more than one {} with name '{}'. uuid must be used".format(item
, item_id_name
),
481 raise ROClientException("No {} found with name '{}'".format(item
[:-1], item_id_name
), http_code
=404)
484 async def _get_item(self
, session
, item
, item_id_name
, extra_item
=None, extra_item_id
=None, all_tenants
=False):
487 elif all_tenants
is None:
491 await self
._get
_tenant
(session
)
492 tenant_text
= "/" + self
.tenant
494 if self
.check_if_uuid(item_id_name
):
498 uuid
= await self
._get
_item
_uuid
(session
, item
, item_id_name
, all_tenants
)
500 url
= "{}{}/{}/{}".format(self
.uri
, tenant_text
, item
, uuid
)
502 url
+= "/" + extra_item
504 url
+= "/" + extra_item_id
505 self
.logger
.debug("GET %s", url
)
506 # timeout = aiohttp.ClientTimeout(total=self.timeout_short)
507 async with session
.get(url
, headers
=self
.headers_req
) as response
:
508 response_text
= await response
.read()
509 self
.logger
.debug("GET {} [{}] {}".format(url
, response
.status
, response_text
[:100]))
510 if response
.status
>= 300:
511 raise ROClientException(self
._parse
_error
_yaml
(response_text
), http_code
=response
.status
)
513 return self
._parse
_yaml
(response_text
, response
=True)
515 async def _get_tenant(self
, session
):
517 self
.tenant
= await self
._get
_item
_uuid
(session
, "tenants", self
.tenant_id_name
, None)
520 async def _get_datacenter(self
, session
):
522 await self
._get
_tenant
(session
)
523 if not self
.datacenter
:
524 self
.datacenter
= await self
._get
_item
_uuid
(session
, "datacenters", self
.datacenter_id_name
, True)
525 return self
.datacenter
527 async def _create_item(self
, session
, item
, descriptor
, item_id_name
=None, action
=None, all_tenants
=False):
530 elif all_tenants
is None:
534 await self
._get
_tenant
(session
)
535 tenant_text
= "/" + self
.tenant
536 payload_req
= yaml
.safe_dump(descriptor
)
539 api_version_text
= ""
541 # assumes version v3 only
542 api_version_text
= "/v3"
544 elif item
== "scenarios":
545 # assumes version v3 only
546 api_version_text
= "/v3"
551 elif self
.check_if_uuid(item_id_name
):
552 uuid
= "/{}".format(item_id_name
)
555 uuid
= await self
._get
_item
_uuid
(session
, item
, item_id_name
, all_tenants
)
556 uuid
= "/{}".format(uuid
)
560 action
= "/{}".format(action
)
562 url
= "{}{apiver}{tenant}/{item}{id}{action}".format(self
.uri
, apiver
=api_version_text
,
563 tenant
=tenant_text
, item
=item
, id=uuid
, action
=action
)
564 self
.logger
.debug("RO POST %s %s", url
, payload_req
)
565 # timeout = aiohttp.ClientTimeout(total=self.timeout_large)
566 async with session
.post(url
, headers
=self
.headers_req
, data
=payload_req
) as response
:
567 response_text
= await response
.read()
568 self
.logger
.debug("POST {} [{}] {}".format(url
, response
.status
, response_text
[:100]))
569 if response
.status
>= 300:
570 raise ROClientException(self
._parse
_error
_yaml
(response_text
), http_code
=response
.status
)
572 return self
._parse
_yaml
(response_text
, response
=True)
574 async def _del_item(self
, session
, item
, item_id_name
, all_tenants
=False):
577 elif all_tenants
is None:
581 await self
._get
_tenant
(session
)
582 tenant_text
= "/" + self
.tenant
583 if not self
.check_if_uuid(item_id_name
):
585 _all_tenants
= all_tenants
586 if item
in ("datacenters", 'wims'):
588 uuid
= await self
._get
_item
_uuid
(session
, item
, item_id_name
, all_tenants
=_all_tenants
)
592 url
= "{}{}/{}/{}".format(self
.uri
, tenant_text
, item
, uuid
)
593 self
.logger
.debug("DELETE %s", url
)
594 # timeout = aiohttp.ClientTimeout(total=self.timeout_short)
595 async with session
.delete(url
, headers
=self
.headers_req
) as response
:
596 response_text
= await response
.read()
597 self
.logger
.debug("DELETE {} [{}] {}".format(url
, response
.status
, response_text
[:100]))
598 if response
.status
>= 300:
599 raise ROClientException(self
._parse
_error
_yaml
(response_text
), http_code
=response
.status
)
601 return self
._parse
_yaml
(response_text
, response
=True)
603 async def _list_item(self
, session
, item
, all_tenants
=False, filter_dict
=None):
606 elif all_tenants
is None:
610 await self
._get
_tenant
(session
)
611 tenant_text
= "/" + self
.tenant
613 url
= "{}{}/{}".format(self
.uri
, tenant_text
, item
)
616 for k
in filter_dict
:
617 url
+= separator
+ quote(str(k
)) + "=" + quote(str(filter_dict
[k
]))
619 self
.logger
.debug("RO GET %s", url
)
620 # timeout = aiohttp.ClientTimeout(total=self.timeout_short)
621 async with session
.get(url
, headers
=self
.headers_req
) as response
:
622 response_text
= await response
.read()
623 self
.logger
.debug("GET {} [{}] {}".format(url
, response
.status
, response_text
[:100]))
624 if response
.status
>= 300:
625 raise ROClientException(self
._parse
_error
_yaml
(response_text
), http_code
=response
.status
)
627 return self
._parse
_yaml
(response_text
, response
=True)
629 async def _edit_item(self
, session
, item
, item_id
, descriptor
, all_tenants
=False):
632 elif all_tenants
is None:
636 await self
._get
_tenant
(session
)
637 tenant_text
= "/" + self
.tenant
639 payload_req
= yaml
.safe_dump(descriptor
)
642 url
= "{}{}/{}/{}".format(self
.uri
, tenant_text
, item
, item_id
)
643 self
.logger
.debug("RO PUT %s %s", url
, payload_req
)
644 # timeout = aiohttp.ClientTimeout(total=self.timeout_large)
645 async with session
.put(url
, headers
=self
.headers_req
, data
=payload_req
) as response
:
646 response_text
= await response
.read()
647 self
.logger
.debug("PUT {} [{}] {}".format(url
, response
.status
, response_text
[:100]))
648 if response
.status
>= 300:
649 raise ROClientException(self
._parse
_error
_yaml
(response_text
), http_code
=response
.status
)
651 return self
._parse
_yaml
(response_text
, response
=True)
653 async def get_version(self
):
655 Obtain RO server version.
656 :return: a list with integers ["major", "minor", "release"]. Raises ROClientException on Error,
660 async with aiohttp
.ClientSession(loop
=self
.loop
) as session
:
661 url
= "{}/version".format(self
.uri
)
662 self
.logger
.debug("RO GET %s", url
)
663 # timeout = aiohttp.ClientTimeout(total=self.timeout_short)
664 async with session
.get(url
, headers
=self
.headers_req
) as response
:
665 response_text
= await response
.read()
666 self
.logger
.debug("GET {} [{}] {}".format(url
, response
.status
, response_text
[:100]))
667 if response
.status
>= 300:
668 raise ROClientException(self
._parse
_error
_yaml
(response_text
), http_code
=response
.status
)
670 for word
in str(response_text
).split(" "):
672 version_text
, _
, _
= word
.partition("-")
674 raise ROClientException("Got invalid version text: '{}'".format(response_text
), http_code
=500)
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 except Exception as e
:
680 raise ROClientException("Got invalid version text: '{}'; causing exception {}".format(response_text
, e
),
683 async def get_list(self
, item
, all_tenants
=False, filter_by
=None):
685 Obtain a list of items filtering by the specigy filter_by.
686 :param item: can be 'tenant', 'vim', 'vnfd', 'nsd', 'ns'
687 :param all_tenants: True if not filtering by tenant. Only allowed for admin
688 :param filter_by: dictionary with filtering
689 :return: a list of dict. It can be empty. Raises ROClientException on Error,
692 if item
not in self
.client_to_RO
:
693 raise ROClientException("Invalid item {}".format(item
))
696 async with aiohttp
.ClientSession(loop
=self
.loop
) as session
:
697 content
= await self
._list
_item
(session
, self
.client_to_RO
[item
], all_tenants
=all_tenants
,
698 filter_dict
=filter_by
)
699 if isinstance(content
, dict):
700 if len(content
) == 1:
701 for _
, v
in content
.items():
703 return content
.values()[0]
705 raise ROClientException("Output not a list neither dict with len equal 1", http_code
=500)
707 except (aiohttp
.ClientOSError
, aiohttp
.ClientError
) as e
:
708 raise ROClientException(e
, http_code
=504)
709 except asyncio
.TimeoutError
:
710 raise ROClientException("Timeout", http_code
=504)
712 async def show(self
, item
, item_id_name
=None, extra_item
=None, extra_item_id
=None, all_tenants
=False):
714 Obtain the information of an item from its id or name
715 :param item: can be 'tenant', 'vim', 'vnfd', 'nsd', 'ns'
716 :param item_id_name: RO id or name of the item. Raise and exception if more than one found
717 :param extra_item: if supplied, it is used to add to the URL.
718 Can be 'action' if item='ns'; 'networks' or'images' if item='vim'
719 :param extra_item_id: if supplied, it is used get details of a concrete extra_item.
720 :param all_tenants: True if not filtering by tenant. Only allowed for admin
721 :return: dictionary with the information or raises ROClientException on Error, NotFound, found several
724 if item
not in self
.client_to_RO
:
725 raise ROClientException("Invalid item {}".format(item
))
730 elif item
== 'vim_account':
733 async with aiohttp
.ClientSession(loop
=self
.loop
) as session
:
734 content
= await self
._get
_item
(session
, self
.client_to_RO
[item
], item_id_name
, extra_item
=extra_item
,
735 extra_item_id
=extra_item_id
, all_tenants
=all_tenants
)
736 return remove_envelop(item
, content
)
737 except (aiohttp
.ClientOSError
, aiohttp
.ClientError
) as e
:
738 raise ROClientException(e
, http_code
=504)
739 except asyncio
.TimeoutError
:
740 raise ROClientException("Timeout", http_code
=504)
742 async def delete(self
, item
, item_id_name
=None, all_tenants
=False):
744 Delete the information of an item from its id or name
745 :param item: can be 'tenant', 'vim', 'vnfd', 'nsd', 'ns'
746 :param item_id_name: RO id or name of the item. Raise and exception if more than one found
747 :param all_tenants: True if not filtering by tenant. Only allowed for admin
748 :return: dictionary with the information or raises ROClientException on Error, NotFound, found several
751 if item
not in self
.client_to_RO
:
752 raise ROClientException("Invalid item {}".format(item
))
753 if item
in ('tenant', 'vim', 'wim'):
756 async with aiohttp
.ClientSession(loop
=self
.loop
) as session
:
757 result
= await self
._del
_item
(session
, self
.client_to_RO
[item
], item_id_name
, all_tenants
=all_tenants
)
758 # in case of ns delete, get the action_id embeded in text
759 if item
== "ns" and result
.get("result"):
760 _
, _
, action_id
= result
["result"].partition("action_id=")
761 action_id
, _
, _
= action_id
.partition(" ")
763 result
["action_id"] = action_id
765 except (aiohttp
.ClientOSError
, aiohttp
.ClientError
) as e
:
766 raise ROClientException(e
, http_code
=504)
767 except asyncio
.TimeoutError
:
768 raise ROClientException("Timeout", http_code
=504)
770 async def edit(self
, item
, item_id_name
, descriptor
=None, descriptor_format
=None, **kwargs
):
772 :param item: can be 'tenant', 'vim', 'vnfd', 'nsd', 'ns', 'vim'
773 :param item_id_name: RO id or name of the item. Raise and exception if more than one found
774 :param descriptor: can be a dict, or a yaml/json text. Autodetect unless descriptor_format is provided
775 :param descriptor_format: Can be 'json' or 'yaml'
776 :param kwargs: Overrides descriptor with values as name, description, vim_url, vim_url_admin, vim_type
777 keys can be a dot separated list to specify elements inside dict
778 :return: dictionary with the information or raises ROClientException on Error
781 if isinstance(descriptor
, str):
782 descriptor
= self
._parse
(descriptor
, descriptor_format
)
788 if item
not in self
.client_to_RO
:
789 raise ROClientException("Invalid item {}".format(item
))
790 desc
= remove_envelop(item
, descriptor
)
792 # Override descriptor with kwargs
794 desc
= self
.update_descriptor(desc
, kwargs
)
796 if item
in ('tenant', 'vim'):
799 create_desc
= self
._create
_envelop
(item
, desc
)
801 async with aiohttp
.ClientSession(loop
=self
.loop
) as session
:
802 _all_tenants
= all_tenants
805 item_id
= await self
._get
_item
_uuid
(session
, self
.client_to_RO
[item
], item_id_name
,
806 all_tenants
=_all_tenants
)
809 # await self._get_tenant(session)
810 outdata
= await self
._edit
_item
(session
, self
.client_to_RO
[item
], item_id
, create_desc
,
811 all_tenants
=_all_tenants
)
812 return remove_envelop(item
, outdata
)
813 except (aiohttp
.ClientOSError
, aiohttp
.ClientError
) as e
:
814 raise ROClientException(e
, http_code
=504)
815 except asyncio
.TimeoutError
:
816 raise ROClientException("Timeout", http_code
=504)
818 async def create(self
, item
, descriptor
=None, descriptor_format
=None, **kwargs
):
820 Creates an item from its descriptor
821 :param item: can be 'tenant', 'vnfd', 'nsd', 'ns', 'vim', 'vim_account', 'sdn'
822 :param descriptor: can be a dict, or a yaml/json text. Autodetect unless descriptor_format is provided
823 :param descriptor_format: Can be 'json' or 'yaml'
824 :param kwargs: Overrides descriptor with values as name, description, vim_url, vim_url_admin, vim_type
825 keys can be a dot separated list to specify elements inside dict
826 :return: dictionary with the information or raises ROClientException on Error
829 if isinstance(descriptor
, str):
830 descriptor
= self
._parse
(descriptor
, descriptor_format
)
836 if item
not in self
.client_to_RO
:
837 raise ROClientException("Invalid item {}".format(item
))
838 desc
= remove_envelop(item
, descriptor
)
840 # Override descriptor with kwargs
842 desc
= self
.update_descriptor(desc
, kwargs
)
844 for mandatory
in self
.mandatory_for_create
[item
]:
845 if mandatory
not in desc
:
846 raise ROClientException("'{}' is mandatory parameter for {}".format(mandatory
, item
))
849 if item
in ('tenant', 'vim', 'wim'):
852 create_desc
= self
._create
_envelop
(item
, desc
)
854 async with aiohttp
.ClientSession(loop
=self
.loop
) as session
:
855 outdata
= await self
._create
_item
(session
, self
.client_to_RO
[item
], create_desc
,
856 all_tenants
=all_tenants
)
857 return remove_envelop(item
, outdata
)
858 except (aiohttp
.ClientOSError
, aiohttp
.ClientError
) as e
:
859 raise ROClientException(e
, http_code
=504)
860 except asyncio
.TimeoutError
:
861 raise ROClientException("Timeout", http_code
=504)
863 async def create_action(self
, item
, item_id_name
, descriptor
=None, descriptor_format
=None, **kwargs
):
865 Performs an action over an item
866 :param item: can be 'tenant', 'vnfd', 'nsd', 'ns', 'vim', 'vim_account', 'sdn'
867 :param item_id_name: RO id or name of the item. Raise and exception if more than one found
868 :param descriptor: can be a dict, or a yaml/json text. Autodetect unless descriptor_format is provided
869 :param descriptor_format: Can be 'json' or 'yaml'
870 :param kwargs: Overrides descriptor with values as name, description, vim_url, vim_url_admin, vim_type
871 keys can be a dot separated list to specify elements inside dict
872 :return: dictionary with the information or raises ROClientException on Error
875 if isinstance(descriptor
, str):
876 descriptor
= self
._parse
(descriptor
, descriptor_format
)
882 if item
not in self
.client_to_RO
:
883 raise ROClientException("Invalid item {}".format(item
))
884 desc
= remove_envelop(item
, descriptor
)
886 # Override descriptor with kwargs
888 desc
= self
.update_descriptor(desc
, kwargs
)
891 if item
in ('tenant', 'vim'):
896 action
= "sdn_mapping"
897 elif item
in ("vim_account", "ns"):
900 # create_desc = self._create_envelop(item, desc)
903 async with aiohttp
.ClientSession(loop
=self
.loop
) as session
:
904 _all_tenants
= all_tenants
907 # item_id = await self._get_item_uuid(session, self.client_to_RO[item], item_id_name,
908 # all_tenants=_all_tenants)
909 outdata
= await self
._create
_item
(session
, self
.client_to_RO
[item
], create_desc
,
910 item_id_name
=item_id_name
, # item_id_name=item_id
911 action
=action
, all_tenants
=_all_tenants
)
912 return remove_envelop(item
, outdata
)
913 except (aiohttp
.ClientOSError
, aiohttp
.ClientError
) as e
:
914 raise ROClientException(e
, http_code
=504)
915 except asyncio
.TimeoutError
:
916 raise ROClientException("Timeout", http_code
=504)
918 async def attach(self
, item
, item_id_name
=None, descriptor
=None, descriptor_format
=None, **kwargs
):
920 Attach a datacenter or wim to a tenant, creating a vim_account, wim_account
921 :param item: can be vim_account or wim_account
922 :param item_id_name: id or name of the datacenter, wim
924 :param descriptor_format:
929 if isinstance(descriptor
, str):
930 descriptor
= self
._parse
(descriptor
, descriptor_format
)
936 desc
= remove_envelop(item
, descriptor
)
939 # uuid = self._get_item_uuid(session, "datacenters", uuid_name, all_tenants=True)
940 # tenant_text = "/" + self._get_tenant()
942 desc
= self
.update_descriptor(desc
, kwargs
)
944 if item
== "vim_account":
945 if not desc
.get("vim_tenant_name") and not desc
.get("vim_tenant_id"):
946 raise ROClientException("Wrong descriptor. At least vim_tenant_name or vim_tenant_id must be "
948 elif item
!= "wim_account":
949 raise ROClientException("Attach with unknown item {}. Must be 'vim_account' or 'wim_account'".
951 create_desc
= self
._create
_envelop
(item
, desc
)
952 payload_req
= yaml
.safe_dump(create_desc
)
953 async with aiohttp
.ClientSession(loop
=self
.loop
) as session
:
955 item_id
= await self
._get
_item
_uuid
(session
, self
.client_to_RO
[item
], item_id_name
, all_tenants
=True)
956 await self
._get
_tenant
(session
)
958 url
= "{}/{tenant}/{item}/{item_id}".format(self
.uri
, tenant
=self
.tenant
,
959 item
=self
.client_to_RO
[item
], item_id
=item_id
)
960 self
.logger
.debug("RO POST %s %s", url
, payload_req
)
961 # timeout = aiohttp.ClientTimeout(total=self.timeout_large)
962 async with session
.post(url
, headers
=self
.headers_req
, data
=payload_req
) as response
:
963 response_text
= await response
.read()
964 self
.logger
.debug("POST {} [{}] {}".format(url
, response
.status
, response_text
[:100]))
965 if response
.status
>= 300:
966 raise ROClientException(self
._parse
_error
_yaml
(response_text
), http_code
=response
.status
)
968 response_desc
= self
._parse
_yaml
(response_text
, response
=True)
969 desc
= remove_envelop(item
, response_desc
)
971 except (aiohttp
.ClientOSError
, aiohttp
.ClientError
) as e
:
972 raise ROClientException(e
, http_code
=504)
973 except asyncio
.TimeoutError
:
974 raise ROClientException("Timeout", http_code
=504)
976 async def detach(self
, item
, item_id_name
=None):
977 # TODO replace the code with delete_item(vim_account,...)
979 async with aiohttp
.ClientSession(loop
=self
.loop
) as session
:
981 item_id
= await self
._get
_item
_uuid
(session
, self
.client_to_RO
[item
], item_id_name
, all_tenants
=False)
982 tenant
= await self
._get
_tenant
(session
)
984 url
= "{}/{tenant}/{item}/{datacenter}".format(self
.uri
, tenant
=tenant
,
985 item
=self
.client_to_RO
[item
], datacenter
=item_id
)
986 self
.logger
.debug("RO DELETE %s", url
)
988 # timeout = aiohttp.ClientTimeout(total=self.timeout_large)
989 async with session
.delete(url
, headers
=self
.headers_req
) as response
:
990 response_text
= await response
.read()
991 self
.logger
.debug("DELETE {} [{}] {}".format(url
, response
.status
, response_text
[:100]))
992 if response
.status
>= 300:
993 raise ROClientException(self
._parse
_error
_yaml
(response_text
), http_code
=response
.status
)
995 response_desc
= self
._parse
_yaml
(response_text
, response
=True)
996 desc
= remove_envelop(item
, response_desc
)
998 except (aiohttp
.ClientOSError
, aiohttp
.ClientError
) as e
:
999 raise ROClientException(e
, http_code
=504)
1000 except asyncio
.TimeoutError
:
1001 raise ROClientException("Timeout", http_code
=504)
1003 # TODO convert to asyncio
1006 def edit_datacenter(self
, uuid
=None, name
=None, descriptor
=None, descriptor_format
=None, all_tenants
=False,
1008 """Edit the parameters of a datacenter
1009 Params: must supply a descriptor or/and a parameter to change
1010 uuid or/and name. If only name is supplied, there must be only one or an exception is raised
1011 descriptor: with format {'datacenter':{params to change info}}
1012 must be a dictionary or a json/yaml text.
1013 parameters to change can be supplyied by the descriptor or as parameters:
1014 new_name: the datacenter name
1015 vim_url: the datacenter URL
1016 vim_url_admin: the datacenter URL for administrative issues
1017 vim_type: the datacenter type, can be openstack or openvim.
1018 public: boolean, available to other tenants
1019 description: datacenter description
1020 Return: Raises an exception on error, not found or found several
1021 Obtain a dictionary with format {'datacenter':{new_datacenter_info}}
1024 if isinstance(descriptor
, str):
1025 descriptor
= self
.parse(descriptor
, descriptor_format
)
1029 descriptor
= {"datacenter": {}}
1031 raise ROClientException("Missing descriptor")
1033 if 'datacenter' not in descriptor
or len(descriptor
) != 1:
1034 raise ROClientException("Descriptor must contain only one 'datacenter' field")
1035 for param
in kwargs
:
1036 if param
== 'new_name':
1037 descriptor
['datacenter']['name'] = kwargs
[param
]
1039 descriptor
['datacenter'][param
] = kwargs
[param
]
1040 return self
._edit
_item
("datacenters", descriptor
, uuid
, name
, all_tenants
=None)
1042 def edit_scenario(self
, uuid
=None, name
=None, descriptor
=None, descriptor_format
=None, all_tenants
=False, **kwargs
):
1043 """Edit the parameters of a scenario
1044 Params: must supply a descriptor or/and a parameters to change
1045 uuid or/and name. If only name is supplied, there must be only one or an exception is raised
1046 descriptor: with format {'scenario':{params to change info}}
1047 must be a dictionary or a json/yaml text.
1048 parameters to change can be supplyied by the descriptor or as parameters:
1049 new_name: the scenario name
1050 public: boolean, available to other tenants
1051 description: scenario description
1052 tenant_id. Propietary tenant
1053 Return: Raises an exception on error, not found or found several
1054 Obtain a dictionary with format {'scenario':{new_scenario_info}}
1057 if isinstance(descriptor
, str):
1058 descriptor
= self
.parse(descriptor
, descriptor_format
)
1062 descriptor
= {"scenario": {}}
1064 raise ROClientException("Missing descriptor")
1066 if 'scenario' not in descriptor
or len(descriptor
) > 2:
1067 raise ROClientException("Descriptor must contain only one 'scenario' field")
1068 for param
in kwargs
:
1069 if param
== 'new_name':
1070 descriptor
['scenario']['name'] = kwargs
[param
]
1072 descriptor
['scenario'][param
] = kwargs
[param
]
1073 return self
._edit
_item
("scenarios", descriptor
, uuid
, name
, all_tenants
=None)
1076 def vim_action(self
, action
, item
, uuid
=None, all_tenants
=False, **kwargs
):
1077 """Perform an action over a vim
1079 action: can be 'list', 'get'/'show', 'delete' or 'create'
1080 item: can be 'tenants' or 'networks'
1081 uuid: uuid of the tenant/net to show or to delete. Ignore otherwise
1083 datacenter_name, datacenter_id: datacenters to act on, if missing uses classes store datacenter
1084 descriptor, descriptor_format: descriptor needed on creation, can be a dict or a yaml/json str
1085 must be a dictionary or a json/yaml text.
1086 name: for created tenant/net Overwrite descriptor name if any
1087 description: tenant descriptor. Overwrite descriptor description if any
1089 Return: Raises an exception on error
1090 Obtain a dictionary with format {'tenant':{new_tenant_info}}
1092 session
= None # TODO remove when changed to asyncio
1093 if item
not in ("tenants", "networks", "images"):
1094 raise ROClientException("Unknown value for item '{}', must be 'tenants', 'nets' or "
1095 "images".format(str(item
)))
1097 image_actions
= ['list', 'get', 'show', 'delete']
1098 if item
== "images" and action
not in image_actions
:
1099 raise ROClientException("Only available actions for item '{}' are {}\n"
1100 "Requested action was '{}'".format(item
, ', '.join(image_actions
), action
))
1102 tenant_text
= "/any"
1104 tenant_text
= "/" + self
._get
_tenant
()
1106 if "datacenter_id" in kwargs
or "datacenter_name" in kwargs
:
1107 datacenter
= self
._get
_item
_uuid
(session
, "datacenters", kwargs
.get("datacenter"), all_tenants
=all_tenants
)
1109 datacenter
= self
.get_datacenter(session
)
1111 if action
== "list":
1112 url
= "{}{}/vim/{}/{}".format(self
.uri
, tenant_text
, datacenter
, item
)
1113 self
.logger
.debug("GET %s", url
)
1114 mano_response
= requests
.get(url
, headers
=self
.headers_req
)
1115 self
.logger
.debug("RO response: %s", mano_response
.text
)
1116 content
= self
._parse
_yaml
(mano_response
.text
, response
=True)
1117 if mano_response
.status_code
== 200:
1120 raise ROClientException(str(content
), http_code
=mano_response
.status
)
1121 elif action
== "get" or action
== "show":
1122 url
= "{}{}/vim/{}/{}/{}".format(self
.uri
, tenant_text
, datacenter
, item
, uuid
)
1123 self
.logger
.debug("GET %s", url
)
1124 mano_response
= requests
.get(url
, headers
=self
.headers_req
)
1125 self
.logger
.debug("RO response: %s", mano_response
.text
)
1126 content
= self
._parse
_yaml
(mano_response
.text
, response
=True)
1127 if mano_response
.status_code
== 200:
1130 raise ROClientException(str(content
), http_code
=mano_response
.status
)
1131 elif action
== "delete":
1132 url
= "{}{}/vim/{}/{}/{}".format(self
.uri
, tenant_text
, datacenter
, item
, uuid
)
1133 self
.logger
.debug("DELETE %s", url
)
1134 mano_response
= requests
.delete(url
, headers
=self
.headers_req
)
1135 self
.logger
.debug("RO response: %s", mano_response
.text
)
1136 content
= self
._parse
_yaml
(mano_response
.text
, response
=True)
1137 if mano_response
.status_code
== 200:
1140 raise ROClientException(str(content
), http_code
=mano_response
.status
)
1141 elif action
== "create":
1142 if "descriptor" in kwargs
:
1143 if isinstance(kwargs
["descriptor"], str):
1144 descriptor
= self
._parse
(kwargs
["descriptor"], kwargs
.get("descriptor_format"))
1146 descriptor
= kwargs
["descriptor"]
1147 elif "name" in kwargs
:
1148 descriptor
= {item
[:-1]: {"name": kwargs
["name"]}}
1150 raise ROClientException("Missing descriptor")
1152 if item
[:-1] not in descriptor
or len(descriptor
) != 1:
1153 raise ROClientException("Descriptor must contain only one 'tenant' field")
1154 if "name" in kwargs
:
1155 descriptor
[item
[:-1]]['name'] = kwargs
["name"]
1156 if "description" in kwargs
:
1157 descriptor
[item
[:-1]]['description'] = kwargs
["description"]
1158 payload_req
= yaml
.safe_dump(descriptor
)
1160 url
= "{}{}/vim/{}/{}".format(self
.uri
, tenant_text
, datacenter
, item
)
1161 self
.logger
.debug("RO POST %s %s", url
, payload_req
)
1162 mano_response
= requests
.post(url
, headers
=self
.headers_req
, data
=payload_req
)
1163 self
.logger
.debug("RO response: %s", mano_response
.text
)
1164 content
= self
._parse
_yaml
(mano_response
.text
, response
=True)
1165 if mano_response
.status_code
== 200:
1168 raise ROClientException(str(content
), http_code
=mano_response
.status
)
1170 raise ROClientException("Unknown value for action '{}".format(str(action
)))
1173 if __name__
== '__main__':
1174 RO_URL
= "http://localhost:9090/openmano"
1175 TEST_TENANT
= "myTenant"
1177 TEST_URL1
= "https://localhost:5000/v1"
1178 TEST_TYPE1
= "openstack"
1179 TEST_CONFIG1
= {"use_floating_ip": True}
1180 TEST_VIM2
= "myvim2"
1181 TEST_URL2
= "https://localhost:5000/v2"
1182 TEST_TYPE2
= "openvim"
1183 TEST_CONFIG2
= {"config2": "config2", "config3": True}
1185 streamformat
= "%(asctime)s %(name)s %(levelname)s: %(message)s"
1186 logging
.basicConfig(format
=streamformat
)
1187 logger
= logging
.getLogger("ROClient")
1191 loop
= asyncio
.get_event_loop()
1192 myClient
= ROClient(uri
=RO_URL
, loop
=loop
, loglevel
="DEBUG")
1195 content
= loop
.run_until_complete(myClient
.get_list("tenant"))
1196 print("tenants", content
)
1197 content
= loop
.run_until_complete(myClient
.create("tenant", name
=TEST_TENANT
))
1199 content
= loop
.run_until_complete(myClient
.show("tenant", TEST_TENANT
))
1200 print("tenant", TEST_TENANT
, content
)
1201 content
= loop
.run_until_complete(myClient
.edit("tenant", TEST_TENANT
, description
="another description"))
1202 content
= loop
.run_until_complete(myClient
.show("tenant", TEST_TENANT
))
1203 print("tenant edited", TEST_TENANT
, content
)
1204 myClient
["tenant"] = TEST_TENANT
1207 content
= loop
.run_until_complete(myClient
.create("vim", name
=TEST_VIM1
, type=TEST_TYPE1
, vim_url
=TEST_URL1
,
1208 config
=TEST_CONFIG1
))
1210 content
= loop
.run_until_complete(myClient
.get_list("vim"))
1211 print("vim", content
)
1212 content
= loop
.run_until_complete(myClient
.show("vim", TEST_VIM1
))
1213 print("vim", TEST_VIM1
, content
)
1214 content
= loop
.run_until_complete(myClient
.edit("vim", TEST_VIM1
, description
="another description",
1215 name
=TEST_VIM2
, type=TEST_TYPE2
, vim_url
=TEST_URL2
,
1216 config
=TEST_CONFIG2
))
1217 content
= loop
.run_until_complete(myClient
.show("vim", TEST_VIM2
))
1218 print("vim edited", TEST_VIM2
, content
)
1221 content
= loop
.run_until_complete(myClient
.attach_datacenter(TEST_VIM2
, vim_username
='user',
1222 vim_password
='pass', vim_tenant_name
='vimtenant1',
1223 config
=TEST_CONFIG1
))
1225 content
= loop
.run_until_complete(myClient
.get_list("vim_account"))
1226 print("vim_account", content
)
1227 content
= loop
.run_until_complete(myClient
.show("vim_account", TEST_VIM2
))
1228 print("vim_account", TEST_VIM2
, content
)
1229 content
= loop
.run_until_complete(myClient
.edit("vim_account", TEST_VIM2
, vim_username
='user2',
1230 vim_password
='pass2', vim_tenant_name
="vimtenant2",
1231 config
=TEST_CONFIG2
))
1232 content
= loop
.run_until_complete(myClient
.show("vim_account", TEST_VIM2
))
1233 print("vim_account edited", TEST_VIM2
, content
)
1235 myClient
["vim"] = TEST_VIM2
1237 except Exception as e
:
1238 logger
.error("Error {}".format(e
), exc_info
=True)
1240 for item
in (("vim_account", TEST_VIM1
), ("vim", TEST_VIM1
),
1241 ("vim_account", TEST_VIM2
), ("vim", TEST_VIM2
),
1242 ("tenant", TEST_TENANT
)):
1244 content
= loop
.run_until_complete(myClient
.delete(item
[0], item
[1]))
1245 print("{} {} deleted; {}".format(item
[0], item
[1], content
))
1246 except Exception as e
:
1247 if e
.http_code
== 404:
1248 print("{} {} not present or already deleted".format(item
[0], item
[1]))
1250 logger
.error("Error {}".format(e
), exc_info
=True)