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