bbea5fe34f0b6f37dafb993db3e15af33a82f0ff
[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
39 __author__ = "Alfonso Tierno, Pablo Montes"
40 __date__ = "$09-Jan-2018 09:09:48$"
41 __version__ = "0.1.0-r470"
42 version_date = "Jan 2018"
43 requests = None
44
45 class 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
52 def 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
104 class 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[str(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 for _, v in content.items():
299 return v
300 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
868 if __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