2 # -*- coding: utf-8 -*-
5 # Copyright 2015 Telefónica Investigación y Desarrollo, S.A.U.
6 # This file is part of openmano
9 # Licensed under the Apache License, Version 2.0 (the "License"); you may
10 # not use this file except in compliance with the License. You may obtain
11 # a copy of the License at
13 # http://www.apache.org/licenses/LICENSE-2.0
15 # Unless required by applicable law or agreed to in writing, software
16 # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
17 # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
18 # License for the specific language governing permissions and limitations
21 # For those usages not covered by the Apache License, Version 2.0 please
22 # contact with: nfvlabs@tid.es
26 asyncio RO python client to interact with RO-server
34 from urllib
.parse
import quote
36 from copy
import deepcopy
38 __author__
= "Alfonso Tierno"
39 __date__
= "$09-Jan-2018 09:09:48$"
41 version_date
= "2018-05-16"
45 class ROClientException(Exception):
46 def __init__(self
, message
, http_code
=400):
47 """Common Exception for all RO client exceptions"""
48 self
.http_code
= http_code
49 Exception.__init
__(self
, message
)
52 def remove_envelop(item
, indata
=None):
54 Obtain the useful data removing the envelop. It goes through the vnfd or nsd catalog and returns the
56 :param item: can be 'tenant', 'vim', 'vnfd', 'nsd', 'ns'
57 :param indata: Content to be inspected
58 :return: the useful part of indata (a reference, not a new dictionay)
64 if clean_indata
.get('vnfd:vnfd-catalog'):
65 clean_indata
= clean_indata
['vnfd:vnfd-catalog']
66 elif clean_indata
.get('vnfd-catalog'):
67 clean_indata
= clean_indata
['vnfd-catalog']
68 if clean_indata
.get('vnfd'):
69 if not isinstance(clean_indata
['vnfd'], list) or len(clean_indata
['vnfd']) != 1:
70 raise ROClientException("'vnfd' must be a list only one element")
71 clean_indata
= clean_indata
['vnfd'][0]
73 if clean_indata
.get('nsd:nsd-catalog'):
74 clean_indata
= clean_indata
['nsd:nsd-catalog']
75 elif clean_indata
.get('nsd-catalog'):
76 clean_indata
= clean_indata
['nsd-catalog']
77 if clean_indata
.get('nsd'):
78 if not isinstance(clean_indata
['nsd'], list) or len(clean_indata
['nsd']) != 1:
79 raise ROClientException("'nsd' must be a list only one element")
80 clean_indata
= clean_indata
['nsd'][0]
82 if len(indata
) == 1 and "sdn_controller" in indata
:
83 clean_indata
= indata
["sdn_controller"]
84 elif item
== "tenant":
85 if len(indata
) == 1 and "tenant" in indata
:
86 clean_indata
= indata
["tenant"]
87 elif item
in ("vim", "vim_account", "datacenters"):
88 if len(indata
) == 1 and "datacenter" in indata
:
89 clean_indata
= indata
["datacenter"]
90 elif item
== "ns" or item
== "instances":
91 if len(indata
) == 1 and "instance" in indata
:
92 clean_indata
= indata
["instance"]
94 assert False, "remove_envelop with unknown item {}".format(item
)
100 headers_req
= {'Accept': 'application/yaml', 'content-type': 'application/yaml'}
101 client_to_RO
= {'tenant': 'tenants', 'vim': 'datacenters', 'vim_account': 'datacenters', 'sdn': 'sdn_controllers',
102 'vnfd': 'vnfs', 'nsd': 'scenarios',
104 mandatory_for_create
= {
105 'tenant': ("name", ),
106 'vnfd': ("name", "id", "connection-point", "vdu"),
107 'nsd': ("name", "id", "constituent-vnfd"),
108 'ns': ("name", "scenario", "datacenter"),
109 'vim': ("name", "vim_url"),
111 'sdn': ("name", "port", 'ip', 'dpid', 'type'),
116 def __init__(self
, loop
, endpoint_url
, **kwargs
):
118 self
.endpoint_url
= endpoint_url
120 self
.username
= kwargs
.get("username")
121 self
.password
= kwargs
.get("password")
122 self
.tenant_id_name
= kwargs
.get("tenant")
124 self
.datacenter_id_name
= kwargs
.get("datacenter")
125 self
.datacenter
= None
126 logger_name
= kwargs
.get('logger_name', 'ROClient')
127 self
.logger
= logging
.getLogger(logger_name
)
128 if kwargs
.get("loglevel"):
129 self
.logger
.setLevel(kwargs
["loglevel"])
131 requests
= kwargs
.get("TODO remove")
133 def __getitem__(self
, index
):
134 if index
== 'tenant':
135 return self
.tenant_id_name
136 elif index
== 'datacenter':
137 return self
.datacenter_id_name
138 elif index
== 'username':
140 elif index
== 'password':
142 elif index
== 'endpoint_url':
143 return self
.endpoint_url
145 raise KeyError("Invalid key '{}'".format(index
))
147 def __setitem__(self
, index
, value
):
148 if index
== 'tenant':
149 self
.tenant_id_name
= value
150 elif index
== 'datacenter' or index
== 'vim':
151 self
.datacenter_id_name
= value
152 elif index
== 'username':
153 self
.username
= value
154 elif index
== 'password':
155 self
.password
= value
156 elif index
== 'endpoint_url':
157 self
.endpoint_url
= value
159 raise KeyError("Invalid key '{}'".format(index
))
160 self
.tenant
= None # force to reload tenant with different credentials
161 self
.datacenter
= None # force to reload datacenter with different credentials
163 def _parse(self
, descriptor
, descriptor_format
, response
=False):
164 if descriptor_format
and descriptor_format
!= "json" and descriptor_format
!= "yaml":
165 raise ROClientException("'descriptor_format' must be a 'json' or 'yaml' text")
166 if descriptor_format
!= "json":
168 return yaml
.load(descriptor
)
169 except yaml
.YAMLError
as exc
:
171 if hasattr(exc
, 'problem_mark'):
172 mark
= exc
.problem_mark
173 error_pos
= " at line:{} column:{}s".format(mark
.line
+ 1, mark
.column
+ 1)
174 error_text
= "yaml format error" + error_pos
175 elif descriptor_format
!= "yaml":
177 return json
.loads(descriptor
)
178 except Exception as e
:
180 error_text
= "json format error" + str(e
)
183 raise ROClientException(error_text
)
184 raise ROClientException(error_text
)
186 def _parse_yaml(self
, descriptor
, response
=False):
188 return yaml
.load(descriptor
)
189 except yaml
.YAMLError
as exc
:
191 if hasattr(exc
, 'problem_mark'):
192 mark
= exc
.problem_mark
193 error_pos
= " at line:{} column:{}s".format(mark
.line
+ 1, mark
.column
+ 1)
194 error_text
= "yaml format error" + error_pos
196 raise ROClientException(error_text
)
197 raise ROClientException(error_text
)
200 def check_if_uuid(uuid_text
):
202 Check if text correspond to an uuid foramt
204 :return: True if it is an uuid False if not
213 def _create_envelop(item
, indata
=None):
215 Returns a new dict that incledes indata with the expected envelop
216 :param item: can be 'tenant', 'vim', 'vnfd', 'nsd', 'ns'
217 :param indata: Content to be enveloped
218 :return: a new dic with {<envelop>: {indata} } where envelop can be e.g. tenant, datacenter, ...
221 return {'vnfd-catalog': {'vnfd': [indata
]}}
223 return {'nsd-catalog': {'nsd': [indata
]}}
224 elif item
== "tenant":
225 return {'tenant': indata
}
226 elif item
in ("vim", "vim_account", "datacenter"):
227 return {'datacenter': indata
}
228 elif item
== "ns" or item
== "instances":
229 return {'instance': indata
}
231 return {'sdn_controller': indata
}
233 assert False, "_create_envelop with unknown item {}".format(item
)
236 def update_descriptor(desc
, kwargs
):
237 desc
= deepcopy(desc
) # do not modify original descriptor
239 for k
, v
in kwargs
.items():
240 update_content
= desc
244 if kitem_old
is not None:
245 update_content
= update_content
[kitem_old
]
246 if isinstance(update_content
, dict):
248 elif isinstance(update_content
, list):
249 kitem_old
= int(kitem
)
251 raise ROClientException(
252 "Invalid query string '{}'. Descriptor is not a list nor dict at '{}'".format(k
, kitem
))
253 if v
== "__DELETE__":
254 del update_content
[kitem_old
]
256 update_content
[kitem_old
] = v
259 raise ROClientException(
260 "Invalid query string '{}'. Descriptor does not contain '{}'".format(k
, kitem_old
))
262 raise ROClientException("Invalid query string '{}'. Expected integer index list instead of '{}'".format(
265 raise ROClientException(
266 "Invalid query string '{}'. Index '{}' out of range".format(k
, kitem_old
))
269 def check_ns_status(ns_descriptor
):
271 Inspect RO instance descriptor and indicates the status
272 :param ns_descriptor: instance descriptor obtained with self.show("ns", )
273 :return: status, message: status can be BUILD,ACTIVE,ERROR, message is a text message
280 for net
in ns_descriptor
["nets"]:
282 if net
["status"] in ("ERROR", "VIM_ERROR"):
283 return "ERROR", net
["error_msg"]
284 elif net
["status"] == "ACTIVE":
286 for vnf
in ns_descriptor
["vnfs"]:
287 for vm
in vnf
["vms"]:
289 if vm
["status"] in ("ERROR", "VIM_ERROR"):
290 return "ERROR", vm
["error_msg"]
291 elif vm
["status"] == "ACTIVE":
294 if net_total
== net_done
and vm_total
== vm_done
:
295 return "ACTIVE", "VMs {}, networks: {}".format(vm_total
, net_total
)
297 return "BUILD", "VMs: {}/{}, networks: {}/{}".format(vm_done
, vm_total
, net_done
, net_total
)
300 def check_action_status(action_descriptor
):
302 Inspect RO instance descriptor and indicates the status
303 :param action_descriptor: action instance descriptor obtained with self.show("ns", "action")
304 :return: status, message: status can be BUILD,ACTIVE,ERROR, message is a text message
313 for vim_action_set
in action_descriptor
["actions"]:
314 for vim_action
in vim_action_set
["vim_actions"]:
315 if vim_action
["item"] == "instance_vms":
317 elif vim_action
["item"] == "instance_nets":
321 if vim_action
["status"] == "FAILED":
322 return "ERROR", vim_action
["error_msg"]
323 elif vim_action
["status"] in ("DONE", "SUPERSEDED"):
324 if vim_action
["item"] == "instance_vms":
326 elif vim_action
["item"] == "instance_nets":
331 if net_total
== net_done
and vm_total
== vm_done
and other_total
== other_done
:
332 return "ACTIVE", "VMs {}, networks: {}, other: {} ".format(vm_total
, net_total
, other_total
)
334 return "BUILD", "VMs: {}/{}, networks: {}/{}, other: {}/{}".format(vm_done
, vm_total
, net_done
, net_total
,
335 other_done
, other_total
)
338 def get_ns_vnf_info(ns_descriptor
):
340 Get a dict with the VIM_id, ip_addresses, mac_addresses of every vnf and vdu
341 :param ns_descriptor: instance descriptor obtained with self.show("ns", )
355 for vnf
in ns_descriptor
["vnfs"]:
356 if not vnf
.get("ip_address"):
357 raise ROClientException("ns member_vnf_index '{}' has no IP address".format(
358 vnf
["member_vnf_index"]), http_code
=409)
360 "ip_address": vnf
.get("ip_address"),
363 for vm
in vnf
["vms"]:
365 "vim_id": vm
.get("vim_vm_id"),
366 "ip_address": vm
.get("ip_address"),
369 for iface
in vm
["interfaces"]:
370 if iface
.get("type") == "mgmt" and not iface
.get("ip_address"):
371 raise ROClientException("ns member_vnf_index '{}' vm '{}' management interface '{}' has no IP "
372 "address".format(vnf
["member_vnf_index"], vm
["vdu_osm_id"],
373 iface
["external_name"]), http_code
=409)
374 vdur
["interfaces"][iface
["internal_name"]] = {"ip_address": iface
.get("ip_address"),
375 "mac_address": iface
.get("mac_address"),
376 "vim_id": iface
.get("vim_interface_id"),
378 vnfr_info
["vdur"][vm
["vdu_osm_id"]] = vdur
379 ns_info
[str(vnf
["member_vnf_index"])] = vnfr_info
382 async def _get_item_uuid(self
, session
, item
, item_id_name
, all_tenants
=False):
385 elif all_tenants
is None:
389 await self
._get
_tenant
(session
)
390 tenant_text
= "/" + self
.tenant
393 url
= "{}{}/{}".format(self
.endpoint_url
, tenant_text
, item
)
394 if self
.check_if_uuid(item_id_name
):
395 item_id
= item_id_name
396 url
+= "/" + item_id_name
397 elif item_id_name
and item_id_name
.startswith("'") and item_id_name
.endswith("'"):
398 item_id_name
= item_id_name
[1:-1]
399 self
.logger
.debug("RO GET %s", url
)
400 with aiohttp
.Timeout(self
.timeout_short
):
401 async with session
.get(url
, headers
=self
.headers_req
) as response
:
402 response_text
= await response
.read()
403 self
.logger
.debug("GET {} [{}] {}".format(url
, response
.status
, response_text
[:100]))
404 if response
.status
== 404: # NOT_FOUND
405 raise ROClientException("No {} found with id '{}'".format(item
[:-1], item_id_name
),
407 if response
.status
>= 300:
408 raise ROClientException(response_text
, http_code
=response
.status
)
409 content
= self
._parse
_yaml
(response_text
, response
=True)
414 assert isinstance(desc
, list), "_get_item_uuid get a non dict with a list inside {}".format(type(desc
))
417 if item_id_name
and i
["name"] != item_id_name
:
419 if uuid
: # found more than one
420 raise ROClientException(
421 "Found more than one {} with name '{}'. uuid must be used".format(item
, item_id_name
),
425 raise ROClientException("No {} found with name '{}'".format(item
[:-1], item_id_name
), http_code
=404)
428 async def _get_item(self
, session
, item
, item_id_name
, extra_item
=None, extra_item_id
=None, all_tenants
=False):
431 elif all_tenants
is None:
435 await self
._get
_tenant
(session
)
436 tenant_text
= "/" + self
.tenant
438 if self
.check_if_uuid(item_id_name
):
442 uuid
= await self
._get
_item
_uuid
(session
, item
, item_id_name
, all_tenants
)
444 url
= "{}{}/{}/{}".format(self
.endpoint_url
, tenant_text
, item
, uuid
)
446 url
+= "/" + extra_item
448 url
+= "/" + extra_item_id
449 self
.logger
.debug("GET %s", url
)
450 with aiohttp
.Timeout(self
.timeout_short
):
451 async with session
.get(url
, headers
=self
.headers_req
) as response
:
452 response_text
= await response
.read()
453 self
.logger
.debug("GET {} [{}] {}".format(url
, response
.status
, response_text
[:100]))
454 if response
.status
>= 300:
455 raise ROClientException(response_text
, http_code
=response
.status
)
457 return self
._parse
_yaml
(response_text
, response
=True)
459 async def _get_tenant(self
, session
):
461 self
.tenant
= await self
._get
_item
_uuid
(session
, "tenants", self
.tenant_id_name
, None)
464 async def _get_datacenter(self
, session
):
466 await self
._get
_tenant
(session
)
467 if not self
.datacenter
:
468 self
.datacenter
= await self
._get
_item
_uuid
(session
, "datacenters", self
.datacenter_id_name
, True)
469 return self
.datacenter
471 async def _create_item(self
, session
, item
, descriptor
, item_id_name
=None, action
=None, all_tenants
=False):
474 elif all_tenants
is None:
478 await self
._get
_tenant
(session
)
479 tenant_text
= "/" + self
.tenant
480 payload_req
= yaml
.safe_dump(descriptor
)
483 api_version_text
= ""
485 # assumes version v3 only
486 api_version_text
= "/v3"
488 elif item
== "scenarios":
489 # assumes version v3 only
490 api_version_text
= "/v3"
495 elif self
.check_if_uuid(item_id_name
):
496 uuid
= "/{}".format(item_id_name
)
499 uuid
= await self
._get
_item
_uuid
(session
, item
, item_id_name
, all_tenants
)
500 uuid
= "/{}".format(uuid
)
504 action
= "/{}".format(action
)
506 url
= "{}{apiver}{tenant}/{item}{id}{action}".format(self
.endpoint_url
, apiver
=api_version_text
,
507 tenant
=tenant_text
, item
=item
, id=uuid
, action
=action
)
508 self
.logger
.debug("RO POST %s %s", url
, payload_req
)
509 with aiohttp
.Timeout(self
.timeout_large
):
510 async with session
.post(url
, headers
=self
.headers_req
, data
=payload_req
) as response
:
511 response_text
= await response
.read()
512 self
.logger
.debug("POST {} [{}] {}".format(url
, response
.status
, response_text
[:100]))
513 if response
.status
>= 300:
514 raise ROClientException(response_text
, http_code
=response
.status
)
516 return self
._parse
_yaml
(response_text
, response
=True)
518 async def _del_item(self
, session
, item
, item_id_name
, all_tenants
=False):
521 elif all_tenants
is None:
525 await self
._get
_tenant
(session
)
526 tenant_text
= "/" + self
.tenant
527 if not self
.check_if_uuid(item_id_name
):
529 _all_tenants
= all_tenants
530 if item
== "datacenters":
532 uuid
= await self
._get
_item
_uuid
(session
, item
, item_id_name
, all_tenants
=_all_tenants
)
536 url
= "{}{}/{}/{}".format(self
.endpoint_url
, tenant_text
, item
, uuid
)
537 self
.logger
.debug("DELETE %s", url
)
538 with aiohttp
.Timeout(self
.timeout_short
):
539 async with session
.delete(url
, headers
=self
.headers_req
) as response
:
540 response_text
= await response
.read()
541 self
.logger
.debug("DELETE {} [{}] {}".format(url
, response
.status
, response_text
[:100]))
542 if response
.status
>= 300:
543 raise ROClientException(response_text
, http_code
=response
.status
)
544 return self
._parse
_yaml
(response_text
, response
=True)
546 async def _list_item(self
, session
, item
, all_tenants
=False, filter_dict
=None):
549 elif all_tenants
is None:
553 await self
._get
_tenant
(session
)
554 tenant_text
= "/" + self
.tenant
556 url
= "{}{}/{}".format(self
.endpoint_url
, tenant_text
, item
)
559 for k
in filter_dict
:
560 url
+= separator
+ quote(str(k
)) + "=" + quote(str(filter_dict
[k
]))
562 self
.logger
.debug("RO GET %s", url
)
563 with aiohttp
.Timeout(self
.timeout_short
):
564 async with session
.get(url
, headers
=self
.headers_req
) as response
:
565 response_text
= await response
.read()
566 self
.logger
.debug("GET {} [{}] {}".format(url
, response
.status
, response_text
[:100]))
567 if response
.status
>= 300:
568 raise ROClientException(response_text
, http_code
=response
.status
)
569 return self
._parse
_yaml
(response_text
, response
=True)
571 async def _edit_item(self
, session
, item
, item_id
, descriptor
, all_tenants
=False):
574 elif all_tenants
is None:
578 await self
._get
_tenant
(session
)
579 tenant_text
= "/" + self
.tenant
581 payload_req
= yaml
.safe_dump(descriptor
)
584 url
= "{}{}/{}/{}".format(self
.endpoint_url
, tenant_text
, item
, item_id
)
585 self
.logger
.debug("RO PUT %s %s", url
, payload_req
)
586 with aiohttp
.Timeout(self
.timeout_large
):
587 async with session
.put(url
, headers
=self
.headers_req
, data
=payload_req
) as response
:
588 response_text
= await response
.read()
589 self
.logger
.debug("PUT {} [{}] {}".format(url
, response
.status
, response_text
[:100]))
590 if response
.status
>= 300:
591 raise ROClientException(response_text
, http_code
=response
.status
)
592 return self
._parse
_yaml
(response_text
, response
=True)
594 async def get_version(self
):
596 Obtain RO server version.
597 :return: a list with integers ["major", "minor", "release"]. Raises ROClientException on Error,
600 with aiohttp
.ClientSession(loop
=self
.loop
) as session
:
601 url
= "{}/version".format(self
.endpoint_url
)
602 self
.logger
.debug("RO GET %s", url
)
603 with aiohttp
.Timeout(self
.timeout_short
):
604 async with session
.get(url
, headers
=self
.headers_req
) as response
:
605 response_text
= await response
.read()
606 self
.logger
.debug("GET {} [{}] {}".format(url
, response
.status
, response_text
[:100]))
607 if response
.status
>= 300:
608 raise ROClientException(response_text
, http_code
=response
.status
)
609 for word
in str(response_text
).split(" "):
611 version_text
, _
, _
= word
.partition("-")
612 return list(map(int, version_text
.split(".")))
613 raise ROClientException("Got invalid version text: '{}'".format(response_text
), http_code
=500)
614 except aiohttp
.errors
.ClientOSError
as e
:
615 raise ROClientException(e
, http_code
=504)
616 except asyncio
.TimeoutError
:
617 raise ROClientException("Timeout", http_code
=504)
618 except Exception as e
:
619 raise ROClientException("Got invalid version text: '{}'; causing exception {}".format(response_text
, e
),
622 async def get_list(self
, item
, all_tenants
=False, filter_by
=None):
624 Obtain a list of items filtering by the specigy filter_by.
625 :param item: can be 'tenant', 'vim', 'vnfd', 'nsd', 'ns'
626 :param all_tenants: True if not filtering by tenant. Only allowed for admin
627 :param filter_by: dictionary with filtering
628 :return: a list of dict. It can be empty. Raises ROClientException on Error,
631 if item
not in self
.client_to_RO
:
632 raise ROClientException("Invalid item {}".format(item
))
635 with aiohttp
.ClientSession(loop
=self
.loop
) as session
:
636 content
= await self
._list
_item
(session
, self
.client_to_RO
[item
], all_tenants
=all_tenants
,
637 filter_dict
=filter_by
)
638 if isinstance(content
, dict):
639 if len(content
) == 1:
640 for _
, v
in content
.items():
642 return content
.values()[0]
644 raise ROClientException("Output not a list neither dict with len equal 1", http_code
=500)
646 except aiohttp
.errors
.ClientOSError
as e
:
647 raise ROClientException(e
, http_code
=504)
648 except asyncio
.TimeoutError
:
649 raise ROClientException("Timeout", http_code
=504)
651 async def show(self
, item
, item_id_name
=None, extra_item
=None, extra_item_id
=None, all_tenants
=False):
653 Obtain the information of an item from its id or name
654 :param item: can be 'tenant', 'vim', 'vnfd', 'nsd', 'ns'
655 :param item_id_name: RO id or name of the item. Raise and exception if more than one found
656 :param extra_item: if supplied, it is used to add to the URL.
657 Can be 'action' if item='ns'; 'networks' or'images' if item='vim'
658 :param extra_item_id: if supplied, it is used get details of a concrete extra_item.
659 :param all_tenants: True if not filtering by tenant. Only allowed for admin
660 :return: dictionary with the information or raises ROClientException on Error, NotFound, found several
663 if item
not in self
.client_to_RO
:
664 raise ROClientException("Invalid item {}".format(item
))
669 elif item
== 'vim_account':
672 with aiohttp
.ClientSession(loop
=self
.loop
) as session
:
673 content
= await self
._get
_item
(session
, self
.client_to_RO
[item
], item_id_name
, extra_item
=extra_item
,
674 extra_item_id
=extra_item_id
, all_tenants
=all_tenants
)
675 return remove_envelop(item
, content
)
676 except aiohttp
.errors
.ClientOSError
as e
:
677 raise ROClientException(e
, http_code
=504)
678 except asyncio
.TimeoutError
:
679 raise ROClientException("Timeout", http_code
=504)
681 async def delete(self
, item
, item_id_name
=None, all_tenants
=False):
683 Delete the information of an item from its id or name
684 :param item: can be 'tenant', 'vim', 'vnfd', 'nsd', 'ns'
685 :param item_id_name: RO id or name of the item. Raise and exception if more than one found
686 :param all_tenants: True if not filtering by tenant. Only allowed for admin
687 :return: dictionary with the information or raises ROClientException on Error, NotFound, found several
690 if item
not in self
.client_to_RO
:
691 raise ROClientException("Invalid item {}".format(item
))
692 if item
== 'tenant' or item
== 'vim':
695 with aiohttp
.ClientSession(loop
=self
.loop
) as session
:
696 result
= await self
._del
_item
(session
, self
.client_to_RO
[item
], item_id_name
, all_tenants
=all_tenants
)
697 # in case of ns delete, get the action_id embeded in text
698 if item
== "ns" and result
.get("result"):
699 _
, _
, action_id
= result
["result"].partition("action_id=")
700 action_id
, _
, _
= action_id
.partition(" ")
702 result
["action_id"] = action_id
704 except aiohttp
.errors
.ClientOSError
as e
:
705 raise ROClientException(e
, http_code
=504)
706 except asyncio
.TimeoutError
:
707 raise ROClientException("Timeout", http_code
=504)
709 async def edit(self
, item
, item_id_name
, descriptor
=None, descriptor_format
=None, **kwargs
):
711 :param item: can be 'tenant', 'vim', 'vnfd', 'nsd', 'ns', 'vim'
712 :param item_id_name: RO id or name of the item. Raise and exception if more than one found
713 :param descriptor: can be a dict, or a yaml/json text. Autodetect unless descriptor_format is provided
714 :param descriptor_format: Can be 'json' or 'yaml'
715 :param kwargs: Overrides descriptor with values as name, description, vim_url, vim_url_admin, vim_type
716 keys can be a dot separated list to specify elements inside dict
717 :return: dictionary with the information or raises ROClientException on Error
720 if isinstance(descriptor
, str):
721 descriptor
= self
._parse
(descriptor
, descriptor_format
)
727 if item
not in self
.client_to_RO
:
728 raise ROClientException("Invalid item {}".format(item
))
729 desc
= remove_envelop(item
, descriptor
)
731 # Override descriptor with kwargs
733 desc
= self
.update_descriptor(desc
, kwargs
)
735 if item
in ('tenant', 'vim'):
738 create_desc
= self
._create
_envelop
(item
, desc
)
740 with aiohttp
.ClientSession(loop
=self
.loop
) as session
:
741 _all_tenants
= all_tenants
744 item_id
= await self
._get
_item
_uuid
(session
, self
.client_to_RO
[item
], item_id_name
,
745 all_tenants
=_all_tenants
)
748 # await self._get_tenant(session)
749 outdata
= await self
._edit
_item
(session
, self
.client_to_RO
[item
], item_id
, create_desc
,
750 all_tenants
=_all_tenants
)
751 return remove_envelop(item
, outdata
)
752 except aiohttp
.errors
.ClientOSError
as e
:
753 raise ROClientException(e
, http_code
=504)
754 except asyncio
.TimeoutError
:
755 raise ROClientException("Timeout", http_code
=504)
757 async def create(self
, item
, descriptor
=None, descriptor_format
=None, **kwargs
):
759 Creates an item from its descriptor
760 :param item: can be 'tenant', 'vnfd', 'nsd', 'ns', 'vim', 'vim_account', 'sdn'
761 :param descriptor: can be a dict, or a yaml/json text. Autodetect unless descriptor_format is provided
762 :param descriptor_format: Can be 'json' or 'yaml'
763 :param kwargs: Overrides descriptor with values as name, description, vim_url, vim_url_admin, vim_type
764 keys can be a dot separated list to specify elements inside dict
765 :return: dictionary with the information or raises ROClientException on Error
768 if isinstance(descriptor
, str):
769 descriptor
= self
._parse
(descriptor
, descriptor_format
)
775 if item
not in self
.client_to_RO
:
776 raise ROClientException("Invalid item {}".format(item
))
777 desc
= remove_envelop(item
, descriptor
)
779 # Override descriptor with kwargs
781 desc
= self
.update_descriptor(desc
, kwargs
)
783 for mandatory
in self
.mandatory_for_create
[item
]:
784 if mandatory
not in desc
:
785 raise ROClientException("'{}' is mandatory parameter for {}".format(mandatory
, item
))
788 if item
in ('tenant', 'vim'):
791 create_desc
= self
._create
_envelop
(item
, desc
)
793 with aiohttp
.ClientSession(loop
=self
.loop
) as session
:
794 outdata
= await self
._create
_item
(session
, self
.client_to_RO
[item
], create_desc
,
795 all_tenants
=all_tenants
)
796 return remove_envelop(item
, outdata
)
797 except aiohttp
.errors
.ClientOSError
as e
:
798 raise ROClientException(e
, http_code
=504)
799 except asyncio
.TimeoutError
:
800 raise ROClientException("Timeout", http_code
=504)
802 async def create_action(self
, item
, item_id_name
, descriptor
=None, descriptor_format
=None, **kwargs
):
804 Performs an action over an item
805 :param item: can be 'tenant', 'vnfd', 'nsd', 'ns', 'vim', 'vim_account', 'sdn'
806 :param item_id_name: RO id or name of the item. Raise and exception if more than one found
807 :param descriptor: can be a dict, or a yaml/json text. Autodetect unless descriptor_format is provided
808 :param descriptor_format: Can be 'json' or 'yaml'
809 :param kwargs: Overrides descriptor with values as name, description, vim_url, vim_url_admin, vim_type
810 keys can be a dot separated list to specify elements inside dict
811 :return: dictionary with the information or raises ROClientException on Error
814 if isinstance(descriptor
, str):
815 descriptor
= self
._parse
(descriptor
, descriptor_format
)
821 if item
not in self
.client_to_RO
:
822 raise ROClientException("Invalid item {}".format(item
))
823 desc
= remove_envelop(item
, descriptor
)
825 # Override descriptor with kwargs
827 desc
= self
.update_descriptor(desc
, kwargs
)
830 if item
in ('tenant', 'vim'):
835 action
= "sdn_mapping"
836 elif item
in ("vim_account", "ns"):
839 # create_desc = self._create_envelop(item, desc)
842 with aiohttp
.ClientSession(loop
=self
.loop
) as session
:
843 _all_tenants
= all_tenants
846 # item_id = await self._get_item_uuid(session, self.client_to_RO[item], item_id_name,
847 # all_tenants=_all_tenants)
848 outdata
= await self
._create
_item
(session
, self
.client_to_RO
[item
], create_desc
,
849 item_id_name
=item_id_name
, # item_id_name=item_id
850 action
=action
, all_tenants
=_all_tenants
)
851 return remove_envelop(item
, outdata
)
852 except aiohttp
.errors
.ClientOSError
as e
:
853 raise ROClientException(e
, http_code
=504)
854 except asyncio
.TimeoutError
:
855 raise ROClientException("Timeout", http_code
=504)
857 async def attach_datacenter(self
, datacenter
=None, descriptor
=None, descriptor_format
=None, **kwargs
):
860 if isinstance(descriptor
, str):
861 descriptor
= self
._parse
(descriptor
, descriptor_format
)
866 desc
= remove_envelop("vim", descriptor
)
869 # uuid = self._get_item_uuid(session, "datacenters", uuid_name, all_tenants=True)
870 # tenant_text = "/" + self._get_tenant()
872 desc
= self
.update_descriptor(desc
, kwargs
)
874 if not desc
.get("vim_tenant_name") and not desc
.get("vim_tenant_id"):
875 raise ROClientException("Wrong descriptor. At least vim_tenant_name or vim_tenant_id must be provided")
876 create_desc
= self
._create
_envelop
("vim", desc
)
877 payload_req
= yaml
.safe_dump(create_desc
)
878 with aiohttp
.ClientSession(loop
=self
.loop
) as session
:
880 item_id
= await self
._get
_item
_uuid
(session
, "datacenters", datacenter
, all_tenants
=True)
881 await self
._get
_tenant
(session
)
883 url
= "{}/{tenant}/datacenters/{datacenter}".format(self
.endpoint_url
, tenant
=self
.tenant
,
885 self
.logger
.debug("RO POST %s %s", url
, payload_req
)
886 with aiohttp
.Timeout(self
.timeout_large
):
887 async with session
.post(url
, headers
=self
.headers_req
, data
=payload_req
) as response
:
888 response_text
= await response
.read()
889 self
.logger
.debug("POST {} [{}] {}".format(url
, response
.status
, response_text
[:100]))
890 if response
.status
>= 300:
891 raise ROClientException(response_text
, http_code
=response
.status
)
893 response_desc
= self
._parse
_yaml
(response_text
, response
=True)
894 desc
= remove_envelop("vim", response_desc
)
896 except aiohttp
.errors
.ClientOSError
as e
:
897 raise ROClientException(e
, http_code
=504)
898 except asyncio
.TimeoutError
:
899 raise ROClientException("Timeout", http_code
=504)
901 async def detach_datacenter(self
, datacenter
=None):
902 # TODO replace the code with delete_item(vim_account,...)
904 with aiohttp
.ClientSession(loop
=self
.loop
) as session
:
906 item_id
= await self
._get
_item
_uuid
(session
, "datacenters", datacenter
, all_tenants
=False)
907 tenant
= await self
._get
_tenant
(session
)
909 url
= "{}/{tenant}/datacenters/{datacenter}".format(self
.endpoint_url
, tenant
=tenant
,
911 self
.logger
.debug("RO DELETE %s", url
)
912 with aiohttp
.Timeout(self
.timeout_large
):
913 async with session
.delete(url
, headers
=self
.headers_req
) as response
:
914 response_text
= await response
.read()
915 self
.logger
.debug("DELETE {} [{}] {}".format(url
, response
.status
, response_text
[:100]))
916 if response
.status
>= 300:
917 raise ROClientException(response_text
, http_code
=response
.status
)
919 response_desc
= self
._parse
_yaml
(response_text
, response
=True)
920 desc
= remove_envelop("vim", response_desc
)
922 except aiohttp
.errors
.ClientOSError
as e
:
923 raise ROClientException(e
, http_code
=504)
924 except asyncio
.TimeoutError
:
925 raise ROClientException("Timeout", http_code
=504)
927 # TODO convert to asyncio
930 def edit_datacenter(self
, uuid
=None, name
=None, descriptor
=None, descriptor_format
=None, all_tenants
=False,
932 """Edit the parameters of a datacenter
933 Params: must supply a descriptor or/and a parameter to change
934 uuid or/and name. If only name is supplied, there must be only one or an exception is raised
935 descriptor: with format {'datacenter':{params to change info}}
936 must be a dictionary or a json/yaml text.
937 parameters to change can be supplyied by the descriptor or as parameters:
938 new_name: the datacenter name
939 vim_url: the datacenter URL
940 vim_url_admin: the datacenter URL for administrative issues
941 vim_type: the datacenter type, can be openstack or openvim.
942 public: boolean, available to other tenants
943 description: datacenter description
944 Return: Raises an exception on error, not found or found several
945 Obtain a dictionary with format {'datacenter':{new_datacenter_info}}
948 if isinstance(descriptor
, str):
949 descriptor
= self
.parse(descriptor
, descriptor_format
)
953 descriptor
= {"datacenter": {}}
955 raise ROClientException("Missing descriptor")
957 if 'datacenter' not in descriptor
or len(descriptor
) != 1:
958 raise ROClientException("Descriptor must contain only one 'datacenter' field")
960 if param
== 'new_name':
961 descriptor
['datacenter']['name'] = kwargs
[param
]
963 descriptor
['datacenter'][param
] = kwargs
[param
]
964 return self
._edit
_item
("datacenters", descriptor
, uuid
, name
, all_tenants
=None)
966 def edit_scenario(self
, uuid
=None, name
=None, descriptor
=None, descriptor_format
=None, all_tenants
=False, **kwargs
):
967 """Edit the parameters of a scenario
968 Params: must supply a descriptor or/and a parameters to change
969 uuid or/and name. If only name is supplied, there must be only one or an exception is raised
970 descriptor: with format {'scenario':{params to change info}}
971 must be a dictionary or a json/yaml text.
972 parameters to change can be supplyied by the descriptor or as parameters:
973 new_name: the scenario name
974 public: boolean, available to other tenants
975 description: scenario description
976 tenant_id. Propietary tenant
977 Return: Raises an exception on error, not found or found several
978 Obtain a dictionary with format {'scenario':{new_scenario_info}}
981 if isinstance(descriptor
, str):
982 descriptor
= self
.parse(descriptor
, descriptor_format
)
986 descriptor
= {"scenario": {}}
988 raise ROClientException("Missing descriptor")
990 if 'scenario' not in descriptor
or len(descriptor
) > 2:
991 raise ROClientException("Descriptor must contain only one 'scenario' field")
993 if param
== 'new_name':
994 descriptor
['scenario']['name'] = kwargs
[param
]
996 descriptor
['scenario'][param
] = kwargs
[param
]
997 return self
._edit
_item
("scenarios", descriptor
, uuid
, name
, all_tenants
=None)
1000 def vim_action(self
, action
, item
, uuid
=None, all_tenants
=False, **kwargs
):
1001 """Perform an action over a vim
1003 action: can be 'list', 'get'/'show', 'delete' or 'create'
1004 item: can be 'tenants' or 'networks'
1005 uuid: uuid of the tenant/net to show or to delete. Ignore otherwise
1007 datacenter_name, datacenter_id: datacenters to act on, if missing uses classes store datacenter
1008 descriptor, descriptor_format: descriptor needed on creation, can be a dict or a yaml/json str
1009 must be a dictionary or a json/yaml text.
1010 name: for created tenant/net Overwrite descriptor name if any
1011 description: tenant descriptor. Overwrite descriptor description if any
1013 Return: Raises an exception on error
1014 Obtain a dictionary with format {'tenant':{new_tenant_info}}
1016 session
= None # TODO remove when changed to asyncio
1017 if item
not in ("tenants", "networks", "images"):
1018 raise ROClientException("Unknown value for item '{}', must be 'tenants', 'nets' or "
1019 "images".format(str(item
)))
1021 image_actions
= ['list', 'get', 'show', 'delete']
1022 if item
== "images" and action
not in image_actions
:
1023 raise ROClientException("Only available actions for item '{}' are {}\n"
1024 "Requested action was '{}'".format(item
, ', '.join(image_actions
), action
))
1026 tenant_text
= "/any"
1028 tenant_text
= "/" + self
._get
_tenant
()
1030 if "datacenter_id" in kwargs
or "datacenter_name" in kwargs
:
1031 datacenter
= self
._get
_item
_uuid
(session
, "datacenters", kwargs
.get("datacenter"), all_tenants
=all_tenants
)
1033 datacenter
= self
.get_datacenter(session
)
1035 if action
== "list":
1036 url
= "{}{}/vim/{}/{}".format(self
.endpoint_url
, tenant_text
, datacenter
, item
)
1037 self
.logger
.debug("GET %s", url
)
1038 mano_response
= requests
.get(url
, headers
=self
.headers_req
)
1039 self
.logger
.debug("RO response: %s", mano_response
.text
)
1040 content
= self
._parse
_yaml
(mano_response
.text
, response
=True)
1041 if mano_response
.status_code
== 200:
1044 raise ROClientException(str(content
), http_code
=mano_response
.status
)
1045 elif action
== "get" or action
== "show":
1046 url
= "{}{}/vim/{}/{}/{}".format(self
.endpoint_url
, tenant_text
, datacenter
, item
, uuid
)
1047 self
.logger
.debug("GET %s", url
)
1048 mano_response
= requests
.get(url
, headers
=self
.headers_req
)
1049 self
.logger
.debug("RO response: %s", mano_response
.text
)
1050 content
= self
._parse
_yaml
(mano_response
.text
, response
=True)
1051 if mano_response
.status_code
== 200:
1054 raise ROClientException(str(content
), http_code
=mano_response
.status
)
1055 elif action
== "delete":
1056 url
= "{}{}/vim/{}/{}/{}".format(self
.endpoint_url
, tenant_text
, datacenter
, item
, uuid
)
1057 self
.logger
.debug("DELETE %s", url
)
1058 mano_response
= requests
.delete(url
, headers
=self
.headers_req
)
1059 self
.logger
.debug("RO response: %s", mano_response
.text
)
1060 content
= self
._parse
_yaml
(mano_response
.text
, response
=True)
1061 if mano_response
.status_code
== 200:
1064 raise ROClientException(str(content
), http_code
=mano_response
.status
)
1065 elif action
== "create":
1066 if "descriptor" in kwargs
:
1067 if isinstance(kwargs
["descriptor"], str):
1068 descriptor
= self
._parse
(kwargs
["descriptor"], kwargs
.get("descriptor_format"))
1070 descriptor
= kwargs
["descriptor"]
1071 elif "name" in kwargs
:
1072 descriptor
= {item
[:-1]: {"name": kwargs
["name"]}}
1074 raise ROClientException("Missing descriptor")
1076 if item
[:-1] not in descriptor
or len(descriptor
) != 1:
1077 raise ROClientException("Descriptor must contain only one 'tenant' field")
1078 if "name" in kwargs
:
1079 descriptor
[item
[:-1]]['name'] = kwargs
["name"]
1080 if "description" in kwargs
:
1081 descriptor
[item
[:-1]]['description'] = kwargs
["description"]
1082 payload_req
= yaml
.safe_dump(descriptor
)
1084 url
= "{}{}/vim/{}/{}".format(self
.endpoint_url
, tenant_text
, datacenter
, item
)
1085 self
.logger
.debug("RO POST %s %s", url
, payload_req
)
1086 mano_response
= requests
.post(url
, headers
=self
.headers_req
, data
=payload_req
)
1087 self
.logger
.debug("RO response: %s", mano_response
.text
)
1088 content
= self
._parse
_yaml
(mano_response
.text
, response
=True)
1089 if mano_response
.status_code
== 200:
1092 raise ROClientException(str(content
), http_code
=mano_response
.status
)
1094 raise ROClientException("Unknown value for action '{}".format(str(action
)))
1097 if __name__
== '__main__':
1098 RO_URL
= "http://localhost:9090/openmano"
1099 TEST_TENANT
= "myTenant"
1101 TEST_URL1
= "https://localhost:5000/v1"
1102 TEST_TYPE1
= "openstack"
1103 TEST_CONFIG1
= {"use_floating_ip": True}
1104 TEST_VIM2
= "myvim2"
1105 TEST_URL2
= "https://localhost:5000/v2"
1106 TEST_TYPE2
= "openvim"
1107 TEST_CONFIG2
= {"config2": "config2", "config3": True}
1109 streamformat
= "%(asctime)s %(name)s %(levelname)s: %(message)s"
1110 logging
.basicConfig(format
=streamformat
)
1111 logger
= logging
.getLogger("ROClient")
1115 loop
= asyncio
.get_event_loop()
1116 myClient
= ROClient(endpoint_url
=RO_URL
, loop
=loop
, loglevel
="DEBUG")
1119 content
= loop
.run_until_complete(myClient
.get_list("tenant"))
1120 print("tenants", content
)
1121 content
= loop
.run_until_complete(myClient
.create("tenant", name
=TEST_TENANT
))
1123 content
= loop
.run_until_complete(myClient
.show("tenant", TEST_TENANT
))
1124 print("tenant", TEST_TENANT
, content
)
1125 content
= loop
.run_until_complete(myClient
.edit("tenant", TEST_TENANT
, description
="another description"))
1126 content
= loop
.run_until_complete(myClient
.show("tenant", TEST_TENANT
))
1127 print("tenant edited", TEST_TENANT
, content
)
1128 myClient
["tenant"] = TEST_TENANT
1131 content
= loop
.run_until_complete(myClient
.create("vim", name
=TEST_VIM1
, type=TEST_TYPE1
, vim_url
=TEST_URL1
,
1132 config
=TEST_CONFIG1
))
1134 content
= loop
.run_until_complete(myClient
.get_list("vim"))
1135 print("vim", content
)
1136 content
= loop
.run_until_complete(myClient
.show("vim", TEST_VIM1
))
1137 print("vim", TEST_VIM1
, content
)
1138 content
= loop
.run_until_complete(myClient
.edit("vim", TEST_VIM1
, description
="another description",
1139 name
=TEST_VIM2
, type=TEST_TYPE2
, vim_url
=TEST_URL2
,
1140 config
=TEST_CONFIG2
))
1141 content
= loop
.run_until_complete(myClient
.show("vim", TEST_VIM2
))
1142 print("vim edited", TEST_VIM2
, content
)
1145 content
= loop
.run_until_complete(myClient
.attach_datacenter(TEST_VIM2
, vim_username
='user',
1146 vim_password
='pass', vim_tenant_name
='vimtenant1',
1147 config
=TEST_CONFIG1
))
1149 content
= loop
.run_until_complete(myClient
.get_list("vim_account"))
1150 print("vim_account", content
)
1151 content
= loop
.run_until_complete(myClient
.show("vim_account", TEST_VIM2
))
1152 print("vim_account", TEST_VIM2
, content
)
1153 content
= loop
.run_until_complete(myClient
.edit("vim_account", TEST_VIM2
, vim_username
='user2',
1154 vim_password
='pass2', vim_tenant_name
="vimtenant2",
1155 config
=TEST_CONFIG2
))
1156 content
= loop
.run_until_complete(myClient
.show("vim_account", TEST_VIM2
))
1157 print("vim_account edited", TEST_VIM2
, content
)
1159 myClient
["vim"] = TEST_VIM2
1161 except Exception as e
:
1162 logger
.error("Error {}".format(e
), exc_info
=True)
1164 for item
in (("vim_account", TEST_VIM1
), ("vim", TEST_VIM1
),
1165 ("vim_account", TEST_VIM2
), ("vim", TEST_VIM2
),
1166 ("tenant", TEST_TENANT
)):
1168 content
= loop
.run_until_complete(myClient
.delete(item
[0], item
[1]))
1169 print("{} {} deleted; {}".format(item
[0], item
[1], content
))
1170 except Exception as e
:
1171 if e
.http_code
== 404:
1172 print("{} {} not present or already deleted".format(item
[0], item
[1]))
1174 logger
.error("Error {}".format(e
), exc_info
=True)