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