adding flake8 test
[osm/LCM.git] / osm_lcm / ROclient.py
1 #!/usr/bin/env python3
2 # -*- coding: utf-8 -*-
3
4 ##
5 # Copyright 2015 Telefónica Investigación y Desarrollo, S.A.U.
6 # This file is part of openmano
7 # All Rights Reserved.
8 #
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
12 #
13 # http://www.apache.org/licenses/LICENSE-2.0
14 #
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
19 # under the License.
20 #
21 # For those usages not covered by the Apache License, Version 2.0 please
22 # contact with: nfvlabs@tid.es
23 ##
24
25 """
26 asyncio RO python client to interact with RO-server
27 """
28
29 import asyncio
30 import aiohttp
31 import json
32 import yaml
33 import logging
34 from urllib.parse import quote
35 from uuid import UUID
36 from copy import deepcopy
37
38 __author__ = "Alfonso Tierno"
39 __date__ = "$09-Jan-2018 09:09:48$"
40 __version__ = "0.1.2"
41 version_date = "2018-05-16"
42 requests = None
43
44
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)
50
51
52 def remove_envelop(item, indata=None):
53 """
54 Obtain the useful data removing the envelop. It goes through the vnfd or nsd catalog and returns the
55 vnfd or nsd content
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)
59 """
60 clean_indata = indata
61 if not indata:
62 return {}
63 if item == "vnfd":
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]
72 elif item == "nsd":
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]
81 elif item == "sdn":
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"]
93 else:
94 assert False, "remove_envelop with unknown item {}".format(item)
95
96 return clean_indata
97
98
99 class ROClient:
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',
103 'ns': 'instances'}
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"),
110 'vim_account': (),
111 'sdn': ("name", "port", 'ip', 'dpid', 'type'),
112 }
113 timeout_large = 120
114 timeout_short = 30
115
116 def __init__(self, loop, endpoint_url, **kwargs):
117 self.loop = loop
118 self.endpoint_url = endpoint_url
119
120 self.username = kwargs.get("username")
121 self.password = kwargs.get("password")
122 self.tenant_id_name = kwargs.get("tenant")
123 self.tenant = None
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"])
130 global requests
131 requests = kwargs.get("TODO remove")
132
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':
139 return self.username
140 elif index == 'password':
141 return self.password
142 elif index == 'endpoint_url':
143 return self.endpoint_url
144 else:
145 raise KeyError("Invalid key '{}'".format(index))
146
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
158 else:
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
162
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":
167 try:
168 return yaml.load(descriptor)
169 except yaml.YAMLError as exc:
170 error_pos = ""
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":
176 try:
177 return json.loads(descriptor)
178 except Exception as e:
179 if response:
180 error_text = "json format error" + str(e)
181
182 if response:
183 raise ROClientException(error_text)
184 raise ROClientException(error_text)
185
186 def _parse_yaml(self, descriptor, response=False):
187 try:
188 return yaml.load(descriptor)
189 except yaml.YAMLError as exc:
190 error_pos = ""
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
195 if response:
196 raise ROClientException(error_text)
197 raise ROClientException(error_text)
198
199 @staticmethod
200 def check_if_uuid(uuid_text):
201 """
202 Check if text correspond to an uuid foramt
203 :param uuid_text:
204 :return: True if it is an uuid False if not
205 """
206 try:
207 UUID(uuid_text)
208 return True
209 except (ValueError, TypeError):
210 return False
211
212 @staticmethod
213 def _create_envelop(item, indata=None):
214 """
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, ...
219 """
220 if item == "vnfd":
221 return {'vnfd-catalog': {'vnfd': [indata]}}
222 elif item == "nsd":
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}
230 elif item == "sdn":
231 return {'sdn_controller': indata}
232 else:
233 assert False, "_create_envelop with unknown item {}".format(item)
234
235 @staticmethod
236 def update_descriptor(desc, kwargs):
237 desc = deepcopy(desc) # do not modify original descriptor
238 try:
239 for k, v in kwargs.items():
240 update_content = desc
241 kitem_old = None
242 klist = k.split(".")
243 for kitem in klist:
244 if kitem_old is not None:
245 update_content = update_content[kitem_old]
246 if isinstance(update_content, dict):
247 kitem_old = kitem
248 elif isinstance(update_content, list):
249 kitem_old = int(kitem)
250 else:
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]
255 else:
256 update_content[kitem_old] = v
257 return desc
258 except KeyError:
259 raise ROClientException(
260 "Invalid query string '{}'. Descriptor does not contain '{}'".format(k, kitem_old))
261 except ValueError:
262 raise ROClientException("Invalid query string '{}'. Expected integer index list instead of '{}'".format(
263 k, kitem))
264 except IndexError:
265 raise ROClientException(
266 "Invalid query string '{}'. Index '{}' out of range".format(k, kitem_old))
267
268 @staticmethod
269 def check_ns_status(ns_descriptor):
270 """
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
274 """
275 net_total = 0
276 vm_total = 0
277 net_done = 0
278 vm_done = 0
279
280 for net in ns_descriptor["nets"]:
281 net_total += 1
282 if net["status"] in ("ERROR", "VIM_ERROR"):
283 return "ERROR", net["error_msg"]
284 elif net["status"] == "ACTIVE":
285 net_done += 1
286 for vnf in ns_descriptor["vnfs"]:
287 for vm in vnf["vms"]:
288 vm_total += 1
289 if vm["status"] in ("ERROR", "VIM_ERROR"):
290 return "ERROR", vm["error_msg"]
291 elif vm["status"] == "ACTIVE":
292 vm_done += 1
293
294 if net_total == net_done and vm_total == vm_done:
295 return "ACTIVE", "VMs {}, networks: {}".format(vm_total, net_total)
296 else:
297 return "BUILD", "VMs: {}/{}, networks: {}/{}".format(vm_done, vm_total, net_done, net_total)
298
299 @staticmethod
300 def get_ns_vnf_info(ns_descriptor):
301 """
302 Get a dict with the VIM_id, ip_addresses, mac_addresses of every vnf and vdu
303 :param ns_descriptor: instance descriptor obtained with self.show("ns", )
304 :return: dict with {<member_vnf_index>: {ip_address: XXXX, vdur:{ip_address: XXX, vim_id: XXXX}}}
305 """
306 ns_info = {}
307 for vnf in ns_descriptor["vnfs"]:
308 if not vnf.get("ip_address"):
309 raise ROClientException("ns member_vnf_index '{}' has no IP address".format(
310 vnf["member_vnf_index"]), http_code=409)
311 vnfr_info = {
312 "ip_address": vnf.get("ip_address"),
313 "vdur": {}
314 }
315 for vm in vnf["vms"]:
316 vdur = {
317 "vim_id": vm.get("vim_vm_id"),
318 "ip_address": vm.get("ip_address")
319 }
320 for iface in vm["interfaces"]:
321 if iface.get("type") == "mgmt" and not iface.get("ip_address"):
322 raise ROClientException("ns member_vnf_index '{}' vm '{}' management interface '{}' has no IP "
323 "address".format(vnf["member_vnf_index"], vm["vdu_osm_id"],
324 iface["external_name"]), http_code=409)
325 vnfr_info["vdur"][vm["vdu_osm_id"]] = vdur
326 ns_info[str(vnf["member_vnf_index"])] = vnfr_info
327 return ns_info
328
329 async def _get_item_uuid(self, session, item, item_id_name, all_tenants=False):
330 if all_tenants:
331 tenant_text = "/any"
332 elif all_tenants is None:
333 tenant_text = ""
334 else:
335 if not self.tenant:
336 await self._get_tenant(session)
337 tenant_text = "/" + self.tenant
338
339 item_id = 0
340 url = "{}{}/{}".format(self.endpoint_url, tenant_text, item)
341 if self.check_if_uuid(item_id_name):
342 item_id = item_id_name
343 url += "/" + item_id_name
344 elif item_id_name and item_id_name.startswith("'") and item_id_name.endswith("'"):
345 item_id_name = item_id_name[1:-1]
346 self.logger.debug("RO GET %s", url)
347 with aiohttp.Timeout(self.timeout_short):
348 async with session.get(url, headers=self.headers_req) as response:
349 response_text = await response.read()
350 self.logger.debug("GET {} [{}] {}".format(url, response.status, response_text[:100]))
351 if response.status == 404: # NOT_FOUND
352 raise ROClientException("No {} found with id '{}'".format(item[:-1], item_id_name),
353 http_code=404)
354 if response.status >= 300:
355 raise ROClientException(response_text, http_code=response.status)
356 content = self._parse_yaml(response_text, response=True)
357
358 if item_id:
359 return item_id
360 desc = content[item]
361 assert isinstance(desc, list), "_get_item_uuid get a non dict with a list inside {}".format(type(desc))
362 uuid = None
363 for i in desc:
364 if item_id_name and i["name"] != item_id_name:
365 continue
366 if uuid: # found more than one
367 raise ROClientException(
368 "Found more than one {} with name '{}'. uuid must be used".format(item, item_id_name),
369 http_code=404)
370 uuid = i["uuid"]
371 if not uuid:
372 raise ROClientException("No {} found with name '{}'".format(item[:-1], item_id_name), http_code=404)
373 return uuid
374
375 async def _get_item(self, session, item, item_id_name, all_tenants=False):
376 if all_tenants:
377 tenant_text = "/any"
378 elif all_tenants is None:
379 tenant_text = ""
380 else:
381 if not self.tenant:
382 await self._get_tenant(session)
383 tenant_text = "/" + self.tenant
384
385 if self.check_if_uuid(item_id_name):
386 uuid = item_id_name
387 else:
388 # check that exist
389 uuid = await self._get_item_uuid(session, item, item_id_name, all_tenants)
390
391 url = "{}{}/{}/{}".format(self.endpoint_url, tenant_text, item, uuid)
392 self.logger.debug("GET %s", url)
393 with aiohttp.Timeout(self.timeout_short):
394 async with session.get(url, headers=self.headers_req) as response:
395 response_text = await response.read()
396 self.logger.debug("GET {} [{}] {}".format(url, response.status, response_text[:100]))
397 if response.status >= 300:
398 raise ROClientException(response_text, http_code=response.status)
399
400 return self._parse_yaml(response_text, response=True)
401
402 async def _get_tenant(self, session):
403 if not self.tenant:
404 self.tenant = await self._get_item_uuid(session, "tenants", self.tenant_id_name, None)
405 return self.tenant
406
407 async def _get_datacenter(self, session):
408 if not self.tenant:
409 await self._get_tenant(session)
410 if not self.datacenter:
411 self.datacenter = await self._get_item_uuid(session, "datacenters", self.datacenter_id_name, True)
412 return self.datacenter
413
414 async def _create_item(self, session, item, descriptor, item_id_name=None, action=None, all_tenants=False):
415 if all_tenants:
416 tenant_text = "/any"
417 elif all_tenants is None:
418 tenant_text = ""
419 else:
420 if not self.tenant:
421 await self._get_tenant(session)
422 tenant_text = "/" + self.tenant
423 payload_req = yaml.safe_dump(descriptor)
424 # print payload_req
425
426 api_version_text = ""
427 if item == "vnfs":
428 # assumes version v3 only
429 api_version_text = "/v3"
430 item = "vnfd"
431 elif item == "scenarios":
432 # assumes version v3 only
433 api_version_text = "/v3"
434 item = "nsd"
435
436 if not item_id_name:
437 uuid = ""
438 elif self.check_if_uuid(item_id_name):
439 uuid = "/{}".format(item_id_name)
440 else:
441 # check that exist
442 uuid = await self._get_item_uuid(session, item, item_id_name, all_tenants)
443 uuid = "/{}".format(uuid)
444 if not action:
445 action = ""
446 else:
447 action = "/".format(action)
448
449 url = "{}{apiver}{tenant}/{item}{id}{action}".format(self.endpoint_url, apiver=api_version_text,
450 tenant=tenant_text, item=item, id=uuid, action=action)
451 self.logger.debug("RO POST %s %s", url, payload_req)
452 with aiohttp.Timeout(self.timeout_large):
453 async with session.post(url, headers=self.headers_req, data=payload_req) as response:
454 response_text = await response.read()
455 self.logger.debug("POST {} [{}] {}".format(url, response.status, response_text[:100]))
456 if response.status >= 300:
457 raise ROClientException(response_text, http_code=response.status)
458
459 return self._parse_yaml(response_text, response=True)
460
461 async def _del_item(self, session, item, item_id_name, all_tenants=False):
462 if all_tenants:
463 tenant_text = "/any"
464 elif all_tenants is None:
465 tenant_text = ""
466 else:
467 if not self.tenant:
468 await self._get_tenant(session)
469 tenant_text = "/" + self.tenant
470 if not self.check_if_uuid(item_id_name):
471 # check that exist
472 _all_tenants = all_tenants
473 if item == "datacenters":
474 _all_tenants = True
475 uuid = await self._get_item_uuid(session, item, item_id_name, all_tenants=_all_tenants)
476 else:
477 uuid = item_id_name
478
479 url = "{}{}/{}/{}".format(self.endpoint_url, tenant_text, item, uuid)
480 self.logger.debug("DELETE %s", url)
481 with aiohttp.Timeout(self.timeout_short):
482 async with session.delete(url, headers=self.headers_req) as response:
483 response_text = await response.read()
484 self.logger.debug("DELETE {} [{}] {}".format(url, response.status, response_text[:100]))
485 if response.status >= 300:
486 raise ROClientException(response_text, http_code=response.status)
487 return self._parse_yaml(response_text, response=True)
488
489 async def _list_item(self, session, item, all_tenants=False, filter_dict=None):
490 if all_tenants:
491 tenant_text = "/any"
492 elif all_tenants is None:
493 tenant_text = ""
494 else:
495 if not self.tenant:
496 await self._get_tenant(session)
497 tenant_text = "/" + self.tenant
498
499 url = "{}{}/{}".format(self.endpoint_url, tenant_text, item)
500 separator = "?"
501 if filter_dict:
502 for k in filter_dict:
503 url += separator + quote(str(k)) + "=" + quote(str(filter_dict[k]))
504 separator = "&"
505 self.logger.debug("RO GET %s", url)
506 with aiohttp.Timeout(self.timeout_short):
507 async with session.get(url, headers=self.headers_req) as response:
508 response_text = await response.read()
509 self.logger.debug("GET {} [{}] {}".format(url, response.status, response_text[:100]))
510 if response.status >= 300:
511 raise ROClientException(response_text, http_code=response.status)
512 return self._parse_yaml(response_text, response=True)
513
514 async def _edit_item(self, session, item, item_id, descriptor, all_tenants=False):
515 if all_tenants:
516 tenant_text = "/any"
517 elif all_tenants is None:
518 tenant_text = ""
519 else:
520 if not self.tenant:
521 await self._get_tenant(session)
522 tenant_text = "/" + self.tenant
523
524 payload_req = yaml.safe_dump(descriptor)
525
526 # print payload_req
527 url = "{}{}/{}/{}".format(self.endpoint_url, tenant_text, item, item_id)
528 self.logger.debug("RO PUT %s %s", url, payload_req)
529 with aiohttp.Timeout(self.timeout_large):
530 async with session.put(url, headers=self.headers_req, data=payload_req) as response:
531 response_text = await response.read()
532 self.logger.debug("PUT {} [{}] {}".format(url, response.status, response_text[:100]))
533 if response.status >= 300:
534 raise ROClientException(response_text, http_code=response.status)
535 return self._parse_yaml(response_text, response=True)
536
537 async def get_list(self, item, all_tenants=False, filter_by=None):
538 """
539 Obtain a list of items filtering by the specigy filter_by.
540 :param item: can be 'tenant', 'vim', 'vnfd', 'nsd', 'ns'
541 :param all_tenants: True if not filtering by tenant. Only allowed for admin
542 :param filter_by: dictionary with filtering
543 :return: a list of dict. It can be empty. Raises ROClientException on Error,
544 """
545 try:
546 if item not in self.client_to_RO:
547 raise ROClientException("Invalid item {}".format(item))
548 if item == 'tenant':
549 all_tenants = None
550 with aiohttp.ClientSession(loop=self.loop) as session:
551 content = await self._list_item(session, self.client_to_RO[item], all_tenants=all_tenants,
552 filter_dict=filter_by)
553 if isinstance(content, dict):
554 if len(content) == 1:
555 for _, v in content.items():
556 return v
557 return content.values()[0]
558 else:
559 raise ROClientException("Output not a list neither dict with len equal 1", http_code=500)
560 return content
561 except aiohttp.errors.ClientOSError as e:
562 raise ROClientException(e, http_code=504)
563
564 async def show(self, item, item_id_name=None, all_tenants=False):
565 """
566 Obtain the information of an item from its id or name
567 :param item: can be 'tenant', 'vim', 'vnfd', 'nsd', 'ns'
568 :param item_id_name: RO id or name of the item. Raise and exception if more than one found
569 :param all_tenants: True if not filtering by tenant. Only allowed for admin
570 :return: dictionary with the information or raises ROClientException on Error, NotFound, found several
571 """
572 try:
573 if item not in self.client_to_RO:
574 raise ROClientException("Invalid item {}".format(item))
575 if item == 'tenant':
576 all_tenants = None
577 elif item == 'vim':
578 all_tenants = True
579 elif item == 'vim_account':
580 all_tenants = False
581
582 with aiohttp.ClientSession(loop=self.loop) as session:
583 content = await self._get_item(session, self.client_to_RO[item], item_id_name, all_tenants=all_tenants)
584 return remove_envelop(item, content)
585 except aiohttp.errors.ClientOSError as e:
586 raise ROClientException(e, http_code=504)
587
588 async def delete(self, item, item_id_name=None, all_tenants=False):
589 """
590 Delete the information of an item from its id or name
591 :param item: can be 'tenant', 'vim', 'vnfd', 'nsd', 'ns'
592 :param item_id_name: RO id or name of the item. Raise and exception if more than one found
593 :param all_tenants: True if not filtering by tenant. Only allowed for admin
594 :return: dictionary with the information or raises ROClientException on Error, NotFound, found several
595 """
596 try:
597 if item not in self.client_to_RO:
598 raise ROClientException("Invalid item {}".format(item))
599 if item == 'tenant' or item == 'vim':
600 all_tenants = None
601
602 with aiohttp.ClientSession(loop=self.loop) as session:
603 return await self._del_item(session, self.client_to_RO[item], item_id_name, all_tenants=all_tenants)
604 except aiohttp.errors.ClientOSError as e:
605 raise ROClientException(e, http_code=504)
606
607 async def edit(self, item, item_id_name, descriptor=None, descriptor_format=None, **kwargs):
608 """ Edit an item
609 :param item: can be 'tenant', 'vim', 'vnfd', 'nsd', 'ns', 'vim'
610 :param descriptor: can be a dict, or a yaml/json text. Autodetect unless descriptor_format is provided
611 :param descriptor_format: Can be 'json' or 'yaml'
612 :param kwargs: Overrides descriptor with values as name, description, vim_url, vim_url_admin, vim_type
613 keys can be a dot separated list to specify elements inside dict
614 :return: dictionary with the information or raises ROClientException on Error
615 """
616 try:
617 if isinstance(descriptor, str):
618 descriptor = self._parse(descriptor, descriptor_format)
619 elif descriptor:
620 pass
621 else:
622 descriptor = {}
623
624 if item not in self.client_to_RO:
625 raise ROClientException("Invalid item {}".format(item))
626 desc = remove_envelop(item, descriptor)
627
628 # Override descriptor with kwargs
629 if kwargs:
630 desc = self.update_descriptor(desc, kwargs)
631 all_tenants = False
632 if item in ('tenant', 'vim'):
633 all_tenants = None
634
635 create_desc = self._create_envelop(item, desc)
636
637 with aiohttp.ClientSession(loop=self.loop) as session:
638 _all_tenants = all_tenants
639 if item == 'vim':
640 _all_tenants = True
641 item_id = await self._get_item_uuid(session, self.client_to_RO[item], item_id_name,
642 all_tenants=_all_tenants)
643 # await self._get_tenant(session)
644 outdata = await self._edit_item(session, self.client_to_RO[item], item_id, create_desc,
645 all_tenants=_all_tenants)
646 return remove_envelop(item, outdata)
647 except aiohttp.errors.ClientOSError as e:
648 raise ROClientException(e, http_code=504)
649
650 async def create(self, item, descriptor=None, descriptor_format=None, **kwargs):
651 """
652 Creates an item from its descriptor
653 :param item: can be 'tenant', 'vnfd', 'nsd', 'ns', 'vim', 'vim_account', 'sdn'
654 :param descriptor: can be a dict, or a yaml/json text. Autodetect unless descriptor_format is provided
655 :param descriptor_format: Can be 'json' or 'yaml'
656 :param kwargs: Overrides descriptor with values as name, description, vim_url, vim_url_admin, vim_type
657 keys can be a dot separated list to specify elements inside dict
658 :return: dictionary with the information or raises ROClientException on Error
659 """
660 try:
661 if isinstance(descriptor, str):
662 descriptor = self._parse(descriptor, descriptor_format)
663 elif descriptor:
664 pass
665 else:
666 descriptor = {}
667
668 if item not in self.client_to_RO:
669 raise ROClientException("Invalid item {}".format(item))
670 desc = remove_envelop(item, descriptor)
671
672 # Override descriptor with kwargs
673 if kwargs:
674 desc = self.update_descriptor(desc, kwargs)
675
676 for mandatory in self.mandatory_for_create[item]:
677 if mandatory not in desc:
678 raise ROClientException("'{}' is mandatory parameter for {}".format(mandatory, item))
679
680 all_tenants = False
681 if item in ('tenant', 'vim'):
682 all_tenants = None
683
684 create_desc = self._create_envelop(item, desc)
685
686 with aiohttp.ClientSession(loop=self.loop) as session:
687 outdata = await self._create_item(session, self.client_to_RO[item], create_desc,
688 all_tenants=all_tenants)
689 return remove_envelop(item, outdata)
690 except aiohttp.errors.ClientOSError as e:
691 raise ROClientException(e, http_code=504)
692
693 async def attach_datacenter(self, datacenter=None, descriptor=None, descriptor_format=None, **kwargs):
694
695 if isinstance(descriptor, str):
696 descriptor = self._parse(descriptor, descriptor_format)
697 elif descriptor:
698 pass
699 else:
700 descriptor = {}
701 desc = remove_envelop("vim", descriptor)
702
703 # # check that exist
704 # uuid = self._get_item_uuid(session, "datacenters", uuid_name, all_tenants=True)
705 # tenant_text = "/" + self._get_tenant()
706 if kwargs:
707 desc = self.update_descriptor(desc, kwargs)
708
709 if not desc.get("vim_tenant_name") and not desc.get("vim_tenant_id"):
710 raise ROClientException("Wrong descriptor. At least vim_tenant_name or vim_tenant_id must be provided")
711 create_desc = self._create_envelop("vim", desc)
712 payload_req = yaml.safe_dump(create_desc)
713 with aiohttp.ClientSession(loop=self.loop) as session:
714 # check that exist
715 item_id = await self._get_item_uuid(session, "datacenters", datacenter, all_tenants=True)
716 await self._get_tenant(session)
717
718 url = "{}/{tenant}/datacenters/{datacenter}".format(self.endpoint_url, tenant=self.tenant,
719 datacenter=item_id)
720 self.logger.debug("RO POST %s %s", url, payload_req)
721 with aiohttp.Timeout(self.timeout_large):
722 async with session.post(url, headers=self.headers_req, data=payload_req) as response:
723 response_text = await response.read()
724 self.logger.debug("POST {} [{}] {}".format(url, response.status, response_text[:100]))
725 if response.status >= 300:
726 raise ROClientException(response_text, http_code=response.status)
727
728 response_desc = self._parse_yaml(response_text, response=True)
729 desc = remove_envelop("vim", response_desc)
730 return desc
731
732 async def detach_datacenter(self, datacenter=None):
733 # TODO replace the code with delete_item(vim_account,...)
734 with aiohttp.ClientSession(loop=self.loop) as session:
735 # check that exist
736 item_id = await self._get_item_uuid(session, "datacenters", datacenter, all_tenants=False)
737 tenant = await self._get_tenant(session)
738
739 url = "{}/{tenant}/datacenters/{datacenter}".format(self.endpoint_url, tenant=tenant,
740 datacenter=item_id)
741 self.logger.debug("RO DELETE %s", url)
742 with aiohttp.Timeout(self.timeout_large):
743 async with session.delete(url, headers=self.headers_req) as response:
744 response_text = await response.read()
745 self.logger.debug("DELETE {} [{}] {}".format(url, response.status, response_text[:100]))
746 if response.status >= 300:
747 raise ROClientException(response_text, http_code=response.status)
748
749 response_desc = self._parse_yaml(response_text, response=True)
750 desc = remove_envelop("vim", response_desc)
751 return desc
752
753 # TODO convert to asyncio
754 # DATACENTERS
755
756 def edit_datacenter(self, uuid=None, name=None, descriptor=None, descriptor_format=None, all_tenants=False,
757 **kwargs):
758 """Edit the parameters of a datacenter
759 Params: must supply a descriptor or/and a parameter to change
760 uuid or/and name. If only name is supplied, there must be only one or an exception is raised
761 descriptor: with format {'datacenter':{params to change info}}
762 must be a dictionary or a json/yaml text.
763 parameters to change can be supplyied by the descriptor or as parameters:
764 new_name: the datacenter name
765 vim_url: the datacenter URL
766 vim_url_admin: the datacenter URL for administrative issues
767 vim_type: the datacenter type, can be openstack or openvim.
768 public: boolean, available to other tenants
769 description: datacenter description
770 Return: Raises an exception on error, not found or found several
771 Obtain a dictionary with format {'datacenter':{new_datacenter_info}}
772 """
773
774 if isinstance(descriptor, str):
775 descriptor = self.parse(descriptor, descriptor_format)
776 elif descriptor:
777 pass
778 elif kwargs:
779 descriptor = {"datacenter": {}}
780 else:
781 raise ROClientException("Missing descriptor")
782
783 if 'datacenter' not in descriptor or len(descriptor) != 1:
784 raise ROClientException("Descriptor must contain only one 'datacenter' field")
785 for param in kwargs:
786 if param == 'new_name':
787 descriptor['datacenter']['name'] = kwargs[param]
788 else:
789 descriptor['datacenter'][param] = kwargs[param]
790 return self._edit_item("datacenters", descriptor, uuid, name, all_tenants=None)
791
792 def edit_scenario(self, uuid=None, name=None, descriptor=None, descriptor_format=None, all_tenants=False, **kwargs):
793 """Edit the parameters of a scenario
794 Params: must supply a descriptor or/and a parameters to change
795 uuid or/and name. If only name is supplied, there must be only one or an exception is raised
796 descriptor: with format {'scenario':{params to change info}}
797 must be a dictionary or a json/yaml text.
798 parameters to change can be supplyied by the descriptor or as parameters:
799 new_name: the scenario name
800 public: boolean, available to other tenants
801 description: scenario description
802 tenant_id. Propietary tenant
803 Return: Raises an exception on error, not found or found several
804 Obtain a dictionary with format {'scenario':{new_scenario_info}}
805 """
806
807 if isinstance(descriptor, str):
808 descriptor = self.parse(descriptor, descriptor_format)
809 elif descriptor:
810 pass
811 elif kwargs:
812 descriptor = {"scenario": {}}
813 else:
814 raise ROClientException("Missing descriptor")
815
816 if 'scenario' not in descriptor or len(descriptor) > 2:
817 raise ROClientException("Descriptor must contain only one 'scenario' field")
818 for param in kwargs:
819 if param == 'new_name':
820 descriptor['scenario']['name'] = kwargs[param]
821 else:
822 descriptor['scenario'][param] = kwargs[param]
823 return self._edit_item("scenarios", descriptor, uuid, name, all_tenants=None)
824
825 # VIM ACTIONS
826 def vim_action(self, action, item, uuid=None, all_tenants=False, **kwargs):
827 """Perform an action over a vim
828 Params:
829 action: can be 'list', 'get'/'show', 'delete' or 'create'
830 item: can be 'tenants' or 'networks'
831 uuid: uuid of the tenant/net to show or to delete. Ignore otherwise
832 other parameters:
833 datacenter_name, datacenter_id: datacenters to act on, if missing uses classes store datacenter
834 descriptor, descriptor_format: descriptor needed on creation, can be a dict or a yaml/json str
835 must be a dictionary or a json/yaml text.
836 name: for created tenant/net Overwrite descriptor name if any
837 description: tenant descriptor. Overwrite descriptor description if any
838
839 Return: Raises an exception on error
840 Obtain a dictionary with format {'tenant':{new_tenant_info}}
841 """
842 session = None # TODO remove when changed to asyncio
843 if item not in ("tenants", "networks", "images"):
844 raise ROClientException("Unknown value for item '{}', must be 'tenants', 'nets' or "
845 "images".format(str(item)))
846
847 image_actions = ['list', 'get', 'show', 'delete']
848 if item == "images" and action not in image_actions:
849 raise ROClientException("Only available actions for item '{}' are {}\n"
850 "Requested action was '{}'".format(item, ', '.join(image_actions), action))
851 if all_tenants:
852 tenant_text = "/any"
853 else:
854 tenant_text = "/" + self._get_tenant()
855
856 if "datacenter_id" in kwargs or "datacenter_name" in kwargs:
857 datacenter = self._get_item_uuid(session, "datacenters", kwargs.get("datacenter"), all_tenants=all_tenants)
858 else:
859 datacenter = self.get_datacenter(session)
860
861 if action == "list":
862 url = "{}{}/vim/{}/{}".format(self.endpoint_url, tenant_text, datacenter, item)
863 self.logger.debug("GET %s", url)
864 mano_response = requests.get(url, headers=self.headers_req)
865 self.logger.debug("RO response: %s", mano_response.text)
866 content = self._parse_yaml(mano_response.text, response=True)
867 if mano_response.status_code == 200:
868 return content
869 else:
870 raise ROClientException(str(content), http_code=mano_response.status)
871 elif action == "get" or action == "show":
872 url = "{}{}/vim/{}/{}/{}".format(self.endpoint_url, tenant_text, datacenter, item, uuid)
873 self.logger.debug("GET %s", url)
874 mano_response = requests.get(url, headers=self.headers_req)
875 self.logger.debug("RO response: %s", mano_response.text)
876 content = self._parse_yaml(mano_response.text, response=True)
877 if mano_response.status_code == 200:
878 return content
879 else:
880 raise ROClientException(str(content), http_code=mano_response.status)
881 elif action == "delete":
882 url = "{}{}/vim/{}/{}/{}".format(self.endpoint_url, tenant_text, datacenter, item, uuid)
883 self.logger.debug("DELETE %s", url)
884 mano_response = requests.delete(url, headers=self.headers_req)
885 self.logger.debug("RO response: %s", mano_response.text)
886 content = self._parse_yaml(mano_response.text, response=True)
887 if mano_response.status_code == 200:
888 return content
889 else:
890 raise ROClientException(str(content), http_code=mano_response.status)
891 elif action == "create":
892 if "descriptor" in kwargs:
893 if isinstance(kwargs["descriptor"], str):
894 descriptor = self._parse(kwargs["descriptor"], kwargs.get("descriptor_format"))
895 else:
896 descriptor = kwargs["descriptor"]
897 elif "name" in kwargs:
898 descriptor = {item[:-1]: {"name": kwargs["name"]}}
899 else:
900 raise ROClientException("Missing descriptor")
901
902 if item[:-1] not in descriptor or len(descriptor) != 1:
903 raise ROClientException("Descriptor must contain only one 'tenant' field")
904 if "name" in kwargs:
905 descriptor[item[:-1]]['name'] = kwargs["name"]
906 if "description" in kwargs:
907 descriptor[item[:-1]]['description'] = kwargs["description"]
908 payload_req = yaml.safe_dump(descriptor)
909 # print payload_req
910 url = "{}{}/vim/{}/{}".format(self.endpoint_url, tenant_text, datacenter, item)
911 self.logger.debug("RO POST %s %s", url, payload_req)
912 mano_response = requests.post(url, headers=self.headers_req, data=payload_req)
913 self.logger.debug("RO response: %s", mano_response.text)
914 content = self._parse_yaml(mano_response.text, response=True)
915 if mano_response.status_code == 200:
916 return content
917 else:
918 raise ROClientException(str(content), http_code=mano_response.status)
919 else:
920 raise ROClientException("Unknown value for action '{}".format(str(action)))
921
922
923 if __name__ == '__main__':
924 RO_URL = "http://localhost:9090/openmano"
925 TEST_TENANT = "myTenant"
926 TEST_VIM1 = "myvim"
927 TEST_URL1 = "https://localhost:5000/v1"
928 TEST_TYPE1 = "openstack"
929 TEST_CONFIG1 = {"use_floating_ip": True}
930 TEST_VIM2 = "myvim2"
931 TEST_URL2 = "https://localhost:5000/v2"
932 TEST_TYPE2 = "openvim"
933 TEST_CONFIG2 = {"config2": "config2", "config3": True}
934
935 streamformat = "%(asctime)s %(name)s %(levelname)s: %(message)s"
936 logging.basicConfig(format=streamformat)
937 logger = logging.getLogger("ROClient")
938
939 tenant_id = None
940 vim_id = False
941 loop = asyncio.get_event_loop()
942 myClient = ROClient(endpoint_url=RO_URL, loop=loop, loglevel="DEBUG")
943 try:
944 # test tenant
945 content = loop.run_until_complete(myClient.get_list("tenant"))
946 print("tenants", content)
947 content = loop.run_until_complete(myClient.create("tenant", name=TEST_TENANT))
948 tenant_id = True
949 content = loop.run_until_complete(myClient.show("tenant", TEST_TENANT))
950 print("tenant", TEST_TENANT, content)
951 content = loop.run_until_complete(myClient.edit("tenant", TEST_TENANT, description="another description"))
952 content = loop.run_until_complete(myClient.show("tenant", TEST_TENANT))
953 print("tenant edited", TEST_TENANT, content)
954 myClient["tenant"] = TEST_TENANT
955
956 # test VIM
957 content = loop.run_until_complete(myClient.create("vim", name=TEST_VIM1, type=TEST_TYPE1, vim_url=TEST_URL1,
958 config=TEST_CONFIG1))
959 vim_id = True
960 content = loop.run_until_complete(myClient.get_list("vim"))
961 print("vim", content)
962 content = loop.run_until_complete(myClient.show("vim", TEST_VIM1))
963 print("vim", TEST_VIM1, content)
964 content = loop.run_until_complete(myClient.edit("vim", TEST_VIM1, description="another description",
965 name=TEST_VIM2, type=TEST_TYPE2, vim_url=TEST_URL2,
966 config=TEST_CONFIG2))
967 content = loop.run_until_complete(myClient.show("vim", TEST_VIM2))
968 print("vim edited", TEST_VIM2, content)
969
970 # test VIM_ACCOUNT
971 content = loop.run_until_complete(myClient.attach_datacenter(TEST_VIM2, vim_username='user',
972 vim_password='pass', vim_tenant_name='vimtenant1',
973 config=TEST_CONFIG1))
974 vim_id = True
975 content = loop.run_until_complete(myClient.get_list("vim_account"))
976 print("vim_account", content)
977 content = loop.run_until_complete(myClient.show("vim_account", TEST_VIM2))
978 print("vim_account", TEST_VIM2, content)
979 content = loop.run_until_complete(myClient.edit("vim_account", TEST_VIM2, vim_username='user2',
980 vim_password='pass2', vim_tenant_name="vimtenant2",
981 config=TEST_CONFIG2))
982 content = loop.run_until_complete(myClient.show("vim_account", TEST_VIM2))
983 print("vim_account edited", TEST_VIM2, content)
984
985 myClient["vim"] = TEST_VIM2
986
987 except Exception as e:
988 logger.error("Error {}".format(e), exc_info=True)
989
990 for item in (("vim_account", TEST_VIM1), ("vim", TEST_VIM1),
991 ("vim_account", TEST_VIM2), ("vim", TEST_VIM2),
992 ("tenant", TEST_TENANT)):
993 try:
994 content = loop.run_until_complete(myClient.delete(item[0], item[1]))
995 print("{} {} deleted; {}".format(item[0], item[1], content))
996 except Exception as e:
997 if e.http_code == 404:
998 print("{} {} not present or already deleted".format(item[0], item[1]))
999 else:
1000 logger.error("Error {}".format(e), exc_info=True)
1001
1002 loop.close()