3869e53c8386318fb3be0231293540f713c31808
[osm/RO.git] / RO-VIM-openvim / osm_rovim_openvim / vimconn_openvim.py
1 # -*- coding: utf-8 -*-
2
3 ##
4 # Copyright 2015 Telefonica Investigacion y Desarrollo, S.A.U.
5 # This file is part of openmano
6 # All Rights Reserved.
7 #
8 # Licensed under the Apache License, Version 2.0 (the "License"); you may
9 # not use this file except in compliance with the License. You may obtain
10 # a copy of the License at
11 #
12 # http://www.apache.org/licenses/LICENSE-2.0
13 #
14 # Unless required by applicable law or agreed to in writing, software
15 # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
16 # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
17 # License for the specific language governing permissions and limitations
18 # under the License.
19 #
20 # For those usages not covered by the Apache License, Version 2.0 please
21 # contact with: nfvlabs@tid.es
22 ##
23
24 """
25 vimconnector implements all the methods to interact with openvim using the openvim API.
26 """
27 __author__ = "Alfonso Tierno, Gerardo Garcia"
28 __date__ = "$26-aug-2014 11:09:29$"
29
30 import json
31 import logging
32 import math
33 from urllib.parse import quote
34
35 from jsonschema import exceptions as js_e, validate as js_v
36 from osm_ro.openmano_schemas import (
37 description_schema,
38 id_schema,
39 integer0_schema,
40 name_schema,
41 nameshort_schema,
42 vlan1000_schema,
43 )
44 from osm_ro_plugin import vimconn
45 import requests
46 import yaml
47
48 """contain the openvim virtual machine status to openmano status"""
49 vmStatus2manoFormat = {
50 "ACTIVE": "ACTIVE",
51 "PAUSED": "PAUSED",
52 "SUSPENDED": "SUSPENDED",
53 "INACTIVE": "INACTIVE",
54 "CREATING": "BUILD",
55 "ERROR": "ERROR",
56 "DELETED": "DELETED",
57 }
58 netStatus2manoFormat = {
59 "ACTIVE": "ACTIVE",
60 "INACTIVE": "INACTIVE",
61 "BUILD": "BUILD",
62 "ERROR": "ERROR",
63 "DELETED": "DELETED",
64 "DOWN": "DOWN",
65 }
66
67
68 host_schema = {
69 "type": "object",
70 "properties": {
71 "id": id_schema,
72 "name": name_schema,
73 },
74 "required": ["id"],
75 }
76 image_schema = {
77 "type": "object",
78 "properties": {
79 "id": id_schema,
80 "name": name_schema,
81 },
82 "required": ["id", "name"],
83 }
84 server_schema = {
85 "type": "object",
86 "properties": {
87 "id": id_schema,
88 "name": name_schema,
89 },
90 "required": ["id", "name"],
91 }
92 new_host_response_schema = {
93 "title": "host response information schema",
94 "$schema": "http://json-schema.org/draft-04/schema#",
95 "type": "object",
96 "properties": {"host": host_schema},
97 "required": ["host"],
98 "additionalProperties": False,
99 }
100
101 get_images_response_schema = {
102 "title": "openvim images response information schema",
103 "$schema": "http://json-schema.org/draft-04/schema#",
104 "type": "object",
105 "properties": {
106 "images": {
107 "type": "array",
108 "items": image_schema,
109 }
110 },
111 "required": ["images"],
112 "additionalProperties": False,
113 }
114
115 get_hosts_response_schema = {
116 "title": "openvim hosts response information schema",
117 "$schema": "http://json-schema.org/draft-04/schema#",
118 "type": "object",
119 "properties": {
120 "hosts": {
121 "type": "array",
122 "items": host_schema,
123 }
124 },
125 "required": ["hosts"],
126 "additionalProperties": False,
127 }
128
129 get_host_detail_response_schema = (
130 new_host_response_schema # TODO: Content is not parsed yet
131 )
132
133 get_server_response_schema = {
134 "title": "openvim server response information schema",
135 "$schema": "http://json-schema.org/draft-04/schema#",
136 "type": "object",
137 "properties": {
138 "servers": {
139 "type": "array",
140 "items": server_schema,
141 }
142 },
143 "required": ["servers"],
144 "additionalProperties": False,
145 }
146
147 new_tenant_response_schema = {
148 "title": "tenant response information schema",
149 "$schema": "http://json-schema.org/draft-04/schema#",
150 "type": "object",
151 "properties": {
152 "tenant": {
153 "type": "object",
154 "properties": {
155 "id": id_schema,
156 "name": nameshort_schema,
157 "description": description_schema,
158 "enabled": {"type": "boolean"},
159 },
160 "required": ["id"],
161 }
162 },
163 "required": ["tenant"],
164 "additionalProperties": False,
165 }
166
167 new_network_response_schema = {
168 "title": "network response information schema",
169 "$schema": "http://json-schema.org/draft-04/schema#",
170 "type": "object",
171 "properties": {
172 "network": {
173 "type": "object",
174 "properties": {
175 "id": id_schema,
176 "name": name_schema,
177 "type": {
178 "type": "string",
179 "enum": ["bridge_man", "bridge_data", "data", "ptp"],
180 },
181 "shared": {"type": "boolean"},
182 "tenant_id": id_schema,
183 "admin_state_up": {"type": "boolean"},
184 "vlan": vlan1000_schema,
185 },
186 "required": ["id"],
187 }
188 },
189 "required": ["network"],
190 "additionalProperties": False,
191 }
192
193
194 # get_network_response_schema = {
195 # "title":"get network response information schema",
196 # "$schema": "http://json-schema.org/draft-04/schema#",
197 # "type":"object",
198 # "properties":{
199 # "network":{
200 # "type":"object",
201 # "properties":{
202 # "id":id_schema,
203 # "name":name_schema,
204 # "type":{"type":"string", "enum":["bridge_man","bridge_data","data", "ptp"]},
205 # "shared":{"type":"boolean"},
206 # "tenant_id":id_schema,
207 # "admin_state_up":{"type":"boolean"},
208 # "vlan":vlan1000_schema
209 # },
210 # "required": ["id"]
211 # }
212 # },
213 # "required": ["network"],
214 # "additionalProperties": False
215 # }
216
217
218 new_port_response_schema = {
219 "title": "port response information schema",
220 "$schema": "http://json-schema.org/draft-04/schema#",
221 "type": "object",
222 "properties": {
223 "port": {
224 "type": "object",
225 "properties": {
226 "id": id_schema,
227 },
228 "required": ["id"],
229 }
230 },
231 "required": ["port"],
232 "additionalProperties": False,
233 }
234
235 get_flavor_response_schema = {
236 "title": "openvim flavors response information schema",
237 "$schema": "http://json-schema.org/draft-04/schema#",
238 "type": "object",
239 "properties": {
240 "flavor": {
241 "type": "object",
242 "properties": {
243 "id": id_schema,
244 "name": name_schema,
245 "extended": {"type": "object"},
246 },
247 "required": ["id", "name"],
248 }
249 },
250 "required": ["flavor"],
251 "additionalProperties": False,
252 }
253
254 new_flavor_response_schema = {
255 "title": "flavor response information schema",
256 "$schema": "http://json-schema.org/draft-04/schema#",
257 "type": "object",
258 "properties": {
259 "flavor": {
260 "type": "object",
261 "properties": {
262 "id": id_schema,
263 },
264 "required": ["id"],
265 }
266 },
267 "required": ["flavor"],
268 "additionalProperties": False,
269 }
270
271 get_image_response_schema = {
272 "title": "openvim images response information schema",
273 "$schema": "http://json-schema.org/draft-04/schema#",
274 "type": "object",
275 "properties": {
276 "image": {
277 "type": "object",
278 "properties": {
279 "id": id_schema,
280 "name": name_schema,
281 },
282 "required": ["id", "name"],
283 }
284 },
285 "required": ["flavor"],
286 "additionalProperties": False,
287 }
288 new_image_response_schema = {
289 "title": "image response information schema",
290 "$schema": "http://json-schema.org/draft-04/schema#",
291 "type": "object",
292 "properties": {
293 "image": {
294 "type": "object",
295 "properties": {
296 "id": id_schema,
297 },
298 "required": ["id"],
299 }
300 },
301 "required": ["image"],
302 "additionalProperties": False,
303 }
304
305 new_vminstance_response_schema = {
306 "title": "server response information schema",
307 "$schema": "http://json-schema.org/draft-04/schema#",
308 "type": "object",
309 "properties": {
310 "server": {
311 "type": "object",
312 "properties": {
313 "id": id_schema,
314 },
315 "required": ["id"],
316 }
317 },
318 "required": ["server"],
319 "additionalProperties": False,
320 }
321
322 get_processor_rankings_response_schema = {
323 "title": "processor rankings information schema",
324 "$schema": "http://json-schema.org/draft-04/schema#",
325 "type": "object",
326 "properties": {
327 "rankings": {
328 "type": "array",
329 "items": {
330 "type": "object",
331 "properties": {"model": description_schema, "value": integer0_schema},
332 "additionalProperties": False,
333 "required": ["model", "value"],
334 },
335 },
336 "additionalProperties": False,
337 "required": ["rankings"],
338 },
339 }
340
341
342 class vimconnector(vimconn.VimConnector):
343 def __init__(
344 self,
345 uuid,
346 name,
347 tenant_id,
348 tenant_name,
349 url,
350 url_admin=None,
351 user=None,
352 passwd=None,
353 log_level="DEBUG",
354 config={},
355 persistent_info={},
356 ):
357 vimconn.VimConnector.__init__(
358 self,
359 uuid,
360 name,
361 tenant_id,
362 tenant_name,
363 url,
364 url_admin,
365 user,
366 passwd,
367 log_level,
368 config,
369 )
370 self.tenant = None
371 self.headers_req = {"content-type": "application/json"}
372 self.logger = logging.getLogger("ro.vim.openvim")
373 self.persistent_info = persistent_info
374 if tenant_id:
375 self.tenant = tenant_id
376
377 def __setitem__(self, index, value):
378 """Set individuals parameters
379 Throw TypeError, KeyError
380 """
381 if index == "tenant_id":
382 self.tenant = value
383 elif index == "tenant_name":
384 self.tenant = None
385 vimconn.VimConnector.__setitem__(self, index, value)
386
387 def _get_my_tenant(self):
388 """Obtain uuid of my tenant from name"""
389 if self.tenant:
390 return self.tenant
391
392 url = self.url + "/tenants?name=" + quote(self.tenant_name)
393 self.logger.info("Getting VIM tenant_id GET %s", url)
394 vim_response = requests.get(url, headers=self.headers_req)
395 self._check_http_request_response(vim_response)
396 try:
397 tenant_list = vim_response.json()["tenants"]
398 if len(tenant_list) == 0:
399 raise vimconn.VimConnNotFoundException(
400 "No tenant found for name '{}'".format(self.tenant_name)
401 )
402 elif len(tenant_list) > 1:
403 raise vimconn.VimConnConflictException(
404 "More that one tenant found for name '{}'".format(self.tenant_name)
405 )
406 self.tenant = tenant_list[0]["id"]
407 return self.tenant
408 except Exception as e:
409 raise vimconn.VimConnUnexpectedResponse(
410 "Get VIM tenant {} '{}'".format(type(e).__name__, str(e))
411 )
412
413 def _format_jsonerror(self, http_response):
414 # DEPRECATED, to delete in the future
415 try:
416 data = http_response.json()
417 return data["error"]["description"]
418 except Exception:
419 return http_response.text
420
421 def _format_in(self, http_response, schema):
422 # DEPRECATED, to delete in the future
423 try:
424 client_data = http_response.json()
425 js_v(client_data, schema)
426 # print "Input data: ", str(client_data)
427 return True, client_data
428 except js_e.ValidationError as exc:
429 print(
430 "validate_in error, jsonschema exception ", exc.message, "at", exc.path
431 )
432 return False, (
433 "validate_in error, jsonschema exception ",
434 exc.message,
435 "at",
436 exc.path,
437 )
438
439 def _remove_extra_items(self, data, schema):
440 deleted = []
441 if type(data) is tuple or type(data) is list:
442 for d in data:
443 a = self._remove_extra_items(d, schema["items"])
444 if a is not None:
445 deleted.append(a)
446 elif type(data) is dict:
447 to_delete = []
448 for k in data.keys():
449 if "properties" not in schema or k not in schema["properties"].keys():
450 to_delete.append(k)
451 deleted.append(k)
452 else:
453 a = self._remove_extra_items(data[k], schema["properties"][k])
454 if a is not None:
455 deleted.append({k: a})
456 for k in to_delete:
457 del data[k]
458 if len(deleted) == 0:
459 return None
460 elif len(deleted) == 1:
461 return deleted[0]
462 else:
463 return deleted
464
465 def _format_request_exception(self, request_exception):
466 """Transform a request exception into a vimconn exception"""
467 if isinstance(request_exception, js_e.ValidationError):
468 raise vimconn.VimConnUnexpectedResponse(
469 "jsonschema exception '{}' at '{}'".format(
470 request_exception.message, request_exception.path
471 )
472 )
473 elif isinstance(request_exception, requests.exceptions.HTTPError):
474 raise vimconn.VimConnUnexpectedResponse(
475 type(request_exception).__name__ + ": " + str(request_exception)
476 )
477 else:
478 raise vimconn.VimConnConnectionException(
479 type(request_exception).__name__ + ": " + str(request_exception)
480 )
481
482 def _check_http_request_response(self, request_response):
483 """Raise a vimconn exception if the response is not Ok"""
484 if request_response.status_code >= 200 and request_response.status_code < 300:
485 return
486 if request_response.status_code == vimconn.HTTP_Unauthorized:
487 raise vimconn.VimConnAuthException(request_response.text)
488 elif request_response.status_code == vimconn.HTTP_Not_Found:
489 raise vimconn.VimConnNotFoundException(request_response.text)
490 elif request_response.status_code == vimconn.HTTP_Conflict:
491 raise vimconn.VimConnConflictException(request_response.text)
492 else:
493 raise vimconn.VimConnUnexpectedResponse(
494 "VIM HTTP_response {}, {}".format(
495 request_response.status_code, str(request_response.text)
496 )
497 )
498
499 def new_tenant(self, tenant_name, tenant_description):
500 """Adds a new tenant to VIM with this name and description, returns the tenant identifier"""
501 # print "VIMConnector: Adding a new tenant to VIM"
502 payload_dict = {
503 "tenant": {
504 "name": tenant_name,
505 "description": tenant_description,
506 "enabled": True,
507 }
508 }
509 payload_req = json.dumps(payload_dict)
510 try:
511 url = self.url_admin + "/tenants"
512 self.logger.info("Adding a new tenant %s", url)
513 vim_response = requests.post(
514 url, headers=self.headers_req, data=payload_req
515 )
516 self._check_http_request_response(vim_response)
517 self.logger.debug(vim_response.text)
518 # print json.dumps(vim_response.json(), indent=4)
519 response = vim_response.json()
520 js_v(response, new_tenant_response_schema)
521 # r = self._remove_extra_items(response, new_tenant_response_schema)
522 # if r is not None:
523 # self.logger.warn("Warning: remove extra items %s", str(r))
524 tenant_id = response["tenant"]["id"]
525 return tenant_id
526 except (requests.exceptions.RequestException, js_e.ValidationError) as e:
527 self._format_request_exception(e)
528
529 def delete_tenant(self, tenant_id):
530 """Delete a tenant from VIM. Returns the old tenant identifier"""
531 try:
532 url = self.url_admin + "/tenants/" + tenant_id
533 self.logger.info("Delete a tenant DELETE %s", url)
534 vim_response = requests.delete(url, headers=self.headers_req)
535 self._check_http_request_response(vim_response)
536 self.logger.debug(vim_response.text)
537 # print json.dumps(vim_response.json(), indent=4)
538 return tenant_id
539 except (requests.exceptions.RequestException, js_e.ValidationError) as e:
540 self._format_request_exception(e)
541
542 def get_tenant_list(self, filter_dict={}):
543 """Obtain tenants of VIM
544 filter_dict can contain the following keys:
545 name: filter by tenant name
546 id: filter by tenant uuid/id
547 <other VIM specific>
548 Returns the tenant list of dictionaries: [{'name':'<name>, 'id':'<id>, ...}, ...]
549 """
550 filterquery = []
551 filterquery_text = ""
552 for k, v in filter_dict.items():
553 filterquery.append(str(k) + "=" + str(v))
554 if len(filterquery) > 0:
555 filterquery_text = "?" + "&".join(filterquery)
556 try:
557 url = self.url + "/tenants" + filterquery_text
558 self.logger.info("get_tenant_list GET %s", url)
559 vim_response = requests.get(url, headers=self.headers_req)
560 self._check_http_request_response(vim_response)
561 self.logger.debug(vim_response.text)
562 # print json.dumps(vim_response.json(), indent=4)
563 return vim_response.json()["tenants"]
564 except requests.exceptions.RequestException as e:
565 self._format_request_exception(e)
566
567 def new_network(
568 self,
569 net_name,
570 net_type,
571 ip_profile=None,
572 shared=False,
573 provider_network_profile=None,
574 ): # , **vim_specific):
575 """Adds a tenant network to VIM
576 Params:
577 'net_name': name of the network
578 'net_type': one of:
579 'bridge': overlay isolated network
580 'data': underlay E-LAN network for Passthrough and SRIOV interfaces
581 'ptp': underlay E-LINE network for Passthrough and SRIOV interfaces.
582 'ip_profile': is a dict containing the IP parameters of the network
583 'ip_version': can be "IPv4" or "IPv6" (Currently only IPv4 is implemented)
584 'subnet_address': ip_prefix_schema, that is X.X.X.X/Y
585 'gateway_address': (Optional) ip_schema, that is X.X.X.X
586 'dns_address': (Optional) comma separated list of ip_schema, e.g. X.X.X.X[,X,X,X,X]
587 'dhcp_enabled': True or False
588 'dhcp_start_address': ip_schema, first IP to grant
589 'dhcp_count': number of IPs to grant.
590 'shared': if this network can be seen/use by other tenants/organization
591 'provider_network_profile': (optional) contains {segmentation-id: vlan, provider-network: vim_netowrk}
592 Returns a tuple with the network identifier and created_items, or raises an exception on error
593 created_items can be None or a dictionary where this method can include key-values that will be passed to
594 the method delete_network. Can be used to store created segments, created l2gw connections, etc.
595 Format is vimconnector dependent, but do not use nested dictionaries and a value of None should be the same
596 as not present.
597 """
598 try:
599 vlan = None
600 if provider_network_profile:
601 vlan = provider_network_profile.get("segmentation-id")
602 created_items = {}
603 self._get_my_tenant()
604 if net_type == "bridge":
605 net_type = "bridge_data"
606 payload_req = {
607 "name": net_name,
608 "type": net_type,
609 "tenant_id": self.tenant,
610 "shared": shared,
611 }
612 if vlan:
613 payload_req["provider:vlan"] = vlan
614 # payload_req.update(vim_specific)
615 url = self.url + "/networks"
616 self.logger.info(
617 "Adding a new network POST: %s DATA: %s", url, str(payload_req)
618 )
619 vim_response = requests.post(
620 url, headers=self.headers_req, data=json.dumps({"network": payload_req})
621 )
622 self._check_http_request_response(vim_response)
623 self.logger.debug(vim_response.text)
624 # print json.dumps(vim_response.json(), indent=4)
625 response = vim_response.json()
626 js_v(response, new_network_response_schema)
627 # r = self._remove_extra_items(response, new_network_response_schema)
628 # if r is not None:
629 # self.logger.warn("Warning: remove extra items %s", str(r))
630 network_id = response["network"]["id"]
631 return network_id, created_items
632 except (requests.exceptions.RequestException, js_e.ValidationError) as e:
633 self._format_request_exception(e)
634
635 def get_network_list(self, filter_dict={}):
636 """Obtain tenant networks of VIM
637 Filter_dict can be:
638 name: network name
639 id: network uuid
640 public: boolean
641 tenant_id: tenant
642 admin_state_up: boolean
643 status: 'ACTIVE'
644 Returns the network list of dictionaries
645 """
646 try:
647 if "tenant_id" not in filter_dict:
648 filter_dict["tenant_id"] = self._get_my_tenant()
649 elif not filter_dict["tenant_id"]:
650 del filter_dict["tenant_id"]
651 filterquery = []
652 filterquery_text = ""
653 for k, v in filter_dict.items():
654 filterquery.append(str(k) + "=" + str(v))
655 if len(filterquery) > 0:
656 filterquery_text = "?" + "&".join(filterquery)
657 url = self.url + "/networks" + filterquery_text
658 self.logger.info("Getting network list GET %s", url)
659 vim_response = requests.get(url, headers=self.headers_req)
660 self._check_http_request_response(vim_response)
661 self.logger.debug(vim_response.text)
662 # print json.dumps(vim_response.json(), indent=4)
663 response = vim_response.json()
664 return response["networks"]
665 except (requests.exceptions.RequestException, js_e.ValidationError) as e:
666 self._format_request_exception(e)
667
668 def get_network(self, net_id):
669 """Obtain network details of network id"""
670 try:
671 url = self.url + "/networks/" + net_id
672 self.logger.info("Getting network GET %s", url)
673 vim_response = requests.get(url, headers=self.headers_req)
674 self._check_http_request_response(vim_response)
675 self.logger.debug(vim_response.text)
676 # print json.dumps(vim_response.json(), indent=4)
677 response = vim_response.json()
678 return response["network"]
679 except (requests.exceptions.RequestException, js_e.ValidationError) as e:
680 self._format_request_exception(e)
681
682 def delete_network(self, net_id, created_items=None):
683 """
684 Removes a tenant network from VIM and its associated elements
685 :param net_id: VIM identifier of the network, provided by method new_network
686 :param created_items: dictionary with extra items to be deleted. provided by method new_network
687 Returns the network identifier or raises an exception upon error or when network is not found
688 """
689 try:
690 self._get_my_tenant()
691 url = self.url + "/networks/" + net_id
692 self.logger.info("Deleting VIM network DELETE %s", url)
693 vim_response = requests.delete(url, headers=self.headers_req)
694 self._check_http_request_response(vim_response)
695 # self.logger.debug(vim_response.text)
696 # print json.dumps(vim_response.json(), indent=4)
697 return net_id
698 except (requests.exceptions.RequestException, js_e.ValidationError) as e:
699 self._format_request_exception(e)
700
701 def get_flavor(self, flavor_id):
702 """Obtain flavor details from the VIM"""
703 try:
704 self._get_my_tenant()
705 url = self.url + "/" + self.tenant + "/flavors/" + flavor_id
706 self.logger.info("Getting flavor GET %s", url)
707 vim_response = requests.get(url, headers=self.headers_req)
708 self._check_http_request_response(vim_response)
709 self.logger.debug(vim_response.text)
710 # print json.dumps(vim_response.json(), indent=4)
711 response = vim_response.json()
712 js_v(response, get_flavor_response_schema)
713 r = self._remove_extra_items(response, get_flavor_response_schema)
714 if r is not None:
715 self.logger.warn("Warning: remove extra items %s", str(r))
716 return response["flavor"]
717 except (requests.exceptions.RequestException, js_e.ValidationError) as e:
718 self._format_request_exception(e)
719
720 def new_flavor(self, flavor_data):
721 """Adds a tenant flavor to VIM"""
722 """Returns the flavor identifier"""
723 try:
724 new_flavor_dict = flavor_data.copy()
725 for device in new_flavor_dict.get("extended", {}).get("devices", ()):
726 if "image name" in device:
727 del device["image name"]
728 if "name" in device:
729 del device["name"]
730 numas = new_flavor_dict.get("extended", {}).get("numas")
731 if numas:
732 numa = numas[0]
733 # translate memory, cpus to EPA
734 if (
735 "cores" not in numa
736 and "threads" not in numa
737 and "paired-threads" not in numa
738 ):
739 numa["paired-threads"] = new_flavor_dict["vcpus"]
740 if "memory" not in numa:
741 numa["memory"] = int(math.ceil(new_flavor_dict["ram"] / 1024.0))
742 for iface in numa.get("interfaces", ()):
743 if not iface.get("bandwidth"):
744 iface["bandwidth"] = "1 Mbps"
745
746 new_flavor_dict["name"] = flavor_data["name"][:64]
747 self._get_my_tenant()
748 payload_req = json.dumps({"flavor": new_flavor_dict})
749 url = self.url + "/" + self.tenant + "/flavors"
750 self.logger.info("Adding a new VIM flavor POST %s", url)
751 vim_response = requests.post(
752 url, headers=self.headers_req, data=payload_req
753 )
754 self._check_http_request_response(vim_response)
755 self.logger.debug(vim_response.text)
756 # print json.dumps(vim_response.json(), indent=4)
757 response = vim_response.json()
758 js_v(response, new_flavor_response_schema)
759 r = self._remove_extra_items(response, new_flavor_response_schema)
760 if r is not None:
761 self.logger.warn("Warning: remove extra items %s", str(r))
762 flavor_id = response["flavor"]["id"]
763 return flavor_id
764 except (requests.exceptions.RequestException, js_e.ValidationError) as e:
765 self._format_request_exception(e)
766
767 def delete_flavor(self, flavor_id):
768 """Deletes a tenant flavor from VIM"""
769 """Returns the old flavor_id"""
770 try:
771 self._get_my_tenant()
772 url = self.url + "/" + self.tenant + "/flavors/" + flavor_id
773 self.logger.info("Deleting VIM flavor DELETE %s", url)
774 vim_response = requests.delete(url, headers=self.headers_req)
775 self._check_http_request_response(vim_response)
776 # self.logger.debug(vim_response.text)
777 # print json.dumps(vim_response.json(), indent=4)
778 return flavor_id
779 except (requests.exceptions.RequestException, js_e.ValidationError) as e:
780 self._format_request_exception(e)
781
782 def get_image(self, image_id):
783 """Obtain image details from the VIM"""
784 try:
785 self._get_my_tenant()
786 url = self.url + "/" + self.tenant + "/images/" + image_id
787 self.logger.info("Getting image GET %s", url)
788 vim_response = requests.get(url, headers=self.headers_req)
789 self._check_http_request_response(vim_response)
790 self.logger.debug(vim_response.text)
791 # print json.dumps(vim_response.json(), indent=4)
792 response = vim_response.json()
793 js_v(response, get_image_response_schema)
794 r = self._remove_extra_items(response, get_image_response_schema)
795 if r is not None:
796 self.logger.warn("Warning: remove extra items %s", str(r))
797 return response["image"]
798 except (requests.exceptions.RequestException, js_e.ValidationError) as e:
799 self._format_request_exception(e)
800
801 def new_image(self, image_dict):
802 """Adds a tenant image to VIM, returns image_id"""
803 try:
804 self._get_my_tenant()
805 new_image_dict = {"name": image_dict["name"][:64]}
806 if image_dict.get("description"):
807 new_image_dict["description"] = image_dict["description"]
808 if image_dict.get("metadata"):
809 new_image_dict["metadata"] = yaml.load(
810 image_dict["metadata"], Loader=yaml.SafeLoader
811 )
812 if image_dict.get("location"):
813 new_image_dict["path"] = image_dict["location"]
814 payload_req = json.dumps({"image": new_image_dict})
815 url = self.url + "/" + self.tenant + "/images"
816 self.logger.info("Adding a new VIM image POST %s", url)
817 vim_response = requests.post(
818 url, headers=self.headers_req, data=payload_req
819 )
820 self._check_http_request_response(vim_response)
821 self.logger.debug(vim_response.text)
822 # print json.dumps(vim_response.json(), indent=4)
823 response = vim_response.json()
824 js_v(response, new_image_response_schema)
825 r = self._remove_extra_items(response, new_image_response_schema)
826 if r is not None:
827 self.logger.warn("Warning: remove extra items %s", str(r))
828 image_id = response["image"]["id"]
829 return image_id
830 except (requests.exceptions.RequestException, js_e.ValidationError) as e:
831 self._format_request_exception(e)
832
833 def delete_image(self, image_id):
834 """Deletes a tenant image from VIM"""
835 """Returns the deleted image_id"""
836 try:
837 self._get_my_tenant()
838 url = self.url + "/" + self.tenant + "/images/" + image_id
839 self.logger.info("Deleting VIM image DELETE %s", url)
840 vim_response = requests.delete(url, headers=self.headers_req)
841 self._check_http_request_response(vim_response)
842 # self.logger.debug(vim_response.text)
843 # print json.dumps(vim_response.json(), indent=4)
844 return image_id
845 except (requests.exceptions.RequestException, js_e.ValidationError) as e:
846 self._format_request_exception(e)
847
848 def get_image_id_from_path(self, path):
849 """Get the image id from image path in the VIM database. Returns the image_id"""
850 try:
851 self._get_my_tenant()
852 url = self.url + "/" + self.tenant + "/images?path=" + quote(path)
853 self.logger.info("Getting images GET %s", url)
854 vim_response = requests.get(url)
855 self._check_http_request_response(vim_response)
856 self.logger.debug(vim_response.text)
857 # print json.dumps(vim_response.json(), indent=4)
858 response = vim_response.json()
859 js_v(response, get_images_response_schema)
860 # r = self._remove_extra_items(response, get_images_response_schema)
861 # if r is not None:
862 # self.logger.warn("Warning: remove extra items %s", str(r))
863 if len(response["images"]) == 0:
864 raise vimconn.VimConnNotFoundException(
865 "Image not found at VIM with path '{}'".format(path)
866 )
867 elif len(response["images"]) > 1:
868 raise vimconn.VimConnConflictException(
869 "More than one image found at VIM with path '{}'".format(path)
870 )
871 return response["images"][0]["id"]
872 except (requests.exceptions.RequestException, js_e.ValidationError) as e:
873 self._format_request_exception(e)
874
875 def get_image_list(self, filter_dict={}):
876 """Obtain tenant images from VIM
877 Filter_dict can be:
878 name: image name
879 id: image uuid
880 checksum: image checksum
881 location: image path
882 Returns the image list of dictionaries:
883 [{<the fields at Filter_dict plus some VIM specific>}, ...]
884 List can be empty
885 """
886 try:
887 self._get_my_tenant()
888 filterquery = []
889 filterquery_text = ""
890 for k, v in filter_dict.items():
891 filterquery.append(str(k) + "=" + str(v))
892 if len(filterquery) > 0:
893 filterquery_text = "?" + "&".join(filterquery)
894 url = self.url + "/" + self.tenant + "/images" + filterquery_text
895 self.logger.info("Getting image list GET %s", url)
896 vim_response = requests.get(url, headers=self.headers_req)
897 self._check_http_request_response(vim_response)
898 self.logger.debug(vim_response.text)
899 # print json.dumps(vim_response.json(), indent=4)
900 response = vim_response.json()
901 return response["images"]
902 except (requests.exceptions.RequestException, js_e.ValidationError) as e:
903 self._format_request_exception(e)
904
905 def new_vminstance(
906 self,
907 name,
908 description,
909 start,
910 image_id,
911 flavor_id,
912 affinity_group_list,
913 net_list,
914 cloud_config=None,
915 disk_list=None,
916 availability_zone_index=None,
917 availability_zone_list=None,
918 ):
919 """Adds a VM instance to VIM
920 Params:
921 start: indicates if VM must start or boot in pause mode. Ignored
922 image_id,flavor_id: image and flavor uuid
923 net_list: list of interfaces, each one is a dictionary with:
924 name:
925 net_id: network uuid to connect
926 vpci: virtual vcpi to assign
927 model: interface model, virtio, e1000, ...
928 mac_address:
929 use: 'data', 'bridge', 'mgmt'
930 type: 'virtual', 'PCI-PASSTHROUGH'('PF'), 'SR-IOV'('VF'), 'VFnotShared'
931 vim_id: filled/added by this function
932 #TODO ip, security groups
933 Returns a tuple with the instance identifier and created_items or raises an exception on error
934 created_items can be None or a dictionary where this method can include key-values that will be passed to
935 the method delete_vminstance and action_vminstance. Can be used to store created ports, volumes, etc.
936 Format is vimconnector dependent, but do not use nested dictionaries and a value of None should be the same
937 as not present.
938 """
939 self.logger.debug(
940 "new_vminstance input: image='%s' flavor='%s' nics='%s'",
941 image_id,
942 flavor_id,
943 str(net_list),
944 )
945 try:
946 self._get_my_tenant()
947 # net_list = []
948 # for k,v in net_dict.items():
949 # print k,v
950 # net_list.append('{"name":"' + k + '", "uuid":"' + v + '"}')
951 # net_list_string = ', '.join(net_list)
952 virtio_net_list = []
953 for net in net_list:
954 if not net.get("net_id"):
955 continue
956 net_dict = {"uuid": net["net_id"]}
957 if net.get("type"):
958 if net["type"] == "SR-IOV":
959 net_dict["type"] = "VF"
960 elif net["type"] == "PCI-PASSTHROUGH":
961 net_dict["type"] = "PF"
962 else:
963 net_dict["type"] = net["type"]
964 if net.get("name"):
965 net_dict["name"] = net["name"]
966 if net.get("vpci"):
967 net_dict["vpci"] = net["vpci"]
968 if net.get("model"):
969 if net["model"] == "VIRTIO" or net["model"] == "paravirt":
970 net_dict["model"] = "virtio"
971 else:
972 net_dict["model"] = net["model"]
973 if net.get("mac_address"):
974 net_dict["mac_address"] = net["mac_address"]
975 if net.get("ip_address"):
976 net_dict["ip_address"] = net["ip_address"]
977 virtio_net_list.append(net_dict)
978 payload_dict = {
979 "name": name[:64],
980 "description": description,
981 "imageRef": image_id,
982 "flavorRef": flavor_id,
983 "networks": virtio_net_list,
984 }
985 if start is not None:
986 payload_dict["start"] = start
987 payload_req = json.dumps({"server": payload_dict})
988 url = self.url + "/" + self.tenant + "/servers"
989 self.logger.info("Adding a new vm POST %s DATA %s", url, payload_req)
990 vim_response = requests.post(
991 url, headers=self.headers_req, data=payload_req
992 )
993 self._check_http_request_response(vim_response)
994 self.logger.debug(vim_response.text)
995 # print json.dumps(vim_response.json(), indent=4)
996 response = vim_response.json()
997 js_v(response, new_vminstance_response_schema)
998 # r = self._remove_extra_items(response, new_vminstance_response_schema)
999 # if r is not None:
1000 # self.logger.warn("Warning: remove extra items %s", str(r))
1001 vminstance_id = response["server"]["id"]
1002
1003 # connect data plane interfaces to network
1004 for net in net_list:
1005 if net["type"] == "virtual":
1006 if not net.get("net_id"):
1007 continue
1008 for iface in response["server"]["networks"]:
1009 if "name" in net:
1010 if net["name"] == iface["name"]:
1011 net["vim_id"] = iface["iface_id"]
1012 break
1013 elif "net_id" in net:
1014 if net["net_id"] == iface["net_id"]:
1015 net["vim_id"] = iface["iface_id"]
1016 break
1017 else: # dataplane
1018 for numa in response["server"].get("extended", {}).get("numas", ()):
1019 for iface in numa.get("interfaces", ()):
1020 if net["name"] == iface["name"]:
1021 net["vim_id"] = iface["iface_id"]
1022 # Code bellow is not needed, current openvim connect dataplane interfaces
1023 # if net.get("net_id"):
1024 # connect dataplane interface
1025 # result, port_id = self.connect_port_network(iface['iface_id'], net["net_id"])
1026 # if result < 0:
1027 # error_text = "Error attaching port %s to network %s: %s." % (iface['iface_id']
1028 # , net["net_id"], port_id)
1029 # print "new_vminstance: " + error_text
1030 # self.delete_vminstance(vminstance_id)
1031 # return result, error_text
1032 break
1033
1034 return vminstance_id, None
1035 except (requests.exceptions.RequestException, js_e.ValidationError) as e:
1036 self._format_request_exception(e)
1037
1038 def get_vminstance(self, vm_id):
1039 """Returns the VM instance information from VIM"""
1040 try:
1041 self._get_my_tenant()
1042 url = self.url + "/" + self.tenant + "/servers/" + vm_id
1043 self.logger.info("Getting vm GET %s", url)
1044 vim_response = requests.get(url, headers=self.headers_req)
1045 vim_response = requests.get(url, headers=self.headers_req)
1046 self._check_http_request_response(vim_response)
1047 self.logger.debug(vim_response.text)
1048 # print json.dumps(vim_response.json(), indent=4)
1049 response = vim_response.json()
1050 js_v(response, new_vminstance_response_schema)
1051 # r = self._remove_extra_items(response, new_vminstance_response_schema)
1052 # if r is not None:
1053 # self.logger.warn("Warning: remove extra items %s", str(r))
1054 return response["server"]
1055 except (requests.exceptions.RequestException, js_e.ValidationError) as e:
1056 self._format_request_exception(e)
1057
1058 def delete_vminstance(self, vm_id, created_items=None, volumes_to_hold=None):
1059 """Removes a VM instance from VIM, returns the deleted vm_id"""
1060 try:
1061 self._get_my_tenant()
1062 url = self.url + "/" + self.tenant + "/servers/" + vm_id
1063 self.logger.info("Deleting VIM vm DELETE %s", url)
1064 vim_response = requests.delete(url, headers=self.headers_req)
1065 self._check_http_request_response(vim_response)
1066 # self.logger.debug(vim_response.text)
1067 # print json.dumps(vim_response.json(), indent=4)
1068 return vm_id
1069 except (requests.exceptions.RequestException, js_e.ValidationError) as e:
1070 self._format_request_exception(e)
1071
1072 def refresh_vms_status(self, vm_list):
1073 """Refreshes the status of the virtual machines"""
1074 try:
1075 self._get_my_tenant()
1076 except requests.exceptions.RequestException as e:
1077 self._format_request_exception(e)
1078 vm_dict = {}
1079 for vm_id in vm_list:
1080 vm = {}
1081 # print "VIMConnector refresh_tenant_vms and nets: Getting tenant VM instance information from VIM"
1082 try:
1083 url = self.url + "/" + self.tenant + "/servers/" + vm_id
1084 self.logger.info("Getting vm GET %s", url)
1085 vim_response = requests.get(url, headers=self.headers_req)
1086 self._check_http_request_response(vim_response)
1087 response = vim_response.json()
1088 js_v(response, new_vminstance_response_schema)
1089 if response["server"]["status"] in vmStatus2manoFormat:
1090 vm["status"] = vmStatus2manoFormat[response["server"]["status"]]
1091 else:
1092 vm["status"] = "OTHER"
1093 vm["error_msg"] = (
1094 "VIM status reported " + response["server"]["status"]
1095 )
1096 if response["server"].get("last_error"):
1097 vm["error_msg"] = response["server"]["last_error"]
1098 vm["vim_info"] = yaml.safe_dump(response["server"])
1099 # get interfaces info
1100 try:
1101 management_ip = False
1102 url2 = self.url + "/ports?device_id=" + quote(vm_id)
1103 self.logger.info("Getting PORTS GET %s", url2)
1104 vim_response2 = requests.get(url2, headers=self.headers_req)
1105 self._check_http_request_response(vim_response2)
1106 client_data = vim_response2.json()
1107 if isinstance(client_data.get("ports"), list):
1108 vm["interfaces"] = []
1109 for port in client_data.get("ports"):
1110 interface = {}
1111 interface["vim_info"] = yaml.safe_dump(port)
1112 interface["mac_address"] = port.get("mac_address")
1113 interface["vim_net_id"] = port.get("network_id")
1114 interface["vim_interface_id"] = port["id"]
1115 interface["ip_address"] = port.get("ip_address")
1116 if interface["ip_address"]:
1117 management_ip = True
1118 if interface["ip_address"] == "0.0.0.0":
1119 interface["ip_address"] = None
1120 vm["interfaces"].append(interface)
1121
1122 except Exception as e:
1123 self.logger.error(
1124 "refresh_vms_and_nets. Port get %s: %s",
1125 type(e).__name__,
1126 str(e),
1127 )
1128
1129 if vm["status"] == "ACTIVE" and not management_ip:
1130 vm["status"] = "ACTIVE:NoMgmtIP"
1131
1132 except vimconn.VimConnNotFoundException as e:
1133 self.logger.error("Exception getting vm status: %s", str(e))
1134 vm["status"] = "DELETED"
1135 vm["error_msg"] = str(e)
1136 except (
1137 requests.exceptions.RequestException,
1138 js_e.ValidationError,
1139 vimconn.VimConnException,
1140 ) as e:
1141 self.logger.error("Exception getting vm status: %s", str(e))
1142 vm["status"] = "VIM_ERROR"
1143 vm["error_msg"] = str(e)
1144 vm_dict[vm_id] = vm
1145 return vm_dict
1146
1147 def refresh_nets_status(self, net_list):
1148 """Get the status of the networks
1149 Params: the list of network identifiers
1150 Returns a dictionary with:
1151 net_id: #VIM id of this network
1152 status: #Mandatory. Text with one of:
1153 # DELETED (not found at vim)
1154 # VIM_ERROR (Cannot connect to VIM, VIM response error, ...)
1155 # OTHER (Vim reported other status not understood)
1156 # ERROR (VIM indicates an ERROR status)
1157 # ACTIVE, INACTIVE, DOWN (admin down),
1158 # BUILD (on building process)
1159 #
1160 error_msg: #Text with VIM error message, if any. Or the VIM connection ERROR
1161 vim_info: #Text with plain information obtained from vim (yaml.safe_dump)
1162
1163 """
1164 try:
1165 self._get_my_tenant()
1166 except requests.exceptions.RequestException as e:
1167 self._format_request_exception(e)
1168
1169 net_dict = {}
1170 for net_id in net_list:
1171 net = {}
1172 # print "VIMConnector refresh_tenant_vms_and_nets:
1173 # Getting tenant network from VIM (tenant: " + str(self.tenant) + "): "
1174 try:
1175 net_vim = self.get_network(net_id)
1176 if net_vim["status"] in netStatus2manoFormat:
1177 net["status"] = netStatus2manoFormat[net_vim["status"]]
1178 else:
1179 net["status"] = "OTHER"
1180 net["error_msg"] = "VIM status reported " + net_vim["status"]
1181
1182 if net["status"] == "ACTIVE" and not net_vim["admin_state_up"]:
1183 net["status"] = "DOWN"
1184 if net_vim.get("last_error"):
1185 net["error_msg"] = net_vim["last_error"]
1186 net["vim_info"] = yaml.safe_dump(net_vim)
1187 except vimconn.VimConnNotFoundException as e:
1188 self.logger.error("Exception getting net status: %s", str(e))
1189 net["status"] = "DELETED"
1190 net["error_msg"] = str(e)
1191 except (
1192 requests.exceptions.RequestException,
1193 js_e.ValidationError,
1194 vimconn.VimConnException,
1195 ) as e:
1196 self.logger.error("Exception getting net status: %s", str(e))
1197 net["status"] = "VIM_ERROR"
1198 net["error_msg"] = str(e)
1199 net_dict[net_id] = net
1200 return net_dict
1201
1202 def action_vminstance(self, vm_id, action_dict, created_items={}):
1203 """Send and action over a VM instance from VIM"""
1204 """Returns the status"""
1205 try:
1206 self._get_my_tenant()
1207 if "console" in action_dict:
1208 raise vimconn.VimConnException(
1209 "getting console is not available at openvim",
1210 http_code=vimconn.HTTP_Service_Unavailable,
1211 )
1212 url = self.url + "/" + self.tenant + "/servers/" + vm_id + "/action"
1213 self.logger.info("Action over VM instance POST %s", url)
1214 vim_response = requests.post(
1215 url, headers=self.headers_req, data=json.dumps(action_dict)
1216 )
1217 self._check_http_request_response(vim_response)
1218 return None
1219 except (requests.exceptions.RequestException, js_e.ValidationError) as e:
1220 self._format_request_exception(e)
1221
1222 def migrate_instance(self, vm_id, compute_host=None):
1223 """
1224 Migrate a vdu
1225 param:
1226 vm_id: ID of an instance
1227 compute_host: Host to migrate the vdu to
1228 """
1229 # TODO: Add support for migration
1230 raise vimconn.VimConnNotImplemented("Not implemented")
1231
1232 def resize_instance(self, vm_id, flavor_id=None):
1233 """
1234 resize a vdu
1235 param:
1236 vm_id: ID of an instance
1237 flavor_id: flavor_id to resize the vdu to
1238 """
1239 # TODO: Add support for resize
1240 raise vimconn.VimConnNotImplemented("Not implemented")