AWS vimconn: Allow config:flavor_data as str
[osm/RO.git] / lcm / 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
32 import json
33 import yaml
34 import logging
35 import sys
36 from urllib.parse import quote
37 from uuid import UUID
38 from copy import deepcopy
39
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"
44 requests = None
45
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"""
51
52
53 def remove_envelop(item, indata=None):
54 """
55 Obtain the useful data removing the envelop. It goes through the vnfd or nsd catalog and returns the
56 vnfd or nsd content
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)
60 """
61 clean_indata = indata
62 if not indata:
63 return {}
64 if item == "vnfd":
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]
73 elif item == "nsd":
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]
82 elif item == "sdn":
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"]
94 else:
95 assert False, "remove_envelop with unknown item {}".format(item)
96
97 return clean_indata
98
99
100 class ROClient:
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',
104 'ns': 'instances'}
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"),
111 'vim_account': (),
112 'sdn': ("name", "port", 'ip', 'dpid', 'type'),
113 }
114 timeout_large = 120
115 timeout_short = 30
116
117 def __init__(self, loop, endpoint_url, **kwargs):
118 self.loop = loop
119 self.endpoint_url = endpoint_url
120
121 self.username = kwargs.get("username")
122 self.password = kwargs.get("password")
123 self.tenant_id_name = kwargs.get("tenant")
124 self.tenant = None
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"])
131 global requests
132 requests = kwargs.get("TODO remove")
133
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':
140 return self.username
141 elif index == 'password':
142 return self.password
143 elif index == 'endpoint_url':
144 return self.endpoint_url
145 else:
146 raise KeyError("Invalid key '%s'" %str(index))
147
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
159 else:
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
163
164 def _parse(self, descriptor, descriptor_format, response=False):
165 #try yaml
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":
169 try:
170 return yaml.load(descriptor)
171 except yaml.YAMLError as exc:
172 error_pos = ""
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":
178 try:
179 return json.loads(descriptor)
180 except Exception as e:
181 if response:
182 error_text = "json format error" + str(e)
183
184 if response:
185 raise ROClientException(error_text)
186 raise ROClientException(error_text)
187
188 def _parse_yaml(self, descriptor, response=False):
189 try:
190 return yaml.load(descriptor)
191 except yaml.YAMLError as exc:
192 error_pos = ""
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
197 if response:
198 raise ROClientException(error_text)
199 raise ROClientException(error_text)
200
201 @staticmethod
202 def check_if_uuid(uuid_text):
203 """
204 Check if text correspond to an uuid foramt
205 :param uuid_text:
206 :return: True if it is an uuid False if not
207 """
208 try:
209 UUID(uuid_text)
210 return True
211 except (ValueError, TypeError):
212 return False
213
214 @staticmethod
215 def _create_envelop(item, indata=None):
216 """
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, ...
221 """
222 if item == "vnfd":
223 return {'vnfd-catalog': {'vnfd': [indata]}}
224 elif item == "nsd":
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}
232 elif item == "sdn":
233 return {'sdn_controller': indata}
234 else:
235 assert False, "_create_envelop with unknown item {}".format(item)
236
237 @staticmethod
238 def update_descriptor(desc, kwargs):
239 desc = deepcopy(desc) # do not modify original descriptor
240 try:
241 for k, v in kwargs.items():
242 update_content = desc
243 kitem_old = None
244 klist = k.split(".")
245 for kitem in klist:
246 if kitem_old is not None:
247 update_content = update_content[kitem_old]
248 if isinstance(update_content, dict):
249 kitem_old = kitem
250 elif isinstance(update_content, list):
251 kitem_old = int(kitem)
252 else:
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]
257 else:
258 update_content[kitem_old] = v
259 return desc
260 except KeyError:
261 raise ROClientException(
262 "Invalid query string '{}'. Descriptor does not contain '{}'".format(k, kitem_old))
263 except ValueError:
264 raise ROClientException("Invalid query string '{}'. Expected integer index list instead of '{}'".format(
265 k, kitem))
266 except IndexError:
267 raise ROClientException(
268 "Invalid query string '{}'. Index '{}' out of range".format(k, kitem_old))
269
270 @staticmethod
271 def check_ns_status(ns_descriptor):
272 """
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
276 """
277 net_total = 0
278 vm_total = 0
279 net_done = 0
280 vm_done = 0
281
282 for net in ns_descriptor["nets"]:
283 net_total += 1
284 if net["status"] in ("ERROR", "VIM_ERROR"):
285 return "ERROR", net["error_msg"]
286 elif net["status"] == "ACTIVE":
287 net_done += 1
288 for vnf in ns_descriptor["vnfs"]:
289 for vm in vnf["vms"]:
290 vm_total += 1
291 if vm["status"] in ("ERROR", "VIM_ERROR"):
292 return "ERROR", vm["error_msg"]
293 elif vm["status"] == "ACTIVE":
294 vm_done += 1
295
296 if net_total == net_done and vm_total == vm_done:
297 return "ACTIVE", "VMs {}, networks: {}".format(vm_total, net_total)
298 else:
299 return "BUILD", "VMs: {}/{}, networks: {}/{}".format(vm_done, vm_total, net_done, net_total)
300
301 @staticmethod
302 def get_ns_vnf_info(ns_descriptor):
303 """
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}}}
307 """
308 ns_info = {}
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)
313 vnfr_info = {
314 "ip_address": vnf.get("ip_address"),
315 "vdur": {}
316 }
317 for vm in vnf["vms"]:
318 vdur = {
319 "vim_id": vm.get("vim_vm_id"),
320 "ip_address": vm.get("ip_address")
321 }
322 vnfr_info["vdur"][vm["vdu_osm_id"]] = vdur
323 ns_info[str(vnf["member_vnf_index"])] = vnfr_info
324 return ns_info
325
326
327 async def _get_item_uuid(self, session, item, item_id_name, all_tenants=False):
328 if all_tenants:
329 tenant_text = "/any"
330 elif all_tenants is None:
331 tenant_text = ""
332 else:
333 if not self.tenant:
334 await self._get_tenant(session)
335 tenant_text = "/" + self.tenant
336
337 item_id = 0
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),
351 http_code=404)
352 if response.status >= 300:
353 raise ROClientException(response_text, http_code=response.status)
354 content = self._parse_yaml(response_text, response=True)
355
356 if item_id:
357 return item_id
358 desc = content[item]
359 assert isinstance(desc, list), "_get_item_uuid get a non dict with a list inside {}".format(type(desc))
360 uuid = None
361 for i in desc:
362 if item_id_name and i["name"] != item_id_name:
363 continue
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),
367 http_code=404)
368 uuid = i["uuid"]
369 if not uuid:
370 raise ROClientException("No {} found with name '{}'".format(item[:-1], item_id_name), http_code=404)
371 return uuid
372
373 async def _get_item(self, session, item, item_id_name, all_tenants=False):
374 if all_tenants:
375 tenant_text = "/any"
376 elif all_tenants is None:
377 tenant_text = ""
378 else:
379 if not self.tenant:
380 await self._get_tenant(session)
381 tenant_text = "/" + self.tenant
382
383 if self.check_if_uuid(item_id_name):
384 uuid = item_id_name
385 else:
386 # check that exist
387 uuid = await self._get_item_uuid(session, item, item_id_name, all_tenants)
388
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)
397
398 return self._parse_yaml(response_text, response=True)
399
400 async def _get_tenant(self, session):
401 if not self.tenant:
402 self.tenant = await self._get_item_uuid(session, "tenants", self.tenant_id_name, None)
403 return self.tenant
404
405 async def _get_datacenter(self, session):
406 if not self.tenant:
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
411
412 async def _create_item(self, session, item, descriptor, item_id_name=None, action=None, all_tenants=False):
413 if all_tenants:
414 tenant_text = "/any"
415 elif all_tenants is None:
416 tenant_text = ""
417 else:
418 if not self.tenant:
419 await self._get_tenant(session)
420 tenant_text = "/" + self.tenant
421 payload_req = yaml.safe_dump(descriptor)
422 #print payload_req
423
424 api_version_text = ""
425 if item == "vnfs":
426 # assumes version v3 only
427 api_version_text = "/v3"
428 item = "vnfd"
429 elif item == "scenarios":
430 # assumes version v3 only
431 api_version_text = "/v3"
432 item = "nsd"
433
434 if not item_id_name:
435 uuid=""
436 elif self.check_if_uuid(item_id_name):
437 uuid = "/{}".format(item_id_name)
438 else:
439 # check that exist
440 uuid = await self._get_item_uuid(session, item, item_id_name, all_tenants)
441 uuid = "/{}".format(uuid)
442 if not action:
443 action = ""
444 else:
445 action = "/".format(action)
446
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)
456
457 return self._parse_yaml(response_text, response=True)
458
459 async def _del_item(self, session, item, item_id_name, all_tenants=False):
460 if all_tenants:
461 tenant_text = "/any"
462 elif all_tenants is None:
463 tenant_text = ""
464 else:
465 if not self.tenant:
466 await self._get_tenant(session)
467 tenant_text = "/" + self.tenant
468 if not self.check_if_uuid(item_id_name):
469 # check that exist
470 _all_tenants = all_tenants
471 if item == "datacenters":
472 _all_tenants = True
473 uuid = await self._get_item_uuid(session, item, item_id_name, all_tenants=_all_tenants)
474 else:
475 uuid = item_id_name
476
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)
486
487 async def _list_item(self, session, item, all_tenants=False, filter_dict=None):
488 if all_tenants:
489 tenant_text = "/any"
490 elif all_tenants is None:
491 tenant_text = ""
492 else:
493 if not self.tenant:
494 await self._get_tenant(session)
495 tenant_text = "/" + self.tenant
496
497 url = "{}{}/{}".format(self.endpoint_url, tenant_text, item)
498 separator = "?"
499 if filter_dict:
500 for k in filter_dict:
501 url += separator + quote(str(k)) + "=" + quote(str(filter_dict[k]))
502 separator = "&"
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)
511
512 async def _edit_item(self, session, item, item_id, descriptor, all_tenants=False):
513 if all_tenants:
514 tenant_text = "/any"
515 elif all_tenants is None:
516 tenant_text = ""
517 else:
518 if not self.tenant:
519 await self._get_tenant(session)
520 tenant_text = "/" + self.tenant
521
522 payload_req = yaml.safe_dump(descriptor)
523
524 #print payload_req
525
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)
535
536 async def get_list(self, item, all_tenants=False, filter_by=None):
537 """
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,
543 """
544 try:
545 if item not in self.client_to_RO:
546 raise ROClientException("Invalid item {}".format(item))
547 if item == 'tenant':
548 all_tenants = None
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():
555 return v
556 return content.values()[0]
557 else:
558 raise ROClientException("Output not a list neither dict with len equal 1", http_code=500)
559 return content
560 except aiohttp.errors.ClientOSError as e:
561 raise ROClientException(e, http_code=504)
562
563 async def show(self, item, item_id_name=None, all_tenants=False):
564 """
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
570 """
571 try:
572 if item not in self.client_to_RO:
573 raise ROClientException("Invalid item {}".format(item))
574 if item == 'tenant':
575 all_tenants = None
576 elif item == 'vim':
577 all_tenants = True
578 elif item == 'vim_account':
579 all_tenants = False
580
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)
586
587 async def delete(self, item, item_id_name=None, all_tenants=False):
588 """
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
594 """
595 try:
596 if item not in self.client_to_RO:
597 raise ROClientException("Invalid item {}".format(item))
598 if item == 'tenant' or item == 'vim':
599 all_tenants = None
600
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)
605
606 async def edit(self, item, item_id_name, descriptor=None, descriptor_format=None, **kwargs):
607 """ Edit an item
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
614 """
615 try:
616 if isinstance(descriptor, str):
617 descriptor = self._parse(descriptor, descriptor_format)
618 elif descriptor:
619 pass
620 else:
621 descriptor = {}
622
623 if item not in self.client_to_RO:
624 raise ROClientException("Invalid item {}".format(item))
625 desc = remove_envelop(item, descriptor)
626
627 # Override descriptor with kwargs
628 if kwargs:
629 desc = self.update_descriptor(desc, kwargs)
630 all_tenants = False
631 if item in ('tenant', 'vim'):
632 all_tenants = None
633
634 create_desc = self._create_envelop(item, desc)
635
636 with aiohttp.ClientSession(loop=self.loop) as session:
637 _all_tenants = all_tenants
638 if item == 'vim':
639 _all_tenants = True
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)
646
647 async def create(self, item, descriptor=None, descriptor_format=None, **kwargs):
648 """
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
656 """
657 try:
658 if isinstance(descriptor, str):
659 descriptor = self._parse(descriptor, descriptor_format)
660 elif descriptor:
661 pass
662 else:
663 descriptor = {}
664
665 if item not in self.client_to_RO:
666 raise ROClientException("Invalid item {}".format(item))
667 desc = remove_envelop(item, descriptor)
668
669 # Override descriptor with kwargs
670 if kwargs:
671 desc = self.update_descriptor(desc, kwargs)
672
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))
676
677 all_tenants = False
678 if item in ('tenant', 'vim'):
679 all_tenants = None
680
681 create_desc = self._create_envelop(item, desc)
682
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)
689
690 async def attach_datacenter(self, datacenter=None, descriptor=None, descriptor_format=None, **kwargs):
691
692 if isinstance(descriptor, str):
693 descriptor = self._parse(descriptor, descriptor_format)
694 elif descriptor:
695 pass
696 else:
697 descriptor = {}
698 desc = remove_envelop("vim", descriptor)
699
700 # # check that exist
701 # uuid = self._get_item_uuid(session, "datacenters", uuid_name, all_tenants=True)
702 # tenant_text = "/" + self._get_tenant()
703 if kwargs:
704 desc = self.update_descriptor(desc, kwargs)
705
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:
711 # check that exist
712 item_id = await self._get_item_uuid(session, "datacenters", datacenter, all_tenants=True)
713 await self._get_tenant(session)
714
715 url = "{}/{tenant}/datacenters/{datacenter}".format(self.endpoint_url, tenant=self.tenant,
716 datacenter=item_id)
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)
724
725 response_desc = self._parse_yaml(response_text, response=True)
726 desc = remove_envelop("vim", response_desc)
727 return desc
728
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:
732 # check that exist
733 item_id = await self._get_item_uuid(session, "datacenters", datacenter, all_tenants=False)
734 tenant = await self._get_tenant(session)
735
736 url = "{}/{tenant}/datacenters/{datacenter}".format(self.endpoint_url, tenant=tenant,
737 datacenter=item_id)
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)
745
746 response_desc = self._parse_yaml(response_text, response=True)
747 desc = remove_envelop("vim", response_desc)
748 return desc
749
750
751 # TODO convert to asyncio
752
753 #DATACENTERS
754
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}}
770 """
771
772 if isinstance(descriptor, str):
773 descriptor = self.parse(descriptor, descriptor_format)
774 elif descriptor:
775 pass
776 elif kwargs:
777 descriptor={"datacenter": {}}
778 else:
779 raise ROClientException("Missing descriptor")
780
781 if 'datacenter' not in descriptor or len(descriptor)!=1:
782 raise ROClientException("Descriptor must contain only one 'datacenter' field")
783 for param in kwargs:
784 if param=='new_name':
785 descriptor['datacenter']['name'] = kwargs[param]
786 else:
787 descriptor['datacenter'][param] = kwargs[param]
788 return self._edit_item("datacenters", descriptor, uuid, name, all_tenants=None)
789
790
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}}
804 """
805
806 if isinstance(descriptor, str):
807 descriptor = self.parse(descriptor, descriptor_format)
808 elif descriptor:
809 pass
810 elif kwargs:
811 descriptor={"scenario": {}}
812 else:
813 raise ROClientException("Missing descriptor")
814
815 if 'scenario' not in descriptor or len(descriptor)>2:
816 raise ROClientException("Descriptor must contain only one 'scenario' field")
817 for param in kwargs:
818 if param=='new_name':
819 descriptor['scenario']['name'] = kwargs[param]
820 else:
821 descriptor['scenario'][param] = kwargs[param]
822 return self._edit_item("scenarios", descriptor, uuid, name, all_tenants=None)
823
824 #VIM ACTIONS
825 def vim_action(self, action, item, uuid=None, all_tenants=False, **kwargs):
826 """Perform an action over a vim
827 Params:
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
831 other parameters:
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
837
838 Return: Raises an exception on error
839 Obtain a dictionary with format {'tenant':{new_tenant_info}}
840 """
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)))
844
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))
849 if all_tenants:
850 tenant_text = "/any"
851 else:
852 tenant_text = "/"+self._get_tenant()
853
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)
856 else:
857 datacenter = self.get_datacenter(session)
858
859 if action=="list":
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:
866 return content
867 else:
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:
876 return content
877 else:
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:
886 return content
887 else:
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") )
893 else:
894 descriptor = kwargs["descriptor"]
895 elif "name" in kwargs:
896 descriptor={item[:-1]: {"name": kwargs["name"]}}
897 else:
898 raise ROClientException("Missing descriptor")
899
900 if item[:-1] not in descriptor or len(descriptor)!=1:
901 raise ROClientException("Descriptor must contain only one 'tenant' field")
902 if "name" in kwargs:
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)
907 #print payload_req
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:
914 return content
915 else:
916 raise ROClientException(str(content), http_code=mano_response.status)
917 else:
918 raise ROClientException("Unknown value for action '{}".format(str(action)))
919
920
921 if __name__ == '__main__':
922 RO_URL = "http://localhost:9090/openmano"
923 TEST_TENANT = "myTenant"
924 TEST_VIM1 = "myvim"
925 TEST_URL1 = "https://localhost:5000/v1"
926 TEST_TYPE1 = "openstack"
927 TEST_CONFIG1 = {"use_floating_ip": True}
928 TEST_VIM2 = "myvim2"
929 TEST_URL2 = "https://localhost:5000/v2"
930 TEST_TYPE2 = "openvim"
931 TEST_CONFIG2 = {"config2": "config2", "config3": True}
932
933 streamformat = "%(asctime)s %(name)s %(levelname)s: %(message)s"
934 logging.basicConfig(format=streamformat)
935 logger = logging.getLogger("ROClient")
936
937 tenant_id = None
938 vim_id = False
939 loop = asyncio.get_event_loop()
940 myClient = ROClient(endpoint_url=RO_URL, loop=loop, loglevel="DEBUG")
941 try:
942 # test tenant
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))
946 tenant_id = True
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
953
954
955 # test VIM
956 content = loop.run_until_complete(myClient.create("vim", name=TEST_VIM1, type=TEST_TYPE1, vim_url=TEST_URL1, config=TEST_CONFIG1))
957 vim_id = True
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)
967
968 # test VIM_ACCOUNT
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))
971 vim_id = True
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)
980
981 myClient["vim"] = TEST_VIM2
982
983 except Exception as e:
984 logger.error("Error {}".format(e), exc_info=True)
985
986 for item in (("vim_account", TEST_VIM1), ("vim", TEST_VIM1),
987 ("vim_account", TEST_VIM2), ("vim", TEST_VIM2),
988 ("tenant", TEST_TENANT)):
989 try:
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]))
995 else:
996 logger.error("Error {}".format(e), exc_info=True)
997
998 loop.close()
999
1000