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