blob: b0c16f9c51fe748e2af1b50a6cfa3ebdaa0f5a9a [file] [log] [blame]
tiernoc0e42e22018-05-11 11:36:10 +02001#!/usr/bin/env python3
2# -*- coding: utf-8 -*-
3
4##
5# Copyright 2015 Telefónica Investigación y Desarrollo, S.A.U.
tiernoc0e42e22018-05-11 11:36:10 +02006#
7# Licensed under the Apache License, Version 2.0 (the "License"); you may
8# not use this file except in compliance with the License. You may obtain
9# a copy of the License at
10#
11# http://www.apache.org/licenses/LICENSE-2.0
12#
13# Unless required by applicable law or agreed to in writing, software
14# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
15# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
16# License for the specific language governing permissions and limitations
17# under the License.
18#
tiernoc0e42e22018-05-11 11:36:10 +020019##
20
21"""
22asyncio RO python client to interact with RO-server
23"""
24
25import asyncio
26import aiohttp
tiernoc0e42e22018-05-11 11:36:10 +020027import json
28import yaml
29import logging
tiernoc0e42e22018-05-11 11:36:10 +020030from urllib.parse import quote
31from uuid import UUID
32from copy import deepcopy
33
tierno275411e2018-05-16 14:33:32 +020034__author__ = "Alfonso Tierno"
tiernoc0e42e22018-05-11 11:36:10 +020035__date__ = "$09-Jan-2018 09:09:48$"
tierno275411e2018-05-16 14:33:32 +020036__version__ = "0.1.2"
37version_date = "2018-05-16"
tiernoc0e42e22018-05-11 11:36:10 +020038requests = None
39
tierno750b2452018-05-17 16:39:29 +020040
tiernoc0e42e22018-05-11 11:36:10 +020041class ROClientException(Exception):
42 def __init__(self, message, http_code=400):
tierno750b2452018-05-17 16:39:29 +020043 """Common Exception for all RO client exceptions"""
tiernoc0e42e22018-05-11 11:36:10 +020044 self.http_code = http_code
45 Exception.__init__(self, message)
tiernoc0e42e22018-05-11 11:36:10 +020046
47
48def remove_envelop(item, indata=None):
49 """
50 Obtain the useful data removing the envelop. It goes through the vnfd or nsd catalog and returns the
51 vnfd or nsd content
52 :param item: can be 'tenant', 'vim', 'vnfd', 'nsd', 'ns'
53 :param indata: Content to be inspected
54 :return: the useful part of indata (a reference, not a new dictionay)
55 """
56 clean_indata = indata
57 if not indata:
58 return {}
59 if item == "vnfd":
60 if clean_indata.get('vnfd:vnfd-catalog'):
61 clean_indata = clean_indata['vnfd:vnfd-catalog']
62 elif clean_indata.get('vnfd-catalog'):
63 clean_indata = clean_indata['vnfd-catalog']
64 if clean_indata.get('vnfd'):
65 if not isinstance(clean_indata['vnfd'], list) or len(clean_indata['vnfd']) != 1:
66 raise ROClientException("'vnfd' must be a list only one element")
67 clean_indata = clean_indata['vnfd'][0]
68 elif item == "nsd":
69 if clean_indata.get('nsd:nsd-catalog'):
70 clean_indata = clean_indata['nsd:nsd-catalog']
71 elif clean_indata.get('nsd-catalog'):
72 clean_indata = clean_indata['nsd-catalog']
73 if clean_indata.get('nsd'):
74 if not isinstance(clean_indata['nsd'], list) or len(clean_indata['nsd']) != 1:
75 raise ROClientException("'nsd' must be a list only one element")
76 clean_indata = clean_indata['nsd'][0]
77 elif item == "sdn":
78 if len(indata) == 1 and "sdn_controller" in indata:
79 clean_indata = indata["sdn_controller"]
80 elif item == "tenant":
81 if len(indata) == 1 and "tenant" in indata:
82 clean_indata = indata["tenant"]
83 elif item in ("vim", "vim_account", "datacenters"):
84 if len(indata) == 1 and "datacenter" in indata:
85 clean_indata = indata["datacenter"]
tiernoe37b57d2018-12-11 17:22:51 +000086 elif item == "wim":
87 if len(indata) == 1 and "wim" in indata:
88 clean_indata = indata["wim"]
89 elif item == "wim_account":
90 if len(indata) == 1 and "wim_account" in indata:
91 clean_indata = indata["wim_account"]
tiernoc0e42e22018-05-11 11:36:10 +020092 elif item == "ns" or item == "instances":
93 if len(indata) == 1 and "instance" in indata:
94 clean_indata = indata["instance"]
95 else:
96 assert False, "remove_envelop with unknown item {}".format(item)
97
98 return clean_indata
99
100
101class ROClient:
102 headers_req = {'Accept': 'application/yaml', 'content-type': 'application/yaml'}
103 client_to_RO = {'tenant': 'tenants', 'vim': 'datacenters', 'vim_account': 'datacenters', 'sdn': 'sdn_controllers',
tiernoe37b57d2018-12-11 17:22:51 +0000104 'vnfd': 'vnfs', 'nsd': 'scenarios', 'wim': 'wims', 'wim_account': 'wims',
tiernoc0e42e22018-05-11 11:36:10 +0200105 'ns': 'instances'}
106 mandatory_for_create = {
107 'tenant': ("name", ),
gcalvino911ff7d2018-11-13 17:19:32 +0100108 'vnfd': ("name", "id"),
gcalvinoea0cc0a2018-11-06 13:20:29 +0100109 'nsd': ("name", "id"),
tiernoc0e42e22018-05-11 11:36:10 +0200110 'ns': ("name", "scenario", "datacenter"),
111 'vim': ("name", "vim_url"),
tiernoe37b57d2018-12-11 17:22:51 +0000112 'wim': ("name", "wim_url"),
tiernoc0e42e22018-05-11 11:36:10 +0200113 'vim_account': (),
tiernoe37b57d2018-12-11 17:22:51 +0000114 'wim_account': (),
tiernof186b442019-12-17 16:43:32 +0000115 'sdn': ("name", 'type'),
tiernoc0e42e22018-05-11 11:36:10 +0200116 }
117 timeout_large = 120
118 timeout_short = 30
119
120 def __init__(self, loop, endpoint_url, **kwargs):
121 self.loop = loop
122 self.endpoint_url = endpoint_url
123
124 self.username = kwargs.get("username")
125 self.password = kwargs.get("password")
126 self.tenant_id_name = kwargs.get("tenant")
127 self.tenant = None
128 self.datacenter_id_name = kwargs.get("datacenter")
129 self.datacenter = None
130 logger_name = kwargs.get('logger_name', 'ROClient')
131 self.logger = logging.getLogger(logger_name)
132 if kwargs.get("loglevel"):
133 self.logger.setLevel(kwargs["loglevel"])
134 global requests
135 requests = kwargs.get("TODO remove")
136
137 def __getitem__(self, index):
138 if index == 'tenant':
139 return self.tenant_id_name
140 elif index == 'datacenter':
141 return self.datacenter_id_name
142 elif index == 'username':
143 return self.username
144 elif index == 'password':
145 return self.password
146 elif index == 'endpoint_url':
147 return self.endpoint_url
148 else:
tierno750b2452018-05-17 16:39:29 +0200149 raise KeyError("Invalid key '{}'".format(index))
tiernoc0e42e22018-05-11 11:36:10 +0200150
tierno750b2452018-05-17 16:39:29 +0200151 def __setitem__(self, index, value):
tiernoc0e42e22018-05-11 11:36:10 +0200152 if index == 'tenant':
153 self.tenant_id_name = value
154 elif index == 'datacenter' or index == 'vim':
155 self.datacenter_id_name = value
156 elif index == 'username':
157 self.username = value
158 elif index == 'password':
159 self.password = value
160 elif index == 'endpoint_url':
161 self.endpoint_url = value
162 else:
163 raise KeyError("Invalid key '{}'".format(index))
164 self.tenant = None # force to reload tenant with different credentials
165 self.datacenter = None # force to reload datacenter with different credentials
tierno750b2452018-05-17 16:39:29 +0200166
tiernoc0e42e22018-05-11 11:36:10 +0200167 def _parse(self, descriptor, descriptor_format, response=False):
tiernoc0e42e22018-05-11 11:36:10 +0200168 if descriptor_format and descriptor_format != "json" and descriptor_format != "yaml":
tierno750b2452018-05-17 16:39:29 +0200169 raise ROClientException("'descriptor_format' must be a 'json' or 'yaml' text")
tiernoc0e42e22018-05-11 11:36:10 +0200170 if descriptor_format != "json":
171 try:
172 return yaml.load(descriptor)
173 except yaml.YAMLError as exc:
174 error_pos = ""
175 if hasattr(exc, 'problem_mark'):
176 mark = exc.problem_mark
tierno750b2452018-05-17 16:39:29 +0200177 error_pos = " at line:{} column:{}s".format(mark.line + 1, mark.column + 1)
tiernoc0e42e22018-05-11 11:36:10 +0200178 error_text = "yaml format error" + error_pos
179 elif descriptor_format != "yaml":
180 try:
tierno750b2452018-05-17 16:39:29 +0200181 return json.loads(descriptor)
tiernoc0e42e22018-05-11 11:36:10 +0200182 except Exception as e:
183 if response:
184 error_text = "json format error" + str(e)
185
186 if response:
187 raise ROClientException(error_text)
tierno750b2452018-05-17 16:39:29 +0200188 raise ROClientException(error_text)
tiernoc0e42e22018-05-11 11:36:10 +0200189
190 def _parse_yaml(self, descriptor, response=False):
191 try:
tierno626e0152019-11-29 14:16:16 +0000192 return yaml.load(descriptor, Loader=yaml.Loader)
tiernoc0e42e22018-05-11 11:36:10 +0200193 except yaml.YAMLError as exc:
194 error_pos = ""
195 if hasattr(exc, 'problem_mark'):
196 mark = exc.problem_mark
tierno750b2452018-05-17 16:39:29 +0200197 error_pos = " at line:{} column:{}s".format(mark.line + 1, mark.column + 1)
tiernoc0e42e22018-05-11 11:36:10 +0200198 error_text = "yaml format error" + error_pos
199 if response:
200 raise ROClientException(error_text)
tierno750b2452018-05-17 16:39:29 +0200201 raise ROClientException(error_text)
tiernoc0e42e22018-05-11 11:36:10 +0200202
203 @staticmethod
204 def check_if_uuid(uuid_text):
205 """
206 Check if text correspond to an uuid foramt
207 :param uuid_text:
208 :return: True if it is an uuid False if not
209 """
210 try:
211 UUID(uuid_text)
212 return True
tierno98768132018-09-11 12:07:21 +0200213 except Exception:
tiernoc0e42e22018-05-11 11:36:10 +0200214 return False
215
216 @staticmethod
217 def _create_envelop(item, indata=None):
218 """
219 Returns a new dict that incledes indata with the expected envelop
220 :param item: can be 'tenant', 'vim', 'vnfd', 'nsd', 'ns'
221 :param indata: Content to be enveloped
222 :return: a new dic with {<envelop>: {indata} } where envelop can be e.g. tenant, datacenter, ...
223 """
224 if item == "vnfd":
225 return {'vnfd-catalog': {'vnfd': [indata]}}
226 elif item == "nsd":
227 return {'nsd-catalog': {'nsd': [indata]}}
228 elif item == "tenant":
229 return {'tenant': indata}
230 elif item in ("vim", "vim_account", "datacenter"):
231 return {'datacenter': indata}
tiernoe37b57d2018-12-11 17:22:51 +0000232 elif item == "wim":
233 return {'wim': indata}
234 elif item == "wim_account":
235 return {'wim_account': indata}
tiernoc0e42e22018-05-11 11:36:10 +0200236 elif item == "ns" or item == "instances":
237 return {'instance': indata}
238 elif item == "sdn":
239 return {'sdn_controller': indata}
240 else:
241 assert False, "_create_envelop with unknown item {}".format(item)
242
243 @staticmethod
244 def update_descriptor(desc, kwargs):
245 desc = deepcopy(desc) # do not modify original descriptor
246 try:
247 for k, v in kwargs.items():
248 update_content = desc
249 kitem_old = None
250 klist = k.split(".")
251 for kitem in klist:
252 if kitem_old is not None:
253 update_content = update_content[kitem_old]
254 if isinstance(update_content, dict):
255 kitem_old = kitem
256 elif isinstance(update_content, list):
257 kitem_old = int(kitem)
258 else:
259 raise ROClientException(
260 "Invalid query string '{}'. Descriptor is not a list nor dict at '{}'".format(k, kitem))
261 if v == "__DELETE__":
262 del update_content[kitem_old]
263 else:
264 update_content[kitem_old] = v
265 return desc
266 except KeyError:
267 raise ROClientException(
268 "Invalid query string '{}'. Descriptor does not contain '{}'".format(k, kitem_old))
269 except ValueError:
270 raise ROClientException("Invalid query string '{}'. Expected integer index list instead of '{}'".format(
271 k, kitem))
272 except IndexError:
273 raise ROClientException(
274 "Invalid query string '{}'. Index '{}' out of range".format(k, kitem_old))
275
276 @staticmethod
277 def check_ns_status(ns_descriptor):
278 """
279 Inspect RO instance descriptor and indicates the status
280 :param ns_descriptor: instance descriptor obtained with self.show("ns", )
281 :return: status, message: status can be BUILD,ACTIVE,ERROR, message is a text message
282 """
tiernof186b442019-12-17 16:43:32 +0000283 error_list = []
284 total = {"VMs": 0, "networks": 0, "SDN_networks": 0}
285 done = {"VMs": 0, "networks": 0, "SDN_networks": 0}
tiernoc0e42e22018-05-11 11:36:10 +0200286
tiernof186b442019-12-17 16:43:32 +0000287 def _get_ref(desc):
288 # return an identification for the network or vm. Try vim_id if exist, if not descriptor id for net
tierno15b1cf12019-08-29 13:21:40 +0000289 if desc.get("vim_net_id"):
290 return "'vim-id={}'".format(desc["vim_net_id"])
291 elif desc.get("ns_net_osm_id"):
292 return "'nsd-vld-id={}'".format(desc["ns_net_osm_id"])
293 elif desc.get("vnf_net_osm_id"):
294 return "'vnfd-vld-id={}'".format(desc["vnf_net_osm_id"])
295 # for VM
296 elif desc.get("vim_vm_id"):
297 return "'vim-id={}'".format(desc["vim_vm_id"])
298 elif desc.get("vdu_osm_id"):
299 return "'vnfd-vdu-id={}'".format(desc["vdu_osm_id"])
300 else:
301 return ""
302
tiernof186b442019-12-17 16:43:32 +0000303 def _get_sdn_ref(sce_net_id):
304 # look for the network associated to the SDN network and obtain the identification
305 net = next((x for x in ns_descriptor["nets"] if x.get("sce_net_id") == sce_net_id), None)
306 if not sce_net_id or not net:
307 return ""
308 return _get_ref(net)
tiernoc0e42e22018-05-11 11:36:10 +0200309
tiernof186b442019-12-17 16:43:32 +0000310 try:
311 total["networks"] = len(ns_descriptor["nets"])
312 for net in ns_descriptor["nets"]:
313 if net["status"] in ("ERROR", "VIM_ERROR"):
314 error_list.append("VIM network ({}) on error: {}".format(_get_ref(net), net["error_msg"]))
315 elif net["status"] == "ACTIVE":
316 done["networks"] += 1
317
318 total["SDN_networks"] = len(ns_descriptor["sdn_nets"])
319 for sdn_net in ns_descriptor["sdn_nets"]:
320 if sdn_net["status"] in ("ERROR", "VIM_ERROR", "WIM_ERROR"):
321 error_list.append("SDN network ({}) on error: {}".format(_get_sdn_ref(sdn_net.get("sce_net_id")),
322 sdn_net["error_msg"]))
323 elif sdn_net["status"] == "ACTIVE":
324 done["SDN_networks"] += 1
325
326 for vnf in ns_descriptor["vnfs"]:
327 for vm in vnf["vms"]:
328 total["VMs"] += 1
329 if vm["status"] in ("ERROR", "VIM_ERROR"):
330 error_list.append("VIM VM ({}) on error: {}".format(_get_ref(vm), vm["error_msg"]))
331 elif vm["status"] == "ACTIVE":
332 done["VMs"] += 1
333 if error_list:
334 return "ERROR", "; ".join(error_list)
335 if all(total[x] == done[x] for x in total): # DONE == TOTAL for all items
336 return "ACTIVE", str({x: total[x] for x in total if total[x]}) # print only those which value is not 0
337 else:
338 return "BUILD", str({x: "{}/{}".format(done[x], total[x]) for x in total if total[x]})
339 # print done/total for each item if total is not 0
340 except Exception as e:
341 raise ROClientException("Unexpected RO ns descriptor. Wrong version? {}".format(e)) from e
tiernoc0e42e22018-05-11 11:36:10 +0200342
343 @staticmethod
tierno22f4f9c2018-06-11 18:53:39 +0200344 def check_action_status(action_descriptor):
345 """
346 Inspect RO instance descriptor and indicates the status
347 :param action_descriptor: action instance descriptor obtained with self.show("ns", "action")
348 :return: status, message: status can be BUILD,ACTIVE,ERROR, message is a text message
349 """
350 net_total = 0
351 vm_total = 0
352 net_done = 0
353 vm_done = 0
354 other_total = 0
355 other_done = 0
356
357 for vim_action_set in action_descriptor["actions"]:
tierno1af43fc2019-03-08 08:54:58 +0000358 for vim_action in vim_action_set["vim_wim_actions"]:
tierno22f4f9c2018-06-11 18:53:39 +0200359 if vim_action["item"] == "instance_vms":
360 vm_total += 1
361 elif vim_action["item"] == "instance_nets":
362 net_total += 1
363 else:
364 other_total += 1
365 if vim_action["status"] == "FAILED":
366 return "ERROR", vim_action["error_msg"]
tierno1af43fc2019-03-08 08:54:58 +0000367 elif vim_action["status"] in ("DONE", "SUPERSEDED", "FINISHED"):
tierno22f4f9c2018-06-11 18:53:39 +0200368 if vim_action["item"] == "instance_vms":
369 vm_done += 1
370 elif vim_action["item"] == "instance_nets":
371 net_done += 1
372 else:
373 other_done += 1
374
375 if net_total == net_done and vm_total == vm_done and other_total == other_done:
376 return "ACTIVE", "VMs {}, networks: {}, other: {} ".format(vm_total, net_total, other_total)
377 else:
378 return "BUILD", "VMs: {}/{}, networks: {}/{}, other: {}/{}".format(vm_done, vm_total, net_done, net_total,
379 other_done, other_total)
380
381 @staticmethod
tiernoc0e42e22018-05-11 11:36:10 +0200382 def get_ns_vnf_info(ns_descriptor):
383 """
384 Get a dict with the VIM_id, ip_addresses, mac_addresses of every vnf and vdu
385 :param ns_descriptor: instance descriptor obtained with self.show("ns", )
tierno22f4f9c2018-06-11 18:53:39 +0200386 :return: dict with:
387 <member_vnf_index>:
388 ip_address: XXXX,
389 vdur:
390 <vdu_osm_id>:
391 ip_address: XXX
392 vim_id: XXXX
393 interfaces:
394 <name>:
395 ip_address: XXX
396 mac_address: XXX
tiernoc0e42e22018-05-11 11:36:10 +0200397 """
398 ns_info = {}
399 for vnf in ns_descriptor["vnfs"]:
gcalvino911ff7d2018-11-13 17:19:32 +0100400 if not vnf.get("ip_address") and vnf.get("vms"):
tierno275411e2018-05-16 14:33:32 +0200401 raise ROClientException("ns member_vnf_index '{}' has no IP address".format(
402 vnf["member_vnf_index"]), http_code=409)
tiernoc0e42e22018-05-11 11:36:10 +0200403 vnfr_info = {
404 "ip_address": vnf.get("ip_address"),
405 "vdur": {}
406 }
407 for vm in vnf["vms"]:
408 vdur = {
409 "vim_id": vm.get("vim_vm_id"),
tierno22f4f9c2018-06-11 18:53:39 +0200410 "ip_address": vm.get("ip_address"),
411 "interfaces": {}
tiernoc0e42e22018-05-11 11:36:10 +0200412 }
tierno275411e2018-05-16 14:33:32 +0200413 for iface in vm["interfaces"]:
414 if iface.get("type") == "mgmt" and not iface.get("ip_address"):
415 raise ROClientException("ns member_vnf_index '{}' vm '{}' management interface '{}' has no IP "
416 "address".format(vnf["member_vnf_index"], vm["vdu_osm_id"],
417 iface["external_name"]), http_code=409)
tierno22f4f9c2018-06-11 18:53:39 +0200418 vdur["interfaces"][iface["internal_name"]] = {"ip_address": iface.get("ip_address"),
419 "mac_address": iface.get("mac_address"),
420 "vim_id": iface.get("vim_interface_id"),
421 }
tiernoc0e42e22018-05-11 11:36:10 +0200422 vnfr_info["vdur"][vm["vdu_osm_id"]] = vdur
423 ns_info[str(vnf["member_vnf_index"])] = vnfr_info
424 return ns_info
425
tiernoc0e42e22018-05-11 11:36:10 +0200426 async def _get_item_uuid(self, session, item, item_id_name, all_tenants=False):
427 if all_tenants:
428 tenant_text = "/any"
429 elif all_tenants is None:
430 tenant_text = ""
431 else:
432 if not self.tenant:
433 await self._get_tenant(session)
434 tenant_text = "/" + self.tenant
435
436 item_id = 0
437 url = "{}{}/{}".format(self.endpoint_url, tenant_text, item)
438 if self.check_if_uuid(item_id_name):
439 item_id = item_id_name
440 url += "/" + item_id_name
441 elif item_id_name and item_id_name.startswith("'") and item_id_name.endswith("'"):
442 item_id_name = item_id_name[1:-1]
tierno750b2452018-05-17 16:39:29 +0200443 self.logger.debug("RO GET %s", url)
calvinosanchd5916fd2020-01-09 17:19:53 +0100444 # timeout = aiohttp.ClientTimeout(total=self.timeout_short)
445 async with session.get(url, headers=self.headers_req) as response:
446 response_text = await response.read()
447 self.logger.debug("GET {} [{}] {}".format(url, response.status, response_text[:100]))
448 if response.status == 404: # NOT_FOUND
449 raise ROClientException("No {} found with id '{}'".format(item[:-1], item_id_name),
450 http_code=404)
451 if response.status >= 300:
452 raise ROClientException(response_text, http_code=response.status)
453 content = self._parse_yaml(response_text, response=True)
tiernoc0e42e22018-05-11 11:36:10 +0200454
455 if item_id:
456 return item_id
457 desc = content[item]
458 assert isinstance(desc, list), "_get_item_uuid get a non dict with a list inside {}".format(type(desc))
459 uuid = None
460 for i in desc:
461 if item_id_name and i["name"] != item_id_name:
462 continue
463 if uuid: # found more than one
464 raise ROClientException(
465 "Found more than one {} with name '{}'. uuid must be used".format(item, item_id_name),
466 http_code=404)
467 uuid = i["uuid"]
468 if not uuid:
469 raise ROClientException("No {} found with name '{}'".format(item[:-1], item_id_name), http_code=404)
470 return uuid
471
tierno22f4f9c2018-06-11 18:53:39 +0200472 async def _get_item(self, session, item, item_id_name, extra_item=None, extra_item_id=None, all_tenants=False):
tiernoc0e42e22018-05-11 11:36:10 +0200473 if all_tenants:
474 tenant_text = "/any"
475 elif all_tenants is None:
476 tenant_text = ""
477 else:
478 if not self.tenant:
479 await self._get_tenant(session)
480 tenant_text = "/" + self.tenant
481
482 if self.check_if_uuid(item_id_name):
483 uuid = item_id_name
484 else:
485 # check that exist
486 uuid = await self._get_item_uuid(session, item, item_id_name, all_tenants)
tierno750b2452018-05-17 16:39:29 +0200487
tiernoc0e42e22018-05-11 11:36:10 +0200488 url = "{}{}/{}/{}".format(self.endpoint_url, tenant_text, item, uuid)
tierno22f4f9c2018-06-11 18:53:39 +0200489 if extra_item:
490 url += "/" + extra_item
491 if extra_item_id:
492 url += "/" + extra_item_id
tierno750b2452018-05-17 16:39:29 +0200493 self.logger.debug("GET %s", url)
calvinosanchd5916fd2020-01-09 17:19:53 +0100494 # timeout = aiohttp.ClientTimeout(total=self.timeout_short)
495 async with session.get(url, headers=self.headers_req) as response:
496 response_text = await response.read()
497 self.logger.debug("GET {} [{}] {}".format(url, response.status, response_text[:100]))
498 if response.status >= 300:
499 raise ROClientException(response_text, http_code=response.status)
tiernoc0e42e22018-05-11 11:36:10 +0200500
501 return self._parse_yaml(response_text, response=True)
502
503 async def _get_tenant(self, session):
504 if not self.tenant:
505 self.tenant = await self._get_item_uuid(session, "tenants", self.tenant_id_name, None)
506 return self.tenant
507
508 async def _get_datacenter(self, session):
509 if not self.tenant:
510 await self._get_tenant(session)
511 if not self.datacenter:
512 self.datacenter = await self._get_item_uuid(session, "datacenters", self.datacenter_id_name, True)
513 return self.datacenter
514
515 async def _create_item(self, session, item, descriptor, item_id_name=None, action=None, all_tenants=False):
516 if all_tenants:
517 tenant_text = "/any"
518 elif all_tenants is None:
519 tenant_text = ""
520 else:
521 if not self.tenant:
522 await self._get_tenant(session)
523 tenant_text = "/" + self.tenant
524 payload_req = yaml.safe_dump(descriptor)
tierno750b2452018-05-17 16:39:29 +0200525 # print payload_req
tiernoc0e42e22018-05-11 11:36:10 +0200526
527 api_version_text = ""
528 if item == "vnfs":
529 # assumes version v3 only
530 api_version_text = "/v3"
531 item = "vnfd"
532 elif item == "scenarios":
533 # assumes version v3 only
534 api_version_text = "/v3"
535 item = "nsd"
536
537 if not item_id_name:
tierno750b2452018-05-17 16:39:29 +0200538 uuid = ""
tiernoc0e42e22018-05-11 11:36:10 +0200539 elif self.check_if_uuid(item_id_name):
540 uuid = "/{}".format(item_id_name)
541 else:
542 # check that exist
543 uuid = await self._get_item_uuid(session, item, item_id_name, all_tenants)
544 uuid = "/{}".format(uuid)
545 if not action:
546 action = ""
547 else:
tierno22f4f9c2018-06-11 18:53:39 +0200548 action = "/{}".format(action)
tiernoc0e42e22018-05-11 11:36:10 +0200549
tierno750b2452018-05-17 16:39:29 +0200550 url = "{}{apiver}{tenant}/{item}{id}{action}".format(self.endpoint_url, apiver=api_version_text,
551 tenant=tenant_text, item=item, id=uuid, action=action)
552 self.logger.debug("RO POST %s %s", url, payload_req)
calvinosanchd5916fd2020-01-09 17:19:53 +0100553 # timeout = aiohttp.ClientTimeout(total=self.timeout_large)
554 async with session.post(url, headers=self.headers_req, data=payload_req) as response:
555 response_text = await response.read()
556 self.logger.debug("POST {} [{}] {}".format(url, response.status, response_text[:100]))
557 if response.status >= 300:
558 raise ROClientException(response_text, http_code=response.status)
tiernoc0e42e22018-05-11 11:36:10 +0200559
560 return self._parse_yaml(response_text, response=True)
561
562 async def _del_item(self, session, item, item_id_name, all_tenants=False):
563 if all_tenants:
564 tenant_text = "/any"
565 elif all_tenants is None:
566 tenant_text = ""
567 else:
568 if not self.tenant:
569 await self._get_tenant(session)
570 tenant_text = "/" + self.tenant
571 if not self.check_if_uuid(item_id_name):
572 # check that exist
573 _all_tenants = all_tenants
tiernoe37b57d2018-12-11 17:22:51 +0000574 if item in ("datacenters", 'wims'):
tiernoc0e42e22018-05-11 11:36:10 +0200575 _all_tenants = True
576 uuid = await self._get_item_uuid(session, item, item_id_name, all_tenants=_all_tenants)
577 else:
578 uuid = item_id_name
tierno750b2452018-05-17 16:39:29 +0200579
tiernoc0e42e22018-05-11 11:36:10 +0200580 url = "{}{}/{}/{}".format(self.endpoint_url, tenant_text, item, uuid)
581 self.logger.debug("DELETE %s", url)
calvinosanchd5916fd2020-01-09 17:19:53 +0100582 # timeout = aiohttp.ClientTimeout(total=self.timeout_short)
583 async with session.delete(url, headers=self.headers_req) as response:
584 response_text = await response.read()
585 self.logger.debug("DELETE {} [{}] {}".format(url, response.status, response_text[:100]))
586 if response.status >= 300:
587 raise ROClientException(response_text, http_code=response.status)
588
tiernoc0e42e22018-05-11 11:36:10 +0200589 return self._parse_yaml(response_text, response=True)
590
591 async def _list_item(self, session, item, all_tenants=False, filter_dict=None):
592 if all_tenants:
593 tenant_text = "/any"
594 elif all_tenants is None:
595 tenant_text = ""
596 else:
597 if not self.tenant:
598 await self._get_tenant(session)
599 tenant_text = "/" + self.tenant
tierno750b2452018-05-17 16:39:29 +0200600
tiernoc0e42e22018-05-11 11:36:10 +0200601 url = "{}{}/{}".format(self.endpoint_url, tenant_text, item)
602 separator = "?"
603 if filter_dict:
604 for k in filter_dict:
tierno750b2452018-05-17 16:39:29 +0200605 url += separator + quote(str(k)) + "=" + quote(str(filter_dict[k]))
tiernoc0e42e22018-05-11 11:36:10 +0200606 separator = "&"
tierno750b2452018-05-17 16:39:29 +0200607 self.logger.debug("RO GET %s", url)
calvinosanchd5916fd2020-01-09 17:19:53 +0100608 # timeout = aiohttp.ClientTimeout(total=self.timeout_short)
609 async with session.get(url, headers=self.headers_req) as response:
610 response_text = await response.read()
611 self.logger.debug("GET {} [{}] {}".format(url, response.status, response_text[:100]))
612 if response.status >= 300:
613 raise ROClientException(response_text, http_code=response.status)
614
tiernoc0e42e22018-05-11 11:36:10 +0200615 return self._parse_yaml(response_text, response=True)
616
617 async def _edit_item(self, session, item, item_id, descriptor, all_tenants=False):
618 if all_tenants:
619 tenant_text = "/any"
620 elif all_tenants is None:
621 tenant_text = ""
622 else:
623 if not self.tenant:
624 await self._get_tenant(session)
625 tenant_text = "/" + self.tenant
626
627 payload_req = yaml.safe_dump(descriptor)
628
tierno750b2452018-05-17 16:39:29 +0200629 # print payload_req
tiernoc0e42e22018-05-11 11:36:10 +0200630 url = "{}{}/{}/{}".format(self.endpoint_url, tenant_text, item, item_id)
tierno750b2452018-05-17 16:39:29 +0200631 self.logger.debug("RO PUT %s %s", url, payload_req)
calvinosanchd5916fd2020-01-09 17:19:53 +0100632 # timeout = aiohttp.ClientTimeout(total=self.timeout_large)
633 async with session.put(url, headers=self.headers_req, data=payload_req) as response:
634 response_text = await response.read()
635 self.logger.debug("PUT {} [{}] {}".format(url, response.status, response_text[:100]))
636 if response.status >= 300:
637 raise ROClientException(response_text, http_code=response.status)
638
tiernoc0e42e22018-05-11 11:36:10 +0200639 return self._parse_yaml(response_text, response=True)
640
tierno22f4f9c2018-06-11 18:53:39 +0200641 async def get_version(self):
642 """
643 Obtain RO server version.
644 :return: a list with integers ["major", "minor", "release"]. Raises ROClientException on Error,
645 """
646 try:
tiernoc231a872020-01-21 08:49:05 +0000647 response_text = ""
calvinosanchd5916fd2020-01-09 17:19:53 +0100648 async with aiohttp.ClientSession(loop=self.loop) as session:
tierno22f4f9c2018-06-11 18:53:39 +0200649 url = "{}/version".format(self.endpoint_url)
650 self.logger.debug("RO GET %s", url)
calvinosanchd5916fd2020-01-09 17:19:53 +0100651 # timeout = aiohttp.ClientTimeout(total=self.timeout_short)
652 async with session.get(url, headers=self.headers_req) as response:
653 response_text = await response.read()
654 self.logger.debug("GET {} [{}] {}".format(url, response.status, response_text[:100]))
655 if response.status >= 300:
656 raise ROClientException(response_text, http_code=response.status)
657
tierno22f4f9c2018-06-11 18:53:39 +0200658 for word in str(response_text).split(" "):
659 if "." in word:
660 version_text, _, _ = word.partition("-")
tierno8069ce52019-08-28 15:34:33 +0000661 return version_text
tierno22f4f9c2018-06-11 18:53:39 +0200662 raise ROClientException("Got invalid version text: '{}'".format(response_text), http_code=500)
calvinosanch30ccee32020-01-13 12:01:36 +0100663 except (aiohttp.ClientOSError, aiohttp.ClientError) as e:
tierno22f4f9c2018-06-11 18:53:39 +0200664 raise ROClientException(e, http_code=504)
665 except asyncio.TimeoutError:
666 raise ROClientException("Timeout", http_code=504)
667 except Exception as e:
668 raise ROClientException("Got invalid version text: '{}'; causing exception {}".format(response_text, e),
669 http_code=500)
670
tiernoc0e42e22018-05-11 11:36:10 +0200671 async def get_list(self, item, all_tenants=False, filter_by=None):
672 """
673 Obtain a list of items filtering by the specigy filter_by.
674 :param item: can be 'tenant', 'vim', 'vnfd', 'nsd', 'ns'
675 :param all_tenants: True if not filtering by tenant. Only allowed for admin
676 :param filter_by: dictionary with filtering
677 :return: a list of dict. It can be empty. Raises ROClientException on Error,
678 """
679 try:
680 if item not in self.client_to_RO:
681 raise ROClientException("Invalid item {}".format(item))
682 if item == 'tenant':
683 all_tenants = None
calvinosanchd5916fd2020-01-09 17:19:53 +0100684 async with aiohttp.ClientSession(loop=self.loop) as session:
tiernoc0e42e22018-05-11 11:36:10 +0200685 content = await self._list_item(session, self.client_to_RO[item], all_tenants=all_tenants,
686 filter_dict=filter_by)
687 if isinstance(content, dict):
688 if len(content) == 1:
689 for _, v in content.items():
690 return v
691 return content.values()[0]
692 else:
693 raise ROClientException("Output not a list neither dict with len equal 1", http_code=500)
694 return content
calvinosanch30ccee32020-01-13 12:01:36 +0100695 except (aiohttp.ClientOSError, aiohttp.ClientError) as e:
tiernoc0e42e22018-05-11 11:36:10 +0200696 raise ROClientException(e, http_code=504)
tierno22f4f9c2018-06-11 18:53:39 +0200697 except asyncio.TimeoutError:
698 raise ROClientException("Timeout", http_code=504)
tiernoc0e42e22018-05-11 11:36:10 +0200699
tierno22f4f9c2018-06-11 18:53:39 +0200700 async def show(self, item, item_id_name=None, extra_item=None, extra_item_id=None, all_tenants=False):
tiernoc0e42e22018-05-11 11:36:10 +0200701 """
702 Obtain the information of an item from its id or name
703 :param item: can be 'tenant', 'vim', 'vnfd', 'nsd', 'ns'
704 :param item_id_name: RO id or name of the item. Raise and exception if more than one found
tierno22f4f9c2018-06-11 18:53:39 +0200705 :param extra_item: if supplied, it is used to add to the URL.
706 Can be 'action' if item='ns'; 'networks' or'images' if item='vim'
707 :param extra_item_id: if supplied, it is used get details of a concrete extra_item.
tiernoc0e42e22018-05-11 11:36:10 +0200708 :param all_tenants: True if not filtering by tenant. Only allowed for admin
709 :return: dictionary with the information or raises ROClientException on Error, NotFound, found several
710 """
711 try:
712 if item not in self.client_to_RO:
713 raise ROClientException("Invalid item {}".format(item))
714 if item == 'tenant':
715 all_tenants = None
716 elif item == 'vim':
717 all_tenants = True
718 elif item == 'vim_account':
719 all_tenants = False
720
calvinosanchd5916fd2020-01-09 17:19:53 +0100721 async with aiohttp.ClientSession(loop=self.loop) as session:
tierno22f4f9c2018-06-11 18:53:39 +0200722 content = await self._get_item(session, self.client_to_RO[item], item_id_name, extra_item=extra_item,
723 extra_item_id=extra_item_id, all_tenants=all_tenants)
tiernoc0e42e22018-05-11 11:36:10 +0200724 return remove_envelop(item, content)
calvinosanch30ccee32020-01-13 12:01:36 +0100725 except (aiohttp.ClientOSError, aiohttp.ClientError) as e:
tiernoc0e42e22018-05-11 11:36:10 +0200726 raise ROClientException(e, http_code=504)
tierno22f4f9c2018-06-11 18:53:39 +0200727 except asyncio.TimeoutError:
728 raise ROClientException("Timeout", http_code=504)
tiernoc0e42e22018-05-11 11:36:10 +0200729
730 async def delete(self, item, item_id_name=None, all_tenants=False):
731 """
732 Delete the information of an item from its id or name
733 :param item: can be 'tenant', 'vim', 'vnfd', 'nsd', 'ns'
734 :param item_id_name: RO id or name of the item. Raise and exception if more than one found
735 :param all_tenants: True if not filtering by tenant. Only allowed for admin
736 :return: dictionary with the information or raises ROClientException on Error, NotFound, found several
737 """
738 try:
739 if item not in self.client_to_RO:
740 raise ROClientException("Invalid item {}".format(item))
tiernoe37b57d2018-12-11 17:22:51 +0000741 if item in ('tenant', 'vim', 'wim'):
tiernoc0e42e22018-05-11 11:36:10 +0200742 all_tenants = None
743
calvinosanchd5916fd2020-01-09 17:19:53 +0100744 async with aiohttp.ClientSession(loop=self.loop) as session:
tiernofa66d152018-08-28 10:13:45 +0000745 result = await self._del_item(session, self.client_to_RO[item], item_id_name, all_tenants=all_tenants)
746 # in case of ns delete, get the action_id embeded in text
747 if item == "ns" and result.get("result"):
748 _, _, action_id = result["result"].partition("action_id=")
749 action_id, _, _ = action_id.partition(" ")
750 if action_id:
751 result["action_id"] = action_id
752 return result
calvinosanch30ccee32020-01-13 12:01:36 +0100753 except (aiohttp.ClientOSError, aiohttp.ClientError) as e:
tiernoc0e42e22018-05-11 11:36:10 +0200754 raise ROClientException(e, http_code=504)
tierno22f4f9c2018-06-11 18:53:39 +0200755 except asyncio.TimeoutError:
756 raise ROClientException("Timeout", http_code=504)
tiernoc0e42e22018-05-11 11:36:10 +0200757
758 async def edit(self, item, item_id_name, descriptor=None, descriptor_format=None, **kwargs):
759 """ Edit an item
760 :param item: can be 'tenant', 'vim', 'vnfd', 'nsd', 'ns', 'vim'
tierno22f4f9c2018-06-11 18:53:39 +0200761 :param item_id_name: RO id or name of the item. Raise and exception if more than one found
tiernoc0e42e22018-05-11 11:36:10 +0200762 :param descriptor: can be a dict, or a yaml/json text. Autodetect unless descriptor_format is provided
763 :param descriptor_format: Can be 'json' or 'yaml'
764 :param kwargs: Overrides descriptor with values as name, description, vim_url, vim_url_admin, vim_type
765 keys can be a dot separated list to specify elements inside dict
766 :return: dictionary with the information or raises ROClientException on Error
767 """
768 try:
769 if isinstance(descriptor, str):
770 descriptor = self._parse(descriptor, descriptor_format)
771 elif descriptor:
772 pass
773 else:
774 descriptor = {}
775
776 if item not in self.client_to_RO:
777 raise ROClientException("Invalid item {}".format(item))
778 desc = remove_envelop(item, descriptor)
779
780 # Override descriptor with kwargs
781 if kwargs:
782 desc = self.update_descriptor(desc, kwargs)
783 all_tenants = False
784 if item in ('tenant', 'vim'):
785 all_tenants = None
786
787 create_desc = self._create_envelop(item, desc)
788
calvinosanchd5916fd2020-01-09 17:19:53 +0100789 async with aiohttp.ClientSession(loop=self.loop) as session:
tiernoc0e42e22018-05-11 11:36:10 +0200790 _all_tenants = all_tenants
791 if item == 'vim':
792 _all_tenants = True
tierno750b2452018-05-17 16:39:29 +0200793 item_id = await self._get_item_uuid(session, self.client_to_RO[item], item_id_name,
794 all_tenants=_all_tenants)
tiernofe1c37f2018-05-17 22:58:04 +0200795 if item == 'vim':
796 _all_tenants = None
tiernoc0e42e22018-05-11 11:36:10 +0200797 # await self._get_tenant(session)
tierno750b2452018-05-17 16:39:29 +0200798 outdata = await self._edit_item(session, self.client_to_RO[item], item_id, create_desc,
799 all_tenants=_all_tenants)
tiernoc0e42e22018-05-11 11:36:10 +0200800 return remove_envelop(item, outdata)
calvinosanch30ccee32020-01-13 12:01:36 +0100801 except (aiohttp.ClientOSError, aiohttp.ClientError) as e:
tiernoc0e42e22018-05-11 11:36:10 +0200802 raise ROClientException(e, http_code=504)
tierno22f4f9c2018-06-11 18:53:39 +0200803 except asyncio.TimeoutError:
804 raise ROClientException("Timeout", http_code=504)
tiernoc0e42e22018-05-11 11:36:10 +0200805
806 async def create(self, item, descriptor=None, descriptor_format=None, **kwargs):
807 """
808 Creates an item from its descriptor
809 :param item: can be 'tenant', 'vnfd', 'nsd', 'ns', 'vim', 'vim_account', 'sdn'
810 :param descriptor: can be a dict, or a yaml/json text. Autodetect unless descriptor_format is provided
811 :param descriptor_format: Can be 'json' or 'yaml'
812 :param kwargs: Overrides descriptor with values as name, description, vim_url, vim_url_admin, vim_type
813 keys can be a dot separated list to specify elements inside dict
814 :return: dictionary with the information or raises ROClientException on Error
815 """
816 try:
817 if isinstance(descriptor, str):
818 descriptor = self._parse(descriptor, descriptor_format)
819 elif descriptor:
820 pass
821 else:
822 descriptor = {}
823
824 if item not in self.client_to_RO:
825 raise ROClientException("Invalid item {}".format(item))
826 desc = remove_envelop(item, descriptor)
827
828 # Override descriptor with kwargs
829 if kwargs:
830 desc = self.update_descriptor(desc, kwargs)
831
832 for mandatory in self.mandatory_for_create[item]:
833 if mandatory not in desc:
834 raise ROClientException("'{}' is mandatory parameter for {}".format(mandatory, item))
835
836 all_tenants = False
tiernoe37b57d2018-12-11 17:22:51 +0000837 if item in ('tenant', 'vim', 'wim'):
tiernoc0e42e22018-05-11 11:36:10 +0200838 all_tenants = None
839
840 create_desc = self._create_envelop(item, desc)
841
calvinosanchd5916fd2020-01-09 17:19:53 +0100842 async with aiohttp.ClientSession(loop=self.loop) as session:
tiernoc0e42e22018-05-11 11:36:10 +0200843 outdata = await self._create_item(session, self.client_to_RO[item], create_desc,
844 all_tenants=all_tenants)
845 return remove_envelop(item, outdata)
calvinosanch30ccee32020-01-13 12:01:36 +0100846 except (aiohttp.ClientOSError, aiohttp.ClientError) as e:
tiernoc0e42e22018-05-11 11:36:10 +0200847 raise ROClientException(e, http_code=504)
tierno22f4f9c2018-06-11 18:53:39 +0200848 except asyncio.TimeoutError:
849 raise ROClientException("Timeout", http_code=504)
850
851 async def create_action(self, item, item_id_name, descriptor=None, descriptor_format=None, **kwargs):
852 """
853 Performs an action over an item
854 :param item: can be 'tenant', 'vnfd', 'nsd', 'ns', 'vim', 'vim_account', 'sdn'
855 :param item_id_name: RO id or name of the item. Raise and exception if more than one found
856 :param descriptor: can be a dict, or a yaml/json text. Autodetect unless descriptor_format is provided
857 :param descriptor_format: Can be 'json' or 'yaml'
858 :param kwargs: Overrides descriptor with values as name, description, vim_url, vim_url_admin, vim_type
859 keys can be a dot separated list to specify elements inside dict
860 :return: dictionary with the information or raises ROClientException on Error
861 """
862 try:
863 if isinstance(descriptor, str):
864 descriptor = self._parse(descriptor, descriptor_format)
865 elif descriptor:
866 pass
867 else:
868 descriptor = {}
869
870 if item not in self.client_to_RO:
871 raise ROClientException("Invalid item {}".format(item))
872 desc = remove_envelop(item, descriptor)
873
874 # Override descriptor with kwargs
875 if kwargs:
876 desc = self.update_descriptor(desc, kwargs)
877
878 all_tenants = False
879 if item in ('tenant', 'vim'):
880 all_tenants = None
881
882 action = None
883 if item == "vims":
884 action = "sdn_mapping"
885 elif item in ("vim_account", "ns"):
886 action = "action"
887
888 # create_desc = self._create_envelop(item, desc)
889 create_desc = desc
890
calvinosanchd5916fd2020-01-09 17:19:53 +0100891 async with aiohttp.ClientSession(loop=self.loop) as session:
tierno22f4f9c2018-06-11 18:53:39 +0200892 _all_tenants = all_tenants
893 if item == 'vim':
894 _all_tenants = True
895 # item_id = await self._get_item_uuid(session, self.client_to_RO[item], item_id_name,
896 # all_tenants=_all_tenants)
897 outdata = await self._create_item(session, self.client_to_RO[item], create_desc,
898 item_id_name=item_id_name, # item_id_name=item_id
899 action=action, all_tenants=_all_tenants)
900 return remove_envelop(item, outdata)
calvinosanch30ccee32020-01-13 12:01:36 +0100901 except (aiohttp.ClientOSError, aiohttp.ClientError) as e:
tierno22f4f9c2018-06-11 18:53:39 +0200902 raise ROClientException(e, http_code=504)
903 except asyncio.TimeoutError:
904 raise ROClientException("Timeout", http_code=504)
tiernoc0e42e22018-05-11 11:36:10 +0200905
tiernoe37b57d2018-12-11 17:22:51 +0000906 async def attach(self, item, item_id_name=None, descriptor=None, descriptor_format=None, **kwargs):
907 """
908 Attach a datacenter or wim to a tenant, creating a vim_account, wim_account
909 :param item: can be vim_account or wim_account
910 :param item_id_name: id or name of the datacenter, wim
911 :param descriptor:
912 :param descriptor_format:
913 :param kwargs:
914 :return:
915 """
tierno22f4f9c2018-06-11 18:53:39 +0200916 try:
917 if isinstance(descriptor, str):
918 descriptor = self._parse(descriptor, descriptor_format)
919 elif descriptor:
920 pass
921 else:
922 descriptor = {}
tiernoe37b57d2018-12-11 17:22:51 +0000923
924 desc = remove_envelop(item, descriptor)
tiernoc0e42e22018-05-11 11:36:10 +0200925
tierno22f4f9c2018-06-11 18:53:39 +0200926 # # check that exist
927 # uuid = self._get_item_uuid(session, "datacenters", uuid_name, all_tenants=True)
928 # tenant_text = "/" + self._get_tenant()
929 if kwargs:
930 desc = self.update_descriptor(desc, kwargs)
tiernoc0e42e22018-05-11 11:36:10 +0200931
tiernoe37b57d2018-12-11 17:22:51 +0000932 if item == "vim_account":
933 if not desc.get("vim_tenant_name") and not desc.get("vim_tenant_id"):
934 raise ROClientException("Wrong descriptor. At least vim_tenant_name or vim_tenant_id must be "
935 "provided")
936 elif item != "wim_account":
937 raise ROClientException("Attach with unknown item {}. Must be 'vim_account' or 'wim_account'".
938 format(item))
939 create_desc = self._create_envelop(item, desc)
tierno22f4f9c2018-06-11 18:53:39 +0200940 payload_req = yaml.safe_dump(create_desc)
calvinosanchd5916fd2020-01-09 17:19:53 +0100941 async with aiohttp.ClientSession(loop=self.loop) as session:
tierno22f4f9c2018-06-11 18:53:39 +0200942 # check that exist
tiernoe37b57d2018-12-11 17:22:51 +0000943 item_id = await self._get_item_uuid(session, self.client_to_RO[item], item_id_name, all_tenants=True)
tierno22f4f9c2018-06-11 18:53:39 +0200944 await self._get_tenant(session)
tiernoc0e42e22018-05-11 11:36:10 +0200945
tiernoe37b57d2018-12-11 17:22:51 +0000946 url = "{}/{tenant}/{item}/{item_id}".format(self.endpoint_url, tenant=self.tenant,
947 item=self.client_to_RO[item], item_id=item_id)
tierno22f4f9c2018-06-11 18:53:39 +0200948 self.logger.debug("RO POST %s %s", url, payload_req)
calvinosanchd5916fd2020-01-09 17:19:53 +0100949 # timeout = aiohttp.ClientTimeout(total=self.timeout_large)
950 async with session.post(url, headers=self.headers_req, data=payload_req) as response:
951 response_text = await response.read()
952 self.logger.debug("POST {} [{}] {}".format(url, response.status, response_text[:100]))
953 if response.status >= 300:
954 raise ROClientException(response_text, http_code=response.status)
tiernoc0e42e22018-05-11 11:36:10 +0200955
tierno22f4f9c2018-06-11 18:53:39 +0200956 response_desc = self._parse_yaml(response_text, response=True)
tiernoe37b57d2018-12-11 17:22:51 +0000957 desc = remove_envelop(item, response_desc)
tierno22f4f9c2018-06-11 18:53:39 +0200958 return desc
calvinosanch30ccee32020-01-13 12:01:36 +0100959 except (aiohttp.ClientOSError, aiohttp.ClientError) as e:
tierno22f4f9c2018-06-11 18:53:39 +0200960 raise ROClientException(e, http_code=504)
961 except asyncio.TimeoutError:
962 raise ROClientException("Timeout", http_code=504)
tiernoc0e42e22018-05-11 11:36:10 +0200963
tiernoe37b57d2018-12-11 17:22:51 +0000964 async def detach(self, item, item_id_name=None):
tierno750b2452018-05-17 16:39:29 +0200965 # TODO replace the code with delete_item(vim_account,...)
tierno22f4f9c2018-06-11 18:53:39 +0200966 try:
calvinosanchd5916fd2020-01-09 17:19:53 +0100967 async with aiohttp.ClientSession(loop=self.loop) as session:
tierno22f4f9c2018-06-11 18:53:39 +0200968 # check that exist
tiernoe37b57d2018-12-11 17:22:51 +0000969 item_id = await self._get_item_uuid(session, self.client_to_RO[item], item_id_name, all_tenants=False)
tierno22f4f9c2018-06-11 18:53:39 +0200970 tenant = await self._get_tenant(session)
tiernoc0e42e22018-05-11 11:36:10 +0200971
tiernoe37b57d2018-12-11 17:22:51 +0000972 url = "{}/{tenant}/{item}/{datacenter}".format(self.endpoint_url, tenant=tenant,
973 item=self.client_to_RO[item], datacenter=item_id)
tierno22f4f9c2018-06-11 18:53:39 +0200974 self.logger.debug("RO DELETE %s", url)
calvinosanchd5916fd2020-01-09 17:19:53 +0100975
976 # timeout = aiohttp.ClientTimeout(total=self.timeout_large)
977 async with session.delete(url, headers=self.headers_req) as response:
978 response_text = await response.read()
979 self.logger.debug("DELETE {} [{}] {}".format(url, response.status, response_text[:100]))
980 if response.status >= 300:
981 raise ROClientException(response_text, http_code=response.status)
982
tierno22f4f9c2018-06-11 18:53:39 +0200983 response_desc = self._parse_yaml(response_text, response=True)
tiernoe37b57d2018-12-11 17:22:51 +0000984 desc = remove_envelop(item, response_desc)
tierno22f4f9c2018-06-11 18:53:39 +0200985 return desc
calvinosanch30ccee32020-01-13 12:01:36 +0100986 except (aiohttp.ClientOSError, aiohttp.ClientError) as e:
tierno22f4f9c2018-06-11 18:53:39 +0200987 raise ROClientException(e, http_code=504)
988 except asyncio.TimeoutError:
989 raise ROClientException("Timeout", http_code=504)
tiernoc0e42e22018-05-11 11:36:10 +0200990
tiernoc0e42e22018-05-11 11:36:10 +0200991 # TODO convert to asyncio
tierno750b2452018-05-17 16:39:29 +0200992 # DATACENTERS
tiernoc0e42e22018-05-11 11:36:10 +0200993
tierno750b2452018-05-17 16:39:29 +0200994 def edit_datacenter(self, uuid=None, name=None, descriptor=None, descriptor_format=None, all_tenants=False,
995 **kwargs):
tiernoc0e42e22018-05-11 11:36:10 +0200996 """Edit the parameters of a datacenter
997 Params: must supply a descriptor or/and a parameter to change
998 uuid or/and name. If only name is supplied, there must be only one or an exception is raised
999 descriptor: with format {'datacenter':{params to change info}}
1000 must be a dictionary or a json/yaml text.
1001 parameters to change can be supplyied by the descriptor or as parameters:
1002 new_name: the datacenter name
1003 vim_url: the datacenter URL
1004 vim_url_admin: the datacenter URL for administrative issues
1005 vim_type: the datacenter type, can be openstack or openvim.
1006 public: boolean, available to other tenants
1007 description: datacenter description
1008 Return: Raises an exception on error, not found or found several
1009 Obtain a dictionary with format {'datacenter':{new_datacenter_info}}
1010 """
1011
1012 if isinstance(descriptor, str):
1013 descriptor = self.parse(descriptor, descriptor_format)
1014 elif descriptor:
1015 pass
1016 elif kwargs:
tierno750b2452018-05-17 16:39:29 +02001017 descriptor = {"datacenter": {}}
tiernoc0e42e22018-05-11 11:36:10 +02001018 else:
1019 raise ROClientException("Missing descriptor")
1020
tierno750b2452018-05-17 16:39:29 +02001021 if 'datacenter' not in descriptor or len(descriptor) != 1:
tiernoc0e42e22018-05-11 11:36:10 +02001022 raise ROClientException("Descriptor must contain only one 'datacenter' field")
1023 for param in kwargs:
tierno750b2452018-05-17 16:39:29 +02001024 if param == 'new_name':
tiernoc0e42e22018-05-11 11:36:10 +02001025 descriptor['datacenter']['name'] = kwargs[param]
1026 else:
1027 descriptor['datacenter'][param] = kwargs[param]
1028 return self._edit_item("datacenters", descriptor, uuid, name, all_tenants=None)
tiernoc0e42e22018-05-11 11:36:10 +02001029
1030 def edit_scenario(self, uuid=None, name=None, descriptor=None, descriptor_format=None, all_tenants=False, **kwargs):
1031 """Edit the parameters of a scenario
1032 Params: must supply a descriptor or/and a parameters to change
1033 uuid or/and name. If only name is supplied, there must be only one or an exception is raised
1034 descriptor: with format {'scenario':{params to change info}}
1035 must be a dictionary or a json/yaml text.
1036 parameters to change can be supplyied by the descriptor or as parameters:
1037 new_name: the scenario name
1038 public: boolean, available to other tenants
1039 description: scenario description
1040 tenant_id. Propietary tenant
1041 Return: Raises an exception on error, not found or found several
1042 Obtain a dictionary with format {'scenario':{new_scenario_info}}
1043 """
1044
1045 if isinstance(descriptor, str):
1046 descriptor = self.parse(descriptor, descriptor_format)
1047 elif descriptor:
1048 pass
1049 elif kwargs:
tierno750b2452018-05-17 16:39:29 +02001050 descriptor = {"scenario": {}}
tiernoc0e42e22018-05-11 11:36:10 +02001051 else:
1052 raise ROClientException("Missing descriptor")
1053
tierno750b2452018-05-17 16:39:29 +02001054 if 'scenario' not in descriptor or len(descriptor) > 2:
tiernoc0e42e22018-05-11 11:36:10 +02001055 raise ROClientException("Descriptor must contain only one 'scenario' field")
1056 for param in kwargs:
tierno750b2452018-05-17 16:39:29 +02001057 if param == 'new_name':
tiernoc0e42e22018-05-11 11:36:10 +02001058 descriptor['scenario']['name'] = kwargs[param]
1059 else:
1060 descriptor['scenario'][param] = kwargs[param]
1061 return self._edit_item("scenarios", descriptor, uuid, name, all_tenants=None)
1062
tierno750b2452018-05-17 16:39:29 +02001063 # VIM ACTIONS
tiernoc0e42e22018-05-11 11:36:10 +02001064 def vim_action(self, action, item, uuid=None, all_tenants=False, **kwargs):
1065 """Perform an action over a vim
tierno750b2452018-05-17 16:39:29 +02001066 Params:
tiernoc0e42e22018-05-11 11:36:10 +02001067 action: can be 'list', 'get'/'show', 'delete' or 'create'
1068 item: can be 'tenants' or 'networks'
1069 uuid: uuid of the tenant/net to show or to delete. Ignore otherwise
1070 other parameters:
tierno750b2452018-05-17 16:39:29 +02001071 datacenter_name, datacenter_id: datacenters to act on, if missing uses classes store datacenter
1072 descriptor, descriptor_format: descriptor needed on creation, can be a dict or a yaml/json str
tiernoc0e42e22018-05-11 11:36:10 +02001073 must be a dictionary or a json/yaml text.
1074 name: for created tenant/net Overwrite descriptor name if any
1075 description: tenant descriptor. Overwrite descriptor description if any
tierno750b2452018-05-17 16:39:29 +02001076
tiernoc0e42e22018-05-11 11:36:10 +02001077 Return: Raises an exception on error
1078 Obtain a dictionary with format {'tenant':{new_tenant_info}}
1079 """
tierno750b2452018-05-17 16:39:29 +02001080 session = None # TODO remove when changed to asyncio
tiernoc0e42e22018-05-11 11:36:10 +02001081 if item not in ("tenants", "networks", "images"):
1082 raise ROClientException("Unknown value for item '{}', must be 'tenants', 'nets' or "
tierno750b2452018-05-17 16:39:29 +02001083 "images".format(str(item)))
tiernoc0e42e22018-05-11 11:36:10 +02001084
tierno750b2452018-05-17 16:39:29 +02001085 image_actions = ['list', 'get', 'show', 'delete']
tiernoc0e42e22018-05-11 11:36:10 +02001086 if item == "images" and action not in image_actions:
1087 raise ROClientException("Only available actions for item '{}' are {}\n"
tierno750b2452018-05-17 16:39:29 +02001088 "Requested action was '{}'".format(item, ', '.join(image_actions), action))
tiernoc0e42e22018-05-11 11:36:10 +02001089 if all_tenants:
1090 tenant_text = "/any"
1091 else:
tierno750b2452018-05-17 16:39:29 +02001092 tenant_text = "/" + self._get_tenant()
1093
tiernoc0e42e22018-05-11 11:36:10 +02001094 if "datacenter_id" in kwargs or "datacenter_name" in kwargs:
1095 datacenter = self._get_item_uuid(session, "datacenters", kwargs.get("datacenter"), all_tenants=all_tenants)
1096 else:
1097 datacenter = self.get_datacenter(session)
1098
tierno750b2452018-05-17 16:39:29 +02001099 if action == "list":
tiernoc0e42e22018-05-11 11:36:10 +02001100 url = "{}{}/vim/{}/{}".format(self.endpoint_url, tenant_text, datacenter, item)
tierno750b2452018-05-17 16:39:29 +02001101 self.logger.debug("GET %s", url)
tiernoc0e42e22018-05-11 11:36:10 +02001102 mano_response = requests.get(url, headers=self.headers_req)
tierno750b2452018-05-17 16:39:29 +02001103 self.logger.debug("RO response: %s", mano_response.text)
tiernoc0e42e22018-05-11 11:36:10 +02001104 content = self._parse_yaml(mano_response.text, response=True)
tierno750b2452018-05-17 16:39:29 +02001105 if mano_response.status_code == 200:
tiernoc0e42e22018-05-11 11:36:10 +02001106 return content
1107 else:
1108 raise ROClientException(str(content), http_code=mano_response.status)
tierno750b2452018-05-17 16:39:29 +02001109 elif action == "get" or action == "show":
tiernoc0e42e22018-05-11 11:36:10 +02001110 url = "{}{}/vim/{}/{}/{}".format(self.endpoint_url, tenant_text, datacenter, item, uuid)
tierno750b2452018-05-17 16:39:29 +02001111 self.logger.debug("GET %s", url)
tiernoc0e42e22018-05-11 11:36:10 +02001112 mano_response = requests.get(url, headers=self.headers_req)
tierno750b2452018-05-17 16:39:29 +02001113 self.logger.debug("RO response: %s", mano_response.text)
tiernoc0e42e22018-05-11 11:36:10 +02001114 content = self._parse_yaml(mano_response.text, response=True)
tierno750b2452018-05-17 16:39:29 +02001115 if mano_response.status_code == 200:
tiernoc0e42e22018-05-11 11:36:10 +02001116 return content
1117 else:
1118 raise ROClientException(str(content), http_code=mano_response.status)
tierno750b2452018-05-17 16:39:29 +02001119 elif action == "delete":
tiernoc0e42e22018-05-11 11:36:10 +02001120 url = "{}{}/vim/{}/{}/{}".format(self.endpoint_url, tenant_text, datacenter, item, uuid)
tierno750b2452018-05-17 16:39:29 +02001121 self.logger.debug("DELETE %s", url)
tiernoc0e42e22018-05-11 11:36:10 +02001122 mano_response = requests.delete(url, headers=self.headers_req)
tierno750b2452018-05-17 16:39:29 +02001123 self.logger.debug("RO response: %s", mano_response.text)
tiernoc0e42e22018-05-11 11:36:10 +02001124 content = self._parse_yaml(mano_response.text, response=True)
tierno750b2452018-05-17 16:39:29 +02001125 if mano_response.status_code == 200:
tiernoc0e42e22018-05-11 11:36:10 +02001126 return content
1127 else:
1128 raise ROClientException(str(content), http_code=mano_response.status)
tierno750b2452018-05-17 16:39:29 +02001129 elif action == "create":
tiernoc0e42e22018-05-11 11:36:10 +02001130 if "descriptor" in kwargs:
1131 if isinstance(kwargs["descriptor"], str):
tierno750b2452018-05-17 16:39:29 +02001132 descriptor = self._parse(kwargs["descriptor"], kwargs.get("descriptor_format"))
tiernoc0e42e22018-05-11 11:36:10 +02001133 else:
1134 descriptor = kwargs["descriptor"]
1135 elif "name" in kwargs:
tierno750b2452018-05-17 16:39:29 +02001136 descriptor = {item[:-1]: {"name": kwargs["name"]}}
tiernoc0e42e22018-05-11 11:36:10 +02001137 else:
1138 raise ROClientException("Missing descriptor")
1139
tierno750b2452018-05-17 16:39:29 +02001140 if item[:-1] not in descriptor or len(descriptor) != 1:
tiernoc0e42e22018-05-11 11:36:10 +02001141 raise ROClientException("Descriptor must contain only one 'tenant' field")
1142 if "name" in kwargs:
tierno750b2452018-05-17 16:39:29 +02001143 descriptor[item[:-1]]['name'] = kwargs["name"]
tiernoc0e42e22018-05-11 11:36:10 +02001144 if "description" in kwargs:
tierno750b2452018-05-17 16:39:29 +02001145 descriptor[item[:-1]]['description'] = kwargs["description"]
tiernoc0e42e22018-05-11 11:36:10 +02001146 payload_req = yaml.safe_dump(descriptor)
tierno750b2452018-05-17 16:39:29 +02001147 # print payload_req
tiernoc0e42e22018-05-11 11:36:10 +02001148 url = "{}{}/vim/{}/{}".format(self.endpoint_url, tenant_text, datacenter, item)
tierno750b2452018-05-17 16:39:29 +02001149 self.logger.debug("RO POST %s %s", url, payload_req)
1150 mano_response = requests.post(url, headers=self.headers_req, data=payload_req)
1151 self.logger.debug("RO response: %s", mano_response.text)
tiernoc0e42e22018-05-11 11:36:10 +02001152 content = self._parse_yaml(mano_response.text, response=True)
tierno750b2452018-05-17 16:39:29 +02001153 if mano_response.status_code == 200:
tiernoc0e42e22018-05-11 11:36:10 +02001154 return content
1155 else:
1156 raise ROClientException(str(content), http_code=mano_response.status)
1157 else:
tierno750b2452018-05-17 16:39:29 +02001158 raise ROClientException("Unknown value for action '{}".format(str(action)))
tiernoc0e42e22018-05-11 11:36:10 +02001159
1160
1161if __name__ == '__main__':
1162 RO_URL = "http://localhost:9090/openmano"
1163 TEST_TENANT = "myTenant"
1164 TEST_VIM1 = "myvim"
1165 TEST_URL1 = "https://localhost:5000/v1"
1166 TEST_TYPE1 = "openstack"
1167 TEST_CONFIG1 = {"use_floating_ip": True}
1168 TEST_VIM2 = "myvim2"
1169 TEST_URL2 = "https://localhost:5000/v2"
1170 TEST_TYPE2 = "openvim"
1171 TEST_CONFIG2 = {"config2": "config2", "config3": True}
1172
1173 streamformat = "%(asctime)s %(name)s %(levelname)s: %(message)s"
1174 logging.basicConfig(format=streamformat)
1175 logger = logging.getLogger("ROClient")
1176
1177 tenant_id = None
1178 vim_id = False
1179 loop = asyncio.get_event_loop()
1180 myClient = ROClient(endpoint_url=RO_URL, loop=loop, loglevel="DEBUG")
1181 try:
1182 # test tenant
1183 content = loop.run_until_complete(myClient.get_list("tenant"))
1184 print("tenants", content)
1185 content = loop.run_until_complete(myClient.create("tenant", name=TEST_TENANT))
1186 tenant_id = True
1187 content = loop.run_until_complete(myClient.show("tenant", TEST_TENANT))
1188 print("tenant", TEST_TENANT, content)
tierno750b2452018-05-17 16:39:29 +02001189 content = loop.run_until_complete(myClient.edit("tenant", TEST_TENANT, description="another description"))
tiernoc0e42e22018-05-11 11:36:10 +02001190 content = loop.run_until_complete(myClient.show("tenant", TEST_TENANT))
1191 print("tenant edited", TEST_TENANT, content)
1192 myClient["tenant"] = TEST_TENANT
1193
tiernoc0e42e22018-05-11 11:36:10 +02001194 # test VIM
tierno750b2452018-05-17 16:39:29 +02001195 content = loop.run_until_complete(myClient.create("vim", name=TEST_VIM1, type=TEST_TYPE1, vim_url=TEST_URL1,
1196 config=TEST_CONFIG1))
tiernoc0e42e22018-05-11 11:36:10 +02001197 vim_id = True
1198 content = loop.run_until_complete(myClient.get_list("vim"))
1199 print("vim", content)
1200 content = loop.run_until_complete(myClient.show("vim", TEST_VIM1))
1201 print("vim", TEST_VIM1, content)
tierno750b2452018-05-17 16:39:29 +02001202 content = loop.run_until_complete(myClient.edit("vim", TEST_VIM1, description="another description",
tiernoc0e42e22018-05-11 11:36:10 +02001203 name=TEST_VIM2, type=TEST_TYPE2, vim_url=TEST_URL2,
1204 config=TEST_CONFIG2))
1205 content = loop.run_until_complete(myClient.show("vim", TEST_VIM2))
1206 print("vim edited", TEST_VIM2, content)
1207
1208 # test VIM_ACCOUNT
1209 content = loop.run_until_complete(myClient.attach_datacenter(TEST_VIM2, vim_username='user',
tierno750b2452018-05-17 16:39:29 +02001210 vim_password='pass', vim_tenant_name='vimtenant1',
1211 config=TEST_CONFIG1))
tiernoc0e42e22018-05-11 11:36:10 +02001212 vim_id = True
1213 content = loop.run_until_complete(myClient.get_list("vim_account"))
1214 print("vim_account", content)
1215 content = loop.run_until_complete(myClient.show("vim_account", TEST_VIM2))
1216 print("vim_account", TEST_VIM2, content)
tierno750b2452018-05-17 16:39:29 +02001217 content = loop.run_until_complete(myClient.edit("vim_account", TEST_VIM2, vim_username='user2',
1218 vim_password='pass2', vim_tenant_name="vimtenant2",
1219 config=TEST_CONFIG2))
tiernoc0e42e22018-05-11 11:36:10 +02001220 content = loop.run_until_complete(myClient.show("vim_account", TEST_VIM2))
1221 print("vim_account edited", TEST_VIM2, content)
1222
1223 myClient["vim"] = TEST_VIM2
1224
1225 except Exception as e:
1226 logger.error("Error {}".format(e), exc_info=True)
1227
1228 for item in (("vim_account", TEST_VIM1), ("vim", TEST_VIM1),
1229 ("vim_account", TEST_VIM2), ("vim", TEST_VIM2),
1230 ("tenant", TEST_TENANT)):
1231 try:
1232 content = loop.run_until_complete(myClient.delete(item[0], item[1]))
1233 print("{} {} deleted; {}".format(item[0], item[1], content))
1234 except Exception as e:
1235 if e.http_code == 404:
1236 print("{} {} not present or already deleted".format(item[0], item[1]))
1237 else:
1238 logger.error("Error {}".format(e), exc_info=True)
1239
1240 loop.close()