84ce7aa151e67a61e852ede2b60040d97d6bf161
[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[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
866 if __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