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"):
66 not isinstance(clean_indata
["vnfd"], list)
67 or len(clean_indata
["vnfd"]) != 1
69 raise ROClientException("'vnfd' must be a list only one element")
70 clean_indata
= clean_indata
["vnfd"][0]
72 if clean_indata
.get("nsd:nsd-catalog"):
73 clean_indata
= clean_indata
["nsd:nsd-catalog"]
74 elif clean_indata
.get("nsd-catalog"):
75 clean_indata
= clean_indata
["nsd-catalog"]
76 if clean_indata
.get("nsd"):
78 not isinstance(clean_indata
["nsd"], list)
79 or len(clean_indata
["nsd"]) != 1
81 raise ROClientException("'nsd' must be a list only one element")
82 clean_indata
= clean_indata
["nsd"][0]
84 if len(indata
) == 1 and "sdn_controller" in indata
:
85 clean_indata
= indata
["sdn_controller"]
86 elif item
== "tenant":
87 if len(indata
) == 1 and "tenant" in indata
:
88 clean_indata
= indata
["tenant"]
89 elif item
in ("vim", "vim_account", "datacenters"):
90 if len(indata
) == 1 and "datacenter" in indata
:
91 clean_indata
= indata
["datacenter"]
93 if len(indata
) == 1 and "wim" in indata
:
94 clean_indata
= indata
["wim"]
95 elif item
== "wim_account":
96 if len(indata
) == 1 and "wim_account" in indata
:
97 clean_indata
= indata
["wim_account"]
98 elif item
== "ns" or item
== "instances":
99 if len(indata
) == 1 and "instance" in indata
:
100 clean_indata
= indata
["instance"]
102 assert False, "remove_envelop with unknown item {}".format(item
)
108 headers_req
= {"Accept": "application/yaml", "content-type": "application/yaml"}
111 "vim": "datacenters",
112 "vim_account": "datacenters",
113 "sdn": "sdn_controllers",
117 "wim_account": "wims",
120 mandatory_for_create
= {
122 "vnfd": ("name", "id"),
123 "nsd": ("name", "id"),
124 "ns": ("name", "scenario", "datacenter"),
125 "vim": ("name", "vim_url"),
126 "wim": ("name", "wim_url"),
129 "sdn": ("name", "type"),
134 def __init__(self
, loop
, uri
, **kwargs
):
138 self
.username
= kwargs
.get("username")
139 self
.password
= kwargs
.get("password")
140 self
.tenant_id_name
= kwargs
.get("tenant")
142 self
.datacenter_id_name
= kwargs
.get("datacenter")
143 self
.datacenter
= None
144 logger_name
= kwargs
.get("logger_name", "lcm.ro")
145 self
.logger
= logging
.getLogger(logger_name
)
146 if kwargs
.get("loglevel"):
147 self
.logger
.setLevel(kwargs
["loglevel"])
149 requests
= kwargs
.get("TODO remove")
151 def __getitem__(self
, index
):
152 if index
== "tenant":
153 return self
.tenant_id_name
154 elif index
== "datacenter":
155 return self
.datacenter_id_name
156 elif index
== "username":
158 elif index
== "password":
163 raise KeyError("Invalid key '{}'".format(index
))
165 def __setitem__(self
, index
, value
):
166 if index
== "tenant":
167 self
.tenant_id_name
= value
168 elif index
== "datacenter" or index
== "vim":
169 self
.datacenter_id_name
= value
170 elif index
== "username":
171 self
.username
= value
172 elif index
== "password":
173 self
.password
= value
177 raise KeyError("Invalid key '{}'".format(index
))
178 self
.tenant
= None # force to reload tenant with different credentials
179 self
.datacenter
= None # force to reload datacenter with different credentials
182 def _parse(descriptor
, descriptor_format
, response
=False):
185 and descriptor_format
!= "json"
186 and descriptor_format
!= "yaml"
188 raise ROClientException(
189 "'descriptor_format' must be a 'json' or 'yaml' text"
191 if descriptor_format
!= "json":
193 return yaml
.load(descriptor
)
194 except yaml
.YAMLError
as exc
:
196 if hasattr(exc
, "problem_mark"):
197 mark
= exc
.problem_mark
198 error_pos
= " at line:{} column:{}s".format(
199 mark
.line
+ 1, mark
.column
+ 1
201 error_text
= "yaml format error" + error_pos
202 elif descriptor_format
!= "yaml":
204 return json
.loads(descriptor
)
205 except Exception as e
:
207 error_text
= "json format error" + str(e
)
210 raise ROClientException(error_text
)
211 raise ROClientException(error_text
)
214 def _parse_error_yaml(descriptor
):
217 json_error
= yaml
.load(descriptor
, Loader
=yaml
.Loader
)
218 return json_error
["error"]["description"]
220 return str(json_error
or descriptor
)
223 def _parse_yaml(descriptor
, response
=False):
225 return yaml
.load(descriptor
, Loader
=yaml
.Loader
)
226 except yaml
.YAMLError
as exc
:
228 if hasattr(exc
, "problem_mark"):
229 mark
= exc
.problem_mark
230 error_pos
= " at line:{} column:{}s".format(
231 mark
.line
+ 1, mark
.column
+ 1
233 error_text
= "yaml format error" + error_pos
235 raise ROClientException(error_text
)
236 raise ROClientException(error_text
)
239 def check_if_uuid(uuid_text
):
241 Check if text correspond to an uuid foramt
243 :return: True if it is an uuid False if not
252 def _create_envelop(item
, indata
=None):
254 Returns a new dict that incledes indata with the expected envelop
255 :param item: can be 'tenant', 'vim', 'vnfd', 'nsd', 'ns'
256 :param indata: Content to be enveloped
257 :return: a new dic with {<envelop>: {indata} } where envelop can be e.g. tenant, datacenter, ...
260 return {"vnfd-catalog": {"vnfd": [indata
]}}
262 return {"nsd-catalog": {"nsd": [indata
]}}
263 elif item
== "tenant":
264 return {"tenant": indata
}
265 elif item
in ("vim", "vim_account", "datacenter"):
266 return {"datacenter": indata
}
268 return {"wim": indata
}
269 elif item
== "wim_account":
270 return {"wim_account": indata
}
271 elif item
== "ns" or item
== "instances":
272 return {"instance": indata
}
274 return {"sdn_controller": indata
}
276 assert False, "_create_envelop with unknown item {}".format(item
)
279 def update_descriptor(desc
, kwargs
):
280 desc
= deepcopy(desc
) # do not modify original descriptor
282 for k
, v
in kwargs
.items():
283 update_content
= desc
287 if kitem_old
is not None:
288 update_content
= update_content
[kitem_old
]
289 if isinstance(update_content
, dict):
291 elif isinstance(update_content
, list):
292 kitem_old
= int(kitem
)
294 raise ROClientException(
295 "Invalid query string '{}'. Descriptor is not a list nor dict at '{}'".format(
299 if v
== "__DELETE__":
300 del update_content
[kitem_old
]
302 update_content
[kitem_old
] = v
305 raise ROClientException(
306 "Invalid query string '{}'. Descriptor does not contain '{}'".format(
311 raise ROClientException(
312 "Invalid query string '{}'. Expected integer index list instead of '{}'".format(
317 raise ROClientException(
318 "Invalid query string '{}'. Index '{}' out of range".format(
324 def check_ns_status(ns_descriptor
):
326 Inspect RO instance descriptor and indicates the status
327 :param ns_descriptor: instance descriptor obtained with self.show("ns", )
328 :return: status, message: status can be BUILD,ACTIVE,ERROR, message is a text message
331 total
= {"VMs": 0, "networks": 0, "SDN_networks": 0}
332 done
= {"VMs": 0, "networks": 0, "SDN_networks": 0}
335 # return an identification for the network or vm. Try vim_id if exist, if not descriptor id for net
336 if desc
.get("vim_net_id"):
337 return "'vim-net-id={}'".format(desc
["vim_net_id"])
338 elif desc
.get("ns_net_osm_id"):
339 return "'nsd-vld-id={}'".format(desc
["ns_net_osm_id"])
340 elif desc
.get("vnf_net_osm_id"):
341 return "'vnfd-vld-id={}'".format(desc
["vnf_net_osm_id"])
343 elif desc
.get("vim_vm_id"):
344 return "'vim-vm-id={}'".format(desc
["vim_vm_id"])
345 elif desc
.get("vdu_osm_id"):
346 return "'vnfd-vdu-id={}'".format(desc
["vdu_osm_id"])
350 def _get_sdn_ref(sce_net_id
):
351 # look for the network associated to the SDN network and obtain the identification
353 (x
for x
in ns_descriptor
["nets"] if x
.get("sce_net_id") == sce_net_id
),
356 if not sce_net_id
or not net
:
361 total
["networks"] = len(ns_descriptor
["nets"])
362 for net
in ns_descriptor
["nets"]:
363 if net
["status"] in ("ERROR", "VIM_ERROR"):
365 "Error at VIM network {}: {}".format(
366 _get_ref(net
), net
["error_msg"]
369 elif net
["status"] == "ACTIVE":
370 done
["networks"] += 1
372 total
["SDN_networks"] = len(ns_descriptor
["sdn_nets"])
373 for sdn_net
in ns_descriptor
["sdn_nets"]:
374 if sdn_net
["status"] in ("ERROR", "VIM_ERROR", "WIM_ERROR"):
376 "Error at SDN network {}: {}".format(
377 _get_sdn_ref(sdn_net
.get("sce_net_id")),
378 sdn_net
["error_msg"],
381 elif sdn_net
["status"] == "ACTIVE":
382 done
["SDN_networks"] += 1
384 for vnf
in ns_descriptor
["vnfs"]:
385 for vm
in vnf
["vms"]:
387 if vm
["status"] in ("ERROR", "VIM_ERROR"):
389 "Error at VIM VM {}: {}".format(
390 _get_ref(vm
), vm
["error_msg"]
393 elif vm
["status"] == "ACTIVE":
396 # skip errors caused because other dependendent task is on error
397 return "ERROR", "; ".join(
401 if "because depends on failed ACTION" not in el
404 if all(total
[x
] == done
[x
] for x
in total
): # DONE == TOTAL for all items
405 return "ACTIVE", str(
406 {x
: total
[x
] for x
in total
if total
[x
]}
407 ) # print only those which value is not 0
410 {x
: "{}/{}".format(done
[x
], total
[x
]) for x
in total
if total
[x
]}
412 # print done/total for each item if total is not 0
413 except Exception as e
:
414 raise ROClientException(
415 "Unexpected RO ns descriptor. Wrong version? {}".format(e
)
419 def check_action_status(action_descriptor
):
421 Inspect RO instance descriptor and indicates the status
422 :param action_descriptor: action instance descriptor obtained with self.show("ns", "action")
423 :return: status, message: status can be BUILD,ACTIVE,ERROR, message is a text message
432 for vim_action_set
in action_descriptor
["actions"]:
433 for vim_action
in vim_action_set
["vim_wim_actions"]:
434 if vim_action
["item"] == "instance_vms":
436 elif vim_action
["item"] == "instance_nets":
440 if vim_action
["status"] == "FAILED":
441 return "ERROR", vim_action
["error_msg"]
442 elif vim_action
["status"] in ("DONE", "SUPERSEDED", "FINISHED"):
443 if vim_action
["item"] == "instance_vms":
445 elif vim_action
["item"] == "instance_nets":
450 if net_total
== net_done
and vm_total
== vm_done
and other_total
== other_done
:
451 return "ACTIVE", "VMs {}, networks: {}, other: {} ".format(
452 vm_total
, net_total
, other_total
455 return "BUILD", "VMs: {}/{}, networks: {}/{}, other: {}/{}".format(
456 vm_done
, vm_total
, net_done
, net_total
, other_done
, other_total
460 def get_ns_vnf_info(ns_descriptor
):
462 Get a dict with the VIM_id, ip_addresses, mac_addresses of every vnf and vdu
463 :param ns_descriptor: instance descriptor obtained with self.show("ns", )
477 for vnf
in ns_descriptor
["vnfs"]:
478 if not vnf
.get("ip_address") and vnf
.get("vms"):
479 raise ROClientException(
480 "ns member_vnf_index '{}' has no IP address".format(
481 vnf
["member_vnf_index"]
485 vnfr_info
= {"ip_address": vnf
.get("ip_address"), "vdur": {}}
486 for vm
in vnf
["vms"]:
488 "vim_id": vm
.get("vim_vm_id"),
489 "ip_address": vm
.get("ip_address"),
492 for iface
in vm
["interfaces"]:
493 if iface
.get("type") == "mgmt" and not iface
.get("ip_address"):
494 raise ROClientException(
495 "ns member_vnf_index '{}' vm '{}' management interface '{}' has no IP "
497 vnf
["member_vnf_index"],
499 iface
["external_name"],
503 vdur
["interfaces"][iface
["internal_name"]] = {
504 "ip_address": iface
.get("ip_address"),
505 "mac_address": iface
.get("mac_address"),
506 "vim_id": iface
.get("vim_interface_id"),
508 vnfr_info
["vdur"][vm
["vdu_osm_id"]] = vdur
509 ns_info
[str(vnf
["member_vnf_index"])] = vnfr_info
512 async def _get_item_uuid(self
, session
, item
, item_id_name
, all_tenants
=False):
515 elif all_tenants
is None:
519 await self
._get
_tenant
(session
)
520 tenant_text
= "/" + self
.tenant
523 url
= "{}{}/{}".format(self
.uri
, tenant_text
, item
)
524 if self
.check_if_uuid(item_id_name
):
525 item_id
= item_id_name
526 url
+= "/" + item_id_name
528 item_id_name
and item_id_name
.startswith("'") and item_id_name
.endswith("'")
530 item_id_name
= item_id_name
[1:-1]
531 self
.logger
.debug("RO GET %s", url
)
532 # timeout = aiohttp.ClientTimeout(total=self.timeout_short)
533 async with session
.get(url
, headers
=self
.headers_req
) as response
:
534 response_text
= await response
.read()
536 "GET {} [{}] {}".format(url
, response
.status
, response_text
[:100])
538 if response
.status
== 404: # NOT_FOUND
539 raise ROClientException(
540 "No {} found with id '{}'".format(item
[:-1], item_id_name
),
543 if response
.status
>= 300:
544 raise ROClientException(
545 self
._parse
_error
_yaml
(response_text
), http_code
=response
.status
547 content
= self
._parse
_yaml
(response_text
, response
=True)
554 ), "_get_item_uuid get a non dict with a list inside {}".format(type(desc
))
557 if item_id_name
and i
["name"] != item_id_name
:
559 if uuid
: # found more than one
560 raise ROClientException(
561 "Found more than one {} with name '{}'. uuid must be used".format(
568 raise ROClientException(
569 "No {} found with name '{}'".format(item
[:-1], item_id_name
),
585 elif all_tenants
is None:
589 await self
._get
_tenant
(session
)
590 tenant_text
= "/" + self
.tenant
592 if self
.check_if_uuid(item_id_name
):
596 uuid
= await self
._get
_item
_uuid
(session
, item
, item_id_name
, all_tenants
)
598 url
= "{}{}/{}/{}".format(self
.uri
, tenant_text
, item
, uuid
)
600 url
+= "/" + extra_item
602 url
+= "/" + extra_item_id
603 self
.logger
.debug("GET %s", url
)
604 # timeout = aiohttp.ClientTimeout(total=self.timeout_short)
605 async with session
.get(url
, headers
=self
.headers_req
) as response
:
606 response_text
= await response
.read()
608 "GET {} [{}] {}".format(url
, response
.status
, response_text
[:100])
610 if response
.status
>= 300:
611 raise ROClientException(
612 self
._parse
_error
_yaml
(response_text
), http_code
=response
.status
615 return self
._parse
_yaml
(response_text
, response
=True)
617 async def _get_tenant(self
, session
):
619 self
.tenant
= await self
._get
_item
_uuid
(
620 session
, "tenants", self
.tenant_id_name
, None
624 async def _get_datacenter(self
, session
):
626 await self
._get
_tenant
(session
)
627 if not self
.datacenter
:
628 self
.datacenter
= await self
._get
_item
_uuid
(
629 session
, "datacenters", self
.datacenter_id_name
, True
631 return self
.datacenter
633 async def _create_item(
644 elif all_tenants
is None:
648 await self
._get
_tenant
(session
)
649 tenant_text
= "/" + self
.tenant
650 payload_req
= yaml
.safe_dump(descriptor
)
653 api_version_text
= ""
655 # assumes version v3 only
656 api_version_text
= "/v3"
658 elif item
== "scenarios":
659 # assumes version v3 only
660 api_version_text
= "/v3"
665 elif self
.check_if_uuid(item_id_name
):
666 uuid
= "/{}".format(item_id_name
)
669 uuid
= await self
._get
_item
_uuid
(session
, item
, item_id_name
, all_tenants
)
670 uuid
= "/{}".format(uuid
)
674 action
= "/{}".format(action
)
676 url
= "{}{apiver}{tenant}/{item}{id}{action}".format(
678 apiver
=api_version_text
,
684 self
.logger
.debug("RO POST %s %s", url
, payload_req
)
685 # timeout = aiohttp.ClientTimeout(total=self.timeout_large)
686 async with session
.post(
687 url
, headers
=self
.headers_req
, data
=payload_req
689 response_text
= await response
.read()
691 "POST {} [{}] {}".format(url
, response
.status
, response_text
[:100])
693 if response
.status
>= 300:
694 raise ROClientException(
695 self
._parse
_error
_yaml
(response_text
), http_code
=response
.status
698 return self
._parse
_yaml
(response_text
, response
=True)
700 async def _del_item(self
, session
, item
, item_id_name
, all_tenants
=False):
703 elif all_tenants
is None:
707 await self
._get
_tenant
(session
)
708 tenant_text
= "/" + self
.tenant
709 if not self
.check_if_uuid(item_id_name
):
711 _all_tenants
= all_tenants
712 if item
in ("datacenters", "wims"):
714 uuid
= await self
._get
_item
_uuid
(
715 session
, item
, item_id_name
, all_tenants
=_all_tenants
720 url
= "{}{}/{}/{}".format(self
.uri
, tenant_text
, item
, uuid
)
721 self
.logger
.debug("DELETE %s", url
)
722 # timeout = aiohttp.ClientTimeout(total=self.timeout_short)
723 async with session
.delete(url
, headers
=self
.headers_req
) as response
:
724 response_text
= await response
.read()
726 "DELETE {} [{}] {}".format(url
, response
.status
, response_text
[:100])
728 if response
.status
>= 300:
729 raise ROClientException(
730 self
._parse
_error
_yaml
(response_text
), http_code
=response
.status
733 return self
._parse
_yaml
(response_text
, response
=True)
735 async def _list_item(self
, session
, item
, all_tenants
=False, filter_dict
=None):
738 elif all_tenants
is None:
742 await self
._get
_tenant
(session
)
743 tenant_text
= "/" + self
.tenant
745 url
= "{}{}/{}".format(self
.uri
, tenant_text
, item
)
748 for k
in filter_dict
:
749 url
+= separator
+ quote(str(k
)) + "=" + quote(str(filter_dict
[k
]))
751 self
.logger
.debug("RO GET %s", url
)
752 # timeout = aiohttp.ClientTimeout(total=self.timeout_short)
753 async with session
.get(url
, headers
=self
.headers_req
) as response
:
754 response_text
= await response
.read()
756 "GET {} [{}] {}".format(url
, response
.status
, response_text
[:100])
758 if response
.status
>= 300:
759 raise ROClientException(
760 self
._parse
_error
_yaml
(response_text
), http_code
=response
.status
763 return self
._parse
_yaml
(response_text
, response
=True)
765 async def _edit_item(self
, session
, item
, item_id
, descriptor
, all_tenants
=False):
768 elif all_tenants
is None:
772 await self
._get
_tenant
(session
)
773 tenant_text
= "/" + self
.tenant
775 payload_req
= yaml
.safe_dump(descriptor
)
778 url
= "{}{}/{}/{}".format(self
.uri
, tenant_text
, item
, item_id
)
779 self
.logger
.debug("RO PUT %s %s", url
, payload_req
)
780 # timeout = aiohttp.ClientTimeout(total=self.timeout_large)
781 async with session
.put(
782 url
, headers
=self
.headers_req
, data
=payload_req
784 response_text
= await response
.read()
786 "PUT {} [{}] {}".format(url
, response
.status
, response_text
[:100])
788 if response
.status
>= 300:
789 raise ROClientException(
790 self
._parse
_error
_yaml
(response_text
), http_code
=response
.status
793 return self
._parse
_yaml
(response_text
, response
=True)
795 async def get_version(self
):
797 Obtain RO server version.
798 :return: a list with integers ["major", "minor", "release"]. Raises ROClientException on Error,
802 async with aiohttp
.ClientSession() as session
:
803 url
= "{}/version".format(self
.uri
)
804 self
.logger
.debug("RO GET %s", url
)
805 # timeout = aiohttp.ClientTimeout(total=self.timeout_short)
806 async with session
.get(url
, headers
=self
.headers_req
) as response
:
807 response_text
= await response
.read()
809 "GET {} [{}] {}".format(
810 url
, response
.status
, response_text
[:100]
813 if response
.status
>= 300:
814 raise ROClientException(
815 self
._parse
_error
_yaml
(response_text
),
816 http_code
=response
.status
,
819 for word
in str(response_text
).split(" "):
821 version_text
, _
, _
= word
.partition("-")
823 raise ROClientException(
824 "Got invalid version text: '{}'".format(response_text
),
827 except (aiohttp
.ClientOSError
, aiohttp
.ClientError
) as e
:
828 raise ROClientException(e
, http_code
=504)
829 except asyncio
.TimeoutError
:
830 raise ROClientException("Timeout", http_code
=504)
831 except Exception as e
:
832 raise ROClientException(
833 "Got invalid version text: '{}'; causing exception {}".format(
839 async def get_list(self
, item
, all_tenants
=False, filter_by
=None):
841 List of items filtered by the contents in the dictionary "filter_by".
842 :param item: can be 'tenant', 'vim', 'vnfd', 'nsd', 'ns'
843 :param all_tenants: True if not filtering by tenant. Only allowed for admin
844 :param filter_by: dictionary with filtering
845 :return: a list of dict. It can be empty. Raises ROClientException on Error,
848 if item
not in self
.client_to_RO
:
849 raise ROClientException("Invalid item {}".format(item
))
852 async with aiohttp
.ClientSession(loop
=self
.loop
) as session
:
853 content
= await self
._list
_item
(
855 self
.client_to_RO
[item
],
856 all_tenants
=all_tenants
,
857 filter_dict
=filter_by
,
859 if isinstance(content
, dict):
860 if len(content
) == 1:
861 for _
, v
in content
.items():
863 return content
.values()[0]
865 raise ROClientException(
866 "Output not a list neither dict with len equal 1", http_code
=500
869 except (aiohttp
.ClientOSError
, aiohttp
.ClientError
) as e
:
870 raise ROClientException(e
, http_code
=504)
871 except asyncio
.TimeoutError
:
872 raise ROClientException("Timeout", http_code
=504)
883 Obtain the information of an item from its id or name
884 :param item: can be 'tenant', 'vim', 'vnfd', 'nsd', 'ns'
885 :param item_id_name: RO id or name of the item. Raise and exception if more than one found
886 :param extra_item: if supplied, it is used to add to the URL.
887 Can be 'action' if item='ns'; 'networks' or'images' if item='vim'
888 :param extra_item_id: if supplied, it is used get details of a concrete extra_item.
889 :param all_tenants: True if not filtering by tenant. Only allowed for admin
890 :return: dictionary with the information or raises ROClientException on Error, NotFound, found several
893 if item
not in self
.client_to_RO
:
894 raise ROClientException("Invalid item {}".format(item
))
899 elif item
== "vim_account":
902 async with aiohttp
.ClientSession(loop
=self
.loop
) as session
:
903 content
= await self
._get
_item
(
905 self
.client_to_RO
[item
],
907 extra_item
=extra_item
,
908 extra_item_id
=extra_item_id
,
909 all_tenants
=all_tenants
,
911 return remove_envelop(item
, content
)
912 except (aiohttp
.ClientOSError
, aiohttp
.ClientError
) as e
:
913 raise ROClientException(e
, http_code
=504)
914 except asyncio
.TimeoutError
:
915 raise ROClientException("Timeout", http_code
=504)
917 async def delete(self
, item
, item_id_name
=None, all_tenants
=False):
919 Delete the information of an item from its id or name
920 :param item: can be 'tenant', 'vim', 'vnfd', 'nsd', 'ns'
921 :param item_id_name: RO id or name of the item. Raise and exception if more than one found
922 :param all_tenants: True if not filtering by tenant. Only allowed for admin
923 :return: dictionary with the information or raises ROClientException on Error, NotFound, found several
926 if item
not in self
.client_to_RO
:
927 raise ROClientException("Invalid item {}".format(item
))
928 if item
in ("tenant", "vim", "wim"):
931 async with aiohttp
.ClientSession(loop
=self
.loop
) as session
:
932 result
= await self
._del
_item
(
934 self
.client_to_RO
[item
],
936 all_tenants
=all_tenants
,
938 # in case of ns delete, get the action_id embeded in text
939 if item
== "ns" and result
.get("result"):
940 _
, _
, action_id
= result
["result"].partition("action_id=")
941 action_id
, _
, _
= action_id
.partition(" ")
943 result
["action_id"] = action_id
945 except (aiohttp
.ClientOSError
, aiohttp
.ClientError
) as e
:
946 raise ROClientException(e
, http_code
=504)
947 except asyncio
.TimeoutError
:
948 raise ROClientException("Timeout", http_code
=504)
951 self
, item
, item_id_name
, descriptor
=None, descriptor_format
=None, **kwargs
954 :param item: can be 'tenant', 'vim', 'vnfd', 'nsd', 'ns', 'vim'
955 :param item_id_name: RO id or name of the item. Raise and exception if more than one found
956 :param descriptor: can be a dict, or a yaml/json text. Autodetect unless descriptor_format is provided
957 :param descriptor_format: Can be 'json' or 'yaml'
958 :param kwargs: Overrides descriptor with values as name, description, vim_url, vim_url_admin, vim_type
959 keys can be a dot separated list to specify elements inside dict
960 :return: dictionary with the information or raises ROClientException on Error
963 if isinstance(descriptor
, str):
964 descriptor
= self
._parse
(descriptor
, descriptor_format
)
970 if item
not in self
.client_to_RO
:
971 raise ROClientException("Invalid item {}".format(item
))
972 desc
= remove_envelop(item
, descriptor
)
974 # Override descriptor with kwargs
976 desc
= self
.update_descriptor(desc
, kwargs
)
978 if item
in ("tenant", "vim"):
981 create_desc
= self
._create
_envelop
(item
, desc
)
983 async with aiohttp
.ClientSession(loop
=self
.loop
) as session
:
984 _all_tenants
= all_tenants
987 item_id
= await self
._get
_item
_uuid
(
989 self
.client_to_RO
[item
],
991 all_tenants
=_all_tenants
,
995 # await self._get_tenant(session)
996 outdata
= await self
._edit
_item
(
998 self
.client_to_RO
[item
],
1001 all_tenants
=_all_tenants
,
1003 return remove_envelop(item
, outdata
)
1004 except (aiohttp
.ClientOSError
, aiohttp
.ClientError
) as e
:
1005 raise ROClientException(e
, http_code
=504)
1006 except asyncio
.TimeoutError
:
1007 raise ROClientException("Timeout", http_code
=504)
1009 async def create(self
, item
, descriptor
=None, descriptor_format
=None, **kwargs
):
1011 Creates an item from its descriptor
1012 :param item: can be 'tenant', 'vnfd', 'nsd', 'ns', 'vim', 'vim_account', 'sdn'
1013 :param descriptor: can be a dict, or a yaml/json text. Autodetect unless descriptor_format is provided
1014 :param descriptor_format: Can be 'json' or 'yaml'
1015 :param kwargs: Overrides descriptor with values as name, description, vim_url, vim_url_admin, vim_type
1016 keys can be a dot separated list to specify elements inside dict
1017 :return: dictionary with the information or raises ROClientException on Error
1020 if isinstance(descriptor
, str):
1021 descriptor
= self
._parse
(descriptor
, descriptor_format
)
1027 if item
not in self
.client_to_RO
:
1028 raise ROClientException("Invalid item {}".format(item
))
1029 desc
= remove_envelop(item
, descriptor
)
1031 # Override descriptor with kwargs
1033 desc
= self
.update_descriptor(desc
, kwargs
)
1035 for mandatory
in self
.mandatory_for_create
[item
]:
1036 if mandatory
not in desc
:
1037 raise ROClientException(
1038 "'{}' is mandatory parameter for {}".format(mandatory
, item
)
1042 if item
in ("tenant", "vim", "wim"):
1045 create_desc
= self
._create
_envelop
(item
, desc
)
1047 async with aiohttp
.ClientSession(loop
=self
.loop
) as session
:
1048 outdata
= await self
._create
_item
(
1050 self
.client_to_RO
[item
],
1052 all_tenants
=all_tenants
,
1054 return remove_envelop(item
, outdata
)
1055 except (aiohttp
.ClientOSError
, aiohttp
.ClientError
) as e
:
1056 raise ROClientException(e
, http_code
=504)
1057 except asyncio
.TimeoutError
:
1058 raise ROClientException("Timeout", http_code
=504)
1060 async def create_action(
1061 self
, item
, item_id_name
, descriptor
=None, descriptor_format
=None, **kwargs
1064 Performs an action over an item
1065 :param item: can be 'tenant', 'vnfd', 'nsd', 'ns', 'vim', 'vim_account', 'sdn'
1066 :param item_id_name: RO id or name of the item. Raise and exception if more than one found
1067 :param descriptor: can be a dict, or a yaml/json text. Autodetect unless descriptor_format is provided
1068 :param descriptor_format: Can be 'json' or 'yaml'
1069 :param kwargs: Overrides descriptor with values as name, description, vim_url, vim_url_admin, vim_type
1070 keys can be a dot separated list to specify elements inside dict
1071 :return: dictionary with the information or raises ROClientException on Error
1074 if isinstance(descriptor
, str):
1075 descriptor
= self
._parse
(descriptor
, descriptor_format
)
1081 if item
not in self
.client_to_RO
:
1082 raise ROClientException("Invalid item {}".format(item
))
1083 desc
= remove_envelop(item
, descriptor
)
1085 # Override descriptor with kwargs
1087 desc
= self
.update_descriptor(desc
, kwargs
)
1090 if item
in ("tenant", "vim"):
1095 action
= "sdn_mapping"
1096 elif item
in ("vim_account", "ns"):
1099 # create_desc = self._create_envelop(item, desc)
1102 async with aiohttp
.ClientSession(loop
=self
.loop
) as session
:
1103 _all_tenants
= all_tenants
1106 # item_id = await self._get_item_uuid(session, self.client_to_RO[item], item_id_name,
1107 # all_tenants=_all_tenants)
1108 outdata
= await self
._create
_item
(
1110 self
.client_to_RO
[item
],
1112 item_id_name
=item_id_name
, # item_id_name=item_id
1114 all_tenants
=_all_tenants
,
1116 return remove_envelop(item
, outdata
)
1117 except (aiohttp
.ClientOSError
, aiohttp
.ClientError
) as e
:
1118 raise ROClientException(e
, http_code
=504)
1119 except asyncio
.TimeoutError
:
1120 raise ROClientException("Timeout", http_code
=504)
1123 self
, item
, item_id_name
=None, descriptor
=None, descriptor_format
=None, **kwargs
1126 Attach a datacenter or wim to a tenant, creating a vim_account, wim_account
1127 :param item: can be vim_account or wim_account
1128 :param item_id_name: id or name of the datacenter, wim
1130 :param descriptor_format:
1135 if isinstance(descriptor
, str):
1136 descriptor
= self
._parse
(descriptor
, descriptor_format
)
1142 desc
= remove_envelop(item
, descriptor
)
1144 # # check that exist
1145 # uuid = self._get_item_uuid(session, "datacenters", uuid_name, all_tenants=True)
1146 # tenant_text = "/" + self._get_tenant()
1148 desc
= self
.update_descriptor(desc
, kwargs
)
1150 if item
== "vim_account":
1151 if not desc
.get("vim_tenant_name") and not desc
.get("vim_tenant_id"):
1152 raise ROClientException(
1153 "Wrong descriptor. At least vim_tenant_name or vim_tenant_id must be "
1156 elif item
!= "wim_account":
1157 raise ROClientException(
1158 "Attach with unknown item {}. Must be 'vim_account' or 'wim_account'".format(
1162 create_desc
= self
._create
_envelop
(item
, desc
)
1163 payload_req
= yaml
.safe_dump(create_desc
)
1164 async with aiohttp
.ClientSession(loop
=self
.loop
) as session
:
1166 item_id
= await self
._get
_item
_uuid
(
1167 session
, self
.client_to_RO
[item
], item_id_name
, all_tenants
=True
1169 await self
._get
_tenant
(session
)
1171 url
= "{}/{tenant}/{item}/{item_id}".format(
1174 item
=self
.client_to_RO
[item
],
1177 self
.logger
.debug("RO POST %s %s", url
, payload_req
)
1178 # timeout = aiohttp.ClientTimeout(total=self.timeout_large)
1179 async with session
.post(
1180 url
, headers
=self
.headers_req
, data
=payload_req
1182 response_text
= await response
.read()
1184 "POST {} [{}] {}".format(
1185 url
, response
.status
, response_text
[:100]
1188 if response
.status
>= 300:
1189 raise ROClientException(
1190 self
._parse
_error
_yaml
(response_text
),
1191 http_code
=response
.status
,
1194 response_desc
= self
._parse
_yaml
(response_text
, response
=True)
1195 desc
= remove_envelop(item
, response_desc
)
1197 except (aiohttp
.ClientOSError
, aiohttp
.ClientError
) as e
:
1198 raise ROClientException(e
, http_code
=504)
1199 except asyncio
.TimeoutError
:
1200 raise ROClientException("Timeout", http_code
=504)
1202 async def detach(self
, item
, item_id_name
=None):
1203 # TODO replace the code with delete_item(vim_account,...)
1205 async with aiohttp
.ClientSession(loop
=self
.loop
) as session
:
1207 item_id
= await self
._get
_item
_uuid
(
1208 session
, self
.client_to_RO
[item
], item_id_name
, all_tenants
=False
1210 tenant
= await self
._get
_tenant
(session
)
1212 url
= "{}/{tenant}/{item}/{datacenter}".format(
1215 item
=self
.client_to_RO
[item
],
1218 self
.logger
.debug("RO DELETE %s", url
)
1220 # timeout = aiohttp.ClientTimeout(total=self.timeout_large)
1221 async with session
.delete(url
, headers
=self
.headers_req
) as response
:
1222 response_text
= await response
.read()
1224 "DELETE {} [{}] {}".format(
1225 url
, response
.status
, response_text
[:100]
1228 if response
.status
>= 300:
1229 raise ROClientException(
1230 self
._parse
_error
_yaml
(response_text
),
1231 http_code
=response
.status
,
1234 response_desc
= self
._parse
_yaml
(response_text
, response
=True)
1235 desc
= remove_envelop(item
, response_desc
)
1237 except (aiohttp
.ClientOSError
, aiohttp
.ClientError
) as e
:
1238 raise ROClientException(e
, http_code
=504)
1239 except asyncio
.TimeoutError
:
1240 raise ROClientException("Timeout", http_code
=504)
1242 # TODO convert to asyncio
1245 def edit_datacenter(
1250 descriptor_format
=None,
1254 """Edit the parameters of a datacenter
1255 Params: must supply a descriptor or/and a parameter to change
1256 uuid or/and name. If only name is supplied, there must be only one or an exception is raised
1257 descriptor: with format {'datacenter':{params to change info}}
1258 must be a dictionary or a json/yaml text.
1259 parameters to change can be supplyied by the descriptor or as parameters:
1260 new_name: the datacenter name
1261 vim_url: the datacenter URL
1262 vim_url_admin: the datacenter URL for administrative issues
1263 vim_type: the datacenter type, can be openstack or openvim.
1264 public: boolean, available to other tenants
1265 description: datacenter description
1266 Return: Raises an exception on error, not found or found several
1267 Obtain a dictionary with format {'datacenter':{new_datacenter_info}}
1270 if isinstance(descriptor
, str):
1271 descriptor
= self
.parse(descriptor
, descriptor_format
)
1275 descriptor
= {"datacenter": {}}
1277 raise ROClientException("Missing descriptor")
1279 if "datacenter" not in descriptor
or len(descriptor
) != 1:
1280 raise ROClientException(
1281 "Descriptor must contain only one 'datacenter' field"
1283 for param
in kwargs
:
1284 if param
== "new_name":
1285 descriptor
["datacenter"]["name"] = kwargs
[param
]
1287 descriptor
["datacenter"][param
] = kwargs
[param
]
1288 return self
._edit
_item
("datacenters", descriptor
, uuid
, name
, all_tenants
=None)
1295 descriptor_format
=None,
1299 """Edit the parameters of a scenario
1300 Params: must supply a descriptor or/and a parameters to change
1301 uuid or/and name. If only name is supplied, there must be only one or an exception is raised
1302 descriptor: with format {'scenario':{params to change info}}
1303 must be a dictionary or a json/yaml text.
1304 parameters to change can be supplyied by the descriptor or as parameters:
1305 new_name: the scenario name
1306 public: boolean, available to other tenants
1307 description: scenario description
1308 tenant_id. Propietary tenant
1309 Return: Raises an exception on error, not found or found several
1310 Obtain a dictionary with format {'scenario':{new_scenario_info}}
1313 if isinstance(descriptor
, str):
1314 descriptor
= self
.parse(descriptor
, descriptor_format
)
1318 descriptor
= {"scenario": {}}
1320 raise ROClientException("Missing descriptor")
1322 if "scenario" not in descriptor
or len(descriptor
) > 2:
1323 raise ROClientException("Descriptor must contain only one 'scenario' field")
1324 for param
in kwargs
:
1325 if param
== "new_name":
1326 descriptor
["scenario"]["name"] = kwargs
[param
]
1328 descriptor
["scenario"][param
] = kwargs
[param
]
1329 return self
._edit
_item
("scenarios", descriptor
, uuid
, name
, all_tenants
=None)
1332 def vim_action(self
, action
, item
, uuid
=None, all_tenants
=False, **kwargs
):
1333 """Perform an action over a vim
1335 action: can be 'list', 'get'/'show', 'delete' or 'create'
1336 item: can be 'tenants' or 'networks'
1337 uuid: uuid of the tenant/net to show or to delete. Ignore otherwise
1339 datacenter_name, datacenter_id: datacenters to act on, if missing uses classes store datacenter
1340 descriptor, descriptor_format: descriptor needed on creation, can be a dict or a yaml/json str
1341 must be a dictionary or a json/yaml text.
1342 name: for created tenant/net Overwrite descriptor name if any
1343 description: tenant descriptor. Overwrite descriptor description if any
1345 Return: Raises an exception on error
1346 Obtain a dictionary with format {'tenant':{new_tenant_info}}
1348 session
= None # TODO remove when changed to asyncio
1349 if item
not in ("tenants", "networks", "images"):
1350 raise ROClientException(
1351 "Unknown value for item '{}', must be 'tenants', 'nets' or "
1352 "images".format(str(item
))
1355 image_actions
= ["list", "get", "show", "delete"]
1356 if item
== "images" and action
not in image_actions
:
1357 raise ROClientException(
1358 "Only available actions for item '{}' are {}\n"
1359 "Requested action was '{}'".format(
1360 item
, ", ".join(image_actions
), action
1364 tenant_text
= "/any"
1366 tenant_text
= "/" + self
._get
_tenant
()
1368 if "datacenter_id" in kwargs
or "datacenter_name" in kwargs
:
1369 datacenter
= self
._get
_item
_uuid
(
1372 kwargs
.get("datacenter"),
1373 all_tenants
=all_tenants
,
1376 datacenter
= self
.get_datacenter(session
)
1378 if action
== "list":
1379 url
= "{}{}/vim/{}/{}".format(self
.uri
, tenant_text
, datacenter
, item
)
1380 self
.logger
.debug("GET %s", url
)
1381 mano_response
= requests
.get(url
, headers
=self
.headers_req
)
1382 self
.logger
.debug("RO response: %s", mano_response
.text
)
1383 content
= self
._parse
_yaml
(mano_response
.text
, response
=True)
1384 if mano_response
.status_code
== 200:
1387 raise ROClientException(str(content
), http_code
=mano_response
.status
)
1388 elif action
== "get" or action
== "show":
1389 url
= "{}{}/vim/{}/{}/{}".format(
1390 self
.uri
, tenant_text
, datacenter
, item
, uuid
1392 self
.logger
.debug("GET %s", url
)
1393 mano_response
= requests
.get(url
, headers
=self
.headers_req
)
1394 self
.logger
.debug("RO response: %s", mano_response
.text
)
1395 content
= self
._parse
_yaml
(mano_response
.text
, response
=True)
1396 if mano_response
.status_code
== 200:
1399 raise ROClientException(str(content
), http_code
=mano_response
.status
)
1400 elif action
== "delete":
1401 url
= "{}{}/vim/{}/{}/{}".format(
1402 self
.uri
, tenant_text
, datacenter
, item
, uuid
1404 self
.logger
.debug("DELETE %s", url
)
1405 mano_response
= requests
.delete(url
, headers
=self
.headers_req
)
1406 self
.logger
.debug("RO response: %s", mano_response
.text
)
1407 content
= self
._parse
_yaml
(mano_response
.text
, response
=True)
1408 if mano_response
.status_code
== 200:
1411 raise ROClientException(str(content
), http_code
=mano_response
.status
)
1412 elif action
== "create":
1413 if "descriptor" in kwargs
:
1414 if isinstance(kwargs
["descriptor"], str):
1415 descriptor
= self
._parse
(
1416 kwargs
["descriptor"], kwargs
.get("descriptor_format")
1419 descriptor
= kwargs
["descriptor"]
1420 elif "name" in kwargs
:
1421 descriptor
= {item
[:-1]: {"name": kwargs
["name"]}}
1423 raise ROClientException("Missing descriptor")
1425 if item
[:-1] not in descriptor
or len(descriptor
) != 1:
1426 raise ROClientException(
1427 "Descriptor must contain only one 'tenant' field"
1429 if "name" in kwargs
:
1430 descriptor
[item
[:-1]]["name"] = kwargs
["name"]
1431 if "description" in kwargs
:
1432 descriptor
[item
[:-1]]["description"] = kwargs
["description"]
1433 payload_req
= yaml
.safe_dump(descriptor
)
1435 url
= "{}{}/vim/{}/{}".format(self
.uri
, tenant_text
, datacenter
, item
)
1436 self
.logger
.debug("RO POST %s %s", url
, payload_req
)
1437 mano_response
= requests
.post(
1438 url
, headers
=self
.headers_req
, data
=payload_req
1440 self
.logger
.debug("RO response: %s", mano_response
.text
)
1441 content
= self
._parse
_yaml
(mano_response
.text
, response
=True)
1442 if mano_response
.status_code
== 200:
1445 raise ROClientException(str(content
), http_code
=mano_response
.status
)
1447 raise ROClientException("Unknown value for action '{}".format(str(action
)))
1450 if __name__
== "__main__":
1451 RO_URL
= "http://localhost:9090/openmano"
1452 TEST_TENANT
= "myTenant"
1454 TEST_URL1
= "https://localhost:5000/v1"
1455 TEST_TYPE1
= "openstack"
1456 TEST_CONFIG1
= {"use_floating_ip": True}
1457 TEST_VIM2
= "myvim2"
1458 TEST_URL2
= "https://localhost:5000/v2"
1459 TEST_TYPE2
= "openvim"
1460 TEST_CONFIG2
= {"config2": "config2", "config3": True}
1462 streamformat
= "%(asctime)s %(name)s %(levelname)s: %(message)s"
1463 logging
.basicConfig(format
=streamformat
)
1464 logger
= logging
.getLogger("ROClient")
1468 loop
= asyncio
.get_event_loop()
1469 myClient
= ROClient(uri
=RO_URL
, loop
=loop
, loglevel
="DEBUG")
1472 content
= loop
.run_until_complete(myClient
.get_list("tenant"))
1473 print("tenants", content
)
1474 content
= loop
.run_until_complete(myClient
.create("tenant", name
=TEST_TENANT
))
1476 content
= loop
.run_until_complete(myClient
.show("tenant", TEST_TENANT
))
1477 print("tenant", TEST_TENANT
, content
)
1478 content
= loop
.run_until_complete(
1479 myClient
.edit("tenant", TEST_TENANT
, description
="another description")
1481 content
= loop
.run_until_complete(myClient
.show("tenant", TEST_TENANT
))
1482 print("tenant edited", TEST_TENANT
, content
)
1483 myClient
["tenant"] = TEST_TENANT
1486 content
= loop
.run_until_complete(
1492 config
=TEST_CONFIG1
,
1496 content
= loop
.run_until_complete(myClient
.get_list("vim"))
1497 print("vim", content
)
1498 content
= loop
.run_until_complete(myClient
.show("vim", TEST_VIM1
))
1499 print("vim", TEST_VIM1
, content
)
1500 content
= loop
.run_until_complete(
1504 description
="another description",
1508 config
=TEST_CONFIG2
,
1511 content
= loop
.run_until_complete(myClient
.show("vim", TEST_VIM2
))
1512 print("vim edited", TEST_VIM2
, content
)
1515 content
= loop
.run_until_complete(
1516 myClient
.attach_datacenter(
1518 vim_username
="user",
1519 vim_password
="pass",
1520 vim_tenant_name
="vimtenant1",
1521 config
=TEST_CONFIG1
,
1525 content
= loop
.run_until_complete(myClient
.get_list("vim_account"))
1526 print("vim_account", content
)
1527 content
= loop
.run_until_complete(myClient
.show("vim_account", TEST_VIM2
))
1528 print("vim_account", TEST_VIM2
, content
)
1529 content
= loop
.run_until_complete(
1533 vim_username
="user2",
1534 vim_password
="pass2",
1535 vim_tenant_name
="vimtenant2",
1536 config
=TEST_CONFIG2
,
1539 content
= loop
.run_until_complete(myClient
.show("vim_account", TEST_VIM2
))
1540 print("vim_account edited", TEST_VIM2
, content
)
1542 myClient
["vim"] = TEST_VIM2
1544 except Exception as e
:
1545 logger
.error("Error {}".format(e
), exc_info
=True)
1548 ("vim_account", TEST_VIM1
),
1550 ("vim_account", TEST_VIM2
),
1552 ("tenant", TEST_TENANT
),
1555 content
= loop
.run_until_complete(myClient
.delete(item
[0], item
[1]))
1556 print("{} {} deleted; {}".format(item
[0], item
[1], content
))
1557 except Exception as e
:
1558 if e
.http_code
== 404:
1559 print("{} {} not present or already deleted".format(item
[0], item
[1]))
1561 logger
.error("Error {}".format(e
), exc_info
=True)