LWB Added vnfR support
[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 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("No ip_address returned for ns member_vnf_index '{}'".format(
310 vnf["member_vnf_index"]), http_code=500)
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 vnfr_info["vdur"][vm["vdu_osm_id"]] = vdur
321 ns_info[str(vnf["member_vnf_index"])] = vnfr_info
322 return ns_info
323
324
325 async def _get_item_uuid(self, session, item, item_id_name, all_tenants=False):
326 if all_tenants:
327 tenant_text = "/any"
328 elif all_tenants is None:
329 tenant_text = ""
330 else:
331 if not self.tenant:
332 await self._get_tenant(session)
333 tenant_text = "/" + self.tenant
334
335 item_id = 0
336 url = "{}{}/{}".format(self.endpoint_url, tenant_text, item)
337 if self.check_if_uuid(item_id_name):
338 item_id = item_id_name
339 url += "/" + item_id_name
340 elif item_id_name and item_id_name.startswith("'") and item_id_name.endswith("'"):
341 item_id_name = item_id_name[1:-1]
342 self.logger.debug("openmano GET %s", url)
343 with aiohttp.Timeout(self.timeout_short):
344 async with session.get(url, headers=self.headers_req) as response:
345 response_text = await response.read()
346 self.logger.debug("GET {} [{}] {}".format(url, response.status, response_text[:100]))
347 if response.status == 404: # NOT_FOUND
348 raise ROClientException("No {} found with id '{}'".format(item[:-1], item_id_name),
349 http_code=404)
350 if response.status >= 300:
351 raise ROClientException(response_text, http_code=response.status)
352 content = self._parse_yaml(response_text, response=True)
353
354 if item_id:
355 return item_id
356 desc = content[item]
357 assert isinstance(desc, list), "_get_item_uuid get a non dict with a list inside {}".format(type(desc))
358 uuid = None
359 for i in desc:
360 if item_id_name and i["name"] != item_id_name:
361 continue
362 if uuid: # found more than one
363 raise ROClientException(
364 "Found more than one {} with name '{}'. uuid must be used".format(item, item_id_name),
365 http_code=404)
366 uuid = i["uuid"]
367 if not uuid:
368 raise ROClientException("No {} found with name '{}'".format(item[:-1], item_id_name), http_code=404)
369 return uuid
370
371 async def _get_item(self, session, item, item_id_name, all_tenants=False):
372 if all_tenants:
373 tenant_text = "/any"
374 elif all_tenants is None:
375 tenant_text = ""
376 else:
377 if not self.tenant:
378 await self._get_tenant(session)
379 tenant_text = "/" + self.tenant
380
381 if self.check_if_uuid(item_id_name):
382 uuid = item_id_name
383 else:
384 # check that exist
385 uuid = await self._get_item_uuid(session, item, item_id_name, all_tenants)
386
387 url = "{}{}/{}/{}".format(self.endpoint_url, tenant_text, item, uuid)
388 self.logger.debug("GET %s", url )
389 with aiohttp.Timeout(self.timeout_short):
390 async with session.get(url, headers=self.headers_req) as response:
391 response_text = await response.read()
392 self.logger.debug("GET {} [{}] {}".format(url, response.status, response_text[:100]))
393 if response.status >= 300:
394 raise ROClientException(response_text, http_code=response.status)
395
396 return self._parse_yaml(response_text, response=True)
397
398 async def _get_tenant(self, session):
399 if not self.tenant:
400 self.tenant = await self._get_item_uuid(session, "tenants", self.tenant_id_name, None)
401 return self.tenant
402
403 async def _get_datacenter(self, session):
404 if not self.tenant:
405 await self._get_tenant(session)
406 if not self.datacenter:
407 self.datacenter = await self._get_item_uuid(session, "datacenters", self.datacenter_id_name, True)
408 return self.datacenter
409
410 async def _create_item(self, session, item, descriptor, item_id_name=None, action=None, all_tenants=False):
411 if all_tenants:
412 tenant_text = "/any"
413 elif all_tenants is None:
414 tenant_text = ""
415 else:
416 if not self.tenant:
417 await self._get_tenant(session)
418 tenant_text = "/" + self.tenant
419 payload_req = yaml.safe_dump(descriptor)
420 #print payload_req
421
422 api_version_text = ""
423 if item == "vnfs":
424 # assumes version v3 only
425 api_version_text = "/v3"
426 item = "vnfd"
427 elif item == "scenarios":
428 # assumes version v3 only
429 api_version_text = "/v3"
430 item = "nsd"
431
432 if not item_id_name:
433 uuid=""
434 elif self.check_if_uuid(item_id_name):
435 uuid = "/{}".format(item_id_name)
436 else:
437 # check that exist
438 uuid = await self._get_item_uuid(session, item, item_id_name, all_tenants)
439 uuid = "/{}".format(uuid)
440 if not action:
441 action = ""
442 else:
443 action = "/".format(action)
444
445 url = "{}{apiver}{tenant}/{item}{id}{action}".format(self.endpoint_url, apiver=api_version_text, tenant=tenant_text,
446 item=item, id=uuid, action=action)
447 self.logger.debug("openmano POST %s %s", url, payload_req)
448 with aiohttp.Timeout(self.timeout_large):
449 async with session.post(url, headers=self.headers_req, data=payload_req) as response:
450 response_text = await response.read()
451 self.logger.debug("POST {} [{}] {}".format(url, response.status, response_text[:100]))
452 if response.status >= 300:
453 raise ROClientException(response_text, http_code=response.status)
454
455 return self._parse_yaml(response_text, response=True)
456
457 async def _del_item(self, session, item, item_id_name, all_tenants=False):
458 if all_tenants:
459 tenant_text = "/any"
460 elif all_tenants is None:
461 tenant_text = ""
462 else:
463 if not self.tenant:
464 await self._get_tenant(session)
465 tenant_text = "/" + self.tenant
466 if not self.check_if_uuid(item_id_name):
467 # check that exist
468 _all_tenants = all_tenants
469 if item == "datacenters":
470 _all_tenants = True
471 uuid = await self._get_item_uuid(session, item, item_id_name, all_tenants=_all_tenants)
472 else:
473 uuid = item_id_name
474
475 url = "{}{}/{}/{}".format(self.endpoint_url, tenant_text, item, uuid)
476 self.logger.debug("DELETE %s", url)
477 with aiohttp.Timeout(self.timeout_short):
478 async with session.delete(url, headers=self.headers_req) as response:
479 response_text = await response.read()
480 self.logger.debug("DELETE {} [{}] {}".format(url, response.status, response_text[:100]))
481 if response.status >= 300:
482 raise ROClientException(response_text, http_code=response.status)
483 return self._parse_yaml(response_text, response=True)
484
485 async def _list_item(self, session, item, all_tenants=False, filter_dict=None):
486 if all_tenants:
487 tenant_text = "/any"
488 elif all_tenants is None:
489 tenant_text = ""
490 else:
491 if not self.tenant:
492 await self._get_tenant(session)
493 tenant_text = "/" + self.tenant
494
495 url = "{}{}/{}".format(self.endpoint_url, tenant_text, item)
496 separator = "?"
497 if filter_dict:
498 for k in filter_dict:
499 url += separator + quote(str(k)) + "=" + quote(str(filter_dict[k]))
500 separator = "&"
501 self.logger.debug("openmano GET %s", url)
502 with aiohttp.Timeout(self.timeout_short):
503 async with session.get(url, headers=self.headers_req) as response:
504 response_text = await response.read()
505 self.logger.debug("GET {} [{}] {}".format(url, response.status, response_text[:100]))
506 if response.status >= 300:
507 raise ROClientException(response_text, http_code=response.status)
508 return self._parse_yaml(response_text, response=True)
509
510 async def _edit_item(self, session, item, item_id, descriptor, all_tenants=False):
511 if all_tenants:
512 tenant_text = "/any"
513 elif all_tenants is None:
514 tenant_text = ""
515 else:
516 if not self.tenant:
517 await self._get_tenant(session)
518 tenant_text = "/" + self.tenant
519
520 payload_req = yaml.safe_dump(descriptor)
521
522 #print payload_req
523
524 url = "{}{}/{}/{}".format(self.endpoint_url, tenant_text, item, item_id)
525 self.logger.debug("openmano PUT %s %s", url, payload_req)
526 with aiohttp.Timeout(self.timeout_large):
527 async with session.put(url, headers=self.headers_req, data=payload_req) as response:
528 response_text = await response.read()
529 self.logger.debug("PUT {} [{}] {}".format(url, response.status, response_text[:100]))
530 if response.status >= 300:
531 raise ROClientException(response_text, http_code=response.status)
532 return self._parse_yaml(response_text, response=True)
533
534 async def get_list(self, item, all_tenants=False, filter_by=None):
535 """
536 Obtain a list of items filtering by the specigy filter_by.
537 :param item: can be 'tenant', 'vim', 'vnfd', 'nsd', 'ns'
538 :param all_tenants: True if not filtering by tenant. Only allowed for admin
539 :param filter_by: dictionary with filtering
540 :return: a list of dict. It can be empty. Raises ROClientException on Error,
541 """
542 try:
543 if item not in self.client_to_RO:
544 raise ROClientException("Invalid item {}".format(item))
545 if item == 'tenant':
546 all_tenants = None
547 with aiohttp.ClientSession(loop=self.loop) as session:
548 content = await self._list_item(session, self.client_to_RO[item], all_tenants=all_tenants,
549 filter_dict=filter_by)
550 if isinstance(content, dict):
551 if len(content) == 1:
552 for _, v in content.items():
553 return v
554 return content.values()[0]
555 else:
556 raise ROClientException("Output not a list neither dict with len equal 1", http_code=500)
557 return content
558 except aiohttp.errors.ClientOSError as e:
559 raise ROClientException(e, http_code=504)
560
561 async def show(self, item, item_id_name=None, all_tenants=False):
562 """
563 Obtain the information of an item from its id or name
564 :param item: can be 'tenant', 'vim', 'vnfd', 'nsd', 'ns'
565 :param item_id_name: RO id or name of the item. Raise and exception if more than one found
566 :param all_tenants: True if not filtering by tenant. Only allowed for admin
567 :return: dictionary with the information or raises ROClientException on Error, NotFound, found several
568 """
569 try:
570 if item not in self.client_to_RO:
571 raise ROClientException("Invalid item {}".format(item))
572 if item == 'tenant':
573 all_tenants = None
574 elif item == 'vim':
575 all_tenants = True
576 elif item == 'vim_account':
577 all_tenants = False
578
579 with aiohttp.ClientSession(loop=self.loop) as session:
580 content = await self._get_item(session, self.client_to_RO[item], item_id_name, all_tenants=all_tenants)
581 return remove_envelop(item, content)
582 except aiohttp.errors.ClientOSError as e:
583 raise ROClientException(e, http_code=504)
584
585 async def delete(self, item, item_id_name=None, all_tenants=False):
586 """
587 Delete the information of an item from its id or name
588 :param item: can be 'tenant', 'vim', 'vnfd', 'nsd', 'ns'
589 :param item_id_name: RO id or name of the item. Raise and exception if more than one found
590 :param all_tenants: True if not filtering by tenant. Only allowed for admin
591 :return: dictionary with the information or raises ROClientException on Error, NotFound, found several
592 """
593 try:
594 if item not in self.client_to_RO:
595 raise ROClientException("Invalid item {}".format(item))
596 if item == 'tenant' or item == 'vim':
597 all_tenants = None
598
599 with aiohttp.ClientSession(loop=self.loop) as session:
600 return await self._del_item(session, self.client_to_RO[item], item_id_name, all_tenants=all_tenants)
601 except aiohttp.errors.ClientOSError as e:
602 raise ROClientException(e, http_code=504)
603
604 async def edit(self, item, item_id_name, descriptor=None, descriptor_format=None, **kwargs):
605 """ Edit an item
606 :param item: can be 'tenant', 'vim', 'vnfd', 'nsd', 'ns', 'vim'
607 :param descriptor: can be a dict, or a yaml/json text. Autodetect unless descriptor_format is provided
608 :param descriptor_format: Can be 'json' or 'yaml'
609 :param kwargs: Overrides descriptor with values as name, description, vim_url, vim_url_admin, vim_type
610 keys can be a dot separated list to specify elements inside dict
611 :return: dictionary with the information or raises ROClientException on Error
612 """
613 try:
614 if isinstance(descriptor, str):
615 descriptor = self._parse(descriptor, descriptor_format)
616 elif descriptor:
617 pass
618 else:
619 descriptor = {}
620
621 if item not in self.client_to_RO:
622 raise ROClientException("Invalid item {}".format(item))
623 desc = remove_envelop(item, descriptor)
624
625 # Override descriptor with kwargs
626 if kwargs:
627 desc = self.update_descriptor(desc, kwargs)
628 all_tenants = False
629 if item in ('tenant', 'vim'):
630 all_tenants = None
631
632 create_desc = self._create_envelop(item, desc)
633
634 with aiohttp.ClientSession(loop=self.loop) as session:
635 _all_tenants = all_tenants
636 if item == 'vim':
637 _all_tenants = True
638 item_id = await self._get_item_uuid(session, self.client_to_RO[item], item_id_name, all_tenants=_all_tenants)
639 # await self._get_tenant(session)
640 outdata = await self._edit_item(session, self.client_to_RO[item], item_id, create_desc, all_tenants=all_tenants)
641 return remove_envelop(item, outdata)
642 except aiohttp.errors.ClientOSError as e:
643 raise ROClientException(e, http_code=504)
644
645 async def create(self, item, descriptor=None, descriptor_format=None, **kwargs):
646 """
647 Creates an item from its descriptor
648 :param item: can be 'tenant', 'vnfd', 'nsd', 'ns', 'vim', 'vim_account', 'sdn'
649 :param descriptor: can be a dict, or a yaml/json text. Autodetect unless descriptor_format is provided
650 :param descriptor_format: Can be 'json' or 'yaml'
651 :param kwargs: Overrides descriptor with values as name, description, vim_url, vim_url_admin, vim_type
652 keys can be a dot separated list to specify elements inside dict
653 :return: dictionary with the information or raises ROClientException on Error
654 """
655 try:
656 if isinstance(descriptor, str):
657 descriptor = self._parse(descriptor, descriptor_format)
658 elif descriptor:
659 pass
660 else:
661 descriptor = {}
662
663 if item not in self.client_to_RO:
664 raise ROClientException("Invalid item {}".format(item))
665 desc = remove_envelop(item, descriptor)
666
667 # Override descriptor with kwargs
668 if kwargs:
669 desc = self.update_descriptor(desc, kwargs)
670
671 for mandatory in self.mandatory_for_create[item]:
672 if mandatory not in desc:
673 raise ROClientException("'{}' is mandatory parameter for {}".format(mandatory, item))
674
675 all_tenants = False
676 if item in ('tenant', 'vim'):
677 all_tenants = None
678
679 create_desc = self._create_envelop(item, desc)
680
681 with aiohttp.ClientSession(loop=self.loop) as session:
682 outdata = await self._create_item(session, self.client_to_RO[item], create_desc,
683 all_tenants=all_tenants)
684 return remove_envelop(item, outdata)
685 except aiohttp.errors.ClientOSError as e:
686 raise ROClientException(e, http_code=504)
687
688 async def attach_datacenter(self, datacenter=None, descriptor=None, descriptor_format=None, **kwargs):
689
690 if isinstance(descriptor, str):
691 descriptor = self._parse(descriptor, descriptor_format)
692 elif descriptor:
693 pass
694 else:
695 descriptor = {}
696 desc = remove_envelop("vim", descriptor)
697
698 # # check that exist
699 # uuid = self._get_item_uuid(session, "datacenters", uuid_name, all_tenants=True)
700 # tenant_text = "/" + self._get_tenant()
701 if kwargs:
702 desc = self.update_descriptor(desc, kwargs)
703
704 if not desc.get("vim_tenant_name") and not desc.get("vim_tenant_id"):
705 raise ROClientException("Wrong descriptor. At least vim_tenant_name or vim_tenant_id must be provided")
706 create_desc = self._create_envelop("vim", desc)
707 payload_req = yaml.safe_dump(create_desc)
708 with aiohttp.ClientSession(loop=self.loop) as session:
709 # check that exist
710 item_id = await self._get_item_uuid(session, "datacenters", datacenter, all_tenants=True)
711 await self._get_tenant(session)
712
713 url = "{}/{tenant}/datacenters/{datacenter}".format(self.endpoint_url, tenant=self.tenant,
714 datacenter=item_id)
715 self.logger.debug("openmano POST %s %s", url, payload_req)
716 with aiohttp.Timeout(self.timeout_large):
717 async with session.post(url, headers=self.headers_req, data=payload_req) as response:
718 response_text = await response.read()
719 self.logger.debug("POST {} [{}] {}".format(url, response.status, response_text[:100]))
720 if response.status >= 300:
721 raise ROClientException(response_text, http_code=response.status)
722
723 response_desc = self._parse_yaml(response_text, response=True)
724 desc = remove_envelop("vim", response_desc)
725 return desc
726
727 async def detach_datacenter(self, datacenter=None):
728 #TODO replace the code with delete_item(vim_account,...)
729 with aiohttp.ClientSession(loop=self.loop) as session:
730 # check that exist
731 item_id = await self._get_item_uuid(session, "datacenters", datacenter, all_tenants=False)
732 tenant = await self._get_tenant(session)
733
734 url = "{}/{tenant}/datacenters/{datacenter}".format(self.endpoint_url, tenant=tenant,
735 datacenter=item_id)
736 self.logger.debug("openmano DELETE %s", url)
737 with aiohttp.Timeout(self.timeout_large):
738 async with session.delete(url, headers=self.headers_req) as response:
739 response_text = await response.read()
740 self.logger.debug("DELETE {} [{}] {}".format(url, response.status, response_text[:100]))
741 if response.status >= 300:
742 raise ROClientException(response_text, http_code=response.status)
743
744 response_desc = self._parse_yaml(response_text, response=True)
745 desc = remove_envelop("vim", response_desc)
746 return desc
747
748
749 # TODO convert to asyncio
750
751 #DATACENTERS
752
753 def edit_datacenter(self, uuid=None, name=None, descriptor=None, descriptor_format=None, all_tenants=False, **kwargs):
754 """Edit the parameters of a datacenter
755 Params: must supply a descriptor or/and a parameter to change
756 uuid or/and name. If only name is supplied, there must be only one or an exception is raised
757 descriptor: with format {'datacenter':{params to change info}}
758 must be a dictionary or a json/yaml text.
759 parameters to change can be supplyied by the descriptor or as parameters:
760 new_name: the datacenter name
761 vim_url: the datacenter URL
762 vim_url_admin: the datacenter URL for administrative issues
763 vim_type: the datacenter type, can be openstack or openvim.
764 public: boolean, available to other tenants
765 description: datacenter description
766 Return: Raises an exception on error, not found or found several
767 Obtain a dictionary with format {'datacenter':{new_datacenter_info}}
768 """
769
770 if isinstance(descriptor, str):
771 descriptor = self.parse(descriptor, descriptor_format)
772 elif descriptor:
773 pass
774 elif kwargs:
775 descriptor={"datacenter": {}}
776 else:
777 raise ROClientException("Missing descriptor")
778
779 if 'datacenter' not in descriptor or len(descriptor)!=1:
780 raise ROClientException("Descriptor must contain only one 'datacenter' field")
781 for param in kwargs:
782 if param=='new_name':
783 descriptor['datacenter']['name'] = kwargs[param]
784 else:
785 descriptor['datacenter'][param] = kwargs[param]
786 return self._edit_item("datacenters", descriptor, uuid, name, all_tenants=None)
787
788
789 def edit_scenario(self, uuid=None, name=None, descriptor=None, descriptor_format=None, all_tenants=False, **kwargs):
790 """Edit the parameters of a scenario
791 Params: must supply a descriptor or/and a parameters to change
792 uuid or/and name. If only name is supplied, there must be only one or an exception is raised
793 descriptor: with format {'scenario':{params to change info}}
794 must be a dictionary or a json/yaml text.
795 parameters to change can be supplyied by the descriptor or as parameters:
796 new_name: the scenario name
797 public: boolean, available to other tenants
798 description: scenario description
799 tenant_id. Propietary tenant
800 Return: Raises an exception on error, not found or found several
801 Obtain a dictionary with format {'scenario':{new_scenario_info}}
802 """
803
804 if isinstance(descriptor, str):
805 descriptor = self.parse(descriptor, descriptor_format)
806 elif descriptor:
807 pass
808 elif kwargs:
809 descriptor={"scenario": {}}
810 else:
811 raise ROClientException("Missing descriptor")
812
813 if 'scenario' not in descriptor or len(descriptor)>2:
814 raise ROClientException("Descriptor must contain only one 'scenario' field")
815 for param in kwargs:
816 if param=='new_name':
817 descriptor['scenario']['name'] = kwargs[param]
818 else:
819 descriptor['scenario'][param] = kwargs[param]
820 return self._edit_item("scenarios", descriptor, uuid, name, all_tenants=None)
821
822 #VIM ACTIONS
823 def vim_action(self, action, item, uuid=None, all_tenants=False, **kwargs):
824 """Perform an action over a vim
825 Params:
826 action: can be 'list', 'get'/'show', 'delete' or 'create'
827 item: can be 'tenants' or 'networks'
828 uuid: uuid of the tenant/net to show or to delete. Ignore otherwise
829 other parameters:
830 datacenter_name, datacenter_id: datacenters to act on, if missing uses classes store datacenter
831 descriptor, descriptor_format: descriptor needed on creation, can be a dict or a yaml/json str
832 must be a dictionary or a json/yaml text.
833 name: for created tenant/net Overwrite descriptor name if any
834 description: tenant descriptor. Overwrite descriptor description if any
835
836 Return: Raises an exception on error
837 Obtain a dictionary with format {'tenant':{new_tenant_info}}
838 """
839 if item not in ("tenants", "networks", "images"):
840 raise ROClientException("Unknown value for item '{}', must be 'tenants', 'nets' or "
841 "images".format(str(item)))
842
843 image_actions = ['list','get','show','delete']
844 if item == "images" and action not in image_actions:
845 raise ROClientException("Only available actions for item '{}' are {}\n"
846 "Requested action was '{}'".format(item, ', '.join(image_actions), action))
847 if all_tenants:
848 tenant_text = "/any"
849 else:
850 tenant_text = "/"+self._get_tenant()
851
852 if "datacenter_id" in kwargs or "datacenter_name" in kwargs:
853 datacenter = self._get_item_uuid(session, "datacenters", kwargs.get("datacenter"), all_tenants=all_tenants)
854 else:
855 datacenter = self.get_datacenter(session)
856
857 if action=="list":
858 url = "{}{}/vim/{}/{}".format(self.endpoint_url, tenant_text, datacenter, item)
859 self.logger.debug("GET %s", url )
860 mano_response = requests.get(url, headers=self.headers_req)
861 self.logger.debug("openmano response: %s", mano_response.text )
862 content = self._parse_yaml(mano_response.text, response=True)
863 if mano_response.status_code==200:
864 return content
865 else:
866 raise ROClientException(str(content), http_code=mano_response.status)
867 elif action=="get" or action=="show":
868 url = "{}{}/vim/{}/{}/{}".format(self.endpoint_url, tenant_text, datacenter, item, uuid)
869 self.logger.debug("GET %s", url )
870 mano_response = requests.get(url, headers=self.headers_req)
871 self.logger.debug("openmano response: %s", mano_response.text )
872 content = self._parse_yaml(mano_response.text, response=True)
873 if mano_response.status_code==200:
874 return content
875 else:
876 raise ROClientException(str(content), http_code=mano_response.status)
877 elif action=="delete":
878 url = "{}{}/vim/{}/{}/{}".format(self.endpoint_url, tenant_text, datacenter, item, uuid)
879 self.logger.debug("DELETE %s", url )
880 mano_response = requests.delete(url, headers=self.headers_req)
881 self.logger.debug("openmano response: %s", mano_response.text )
882 content = self._parse_yaml(mano_response.text, response=True)
883 if mano_response.status_code==200:
884 return content
885 else:
886 raise ROClientException(str(content), http_code=mano_response.status)
887 elif action=="create":
888 if "descriptor" in kwargs:
889 if isinstance(kwargs["descriptor"], str):
890 descriptor = self._parse(kwargs["descriptor"], kwargs.get("descriptor_format") )
891 else:
892 descriptor = kwargs["descriptor"]
893 elif "name" in kwargs:
894 descriptor={item[:-1]: {"name": kwargs["name"]}}
895 else:
896 raise ROClientException("Missing descriptor")
897
898 if item[:-1] not in descriptor or len(descriptor)!=1:
899 raise ROClientException("Descriptor must contain only one 'tenant' field")
900 if "name" in kwargs:
901 descriptor[ item[:-1] ]['name'] = kwargs["name"]
902 if "description" in kwargs:
903 descriptor[ item[:-1] ]['description'] = kwargs["description"]
904 payload_req = yaml.safe_dump(descriptor)
905 #print payload_req
906 url = "{}{}/vim/{}/{}".format(self.endpoint_url, tenant_text, datacenter, item)
907 self.logger.debug("openmano POST %s %s", url, payload_req)
908 mano_response = requests.post(url, headers = self.headers_req, data=payload_req)
909 self.logger.debug("openmano response: %s", mano_response.text )
910 content = self._parse_yaml(mano_response.text, response=True)
911 if mano_response.status_code==200:
912 return content
913 else:
914 raise ROClientException(str(content), http_code=mano_response.status)
915 else:
916 raise ROClientException("Unknown value for action '{}".format(str(action)))
917
918
919 if __name__ == '__main__':
920 RO_URL = "http://localhost:9090/openmano"
921 TEST_TENANT = "myTenant"
922 TEST_VIM1 = "myvim"
923 TEST_URL1 = "https://localhost:5000/v1"
924 TEST_TYPE1 = "openstack"
925 TEST_CONFIG1 = {"use_floating_ip": True}
926 TEST_VIM2 = "myvim2"
927 TEST_URL2 = "https://localhost:5000/v2"
928 TEST_TYPE2 = "openvim"
929 TEST_CONFIG2 = {"config2": "config2", "config3": True}
930
931 streamformat = "%(asctime)s %(name)s %(levelname)s: %(message)s"
932 logging.basicConfig(format=streamformat)
933 logger = logging.getLogger("ROClient")
934
935 tenant_id = None
936 vim_id = False
937 loop = asyncio.get_event_loop()
938 myClient = ROClient(endpoint_url=RO_URL, loop=loop, loglevel="DEBUG")
939 try:
940 # test tenant
941 content = loop.run_until_complete(myClient.get_list("tenant"))
942 print("tenants", content)
943 content = loop.run_until_complete(myClient.create("tenant", name=TEST_TENANT))
944 tenant_id = True
945 content = loop.run_until_complete(myClient.show("tenant", TEST_TENANT))
946 print("tenant", TEST_TENANT, content)
947 content = loop.run_until_complete(myClient.edit("tenant", TEST_TENANT, description="another description"))
948 content = loop.run_until_complete(myClient.show("tenant", TEST_TENANT))
949 print("tenant edited", TEST_TENANT, content)
950 myClient["tenant"] = TEST_TENANT
951
952
953 # test VIM
954 content = loop.run_until_complete(myClient.create("vim", name=TEST_VIM1, type=TEST_TYPE1, vim_url=TEST_URL1, config=TEST_CONFIG1))
955 vim_id = True
956 content = loop.run_until_complete(myClient.get_list("vim"))
957 print("vim", content)
958 content = loop.run_until_complete(myClient.show("vim", TEST_VIM1))
959 print("vim", TEST_VIM1, content)
960 content = loop.run_until_complete(myClient.edit("vim", TEST_VIM1, description="another description",
961 name=TEST_VIM2, type=TEST_TYPE2, vim_url=TEST_URL2,
962 config=TEST_CONFIG2))
963 content = loop.run_until_complete(myClient.show("vim", TEST_VIM2))
964 print("vim edited", TEST_VIM2, content)
965
966 # test VIM_ACCOUNT
967 content = loop.run_until_complete(myClient.attach_datacenter(TEST_VIM2, vim_username='user',
968 vim_password='pass', vim_tenant_name='vimtenant1', config=TEST_CONFIG1))
969 vim_id = True
970 content = loop.run_until_complete(myClient.get_list("vim_account"))
971 print("vim_account", content)
972 content = loop.run_until_complete(myClient.show("vim_account", TEST_VIM2))
973 print("vim_account", TEST_VIM2, content)
974 content = loop.run_until_complete(myClient.edit("vim_account", TEST_VIM2, vim_username='user2', vim_password='pass2',
975 vim_tenant_name="vimtenant2", config=TEST_CONFIG2))
976 content = loop.run_until_complete(myClient.show("vim_account", TEST_VIM2))
977 print("vim_account edited", TEST_VIM2, content)
978
979 myClient["vim"] = TEST_VIM2
980
981 except Exception as e:
982 logger.error("Error {}".format(e), exc_info=True)
983
984 for item in (("vim_account", TEST_VIM1), ("vim", TEST_VIM1),
985 ("vim_account", TEST_VIM2), ("vim", TEST_VIM2),
986 ("tenant", TEST_TENANT)):
987 try:
988 content = loop.run_until_complete(myClient.delete(item[0], item[1]))
989 print("{} {} deleted; {}".format(item[0], item[1], content))
990 except Exception as e:
991 if e.http_code == 404:
992 print("{} {} not present or already deleted".format(item[0], item[1]))
993 else:
994 logger.error("Error {}".format(e), exc_info=True)
995
996 loop.close()
997
998