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
36 from urllib
.parse
import quote
38 from copy
import deepcopy
40 __author__
= "Alfonso Tierno, Pablo Montes"
41 __date__
= "$09-Jan-2018 09:09:48$"
42 __version__
= "0.1.0-r470"
43 version_date
= "Jan 2018"
46 class ROClientException(Exception):
47 def __init__(self
, message
, http_code
=400):
48 self
.http_code
= http_code
49 Exception.__init
__(self
, message
)
50 """Common Exception for all openmano client exceptions"""
53 def remove_envelop(item
, indata
=None):
55 Obtain the useful data removing the envelop. It goes through the vnfd or nsd catalog and returns the
57 :param item: can be 'tenant', 'vim', 'vnfd', 'nsd', 'ns'
58 :param indata: Content to be inspected
59 :return: the useful part of indata (a reference, not a new dictionay)
65 if clean_indata
.get('vnfd:vnfd-catalog'):
66 clean_indata
= clean_indata
['vnfd:vnfd-catalog']
67 elif clean_indata
.get('vnfd-catalog'):
68 clean_indata
= clean_indata
['vnfd-catalog']
69 if clean_indata
.get('vnfd'):
70 if not isinstance(clean_indata
['vnfd'], list) or len(clean_indata
['vnfd']) != 1:
71 raise ROClientException("'vnfd' must be a list only one element")
72 clean_indata
= clean_indata
['vnfd'][0]
74 if clean_indata
.get('nsd:nsd-catalog'):
75 clean_indata
= clean_indata
['nsd:nsd-catalog']
76 elif clean_indata
.get('nsd-catalog'):
77 clean_indata
= clean_indata
['nsd-catalog']
78 if clean_indata
.get('nsd'):
79 if not isinstance(clean_indata
['nsd'], list) or len(clean_indata
['nsd']) != 1:
80 raise ROClientException("'nsd' must be a list only one element")
81 clean_indata
= clean_indata
['nsd'][0]
83 if len(indata
) == 1 and "sdn_controller" in indata
:
84 clean_indata
= indata
["sdn_controller"]
85 elif item
== "tenant":
86 if len(indata
) == 1 and "tenant" in indata
:
87 clean_indata
= indata
["tenant"]
88 elif item
in ("vim", "vim_account", "datacenters"):
89 if len(indata
) == 1 and "datacenter" in indata
:
90 clean_indata
= indata
["datacenter"]
91 elif item
== "ns" or item
== "instances":
92 if len(indata
) == 1 and "instance" in indata
:
93 clean_indata
= indata
["instance"]
95 assert False, "remove_envelop with unknown item {}".format(item
)
101 headers_req
= {'Accept': 'application/yaml', 'content-type': 'application/yaml'}
102 client_to_RO
= {'tenant': 'tenants', 'vim': 'datacenters', 'vim_account': 'datacenters', 'sdn': 'sdn_controllers',
103 'vnfd': 'vnfs', 'nsd': 'scenarios',
105 mandatory_for_create
= {
106 'tenant': ("name", ),
107 'vnfd': ("name", "id", "connection-point", "vdu"),
108 'nsd': ("name", "id", "constituent-vnfd"),
109 'ns': ("name", "scenario", "datacenter"),
110 'vim': ("name", "vim_url"),
112 'sdn': ("name", "port", 'ip', 'dpid', 'type'),
117 def __init__(self
, loop
, endpoint_url
, **kwargs
):
119 self
.endpoint_url
= endpoint_url
121 self
.username
= kwargs
.get("username")
122 self
.password
= kwargs
.get("password")
123 self
.tenant_id_name
= kwargs
.get("tenant")
125 self
.datacenter_id_name
= kwargs
.get("datacenter")
126 self
.datacenter
= None
127 logger_name
= kwargs
.get('logger_name', 'ROClient')
128 self
.logger
= logging
.getLogger(logger_name
)
129 if kwargs
.get("loglevel"):
130 self
.logger
.setLevel(kwargs
["loglevel"])
132 requests
= kwargs
.get("TODO remove")
134 def __getitem__(self
, index
):
135 if index
== 'tenant':
136 return self
.tenant_id_name
137 elif index
== 'datacenter':
138 return self
.datacenter_id_name
139 elif index
== 'username':
141 elif index
== 'password':
143 elif index
== 'endpoint_url':
144 return self
.endpoint_url
146 raise KeyError("Invalid key '%s'" %str
(index
))
148 def __setitem__(self
,index
, value
):
149 if index
== 'tenant':
150 self
.tenant_id_name
= value
151 elif index
== 'datacenter' or index
== 'vim':
152 self
.datacenter_id_name
= value
153 elif index
== 'username':
154 self
.username
= value
155 elif index
== 'password':
156 self
.password
= value
157 elif index
== 'endpoint_url':
158 self
.endpoint_url
= value
160 raise KeyError("Invalid key '{}'".format(index
))
161 self
.tenant
= None # force to reload tenant with different credentials
162 self
.datacenter
= None # force to reload datacenter with different credentials
164 def _parse(self
, descriptor
, descriptor_format
, response
=False):
166 if descriptor_format
and descriptor_format
!= "json" and descriptor_format
!= "yaml":
167 raise ROClientException("'descriptor_format' must be a 'json' or 'yaml' text")
168 if descriptor_format
!= "json":
170 return yaml
.load(descriptor
)
171 except yaml
.YAMLError
as exc
:
173 if hasattr(exc
, 'problem_mark'):
174 mark
= exc
.problem_mark
175 error_pos
= " at line:{} column:{}s".format(mark
.line
+1, mark
.column
+1)
176 error_text
= "yaml format error" + error_pos
177 elif descriptor_format
!= "yaml":
179 return json
.loads(descriptor
)
180 except Exception as e
:
182 error_text
= "json format error" + str(e
)
185 raise ROClientException(error_text
)
186 raise ROClientException(error_text
)
188 def _parse_yaml(self
, descriptor
, response
=False):
190 return yaml
.load(descriptor
)
191 except yaml
.YAMLError
as exc
:
193 if hasattr(exc
, 'problem_mark'):
194 mark
= exc
.problem_mark
195 error_pos
= " at line:{} column:{}s".format(mark
.line
+1, mark
.column
+1)
196 error_text
= "yaml format error" + error_pos
198 raise ROClientException(error_text
)
199 raise ROClientException(error_text
)
202 def check_if_uuid(uuid_text
):
204 Check if text correspond to an uuid foramt
206 :return: True if it is an uuid False if not
211 except (ValueError, TypeError):
215 def _create_envelop(item
, indata
=None):
217 Returns a new dict that incledes indata with the expected envelop
218 :param item: can be 'tenant', 'vim', 'vnfd', 'nsd', 'ns'
219 :param indata: Content to be enveloped
220 :return: a new dic with {<envelop>: {indata} } where envelop can be e.g. tenant, datacenter, ...
223 return {'vnfd-catalog': {'vnfd': [indata
]}}
225 return {'nsd-catalog': {'nsd': [indata
]}}
226 elif item
== "tenant":
227 return {'tenant': indata
}
228 elif item
in ("vim", "vim_account", "datacenter"):
229 return {'datacenter': indata
}
230 elif item
== "ns" or item
== "instances":
231 return {'instance': indata
}
233 return {'sdn_controller': indata
}
235 assert False, "_create_envelop with unknown item {}".format(item
)
238 def update_descriptor(desc
, kwargs
):
239 desc
= deepcopy(desc
) # do not modify original descriptor
241 for k
, v
in kwargs
.items():
242 update_content
= desc
246 if kitem_old
is not None:
247 update_content
= update_content
[kitem_old
]
248 if isinstance(update_content
, dict):
250 elif isinstance(update_content
, list):
251 kitem_old
= int(kitem
)
253 raise ROClientException(
254 "Invalid query string '{}'. Descriptor is not a list nor dict at '{}'".format(k
, kitem
))
255 if v
== "__DELETE__":
256 del update_content
[kitem_old
]
258 update_content
[kitem_old
] = v
261 raise ROClientException(
262 "Invalid query string '{}'. Descriptor does not contain '{}'".format(k
, kitem_old
))
264 raise ROClientException("Invalid query string '{}'. Expected integer index list instead of '{}'".format(
267 raise ROClientException(
268 "Invalid query string '{}'. Index '{}' out of range".format(k
, kitem_old
))
271 def check_ns_status(ns_descriptor
):
273 Inspect RO instance descriptor and indicates the status
274 :param ns_descriptor: instance descriptor obtained with self.show("ns", )
275 :return: status, message: status can be BUILD,ACTIVE,ERROR, message is a text message
282 for net
in ns_descriptor
["nets"]:
284 if net
["status"] in ("ERROR", "VIM_ERROR"):
285 return "ERROR", net
["error_msg"]
286 elif net
["status"] == "ACTIVE":
288 for vnf
in ns_descriptor
["vnfs"]:
289 for vm
in vnf
["vms"]:
291 if vm
["status"] in ("ERROR", "VIM_ERROR"):
292 return "ERROR", vm
["error_msg"]
293 elif vm
["status"] == "ACTIVE":
296 if net_total
== net_done
and vm_total
== vm_done
:
297 return "ACTIVE", "VMs {}, networks: {}".format(vm_total
, net_total
)
299 return "BUILD", "VMs: {}/{}, networks: {}/{}".format(vm_done
, vm_total
, net_done
, net_total
)
302 def get_ns_vnf_info(ns_descriptor
):
304 Get a dict with the VIM_id, ip_addresses, mac_addresses of every vnf and vdu
305 :param ns_descriptor: instance descriptor obtained with self.show("ns", )
306 :return: dict with {<member_vnf_index>: {ip_address: XXXX, vdur:{ip_address: XXX, vim_id: XXXX}}}
309 for vnf
in ns_descriptor
["vnfs"]:
310 if not vnf
.get("ip_address"):
311 raise ROClientException("No ip_address returned for ns member_vnf_index '{}'".format(
312 vnf
["member_vnf_index"]), http_code
=500)
314 "ip_address": vnf
.get("ip_address"),
317 for vm
in vnf
["vms"]:
319 "vim_id": vm
.get("vim_vm_id"),
320 "ip_address": vm
.get("ip_address")
322 vnfr_info
["vdur"][vm
["vdu_osm_id"]] = vdur
323 ns_info
[str(vnf
["member_vnf_index"])] = vnfr_info
327 async def _get_item_uuid(self
, session
, item
, item_id_name
, all_tenants
=False):
330 elif all_tenants
is None:
334 await self
._get
_tenant
(session
)
335 tenant_text
= "/" + self
.tenant
338 url
= "{}{}/{}".format(self
.endpoint_url
, tenant_text
, item
)
339 if self
.check_if_uuid(item_id_name
):
340 item_id
= item_id_name
341 url
+= "/" + item_id_name
342 elif item_id_name
and item_id_name
.startswith("'") and item_id_name
.endswith("'"):
343 item_id_name
= item_id_name
[1:-1]
344 self
.logger
.debug("openmano GET %s", url
)
345 with aiohttp
.Timeout(self
.timeout_short
):
346 async with session
.get(url
, headers
=self
.headers_req
) as response
:
347 response_text
= await response
.read()
348 self
.logger
.debug("GET {} [{}] {}".format(url
, response
.status
, response_text
[:100]))
349 if response
.status
== 404: # NOT_FOUND
350 raise ROClientException("No {} found with id '{}'".format(item
[:-1], item_id_name
),
352 if response
.status
>= 300:
353 raise ROClientException(response_text
, http_code
=response
.status
)
354 content
= self
._parse
_yaml
(response_text
, response
=True)
359 assert isinstance(desc
, list), "_get_item_uuid get a non dict with a list inside {}".format(type(desc
))
362 if item_id_name
and i
["name"] != item_id_name
:
364 if uuid
: # found more than one
365 raise ROClientException(
366 "Found more than one {} with name '{}'. uuid must be used".format(item
, item_id_name
),
370 raise ROClientException("No {} found with name '{}'".format(item
[:-1], item_id_name
), http_code
=404)
373 async def _get_item(self
, session
, item
, item_id_name
, all_tenants
=False):
376 elif all_tenants
is None:
380 await self
._get
_tenant
(session
)
381 tenant_text
= "/" + self
.tenant
383 if self
.check_if_uuid(item_id_name
):
387 uuid
= await self
._get
_item
_uuid
(session
, item
, item_id_name
, all_tenants
)
389 url
= "{}{}/{}/{}".format(self
.endpoint_url
, tenant_text
, item
, uuid
)
390 self
.logger
.debug("GET %s", url
)
391 with aiohttp
.Timeout(self
.timeout_short
):
392 async with session
.get(url
, headers
=self
.headers_req
) as response
:
393 response_text
= await response
.read()
394 self
.logger
.debug("GET {} [{}] {}".format(url
, response
.status
, response_text
[:100]))
395 if response
.status
>= 300:
396 raise ROClientException(response_text
, http_code
=response
.status
)
398 return self
._parse
_yaml
(response_text
, response
=True)
400 async def _get_tenant(self
, session
):
402 self
.tenant
= await self
._get
_item
_uuid
(session
, "tenants", self
.tenant_id_name
, None)
405 async def _get_datacenter(self
, session
):
407 await self
._get
_tenant
(session
)
408 if not self
.datacenter
:
409 self
.datacenter
= await self
._get
_item
_uuid
(session
, "datacenters", self
.datacenter_id_name
, True)
410 return self
.datacenter
412 async def _create_item(self
, session
, item
, descriptor
, item_id_name
=None, action
=None, all_tenants
=False):
415 elif all_tenants
is None:
419 await self
._get
_tenant
(session
)
420 tenant_text
= "/" + self
.tenant
421 payload_req
= yaml
.safe_dump(descriptor
)
424 api_version_text
= ""
426 # assumes version v3 only
427 api_version_text
= "/v3"
429 elif item
== "scenarios":
430 # assumes version v3 only
431 api_version_text
= "/v3"
436 elif self
.check_if_uuid(item_id_name
):
437 uuid
= "/{}".format(item_id_name
)
440 uuid
= await self
._get
_item
_uuid
(session
, item
, item_id_name
, all_tenants
)
441 uuid
= "/{}".format(uuid
)
445 action
= "/".format(action
)
447 url
= "{}{apiver}{tenant}/{item}{id}{action}".format(self
.endpoint_url
, apiver
=api_version_text
, tenant
=tenant_text
,
448 item
=item
, id=uuid
, action
=action
)
449 self
.logger
.debug("openmano POST %s %s", url
, payload_req
)
450 with aiohttp
.Timeout(self
.timeout_large
):
451 async with session
.post(url
, headers
=self
.headers_req
, data
=payload_req
) as response
:
452 response_text
= await response
.read()
453 self
.logger
.debug("POST {} [{}] {}".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 _del_item(self
, session
, item
, item_id_name
, all_tenants
=False):
462 elif all_tenants
is None:
466 await self
._get
_tenant
(session
)
467 tenant_text
= "/" + self
.tenant
468 if not self
.check_if_uuid(item_id_name
):
470 _all_tenants
= all_tenants
471 if item
== "datacenters":
473 uuid
= await self
._get
_item
_uuid
(session
, item
, item_id_name
, all_tenants
=_all_tenants
)
477 url
= "{}{}/{}/{}".format(self
.endpoint_url
, tenant_text
, item
, uuid
)
478 self
.logger
.debug("DELETE %s", url
)
479 with aiohttp
.Timeout(self
.timeout_short
):
480 async with session
.delete(url
, headers
=self
.headers_req
) as response
:
481 response_text
= await response
.read()
482 self
.logger
.debug("DELETE {} [{}] {}".format(url
, response
.status
, response_text
[:100]))
483 if response
.status
>= 300:
484 raise ROClientException(response_text
, http_code
=response
.status
)
485 return self
._parse
_yaml
(response_text
, response
=True)
487 async def _list_item(self
, session
, item
, all_tenants
=False, filter_dict
=None):
490 elif all_tenants
is None:
494 await self
._get
_tenant
(session
)
495 tenant_text
= "/" + self
.tenant
497 url
= "{}{}/{}".format(self
.endpoint_url
, tenant_text
, item
)
500 for k
in filter_dict
:
501 url
+= separator
+ quote(str(k
)) + "=" + quote(str(filter_dict
[k
]))
503 self
.logger
.debug("openmano GET %s", url
)
504 with aiohttp
.Timeout(self
.timeout_short
):
505 async with session
.get(url
, headers
=self
.headers_req
) as response
:
506 response_text
= await response
.read()
507 self
.logger
.debug("GET {} [{}] {}".format(url
, response
.status
, response_text
[:100]))
508 if response
.status
>= 300:
509 raise ROClientException(response_text
, http_code
=response
.status
)
510 return self
._parse
_yaml
(response_text
, response
=True)
512 async def _edit_item(self
, session
, item
, item_id
, descriptor
, all_tenants
=False):
515 elif all_tenants
is None:
519 await self
._get
_tenant
(session
)
520 tenant_text
= "/" + self
.tenant
522 payload_req
= yaml
.safe_dump(descriptor
)
526 url
= "{}{}/{}/{}".format(self
.endpoint_url
, tenant_text
, item
, item_id
)
527 self
.logger
.debug("openmano PUT %s %s", url
, payload_req
)
528 with aiohttp
.Timeout(self
.timeout_large
):
529 async with session
.put(url
, headers
=self
.headers_req
, data
=payload_req
) as response
:
530 response_text
= await response
.read()
531 self
.logger
.debug("PUT {} [{}] {}".format(url
, response
.status
, response_text
[:100]))
532 if response
.status
>= 300:
533 raise ROClientException(response_text
, http_code
=response
.status
)
534 return self
._parse
_yaml
(response_text
, response
=True)
536 async def get_list(self
, item
, all_tenants
=False, filter_by
=None):
538 Obtain a list of items filtering by the specigy filter_by.
539 :param item: can be 'tenant', 'vim', 'vnfd', 'nsd', 'ns'
540 :param all_tenants: True if not filtering by tenant. Only allowed for admin
541 :param filter_by: dictionary with filtering
542 :return: a list of dict. It can be empty. Raises ROClientException on Error,
545 if item
not in self
.client_to_RO
:
546 raise ROClientException("Invalid item {}".format(item
))
549 with aiohttp
.ClientSession(loop
=self
.loop
) as session
:
550 content
= await self
._list
_item
(session
, self
.client_to_RO
[item
], all_tenants
=all_tenants
,
551 filter_dict
=filter_by
)
552 if isinstance(content
, dict):
553 if len(content
) == 1:
554 for _
, v
in content
.items():
556 return content
.values()[0]
558 raise ROClientException("Output not a list neither dict with len equal 1", http_code
=500)
560 except aiohttp
.errors
.ClientOSError
as e
:
561 raise ROClientException(e
, http_code
=504)
563 async def show(self
, item
, item_id_name
=None, all_tenants
=False):
565 Obtain the information of an item from its id or name
566 :param item: can be 'tenant', 'vim', 'vnfd', 'nsd', 'ns'
567 :param item_id_name: RO id or name of the item. Raise and exception if more than one found
568 :param all_tenants: True if not filtering by tenant. Only allowed for admin
569 :return: dictionary with the information or raises ROClientException on Error, NotFound, found several
572 if item
not in self
.client_to_RO
:
573 raise ROClientException("Invalid item {}".format(item
))
578 elif item
== 'vim_account':
581 with aiohttp
.ClientSession(loop
=self
.loop
) as session
:
582 content
= await self
._get
_item
(session
, self
.client_to_RO
[item
], item_id_name
, all_tenants
=all_tenants
)
583 return remove_envelop(item
, content
)
584 except aiohttp
.errors
.ClientOSError
as e
:
585 raise ROClientException(e
, http_code
=504)
587 async def delete(self
, item
, item_id_name
=None, all_tenants
=False):
589 Delete the information of an item from its id or name
590 :param item: can be 'tenant', 'vim', 'vnfd', 'nsd', 'ns'
591 :param item_id_name: RO id or name of the item. Raise and exception if more than one found
592 :param all_tenants: True if not filtering by tenant. Only allowed for admin
593 :return: dictionary with the information or raises ROClientException on Error, NotFound, found several
596 if item
not in self
.client_to_RO
:
597 raise ROClientException("Invalid item {}".format(item
))
598 if item
== 'tenant' or item
== 'vim':
601 with aiohttp
.ClientSession(loop
=self
.loop
) as session
:
602 return await self
._del
_item
(session
, self
.client_to_RO
[item
], item_id_name
, all_tenants
=all_tenants
)
603 except aiohttp
.errors
.ClientOSError
as e
:
604 raise ROClientException(e
, http_code
=504)
606 async def edit(self
, item
, item_id_name
, descriptor
=None, descriptor_format
=None, **kwargs
):
608 :param item: can be 'tenant', 'vim', 'vnfd', 'nsd', 'ns', 'vim'
609 :param descriptor: can be a dict, or a yaml/json text. Autodetect unless descriptor_format is provided
610 :param descriptor_format: Can be 'json' or 'yaml'
611 :param kwargs: Overrides descriptor with values as name, description, vim_url, vim_url_admin, vim_type
612 keys can be a dot separated list to specify elements inside dict
613 :return: dictionary with the information or raises ROClientException on Error
616 if isinstance(descriptor
, str):
617 descriptor
= self
._parse
(descriptor
, descriptor_format
)
623 if item
not in self
.client_to_RO
:
624 raise ROClientException("Invalid item {}".format(item
))
625 desc
= remove_envelop(item
, descriptor
)
627 # Override descriptor with kwargs
629 desc
= self
.update_descriptor(desc
, kwargs
)
631 if item
in ('tenant', 'vim'):
634 create_desc
= self
._create
_envelop
(item
, desc
)
636 with aiohttp
.ClientSession(loop
=self
.loop
) as session
:
637 _all_tenants
= all_tenants
640 item_id
= await self
._get
_item
_uuid
(session
, self
.client_to_RO
[item
], item_id_name
, all_tenants
=_all_tenants
)
641 # await self._get_tenant(session)
642 outdata
= await self
._edit
_item
(session
, self
.client_to_RO
[item
], item_id
, create_desc
, all_tenants
=all_tenants
)
643 return remove_envelop(item
, outdata
)
644 except aiohttp
.errors
.ClientOSError
as e
:
645 raise ROClientException(e
, http_code
=504)
647 async def create(self
, item
, descriptor
=None, descriptor_format
=None, **kwargs
):
649 Creates an item from its descriptor
650 :param item: can be 'tenant', 'vnfd', 'nsd', 'ns', 'vim', 'vim_account', 'sdn'
651 :param descriptor: can be a dict, or a yaml/json text. Autodetect unless descriptor_format is provided
652 :param descriptor_format: Can be 'json' or 'yaml'
653 :param kwargs: Overrides descriptor with values as name, description, vim_url, vim_url_admin, vim_type
654 keys can be a dot separated list to specify elements inside dict
655 :return: dictionary with the information or raises ROClientException on Error
658 if isinstance(descriptor
, str):
659 descriptor
= self
._parse
(descriptor
, descriptor_format
)
665 if item
not in self
.client_to_RO
:
666 raise ROClientException("Invalid item {}".format(item
))
667 desc
= remove_envelop(item
, descriptor
)
669 # Override descriptor with kwargs
671 desc
= self
.update_descriptor(desc
, kwargs
)
673 for mandatory
in self
.mandatory_for_create
[item
]:
674 if mandatory
not in desc
:
675 raise ROClientException("'{}' is mandatory parameter for {}".format(mandatory
, item
))
678 if item
in ('tenant', 'vim'):
681 create_desc
= self
._create
_envelop
(item
, desc
)
683 with aiohttp
.ClientSession(loop
=self
.loop
) as session
:
684 outdata
= await self
._create
_item
(session
, self
.client_to_RO
[item
], create_desc
,
685 all_tenants
=all_tenants
)
686 return remove_envelop(item
, outdata
)
687 except aiohttp
.errors
.ClientOSError
as e
:
688 raise ROClientException(e
, http_code
=504)
690 async def attach_datacenter(self
, datacenter
=None, descriptor
=None, descriptor_format
=None, **kwargs
):
692 if isinstance(descriptor
, str):
693 descriptor
= self
._parse
(descriptor
, descriptor_format
)
698 desc
= remove_envelop("vim", descriptor
)
701 # uuid = self._get_item_uuid(session, "datacenters", uuid_name, all_tenants=True)
702 # tenant_text = "/" + self._get_tenant()
704 desc
= self
.update_descriptor(desc
, kwargs
)
706 if not desc
.get("vim_tenant_name") and not desc
.get("vim_tenant_id"):
707 raise ROClientException("Wrong descriptor. At least vim_tenant_name or vim_tenant_id must be provided")
708 create_desc
= self
._create
_envelop
("vim", desc
)
709 payload_req
= yaml
.safe_dump(create_desc
)
710 with aiohttp
.ClientSession(loop
=self
.loop
) as session
:
712 item_id
= await self
._get
_item
_uuid
(session
, "datacenters", datacenter
, all_tenants
=True)
713 await self
._get
_tenant
(session
)
715 url
= "{}/{tenant}/datacenters/{datacenter}".format(self
.endpoint_url
, tenant
=self
.tenant
,
717 self
.logger
.debug("openmano POST %s %s", url
, payload_req
)
718 with aiohttp
.Timeout(self
.timeout_large
):
719 async with session
.post(url
, headers
=self
.headers_req
, data
=payload_req
) as response
:
720 response_text
= await response
.read()
721 self
.logger
.debug("POST {} [{}] {}".format(url
, response
.status
, response_text
[:100]))
722 if response
.status
>= 300:
723 raise ROClientException(response_text
, http_code
=response
.status
)
725 response_desc
= self
._parse
_yaml
(response_text
, response
=True)
726 desc
= remove_envelop("vim", response_desc
)
729 async def detach_datacenter(self
, datacenter
=None):
730 #TODO replace the code with delete_item(vim_account,...)
731 with aiohttp
.ClientSession(loop
=self
.loop
) as session
:
733 item_id
= await self
._get
_item
_uuid
(session
, "datacenters", datacenter
, all_tenants
=False)
734 tenant
= await self
._get
_tenant
(session
)
736 url
= "{}/{tenant}/datacenters/{datacenter}".format(self
.endpoint_url
, tenant
=tenant
,
738 self
.logger
.debug("openmano DELETE %s", url
)
739 with aiohttp
.Timeout(self
.timeout_large
):
740 async with session
.delete(url
, headers
=self
.headers_req
) as response
:
741 response_text
= await response
.read()
742 self
.logger
.debug("DELETE {} [{}] {}".format(url
, response
.status
, response_text
[:100]))
743 if response
.status
>= 300:
744 raise ROClientException(response_text
, http_code
=response
.status
)
746 response_desc
= self
._parse
_yaml
(response_text
, response
=True)
747 desc
= remove_envelop("vim", response_desc
)
751 # TODO convert to asyncio
755 def edit_datacenter(self
, uuid
=None, name
=None, descriptor
=None, descriptor_format
=None, all_tenants
=False, **kwargs
):
756 """Edit the parameters of a datacenter
757 Params: must supply a descriptor or/and a parameter to change
758 uuid or/and name. If only name is supplied, there must be only one or an exception is raised
759 descriptor: with format {'datacenter':{params to change info}}
760 must be a dictionary or a json/yaml text.
761 parameters to change can be supplyied by the descriptor or as parameters:
762 new_name: the datacenter name
763 vim_url: the datacenter URL
764 vim_url_admin: the datacenter URL for administrative issues
765 vim_type: the datacenter type, can be openstack or openvim.
766 public: boolean, available to other tenants
767 description: datacenter description
768 Return: Raises an exception on error, not found or found several
769 Obtain a dictionary with format {'datacenter':{new_datacenter_info}}
772 if isinstance(descriptor
, str):
773 descriptor
= self
.parse(descriptor
, descriptor_format
)
777 descriptor
={"datacenter": {}}
779 raise ROClientException("Missing descriptor")
781 if 'datacenter' not in descriptor
or len(descriptor
)!=1:
782 raise ROClientException("Descriptor must contain only one 'datacenter' field")
784 if param
=='new_name':
785 descriptor
['datacenter']['name'] = kwargs
[param
]
787 descriptor
['datacenter'][param
] = kwargs
[param
]
788 return self
._edit
_item
("datacenters", descriptor
, uuid
, name
, all_tenants
=None)
791 def edit_scenario(self
, uuid
=None, name
=None, descriptor
=None, descriptor_format
=None, all_tenants
=False, **kwargs
):
792 """Edit the parameters of a scenario
793 Params: must supply a descriptor or/and a parameters to change
794 uuid or/and name. If only name is supplied, there must be only one or an exception is raised
795 descriptor: with format {'scenario':{params to change info}}
796 must be a dictionary or a json/yaml text.
797 parameters to change can be supplyied by the descriptor or as parameters:
798 new_name: the scenario name
799 public: boolean, available to other tenants
800 description: scenario description
801 tenant_id. Propietary tenant
802 Return: Raises an exception on error, not found or found several
803 Obtain a dictionary with format {'scenario':{new_scenario_info}}
806 if isinstance(descriptor
, str):
807 descriptor
= self
.parse(descriptor
, descriptor_format
)
811 descriptor
={"scenario": {}}
813 raise ROClientException("Missing descriptor")
815 if 'scenario' not in descriptor
or len(descriptor
)>2:
816 raise ROClientException("Descriptor must contain only one 'scenario' field")
818 if param
=='new_name':
819 descriptor
['scenario']['name'] = kwargs
[param
]
821 descriptor
['scenario'][param
] = kwargs
[param
]
822 return self
._edit
_item
("scenarios", descriptor
, uuid
, name
, all_tenants
=None)
825 def vim_action(self
, action
, item
, uuid
=None, all_tenants
=False, **kwargs
):
826 """Perform an action over a vim
828 action: can be 'list', 'get'/'show', 'delete' or 'create'
829 item: can be 'tenants' or 'networks'
830 uuid: uuid of the tenant/net to show or to delete. Ignore otherwise
832 datacenter_name, datacenter_id: datacenters to act on, if missing uses classes store datacenter
833 descriptor, descriptor_format: descriptor needed on creation, can be a dict or a yaml/json str
834 must be a dictionary or a json/yaml text.
835 name: for created tenant/net Overwrite descriptor name if any
836 description: tenant descriptor. Overwrite descriptor description if any
838 Return: Raises an exception on error
839 Obtain a dictionary with format {'tenant':{new_tenant_info}}
841 if item
not in ("tenants", "networks", "images"):
842 raise ROClientException("Unknown value for item '{}', must be 'tenants', 'nets' or "
843 "images".format(str(item
)))
845 image_actions
= ['list','get','show','delete']
846 if item
== "images" and action
not in image_actions
:
847 raise ROClientException("Only available actions for item '{}' are {}\n"
848 "Requested action was '{}'".format(item
, ', '.join(image_actions
), action
))
852 tenant_text
= "/"+self
._get
_tenant
()
854 if "datacenter_id" in kwargs
or "datacenter_name" in kwargs
:
855 datacenter
= self
._get
_item
_uuid
(session
, "datacenters", kwargs
.get("datacenter"), all_tenants
=all_tenants
)
857 datacenter
= self
.get_datacenter(session
)
860 url
= "{}{}/vim/{}/{}".format(self
.endpoint_url
, tenant_text
, datacenter
, item
)
861 self
.logger
.debug("GET %s", url
)
862 mano_response
= requests
.get(url
, headers
=self
.headers_req
)
863 self
.logger
.debug("openmano response: %s", mano_response
.text
)
864 content
= self
._parse
_yaml
(mano_response
.text
, response
=True)
865 if mano_response
.status_code
==200:
868 raise ROClientException(str(content
), http_code
=mano_response
.status
)
869 elif action
=="get" or action
=="show":
870 url
= "{}{}/vim/{}/{}/{}".format(self
.endpoint_url
, tenant_text
, datacenter
, item
, uuid
)
871 self
.logger
.debug("GET %s", url
)
872 mano_response
= requests
.get(url
, headers
=self
.headers_req
)
873 self
.logger
.debug("openmano response: %s", mano_response
.text
)
874 content
= self
._parse
_yaml
(mano_response
.text
, response
=True)
875 if mano_response
.status_code
==200:
878 raise ROClientException(str(content
), http_code
=mano_response
.status
)
879 elif action
=="delete":
880 url
= "{}{}/vim/{}/{}/{}".format(self
.endpoint_url
, tenant_text
, datacenter
, item
, uuid
)
881 self
.logger
.debug("DELETE %s", url
)
882 mano_response
= requests
.delete(url
, headers
=self
.headers_req
)
883 self
.logger
.debug("openmano response: %s", mano_response
.text
)
884 content
= self
._parse
_yaml
(mano_response
.text
, response
=True)
885 if mano_response
.status_code
==200:
888 raise ROClientException(str(content
), http_code
=mano_response
.status
)
889 elif action
=="create":
890 if "descriptor" in kwargs
:
891 if isinstance(kwargs
["descriptor"], str):
892 descriptor
= self
._parse
(kwargs
["descriptor"], kwargs
.get("descriptor_format") )
894 descriptor
= kwargs
["descriptor"]
895 elif "name" in kwargs
:
896 descriptor
={item
[:-1]: {"name": kwargs
["name"]}}
898 raise ROClientException("Missing descriptor")
900 if item
[:-1] not in descriptor
or len(descriptor
)!=1:
901 raise ROClientException("Descriptor must contain only one 'tenant' field")
903 descriptor
[ item
[:-1] ]['name'] = kwargs
["name"]
904 if "description" in kwargs
:
905 descriptor
[ item
[:-1] ]['description'] = kwargs
["description"]
906 payload_req
= yaml
.safe_dump(descriptor
)
908 url
= "{}{}/vim/{}/{}".format(self
.endpoint_url
, tenant_text
, datacenter
, item
)
909 self
.logger
.debug("openmano POST %s %s", url
, payload_req
)
910 mano_response
= requests
.post(url
, headers
= self
.headers_req
, data
=payload_req
)
911 self
.logger
.debug("openmano response: %s", mano_response
.text
)
912 content
= self
._parse
_yaml
(mano_response
.text
, response
=True)
913 if mano_response
.status_code
==200:
916 raise ROClientException(str(content
), http_code
=mano_response
.status
)
918 raise ROClientException("Unknown value for action '{}".format(str(action
)))
921 if __name__
== '__main__':
922 RO_URL
= "http://localhost:9090/openmano"
923 TEST_TENANT
= "myTenant"
925 TEST_URL1
= "https://localhost:5000/v1"
926 TEST_TYPE1
= "openstack"
927 TEST_CONFIG1
= {"use_floating_ip": True}
929 TEST_URL2
= "https://localhost:5000/v2"
930 TEST_TYPE2
= "openvim"
931 TEST_CONFIG2
= {"config2": "config2", "config3": True}
933 streamformat
= "%(asctime)s %(name)s %(levelname)s: %(message)s"
934 logging
.basicConfig(format
=streamformat
)
935 logger
= logging
.getLogger("ROClient")
939 loop
= asyncio
.get_event_loop()
940 myClient
= ROClient(endpoint_url
=RO_URL
, loop
=loop
, loglevel
="DEBUG")
943 content
= loop
.run_until_complete(myClient
.get_list("tenant"))
944 print("tenants", content
)
945 content
= loop
.run_until_complete(myClient
.create("tenant", name
=TEST_TENANT
))
947 content
= loop
.run_until_complete(myClient
.show("tenant", TEST_TENANT
))
948 print("tenant", TEST_TENANT
, content
)
949 content
= loop
.run_until_complete(myClient
.edit("tenant", TEST_TENANT
, description
="another description"))
950 content
= loop
.run_until_complete(myClient
.show("tenant", TEST_TENANT
))
951 print("tenant edited", TEST_TENANT
, content
)
952 myClient
["tenant"] = TEST_TENANT
956 content
= loop
.run_until_complete(myClient
.create("vim", name
=TEST_VIM1
, type=TEST_TYPE1
, vim_url
=TEST_URL1
, config
=TEST_CONFIG1
))
958 content
= loop
.run_until_complete(myClient
.get_list("vim"))
959 print("vim", content
)
960 content
= loop
.run_until_complete(myClient
.show("vim", TEST_VIM1
))
961 print("vim", TEST_VIM1
, content
)
962 content
= loop
.run_until_complete(myClient
.edit("vim", TEST_VIM1
, description
="another description",
963 name
=TEST_VIM2
, type=TEST_TYPE2
, vim_url
=TEST_URL2
,
964 config
=TEST_CONFIG2
))
965 content
= loop
.run_until_complete(myClient
.show("vim", TEST_VIM2
))
966 print("vim edited", TEST_VIM2
, content
)
969 content
= loop
.run_until_complete(myClient
.attach_datacenter(TEST_VIM2
, vim_username
='user',
970 vim_password
='pass', vim_tenant_name
='vimtenant1', config
=TEST_CONFIG1
))
972 content
= loop
.run_until_complete(myClient
.get_list("vim_account"))
973 print("vim_account", content
)
974 content
= loop
.run_until_complete(myClient
.show("vim_account", TEST_VIM2
))
975 print("vim_account", TEST_VIM2
, content
)
976 content
= loop
.run_until_complete(myClient
.edit("vim_account", TEST_VIM2
, vim_username
='user2', vim_password
='pass2',
977 vim_tenant_name
="vimtenant2", config
=TEST_CONFIG2
))
978 content
= loop
.run_until_complete(myClient
.show("vim_account", TEST_VIM2
))
979 print("vim_account edited", TEST_VIM2
, content
)
981 myClient
["vim"] = TEST_VIM2
983 except Exception as e
:
984 logger
.error("Error {}".format(e
), exc_info
=True)
986 for item
in (("vim_account", TEST_VIM1
), ("vim", TEST_VIM1
),
987 ("vim_account", TEST_VIM2
), ("vim", TEST_VIM2
),
988 ("tenant", TEST_TENANT
)):
990 content
= loop
.run_until_complete(myClient
.delete(item
[0], item
[1]))
991 print("{} {} deleted; {}".format(item
[0], item
[1], content
))
992 except Exception as e
:
993 if e
.http_code
== 404:
994 print("{} {} not present or already deleted".format(item
[0], item
[1]))
996 logger
.error("Error {}".format(e
), exc_info
=True)