blob: bbea5fe34f0b6f37dafb993db3e15af33a82f0ff [file] [log] [blame]
tierno6ce7cc82018-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"]:
tiernof3c4dbc2018-02-05 14:53:28 +0100275 ns_ip[str(vnf["member_vnf_index"])] = vnf["ip_address"]
tierno6ce7cc82018-01-29 18:44:04 +0100276 #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:
tiernof3c4dbc2018-02-05 14:53:28 +0100298 for _, v in content.items():
299 return v
tierno6ce7cc82018-01-29 18:44:04 +0100300 return content.values()[0]
301 else:
302 raise ROClientException("Output not a list neither dict with len equal 1", http_code=500)
303 return content
304
305 async def _get_item_uuid(self, session, item, item_id_name, all_tenants=False):
306 if all_tenants:
307 tenant_text = "/any"
308 elif all_tenants is None:
309 tenant_text = ""
310 else:
311 if not self.tenant:
312 await self._get_tenant(session)
313 tenant_text = "/" + self.tenant
314
315 item_id = 0
316 url = "{}{}/{}".format(self.endpoint_url, tenant_text, item)
317 if self.check_if_uuid(item_id_name):
318 item_id = item_id_name
319 url += "/" + item_id_name
320 elif item_id_name and item_id_name.startswith("'") and item_id_name.endswith("'"):
321 item_id_name = item_id_name[1:-1]
322 self.logger.debug("openmano GET %s", url)
323 with aiohttp.Timeout(self.timeout_short):
324 async with session.get(url, headers=self.headers_req) as response:
325 response_text = await response.read()
326 self.logger.debug("GET {} [{}] {}".format(url, response.status, response_text[:100]))
327 if response.status == 404: # NOT_FOUND
328 raise ROClientException("No {} found with id '{}'".format(item[:-1], item_id_name),
329 http_code=404)
330 if response.status >= 300:
331 raise ROClientException(response_text, http_code=response.status)
332 content = self._parse_yaml(response_text, response=True)
333
334 if item_id:
335 return item_id
336 desc = content[item]
337 assert isinstance(desc, list), "_get_item_uuid get a non dict with a list inside {}".format(type(desc))
338 uuid = None
339 for i in desc:
340 if item_id_name and i["name"] != item_id_name:
341 continue
342 if uuid: # found more than one
343 raise ROClientException(
344 "Found more than one {} with name '{}'. uuid must be used".format(item, item_id_name),
345 http_code=404)
346 uuid = i["uuid"]
347 if not uuid:
348 raise ROClientException("No {} found with name '{}'".format(item[:-1], item_id_name), http_code=404)
349 return uuid
350
351 async def _get_item(self, session, item, item_id_name, all_tenants=False):
352 if all_tenants:
353 tenant_text = "/any"
354 elif all_tenants is None:
355 tenant_text = ""
356 else:
357 if not self.tenant:
358 await self._get_tenant(session)
359 tenant_text = "/" + self.tenant
360
361 if self.check_if_uuid(item_id_name):
362 uuid = item_id_name
363 else:
364 # check that exist
365 uuid = self._get_item_uuid(session, item, item_id_name, all_tenants)
366
367 url = "{}{}/{}/{}".format(self.endpoint_url, tenant_text, item, uuid)
368 self.logger.debug("GET %s", url )
369 with aiohttp.Timeout(self.timeout_short):
370 async with session.get(url, headers=self.headers_req) as response:
371 response_text = await response.read()
372 self.logger.debug("GET {} [{}] {}".format(url, response.status, response_text[:100]))
373 if response.status >= 300:
374 raise ROClientException(response_text, http_code=response.status)
375
376 return self._parse_yaml(response_text, response=True)
377
378 async def _get_tenant(self, session):
379 if not self.tenant:
380 self.tenant = await self._get_item_uuid(session, "tenants", self.tenant_id_name, None)
381 return self.tenant
382
383 async def _get_datacenter(self, session):
384 if not self.tenant:
385 await self._get_tenant(session)
386 if not self.datacenter:
387 self.datacenter = await self._get_item_uuid(session, "datacenters", self.datacenter_id_name, True)
388 return self.datacenter
389
390 async def _create_item(self, session, item, descriptor, all_tenants=False):
391 if all_tenants:
392 tenant_text = "/any"
393 elif all_tenants is None:
394 tenant_text = ""
395 else:
396 if not self.tenant:
397 await self._get_tenant(session)
398 tenant_text = "/" + self.tenant
399 payload_req = yaml.safe_dump(descriptor)
400
401 api_version_text = ""
402 if item == "vnfs":
403 # assumes version v3 only
404 api_version_text = "/v3"
405 item = "vnfd"
406 elif item == "scenarios":
407 # assumes version v3 only
408 api_version_text = "/v3"
409 item = "nsd"
410
411 #print payload_req
412
413 url = "{}{apiver}{tenant}/{item}".format(self.endpoint_url, apiver=api_version_text, tenant=tenant_text,
414 item=item)
415 self.logger.debug("openmano POST %s %s", url, payload_req)
416 with aiohttp.Timeout(self.timeout_large):
417 async with session.post(url, headers=self.headers_req, data=payload_req) as response:
418 response_text = await response.read()
419 self.logger.debug("POST {} [{}] {}".format(url, response.status, response_text[:100]))
420 if response.status >= 300:
421 raise ROClientException(response_text, http_code=response.status)
422
423 response_desc = self._parse_yaml(response_text, response=True)
424 desc, _ = remove_envelop(item, response_desc)
425 return desc
426
427 async def _del_item(self, session, item, item_id_name, all_tenants=False):
428 if all_tenants:
429 tenant_text = "/any"
430 elif all_tenants is None:
431 tenant_text = ""
432 else:
433 if not self.tenant:
434 await self._get_tenant(session)
435 tenant_text = "/" + self.tenant
436 if not self.check_if_uuid(item_id_name):
437 # check that exist
438 uuid = await self._get_item_uuid(session, item, item_id_name, all_tenants)
439 else:
440 uuid = item_id_name
441
442 url = "{}{}/{}/{}".format(self.endpoint_url, tenant_text, item, uuid)
443 self.logger.debug("DELETE %s", url)
444 with aiohttp.Timeout(self.timeout_short):
445 async with session.delete(url, headers=self.headers_req) as response:
446 response_text = await response.read()
447 self.logger.debug("DELETE {} [{}] {}".format(url, response.status, response_text[:100]))
448 if response.status >= 300:
449 raise ROClientException(response_text, http_code=response.status)
450 return self._parse_yaml(response_text, response=True)
451
452 async def _list_item(self, session, item, all_tenants=False, filter_dict=None):
453 if all_tenants:
454 tenant_text = "/any"
455 elif all_tenants is None:
456 tenant_text = ""
457 else:
458 if not self.tenant:
459 await self._get_tenant(session)
460 tenant_text = "/" + self.tenant
461
462 url = "{}{}/{}".format(self.endpoint_url, tenant_text, item)
463 separator = "?"
464 if filter_dict:
465 for k in filter_dict:
466 url += separator + quote(str(k)) + "=" + quote(str(filter_dict[k]))
467 separator = "&"
468 self.logger.debug("openmano GET %s", url)
469 with aiohttp.Timeout(self.timeout_short):
470 async with session.get(url, headers=self.headers_req) as response:
471 response_text = await response.read()
472 self.logger.debug("GET {} [{}] {}".format(url, response.status, response_text[:100]))
473 if response.status >= 300:
474 raise ROClientException(response_text, http_code=response.status)
475 return self._parse_yaml(response_text, response=True)
476
477 async def _edit_item(self, session, item, descriptor, item_id_name, all_tenants=False):
478 if all_tenants:
479 tenant_text = "/any"
480 elif all_tenants is None:
481 tenant_text = ""
482 else:
483 if not self.tenant:
484 await self._get_tenant(session)
485 tenant_text = "/" + self.tenant
486
487 if not uuid:
488 #check that exist
489 uuid = self._get_item_uuid(session, "tenants", item_id_name, all_tenants)
490
491 payload_req = yaml.safe_dump(descriptor)
492
493 #print payload_req
494
495 url = "{}{}/{}/{}".format(self.endpoint_url, tenant_text, item, uuid)
496 self.logger.debug("openmano PUT %s %s", url, payload_req)
497 with aiohttp.Timeout(self.timeout_large):
498 async with session.put(url, headers=self.headers_req, data=payload_req) as response:
499 response_text = await response.read()
500 self.logger.debug("PUT {} [{}] {}".format(url, response.status, response_text[:100]))
501 if response.status >= 300:
502 raise ROClientException(response_text, http_code=response.status)
503 return self._parse_yaml(response_text, response=True)
504
505 async def show(self, item, item_id_name=None, all_tenants=False):
506 """
507 Obtain the information of an item from its id or name
508 :param item: can be 'tenant', 'vim', 'vnfd', 'nsd', 'ns'
509 :param item_id_name: RO id or name of the item. Raise and exception if more than one found
510 :param all_tenants: True if not filtering by tenant. Only allowed for admin
511 :return: dictionary with the information or raises ROClientException on Error, NotFound, found several
512 """
513 if item not in self.client_to_RO:
514 raise ROClientException("Invalid item {}".format(item))
515 if item == 'tenant':
516 all_tenants = None
517
518 with aiohttp.ClientSession(loop=self.loop) as session:
519 content = await self._get_item(session, self.client_to_RO[item], item_id_name, all_tenants=all_tenants)
520 if len(content) == 1:
521 return content.values()[0]
522 else:
523 return content
524
525 async def delete(self, item, item_id_name=None, all_tenants=False):
526 """
527 Delete the information of an item from its id or name
528 :param item: can be 'tenant', 'vim', 'vnfd', 'nsd', 'ns'
529 :param item_id_name: RO id or name of the item. Raise and exception if more than one found
530 :param all_tenants: True if not filtering by tenant. Only allowed for admin
531 :return: dictionary with the information or raises ROClientException on Error, NotFound, found several
532 """
533 if item not in self.client_to_RO:
534 raise ROClientException("Invalid item {}".format(item))
535 if item == 'tenant':
536 all_tenants = None
537
538 with aiohttp.ClientSession(loop=self.loop) as session:
539 if item == 'vim':
540 # check that exist
541 item_id = await self._get_item_uuid(session, "datacenters", item_id_name, all_tenants=True)
542 all_tenants = None
543 return await self._del_item(session, self.client_to_RO[item], item_id_name, all_tenants=all_tenants)
544
545 async def create(self, item, descriptor=None, descriptor_format=None, **kwargs):
546 """
547 Creates an item from its descriptor
548 :param item: can be 'tenant', 'vim', 'vnfd', 'nsd', 'ns'
549 :param descriptor: can be a dict, or a yaml/json text. Autodetect unless descriptor_format is provided
550 :param descriptor_format: Can be 'json' or 'yaml'
551 :param kwargs: Overrides descriptor with values as name, description, vim_url, vim_url_admin, vim_type
552 keys can be a dot separated list to specify elements inside dict
553 :return: dictionary with the information or raises ROClientException on Error
554 """
555 if isinstance(descriptor, str):
556 descriptor = self._parse(descriptor, descriptor_format)
557 elif descriptor:
558 pass
559 else:
560 descriptor = {}
561
562 if item not in self.client_to_RO:
563 raise ROClientException("Invalid item {}".format(item))
564 desc, enveloped = remove_envelop(item, descriptor)
565
566 # Override descriptor with kwargs
567 if kwargs:
568 try:
569 for k, v in kwargs.items():
570 update_content = desc
571 kitem_old = None
572 klist = k.split(".")
573 for kitem in klist:
574 if kitem_old is not None:
575 update_content = update_content[kitem_old]
576 if isinstance(update_content, dict):
577 kitem_old = kitem
578 elif isinstance(update_content, list):
579 kitem_old = int(kitem)
580 else:
581 raise ROClientException(
582 "Invalid query string '{}'. Descriptor is not a list nor dict at '{}'".format(k, kitem))
583 update_content[kitem_old] = v
584 except KeyError:
585 raise ROClientException(
586 "Invalid query string '{}'. Descriptor does not contain '{}'".format(k, kitem_old))
587 except ValueError:
588 raise ROClientException("Invalid query string '{}'. Expected integer index list instead of '{}'".format(
589 k, kitem))
590 except IndexError:
591 raise ROClientException(
592 "Invalid query string '{}'. Index '{}' out of range".format(k, kitem_old))
593
594 for mandatory in self.mandatory_for_create[item]:
595 if mandatory not in desc:
596 raise ROClientException("'{}' is mandatory parameter for {}".format(mandatory, item))
597
598 all_tenants = False
599 if item in ('tenant', 'vim'):
600 all_tenants = None
601
602 if not enveloped:
603 create_desc = self._create_envelop(item, desc)
604 else:
605 create_desc = descriptor
606
607 with aiohttp.ClientSession(loop=self.loop) as session:
608 return await self._create_item(session, self.client_to_RO[item], create_desc, all_tenants)
609
610 def edit_tenant(self, uuid=None, name=None, descriptor=None, descriptor_format=None, new_name=None, new_description=None):
611 """Edit the parameters of a tenant
612 Params: must supply a descriptor or/and a new_name or new_description
613 uuid or/and name. If only name is supplied, there must be only one or an exception is raised
614 descriptor: with format {'tenant':{params to change info}}
615 must be a dictionary or a json/yaml text.
616 name: the tenant name. Overwrite descriptor name if any
617 description: tenant descriptor.. Overwrite descriptor description if any
618 Return: Raises an exception on error, not found or found several
619 Obtain a dictionary with format {'tenant':{newtenant_info}}
620 """
621 # TODO revise
622 if isinstance(descriptor, str):
623 descriptor = self.parse(descriptor, descriptor_format)
624 elif descriptor:
625 pass
626 elif new_name or new_description:
627 descriptor={"tenant": {}}
628 else:
629 raise ROClientException("Missing descriptor")
630
631 if 'tenant' not in descriptor or len(descriptor)!=1:
632 raise ROClientException("Descriptor must contain only one 'tenant' field")
633 if new_name:
634 descriptor['tenant']['name'] = new_name
635 if new_description:
636 descriptor['tenant']['description'] = new_description
637
638 return self._edit_item("tenants", descriptor, uuid, name, all_tenants=None)
639
640 #DATACENTERS
641
642 def edit_datacenter(self, uuid=None, name=None, descriptor=None, descriptor_format=None, all_tenants=False, **kwargs):
643 """Edit the parameters of a datacenter
644 Params: must supply a descriptor or/and a parameter to change
645 uuid or/and name. If only name is supplied, there must be only one or an exception is raised
646 descriptor: with format {'datacenter':{params to change info}}
647 must be a dictionary or a json/yaml text.
648 parameters to change can be supplyied by the descriptor or as parameters:
649 new_name: the datacenter name
650 vim_url: the datacenter URL
651 vim_url_admin: the datacenter URL for administrative issues
652 vim_type: the datacenter type, can be openstack or openvim.
653 public: boolean, available to other tenants
654 description: datacenter description
655 Return: Raises an exception on error, not found or found several
656 Obtain a dictionary with format {'datacenter':{new_datacenter_info}}
657 """
658
659 if isinstance(descriptor, str):
660 descriptor = self.parse(descriptor, descriptor_format)
661 elif descriptor:
662 pass
663 elif kwargs:
664 descriptor={"datacenter": {}}
665 else:
666 raise ROClientException("Missing descriptor")
667
668 if 'datacenter' not in descriptor or len(descriptor)!=1:
669 raise ROClientException("Descriptor must contain only one 'datacenter' field")
670 for param in kwargs:
671 if param=='new_name':
672 descriptor['datacenter']['name'] = kwargs[param]
673 else:
674 descriptor['datacenter'][param] = kwargs[param]
675 return self._edit_item("datacenters", descriptor, uuid, name, all_tenants=None)
676
677 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):
678 #check that exist
679 uuid = self._get_item_uuid(session, "datacenters", uuid_name, all_tenants=True)
680 tenant_text = "/"+self._get_tenant()
681
682 if isinstance(descriptor, str):
683 descriptor = self.parse(descriptor, descriptor_format)
684 elif descriptor:
685 pass
686 elif vim_user or vim_password or vim_tenant_name or vim_tenant_id:
687 descriptor={"datacenter": {}}
688 else:
689 raise ROClientException("Missing descriptor or params")
690
691 if vim_user or vim_password or vim_tenant_name or vim_tenant_id:
692 #print args.name
693 try:
694 if vim_user:
695 descriptor['datacenter']['vim_user'] = vim_user
696 if vim_password:
697 descriptor['datacenter']['vim_password'] = vim_password
698 if vim_tenant_name:
699 descriptor['datacenter']['vim_tenant_name'] = vim_tenant_name
700 if vim_tenant_id:
701 descriptor['datacenter']['vim_tenant'] = vim_tenant_id
702 except (KeyError, TypeError) as e:
703 if str(e)=='datacenter': error_pos= "missing field 'datacenter'"
704 else: error_pos="wrong format"
705 raise ROClientException("Wrong datacenter descriptor: " + error_pos)
706
707 payload_req = yaml.safe_dump(descriptor)
708 #print payload_req
709 url = "{}{}/datacenters/{}".format(self.endpoint_url, tenant_text, uuid)
710 self.logger.debug("openmano POST %s %s", url, payload_req)
711 mano_response = requests.post(url, headers = self.headers_req, data=payload_req)
712 self.logger.debug("openmano response: %s", mano_response.text )
713
714 content = self._parse_yaml(mano_response.text, response=True)
715 if mano_response.status_code==200:
716 return content
717 else:
718 raise ROClientException(str(content), http_code=mano_response.status)
719
720 def detach_datacenter(self, uuid_name=None):
721 if not uuid:
722 #check that exist
723 uuid = self._get_item_uuid(session, "datacenters", uuid_name, all_tenants=False)
724 tenant_text = "/"+self._get_tenant()
725 url = "{}{}/datacenters/{}".format(self.endpoint_url, tenant_text, uuid)
726 self.logger.debug("openmano DELETE %s", url)
727 mano_response = requests.delete(url, headers = self.headers_req)
728 self.logger.debug("openmano response: %s", mano_response.text )
729
730 content = self._parse_yaml(mano_response.text, response=True)
731 if mano_response.status_code==200:
732 return content
733 else:
734 raise ROClientException(str(content), http_code=mano_response.status)
735
736 #VNFS
737
738 def edit_scenario(self, uuid=None, name=None, descriptor=None, descriptor_format=None, all_tenants=False, **kwargs):
739 """Edit the parameters of a scenario
740 Params: must supply a descriptor or/and a parameters to change
741 uuid or/and name. If only name is supplied, there must be only one or an exception is raised
742 descriptor: with format {'scenario':{params to change info}}
743 must be a dictionary or a json/yaml text.
744 parameters to change can be supplyied by the descriptor or as parameters:
745 new_name: the scenario name
746 public: boolean, available to other tenants
747 description: scenario description
748 tenant_id. Propietary tenant
749 Return: Raises an exception on error, not found or found several
750 Obtain a dictionary with format {'scenario':{new_scenario_info}}
751 """
752
753 if isinstance(descriptor, str):
754 descriptor = self.parse(descriptor, descriptor_format)
755 elif descriptor:
756 pass
757 elif kwargs:
758 descriptor={"scenario": {}}
759 else:
760 raise ROClientException("Missing descriptor")
761
762 if 'scenario' not in descriptor or len(descriptor)>2:
763 raise ROClientException("Descriptor must contain only one 'scenario' field")
764 for param in kwargs:
765 if param=='new_name':
766 descriptor['scenario']['name'] = kwargs[param]
767 else:
768 descriptor['scenario'][param] = kwargs[param]
769 return self._edit_item("scenarios", descriptor, uuid, name, all_tenants=None)
770
771 #VIM ACTIONS
772 def vim_action(self, action, item, uuid=None, all_tenants=False, **kwargs):
773 """Perform an action over a vim
774 Params:
775 action: can be 'list', 'get'/'show', 'delete' or 'create'
776 item: can be 'tenants' or 'networks'
777 uuid: uuid of the tenant/net to show or to delete. Ignore otherwise
778 other parameters:
779 datacenter_name, datacenter_id: datacenters to act on, if missing uses classes store datacenter
780 descriptor, descriptor_format: descriptor needed on creation, can be a dict or a yaml/json str
781 must be a dictionary or a json/yaml text.
782 name: for created tenant/net Overwrite descriptor name if any
783 description: tenant descriptor. Overwrite descriptor description if any
784
785 Return: Raises an exception on error
786 Obtain a dictionary with format {'tenant':{new_tenant_info}}
787 """
788 if item not in ("tenants", "networks", "images"):
789 raise ROClientException("Unknown value for item '{}', must be 'tenants', 'nets' or "
790 "images".format(str(item)))
791
792 image_actions = ['list','get','show','delete']
793 if item == "images" and action not in image_actions:
794 raise ROClientException("Only available actions for item '{}' are {}\n"
795 "Requested action was '{}'".format(item, ', '.join(image_actions), action))
796 if all_tenants:
797 tenant_text = "/any"
798 else:
799 tenant_text = "/"+self._get_tenant()
800
801 if "datacenter_id" in kwargs or "datacenter_name" in kwargs:
802 datacenter = self._get_item_uuid(session, "datacenters", kwargs.get("datacenter"), all_tenants=all_tenants)
803 else:
804 datacenter = self.get_datacenter(session)
805
806 if action=="list":
807 url = "{}{}/vim/{}/{}".format(self.endpoint_url, tenant_text, datacenter, item)
808 self.logger.debug("GET %s", url )
809 mano_response = requests.get(url, headers=self.headers_req)
810 self.logger.debug("openmano response: %s", mano_response.text )
811 content = self._parse_yaml(mano_response.text, response=True)
812 if mano_response.status_code==200:
813 return content
814 else:
815 raise ROClientException(str(content), http_code=mano_response.status)
816 elif action=="get" or action=="show":
817 url = "{}{}/vim/{}/{}/{}".format(self.endpoint_url, tenant_text, datacenter, item, uuid)
818 self.logger.debug("GET %s", url )
819 mano_response = requests.get(url, headers=self.headers_req)
820 self.logger.debug("openmano response: %s", mano_response.text )
821 content = self._parse_yaml(mano_response.text, response=True)
822 if mano_response.status_code==200:
823 return content
824 else:
825 raise ROClientException(str(content), http_code=mano_response.status)
826 elif action=="delete":
827 url = "{}{}/vim/{}/{}/{}".format(self.endpoint_url, tenant_text, datacenter, item, uuid)
828 self.logger.debug("DELETE %s", url )
829 mano_response = requests.delete(url, headers=self.headers_req)
830 self.logger.debug("openmano response: %s", mano_response.text )
831 content = self._parse_yaml(mano_response.text, response=True)
832 if mano_response.status_code==200:
833 return content
834 else:
835 raise ROClientException(str(content), http_code=mano_response.status)
836 elif action=="create":
837 if "descriptor" in kwargs:
838 if isinstance(kwargs["descriptor"], str):
839 descriptor = self._parse(kwargs["descriptor"], kwargs.get("descriptor_format") )
840 else:
841 descriptor = kwargs["descriptor"]
842 elif "name" in kwargs:
843 descriptor={item[:-1]: {"name": kwargs["name"]}}
844 else:
845 raise ROClientException("Missing descriptor")
846
847 if item[:-1] not in descriptor or len(descriptor)!=1:
848 raise ROClientException("Descriptor must contain only one 'tenant' field")
849 if "name" in kwargs:
850 descriptor[ item[:-1] ]['name'] = kwargs["name"]
851 if "description" in kwargs:
852 descriptor[ item[:-1] ]['description'] = kwargs["description"]
853 payload_req = yaml.safe_dump(descriptor)
854 #print payload_req
855 url = "{}{}/vim/{}/{}".format(self.endpoint_url, tenant_text, datacenter, item)
856 self.logger.debug("openmano POST %s %s", url, payload_req)
857 mano_response = requests.post(url, headers = self.headers_req, data=payload_req)
858 self.logger.debug("openmano response: %s", mano_response.text )
859 content = self._parse_yaml(mano_response.text, response=True)
860 if mano_response.status_code==200:
861 return content
862 else:
863 raise ROClientException(str(content), http_code=mano_response.status)
864 else:
865 raise ROClientException("Unknown value for action '{}".format(str(action)))
866
867
868if __name__ == '__main__':
869 RO_URL = "http://localhost:9090/openmano"
870 RO_TENANT = "2c94f639-cefc-4f3a-a8f9-bbab0471946a"
871 RO_VIM = "3e70deb6-aea1-11e7-af13-080027429aaf"
872
873 streamformat = "%(asctime)s %(name)s %(levelname)s: %(message)s"
874 logging.basicConfig(format=streamformat)
875
876 loop = asyncio.get_event_loop()
877 myClient = ROClient(endpoint_url=RO_URL, loop=loop, tenant_id=RO_TENANT, datacenter_id=RO_VIM, debug=True)
878 content = loop.run_until_complete(myClient.list_tenants())
879 print(content)
880 loop.close()
881
882