lightweight exception capturing, logging
[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) plus boolean indicating if it was enveloped
60 """
61 clean_indata = indata
62 enveloped = False
63 if not indata:
64 return {}, False
65 if item == "vnfd":
66 if clean_indata.get('vnfd:vnfd-catalog'):
67 enveloped = True
68 clean_indata = clean_indata['vnfd:vnfd-catalog']
69 elif clean_indata.get('vnfd-catalog'):
70 enveloped = True
71 clean_indata = clean_indata['vnfd-catalog']
72 if clean_indata.get('vnfd'):
73 if not isinstance(clean_indata['vnfd'], list) or len(clean_indata['vnfd']) != 1:
74 raise ROClientException("'vnfd' must be a list only one element")
75 clean_indata = clean_indata['vnfd'][0]
76 elif item == "nsd":
77 if clean_indata.get('nsd:nsd-catalog'):
78 enveloped = True
79 clean_indata = clean_indata['nsd:nsd-catalog']
80 elif clean_indata.get('nsd-catalog'):
81 enveloped = True
82 clean_indata = clean_indata['nsd-catalog']
83 if clean_indata.get('nsd'):
84 if not isinstance(clean_indata['nsd'], list) or len(clean_indata['nsd']) != 1:
85 raise ROClientException("'nsd' must be a list only one element")
86 clean_indata = clean_indata['nsd'][0]
87 elif item == "tenant":
88 if len(indata) == 1 and "tenant" in indata:
89 enveloped = True
90 clean_indata = indata["tenant"]
91 elif item == "vim" or item == "datacenter":
92 if len(indata) == 1 and "datacenter" in indata:
93 enveloped = True
94 clean_indata = indata["datacenter"]
95 elif item == "ns" or item == "instances":
96 if len(indata) == 1 and "instance" in indata:
97 enveloped = True
98 clean_indata = indata["instance"]
99 else:
100 assert False, "remove_envelop with unknown item {}".format(item)
101
102 return clean_indata, enveloped
103
104
105 class ROClient:
106 headers_req = {'Accept': 'application/yaml', 'content-type': 'application/yaml'}
107 client_to_RO = {'tenant': 'tenants', 'vim': 'datacenters', 'vnfd': 'vnfs', 'nsd': 'scenarios',
108 'ns': 'instances'}
109 mandatory_for_create = {
110 'tenant': ("name", ),
111 'vim': ("name", "vim_url"),
112 'vnfd': ("name", "id", "connection-point", "vdu"),
113 'nsd': ("name", "id", "constituent-vnfd"),
114 'ns': ("name", "scenario", "datacenter"),
115 }
116 timeout_large = 120
117 timeout_short = 30
118
119 def __init__(self, loop, endpoint_url, **kwargs):
120 self.loop = loop
121 self.endpoint_url = endpoint_url
122
123 self.username = kwargs.get("username")
124 self.password = kwargs.get("password")
125 self.tenant_id_name = kwargs.get("tenant")
126 self.tenant = None
127 self.datacenter_id_name = kwargs.get("datacenter")
128 self.datacenter = None
129 logger_name = kwargs.get('logger_name', 'ROClient')
130 self.logger = logging.getLogger(logger_name)
131 if kwargs.get("loglevel"):
132 self.logger.setLevel(kwargs["loglevel"])
133 global requests
134 requests = kwargs.get("TODO remove")
135
136 def __getitem__(self, index):
137 if index == 'tenant':
138 return self.tenant_id_name
139 elif index == 'datacenter':
140 return self.datacenter_id_name
141 elif index == 'username':
142 return self.username
143 elif index == 'password':
144 return self.password
145 elif index == 'endpoint_url':
146 return self.endpoint_url
147 else:
148 raise KeyError("Invalid key '%s'" %str(index))
149
150 def __setitem__(self,index, value):
151 if index == 'tenant':
152 self.tenant_id_name = value
153 elif index == 'datacenter':
154 self.datacenter_id_name = value
155 elif index == 'username':
156 self.username = value
157 elif index == 'password':
158 self.password = value
159 elif index == 'endpoint_url':
160 self.endpoint_url = value
161 else:
162 raise KeyError("Invalid key '{}'".format(index))
163 self.tenant = None # force to reload tenant with different credentials
164 self.datacenter = None # force to reload datacenter with different credentials
165
166 def _parse(self, descriptor, descriptor_format, response=False):
167 #try yaml
168 if descriptor_format and descriptor_format != "json" and descriptor_format != "yaml":
169 raise ROClientException("'descriptor_format' must be a 'json' or 'yaml' text")
170 if descriptor_format != "json":
171 try:
172 return yaml.load(descriptor)
173 except yaml.YAMLError as exc:
174 error_pos = ""
175 if hasattr(exc, 'problem_mark'):
176 mark = exc.problem_mark
177 error_pos = " at line:{} column:{}s".format(mark.line+1, mark.column+1)
178 error_text = "yaml format error" + error_pos
179 elif descriptor_format != "yaml":
180 try:
181 return json.loads(descriptor)
182 except Exception as e:
183 if response:
184 error_text = "json format error" + str(e)
185
186 if response:
187 raise ROClientException(error_text)
188 raise ROClientException(error_text)
189
190 def _parse_yaml(self, descriptor, response=False):
191 try:
192 return yaml.load(descriptor)
193 except yaml.YAMLError as exc:
194 error_pos = ""
195 if hasattr(exc, 'problem_mark'):
196 mark = exc.problem_mark
197 error_pos = " at line:{} column:{}s".format(mark.line+1, mark.column+1)
198 error_text = "yaml format error" + error_pos
199 if response:
200 raise ROClientException(error_text)
201 raise ROClientException(error_text)
202
203 @staticmethod
204 def check_if_uuid(uuid_text):
205 """
206 Check if text correspond to an uuid foramt
207 :param uuid_text:
208 :return: True if it is an uuid False if not
209 """
210 try:
211 UUID(uuid_text)
212 return True
213 except (ValueError, TypeError):
214 return False
215
216 @staticmethod
217 def _create_envelop(item, indata=None):
218 """
219 Returns a new dict that incledes indata with the expected envelop
220 :param item: can be 'tenant', 'vim', 'vnfd', 'nsd', 'ns'
221 :param indata: Content to be enveloped
222 :return: a new dic with {<envelop>: {indata} } where envelop can be e.g. tenant, datacenter, ...
223 """
224 if item == "vnfd":
225 return {'vnfd-catalog': {'vnfd': [indata]}}
226 elif item == "nsd":
227 return {'nsd-catalog': {'nsd': [indata]}}
228 elif item == "tenant":
229 return {'tenant': indata}
230 elif item == "vim" or item == "datacenter":
231 return {'datacenter': indata}
232 elif item == "ns" or item == "instances":
233 return {'instance': indata}
234 else:
235 assert False, "_create_envelop with unknown item {}".format(item)
236
237 @staticmethod
238 def check_ns_status(ns_descriptor):
239 """
240 Inspect RO instance descriptor and indicates the status
241 :param ns_descriptor: instance descriptor obtained with self.show("ns", )
242 :return: status, message: status can be BUILD,ACTIVE,ERROR, message is a text message
243 """
244 net_total = 0
245 vm_total = 0
246 net_done = 0
247 vm_done = 0
248
249 for net in ns_descriptor["nets"]:
250 net_total += 1
251 if net["status"] == "ERROR":
252 return "ERROR", net["error_msg"]
253 elif net["status"] == "ACTIVE":
254 net_done += 1
255 for vnf in ns_descriptor["vnfs"]:
256 for vm in vnf["vms"]:
257 vm_total += 1
258 if vm["status"] == "ERROR":
259 return "ERROR", vm["error_msg"]
260 elif vm["status"] == "ACTIVE":
261 vm_done += 1
262
263 if net_total == net_done and vm_total == vm_done:
264 return "ACTIVE", "VMs {}, networks: {}".format(vm_total, net_total)
265 else:
266 return "BUILD", "VMs: {}/{}, networks: {}/{}".format(vm_done, vm_total, net_done, net_total)
267
268 @staticmethod
269 def get_ns_vnf_ip(ns_descriptor):
270 """
271 Get a dict with the IPs of every vnf.
272 :param ns_descriptor: instance descriptor obtained with self.show("ns", )
273 :return: dict iwth key member_vnf_index, value ip_address
274 """
275 ns_ip={}
276 for vnf in ns_descriptor["vnfs"]:
277 ns_ip[str(vnf["member_vnf_index"])] = vnf["ip_address"]
278 #uuid sce_vnf_id
279 # vnf[mgmt_access]: '{interface_id: cf3cbf00-385c-49b4-9a3f-b400b7b15dc6, vm_id: d0dd22a9-91ef-46f1-8e8f-8cf4b2d5b2d7}'
280 # vnf[vms]
281 return ns_ip
282
283 async def get_list(self, item, all_tenants=False, filter_by=None):
284 """
285 Obtain a list of items filtering by the specigy filter_by.
286 :param item: can be 'tenant', 'vim', 'vnfd', 'nsd', 'ns'
287 :param all_tenants: True if not filtering by tenant. Only allowed for admin
288 :param filter_by: dictionary with filtering
289 :return: a list of dict. It can be empty. Raises ROClientException on Error,
290 """
291 try:
292 if item not in self.client_to_RO:
293 raise ROClientException("Invalid item {}".format(item))
294 if item == 'tenant':
295 all_tenants = None
296 with aiohttp.ClientSession(loop=self.loop) as session:
297 content = await self._list_item(session, self.client_to_RO[item], all_tenants=all_tenants,
298 filter_dict=filter_by)
299 if isinstance(content, dict):
300 if len(content) == 1:
301 for _, v in content.items():
302 return v
303 return content.values()[0]
304 else:
305 raise ROClientException("Output not a list neither dict with len equal 1", http_code=500)
306 return content
307 except aiohttp.errors.ClientOSError as e:
308 raise ROClientException(e, http_code=504)
309
310 async def _get_item_uuid(self, session, item, item_id_name, all_tenants=False):
311 if all_tenants:
312 tenant_text = "/any"
313 elif all_tenants is None:
314 tenant_text = ""
315 else:
316 if not self.tenant:
317 await self._get_tenant(session)
318 tenant_text = "/" + self.tenant
319
320 item_id = 0
321 url = "{}{}/{}".format(self.endpoint_url, tenant_text, item)
322 if self.check_if_uuid(item_id_name):
323 item_id = item_id_name
324 url += "/" + item_id_name
325 elif item_id_name and item_id_name.startswith("'") and item_id_name.endswith("'"):
326 item_id_name = item_id_name[1:-1]
327 self.logger.debug("openmano GET %s", url)
328 with aiohttp.Timeout(self.timeout_short):
329 async with session.get(url, headers=self.headers_req) as response:
330 response_text = await response.read()
331 self.logger.debug("GET {} [{}] {}".format(url, response.status, response_text[:100]))
332 if response.status == 404: # NOT_FOUND
333 raise ROClientException("No {} found with id '{}'".format(item[:-1], item_id_name),
334 http_code=404)
335 if response.status >= 300:
336 raise ROClientException(response_text, http_code=response.status)
337 content = self._parse_yaml(response_text, response=True)
338
339 if item_id:
340 return item_id
341 desc = content[item]
342 assert isinstance(desc, list), "_get_item_uuid get a non dict with a list inside {}".format(type(desc))
343 uuid = None
344 for i in desc:
345 if item_id_name and i["name"] != item_id_name:
346 continue
347 if uuid: # found more than one
348 raise ROClientException(
349 "Found more than one {} with name '{}'. uuid must be used".format(item, item_id_name),
350 http_code=404)
351 uuid = i["uuid"]
352 if not uuid:
353 raise ROClientException("No {} found with name '{}'".format(item[:-1], item_id_name), http_code=404)
354 return uuid
355
356 async def _get_item(self, session, item, item_id_name, all_tenants=False):
357 if all_tenants:
358 tenant_text = "/any"
359 elif all_tenants is None:
360 tenant_text = ""
361 else:
362 if not self.tenant:
363 await self._get_tenant(session)
364 tenant_text = "/" + self.tenant
365
366 if self.check_if_uuid(item_id_name):
367 uuid = item_id_name
368 else:
369 # check that exist
370 uuid = self._get_item_uuid(session, item, item_id_name, all_tenants)
371
372 url = "{}{}/{}/{}".format(self.endpoint_url, tenant_text, item, uuid)
373 self.logger.debug("GET %s", url )
374 with aiohttp.Timeout(self.timeout_short):
375 async with session.get(url, headers=self.headers_req) as response:
376 response_text = await response.read()
377 self.logger.debug("GET {} [{}] {}".format(url, response.status, response_text[:100]))
378 if response.status >= 300:
379 raise ROClientException(response_text, http_code=response.status)
380
381 return self._parse_yaml(response_text, response=True)
382
383 async def _get_tenant(self, session):
384 if not self.tenant:
385 self.tenant = await self._get_item_uuid(session, "tenants", self.tenant_id_name, None)
386 return self.tenant
387
388 async def _get_datacenter(self, session):
389 if not self.tenant:
390 await self._get_tenant(session)
391 if not self.datacenter:
392 self.datacenter = await self._get_item_uuid(session, "datacenters", self.datacenter_id_name, True)
393 return self.datacenter
394
395 async def _create_item(self, session, item, descriptor, all_tenants=False):
396 if all_tenants:
397 tenant_text = "/any"
398 elif all_tenants is None:
399 tenant_text = ""
400 else:
401 if not self.tenant:
402 await self._get_tenant(session)
403 tenant_text = "/" + self.tenant
404 payload_req = yaml.safe_dump(descriptor)
405
406 api_version_text = ""
407 if item == "vnfs":
408 # assumes version v3 only
409 api_version_text = "/v3"
410 item = "vnfd"
411 elif item == "scenarios":
412 # assumes version v3 only
413 api_version_text = "/v3"
414 item = "nsd"
415
416 #print payload_req
417
418 url = "{}{apiver}{tenant}/{item}".format(self.endpoint_url, apiver=api_version_text, tenant=tenant_text,
419 item=item)
420 self.logger.debug("openmano POST %s %s", url, payload_req)
421 with aiohttp.Timeout(self.timeout_large):
422 async with session.post(url, headers=self.headers_req, data=payload_req) as response:
423 response_text = await response.read()
424 self.logger.debug("POST {} [{}] {}".format(url, response.status, response_text[:100]))
425 if response.status >= 300:
426 raise ROClientException(response_text, http_code=response.status)
427
428 response_desc = self._parse_yaml(response_text, response=True)
429 desc, _ = remove_envelop(item, response_desc)
430 return desc
431
432 async def _del_item(self, session, item, item_id_name, all_tenants=False):
433 if all_tenants:
434 tenant_text = "/any"
435 elif all_tenants is None:
436 tenant_text = ""
437 else:
438 if not self.tenant:
439 await self._get_tenant(session)
440 tenant_text = "/" + self.tenant
441 if not self.check_if_uuid(item_id_name):
442 # check that exist
443 uuid = await self._get_item_uuid(session, item, item_id_name, all_tenants)
444 else:
445 uuid = item_id_name
446
447 url = "{}{}/{}/{}".format(self.endpoint_url, tenant_text, item, uuid)
448 self.logger.debug("DELETE %s", url)
449 with aiohttp.Timeout(self.timeout_short):
450 async with session.delete(url, headers=self.headers_req) as response:
451 response_text = await response.read()
452 self.logger.debug("DELETE {} [{}] {}".format(url, response.status, response_text[:100]))
453 if response.status >= 300:
454 raise ROClientException(response_text, http_code=response.status)
455 return self._parse_yaml(response_text, response=True)
456
457 async def _list_item(self, session, item, all_tenants=False, filter_dict=None):
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
467 url = "{}{}/{}".format(self.endpoint_url, tenant_text, item)
468 separator = "?"
469 if filter_dict:
470 for k in filter_dict:
471 url += separator + quote(str(k)) + "=" + quote(str(filter_dict[k]))
472 separator = "&"
473 self.logger.debug("openmano GET %s", url)
474 with aiohttp.Timeout(self.timeout_short):
475 async with session.get(url, headers=self.headers_req) as response:
476 response_text = await response.read()
477 self.logger.debug("GET {} [{}] {}".format(url, response.status, response_text[:100]))
478 if response.status >= 300:
479 raise ROClientException(response_text, http_code=response.status)
480 return self._parse_yaml(response_text, response=True)
481
482 async def _edit_item(self, session, item, descriptor, item_id_name, all_tenants=False):
483 if all_tenants:
484 tenant_text = "/any"
485 elif all_tenants is None:
486 tenant_text = ""
487 else:
488 if not self.tenant:
489 await self._get_tenant(session)
490 tenant_text = "/" + self.tenant
491
492 if not uuid:
493 #check that exist
494 uuid = self._get_item_uuid(session, "tenants", item_id_name, all_tenants)
495
496 payload_req = yaml.safe_dump(descriptor)
497
498 #print payload_req
499
500 url = "{}{}/{}/{}".format(self.endpoint_url, tenant_text, item, uuid)
501 self.logger.debug("openmano PUT %s %s", url, payload_req)
502 with aiohttp.Timeout(self.timeout_large):
503 async with session.put(url, headers=self.headers_req, data=payload_req) as response:
504 response_text = await response.read()
505 self.logger.debug("PUT {} [{}] {}".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 show(self, item, item_id_name=None, all_tenants=False):
511 """
512 Obtain the information of an item from its id or name
513 :param item: can be 'tenant', 'vim', 'vnfd', 'nsd', 'ns'
514 :param item_id_name: RO id or name of the item. Raise and exception if more than one found
515 :param all_tenants: True if not filtering by tenant. Only allowed for admin
516 :return: dictionary with the information or raises ROClientException on Error, NotFound, found several
517 """
518 try:
519 if item not in self.client_to_RO:
520 raise ROClientException("Invalid item {}".format(item))
521 if item == 'tenant':
522 all_tenants = None
523
524 with aiohttp.ClientSession(loop=self.loop) as session:
525 content = await self._get_item(session, self.client_to_RO[item], item_id_name, all_tenants=all_tenants)
526 if len(content) == 1:
527 return content.values()[0]
528 else:
529 return content
530 except aiohttp.errors.ClientOSError as e:
531 raise ROClientException(e, http_code=504)
532
533 async def delete(self, item, item_id_name=None, all_tenants=False):
534 """
535 Delete the information of an item from its id or name
536 :param item: can be 'tenant', 'vim', 'vnfd', 'nsd', 'ns'
537 :param item_id_name: RO id or name of the item. Raise and exception if more than one found
538 :param all_tenants: True if not filtering by tenant. Only allowed for admin
539 :return: dictionary with the information or raises ROClientException on Error, NotFound, found several
540 """
541 try:
542 if item not in self.client_to_RO:
543 raise ROClientException("Invalid item {}".format(item))
544 if item == 'tenant':
545 all_tenants = None
546
547 with aiohttp.ClientSession(loop=self.loop) as session:
548 if item == 'vim':
549 # check that exist
550 item_id = await self._get_item_uuid(session, "datacenters", item_id_name, all_tenants=True)
551 all_tenants = None
552 return await self._del_item(session, self.client_to_RO[item], item_id_name, all_tenants=all_tenants)
553 except aiohttp.errors.ClientOSError as e:
554 raise ROClientException(e, http_code=504)
555
556 async def create(self, item, descriptor=None, descriptor_format=None, **kwargs):
557 """
558 Creates an item from its descriptor
559 :param item: can be 'tenant', 'vim', 'vnfd', 'nsd', 'ns'
560 :param descriptor: can be a dict, or a yaml/json text. Autodetect unless descriptor_format is provided
561 :param descriptor_format: Can be 'json' or 'yaml'
562 :param kwargs: Overrides descriptor with values as name, description, vim_url, vim_url_admin, vim_type
563 keys can be a dot separated list to specify elements inside dict
564 :return: dictionary with the information or raises ROClientException on Error
565 """
566 try:
567 if isinstance(descriptor, str):
568 descriptor = self._parse(descriptor, descriptor_format)
569 elif descriptor:
570 pass
571 else:
572 descriptor = {}
573
574 if item not in self.client_to_RO:
575 raise ROClientException("Invalid item {}".format(item))
576 desc, enveloped = remove_envelop(item, descriptor)
577
578 # Override descriptor with kwargs
579 if kwargs:
580 desc = deepcopy(desc) # do not modify original descriptor
581 try:
582 for k, v in kwargs.items():
583 update_content = desc
584 kitem_old = None
585 klist = k.split(".")
586 for kitem in klist:
587 if kitem_old is not None:
588 update_content = update_content[kitem_old]
589 if isinstance(update_content, dict):
590 kitem_old = kitem
591 elif isinstance(update_content, list):
592 kitem_old = int(kitem)
593 else:
594 raise ROClientException(
595 "Invalid query string '{}'. Descriptor is not a list nor dict at '{}'".format(k, kitem))
596 if v == "__DELETE__":
597 del update_content[kitem_old]
598 else:
599 update_content[kitem_old] = v
600 except KeyError:
601 raise ROClientException(
602 "Invalid query string '{}'. Descriptor does not contain '{}'".format(k, kitem_old))
603 except ValueError:
604 raise ROClientException("Invalid query string '{}'. Expected integer index list instead of '{}'".format(
605 k, kitem))
606 except IndexError:
607 raise ROClientException(
608 "Invalid query string '{}'. Index '{}' out of range".format(k, kitem_old))
609
610 for mandatory in self.mandatory_for_create[item]:
611 if mandatory not in desc:
612 raise ROClientException("'{}' is mandatory parameter for {}".format(mandatory, item))
613
614 all_tenants = False
615 if item in ('tenant', 'vim'):
616 all_tenants = None
617
618 if not enveloped:
619 create_desc = self._create_envelop(item, desc)
620 else:
621 create_desc = descriptor
622
623 with aiohttp.ClientSession(loop=self.loop) as session:
624 return await self._create_item(session, self.client_to_RO[item], create_desc, all_tenants)
625 except aiohttp.errors.ClientOSError as e:
626 raise ROClientException(e, http_code=504)
627
628 def edit_tenant(self, uuid=None, name=None, descriptor=None, descriptor_format=None, new_name=None, new_description=None):
629 """Edit the parameters of a tenant
630 Params: must supply a descriptor or/and a new_name or new_description
631 uuid or/and name. If only name is supplied, there must be only one or an exception is raised
632 descriptor: with format {'tenant':{params to change info}}
633 must be a dictionary or a json/yaml text.
634 name: the tenant name. Overwrite descriptor name if any
635 description: tenant descriptor.. Overwrite descriptor description if any
636 Return: Raises an exception on error, not found or found several
637 Obtain a dictionary with format {'tenant':{newtenant_info}}
638 """
639 try:
640 # TODO revise
641 if isinstance(descriptor, str):
642 descriptor = self.parse(descriptor, descriptor_format)
643 elif descriptor:
644 pass
645 elif new_name or new_description:
646 descriptor={"tenant": {}}
647 else:
648 raise ROClientException("Missing descriptor")
649
650 if 'tenant' not in descriptor or len(descriptor)!=1:
651 raise ROClientException("Descriptor must contain only one 'tenant' field")
652 if new_name:
653 descriptor['tenant']['name'] = new_name
654 if new_description:
655 descriptor['tenant']['description'] = new_description
656
657 return self._edit_item("tenants", descriptor, uuid, name, all_tenants=None)
658 except aiohttp.errors.ClientOSError as e:
659 raise ROClientException(e, http_code=504)
660
661 #DATACENTERS
662
663 def edit_datacenter(self, uuid=None, name=None, descriptor=None, descriptor_format=None, all_tenants=False, **kwargs):
664 """Edit the parameters of a datacenter
665 Params: must supply a descriptor or/and a parameter to change
666 uuid or/and name. If only name is supplied, there must be only one or an exception is raised
667 descriptor: with format {'datacenter':{params to change info}}
668 must be a dictionary or a json/yaml text.
669 parameters to change can be supplyied by the descriptor or as parameters:
670 new_name: the datacenter name
671 vim_url: the datacenter URL
672 vim_url_admin: the datacenter URL for administrative issues
673 vim_type: the datacenter type, can be openstack or openvim.
674 public: boolean, available to other tenants
675 description: datacenter description
676 Return: Raises an exception on error, not found or found several
677 Obtain a dictionary with format {'datacenter':{new_datacenter_info}}
678 """
679
680 if isinstance(descriptor, str):
681 descriptor = self.parse(descriptor, descriptor_format)
682 elif descriptor:
683 pass
684 elif kwargs:
685 descriptor={"datacenter": {}}
686 else:
687 raise ROClientException("Missing descriptor")
688
689 if 'datacenter' not in descriptor or len(descriptor)!=1:
690 raise ROClientException("Descriptor must contain only one 'datacenter' field")
691 for param in kwargs:
692 if param=='new_name':
693 descriptor['datacenter']['name'] = kwargs[param]
694 else:
695 descriptor['datacenter'][param] = kwargs[param]
696 return self._edit_item("datacenters", descriptor, uuid, name, all_tenants=None)
697
698 def attach_datacenter(self, uuid_name=None, descriptor=None, descriptor_format=None, vim_user=None, vim_password=None, vim_tenant_name=None, vim_tenant_id=None):
699 #check that exist
700 uuid = self._get_item_uuid(session, "datacenters", uuid_name, all_tenants=True)
701 tenant_text = "/"+self._get_tenant()
702
703 if isinstance(descriptor, str):
704 descriptor = self.parse(descriptor, descriptor_format)
705 elif descriptor:
706 pass
707 elif vim_user or vim_password or vim_tenant_name or vim_tenant_id:
708 descriptor={"datacenter": {}}
709 else:
710 raise ROClientException("Missing descriptor or params")
711
712 if vim_user or vim_password or vim_tenant_name or vim_tenant_id:
713 #print args.name
714 try:
715 if vim_user:
716 descriptor['datacenter']['vim_user'] = vim_user
717 if vim_password:
718 descriptor['datacenter']['vim_password'] = vim_password
719 if vim_tenant_name:
720 descriptor['datacenter']['vim_tenant_name'] = vim_tenant_name
721 if vim_tenant_id:
722 descriptor['datacenter']['vim_tenant'] = vim_tenant_id
723 except (KeyError, TypeError) as e:
724 if str(e)=='datacenter': error_pos= "missing field 'datacenter'"
725 else: error_pos="wrong format"
726 raise ROClientException("Wrong datacenter descriptor: " + error_pos)
727
728 payload_req = yaml.safe_dump(descriptor)
729 #print payload_req
730 url = "{}{}/datacenters/{}".format(self.endpoint_url, tenant_text, uuid)
731 self.logger.debug("openmano POST %s %s", url, payload_req)
732 mano_response = requests.post(url, headers = self.headers_req, data=payload_req)
733 self.logger.debug("openmano response: %s", mano_response.text )
734
735 content = self._parse_yaml(mano_response.text, response=True)
736 if mano_response.status_code==200:
737 return content
738 else:
739 raise ROClientException(str(content), http_code=mano_response.status)
740
741 def detach_datacenter(self, uuid_name=None):
742 if not uuid:
743 #check that exist
744 uuid = self._get_item_uuid(session, "datacenters", uuid_name, all_tenants=False)
745 tenant_text = "/"+self._get_tenant()
746 url = "{}{}/datacenters/{}".format(self.endpoint_url, tenant_text, uuid)
747 self.logger.debug("openmano DELETE %s", url)
748 mano_response = requests.delete(url, headers = self.headers_req)
749 self.logger.debug("openmano response: %s", mano_response.text )
750
751 content = self._parse_yaml(mano_response.text, response=True)
752 if mano_response.status_code==200:
753 return content
754 else:
755 raise ROClientException(str(content), http_code=mano_response.status)
756
757 #VNFS
758
759 def edit_scenario(self, uuid=None, name=None, descriptor=None, descriptor_format=None, all_tenants=False, **kwargs):
760 """Edit the parameters of a scenario
761 Params: must supply a descriptor or/and a parameters to change
762 uuid or/and name. If only name is supplied, there must be only one or an exception is raised
763 descriptor: with format {'scenario':{params to change info}}
764 must be a dictionary or a json/yaml text.
765 parameters to change can be supplyied by the descriptor or as parameters:
766 new_name: the scenario name
767 public: boolean, available to other tenants
768 description: scenario description
769 tenant_id. Propietary tenant
770 Return: Raises an exception on error, not found or found several
771 Obtain a dictionary with format {'scenario':{new_scenario_info}}
772 """
773
774 if isinstance(descriptor, str):
775 descriptor = self.parse(descriptor, descriptor_format)
776 elif descriptor:
777 pass
778 elif kwargs:
779 descriptor={"scenario": {}}
780 else:
781 raise ROClientException("Missing descriptor")
782
783 if 'scenario' not in descriptor or len(descriptor)>2:
784 raise ROClientException("Descriptor must contain only one 'scenario' field")
785 for param in kwargs:
786 if param=='new_name':
787 descriptor['scenario']['name'] = kwargs[param]
788 else:
789 descriptor['scenario'][param] = kwargs[param]
790 return self._edit_item("scenarios", descriptor, uuid, name, all_tenants=None)
791
792 #VIM ACTIONS
793 def vim_action(self, action, item, uuid=None, all_tenants=False, **kwargs):
794 """Perform an action over a vim
795 Params:
796 action: can be 'list', 'get'/'show', 'delete' or 'create'
797 item: can be 'tenants' or 'networks'
798 uuid: uuid of the tenant/net to show or to delete. Ignore otherwise
799 other parameters:
800 datacenter_name, datacenter_id: datacenters to act on, if missing uses classes store datacenter
801 descriptor, descriptor_format: descriptor needed on creation, can be a dict or a yaml/json str
802 must be a dictionary or a json/yaml text.
803 name: for created tenant/net Overwrite descriptor name if any
804 description: tenant descriptor. Overwrite descriptor description if any
805
806 Return: Raises an exception on error
807 Obtain a dictionary with format {'tenant':{new_tenant_info}}
808 """
809 if item not in ("tenants", "networks", "images"):
810 raise ROClientException("Unknown value for item '{}', must be 'tenants', 'nets' or "
811 "images".format(str(item)))
812
813 image_actions = ['list','get','show','delete']
814 if item == "images" and action not in image_actions:
815 raise ROClientException("Only available actions for item '{}' are {}\n"
816 "Requested action was '{}'".format(item, ', '.join(image_actions), action))
817 if all_tenants:
818 tenant_text = "/any"
819 else:
820 tenant_text = "/"+self._get_tenant()
821
822 if "datacenter_id" in kwargs or "datacenter_name" in kwargs:
823 datacenter = self._get_item_uuid(session, "datacenters", kwargs.get("datacenter"), all_tenants=all_tenants)
824 else:
825 datacenter = self.get_datacenter(session)
826
827 if action=="list":
828 url = "{}{}/vim/{}/{}".format(self.endpoint_url, tenant_text, datacenter, item)
829 self.logger.debug("GET %s", url )
830 mano_response = requests.get(url, headers=self.headers_req)
831 self.logger.debug("openmano response: %s", mano_response.text )
832 content = self._parse_yaml(mano_response.text, response=True)
833 if mano_response.status_code==200:
834 return content
835 else:
836 raise ROClientException(str(content), http_code=mano_response.status)
837 elif action=="get" or action=="show":
838 url = "{}{}/vim/{}/{}/{}".format(self.endpoint_url, tenant_text, datacenter, item, uuid)
839 self.logger.debug("GET %s", url )
840 mano_response = requests.get(url, headers=self.headers_req)
841 self.logger.debug("openmano response: %s", mano_response.text )
842 content = self._parse_yaml(mano_response.text, response=True)
843 if mano_response.status_code==200:
844 return content
845 else:
846 raise ROClientException(str(content), http_code=mano_response.status)
847 elif action=="delete":
848 url = "{}{}/vim/{}/{}/{}".format(self.endpoint_url, tenant_text, datacenter, item, uuid)
849 self.logger.debug("DELETE %s", url )
850 mano_response = requests.delete(url, headers=self.headers_req)
851 self.logger.debug("openmano response: %s", mano_response.text )
852 content = self._parse_yaml(mano_response.text, response=True)
853 if mano_response.status_code==200:
854 return content
855 else:
856 raise ROClientException(str(content), http_code=mano_response.status)
857 elif action=="create":
858 if "descriptor" in kwargs:
859 if isinstance(kwargs["descriptor"], str):
860 descriptor = self._parse(kwargs["descriptor"], kwargs.get("descriptor_format") )
861 else:
862 descriptor = kwargs["descriptor"]
863 elif "name" in kwargs:
864 descriptor={item[:-1]: {"name": kwargs["name"]}}
865 else:
866 raise ROClientException("Missing descriptor")
867
868 if item[:-1] not in descriptor or len(descriptor)!=1:
869 raise ROClientException("Descriptor must contain only one 'tenant' field")
870 if "name" in kwargs:
871 descriptor[ item[:-1] ]['name'] = kwargs["name"]
872 if "description" in kwargs:
873 descriptor[ item[:-1] ]['description'] = kwargs["description"]
874 payload_req = yaml.safe_dump(descriptor)
875 #print payload_req
876 url = "{}{}/vim/{}/{}".format(self.endpoint_url, tenant_text, datacenter, item)
877 self.logger.debug("openmano POST %s %s", url, payload_req)
878 mano_response = requests.post(url, headers = self.headers_req, data=payload_req)
879 self.logger.debug("openmano response: %s", mano_response.text )
880 content = self._parse_yaml(mano_response.text, response=True)
881 if mano_response.status_code==200:
882 return content
883 else:
884 raise ROClientException(str(content), http_code=mano_response.status)
885 else:
886 raise ROClientException("Unknown value for action '{}".format(str(action)))
887
888
889 if __name__ == '__main__':
890 RO_URL = "http://localhost:9090/openmano"
891 RO_TENANT = "osm"
892 RO_VIM = "myvim"
893
894 streamformat = "%(asctime)s %(name)s %(levelname)s: %(message)s"
895 logging.basicConfig(format=streamformat)
896
897 loop = asyncio.get_event_loop()
898 myClient = ROClient(endpoint_url=RO_URL, loop=loop, tenant_id=RO_TENANT, datacenter_id=RO_VIM, debug=True)
899 content = loop.run_until_complete(myClient.list_tenants())
900 print(content)
901 loop.close()
902
903