blob: a646036db4a70a59ef29463ae0d4db909bef1380 [file] [log] [blame]
jomacarpeea2a73e2018-02-27 13:48:22 +01001# -*- coding: utf-8 -*-
2
3##
tierno92021022018-09-12 16:29:23 +02004# Copyright 2017 Telefonica Digital Spain S.L.U.
jomacarpeea2a73e2018-02-27 13:48:22 +01005# This file is part of ETSI OSM
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
18# License for the specific language governing permissions and limitations
19# under the License.
20#
21# For those usages not covered by the Apache License, Version 2.0 please
22# contact with: patent-office@telefonica.com
23##
24
25"""
26vimconnector implements all the methods to interact with OpenNebula using the XML-RPC API.
27"""
28__author__ = "Jose Maria Carmona Perez,Juan Antonio Hernando Labajo, Emilio Abraham Garrido Garcia,Alberto Florez " \
tierno92021022018-09-12 16:29:23 +020029 "Pages, Andres Pozo Munoz, Santiago Perez Marin, Onlife Networks Telefonica I+D Product Innovation "
jomacarpeea2a73e2018-02-27 13:48:22 +010030__date__ = "$13-dec-2017 11:09:29$"
sousaedue493e9b2021-02-09 15:30:01 +010031
tierno72774862020-05-04 11:44:15 +000032from osm_ro_plugin import vimconn
sousaedue493e9b2021-02-09 15:30:01 +010033import logging
jomacarpeea2a73e2018-02-27 13:48:22 +010034import requests
tierno1ec592d2020-06-16 15:29:47 +000035# import logging
jomacarpeea2a73e2018-02-27 13:48:22 +010036import oca
tierno1ec592d2020-06-16 15:29:47 +000037# import untangle
jomacarpeea2a73e2018-02-27 13:48:22 +010038import math
39import random
jomacarpe20c13232019-06-12 21:20:51 +000040import pyone
jomacarpeea2a73e2018-02-27 13:48:22 +010041
tierno1ec592d2020-06-16 15:29:47 +000042
tierno72774862020-05-04 11:44:15 +000043class vimconnector(vimconn.VimConnector):
jomacarpeea2a73e2018-02-27 13:48:22 +010044 def __init__(self, uuid, name, tenant_id, tenant_name, url, url_admin=None, user=None, passwd=None,
45 log_level="DEBUG", config={}, persistent_info={}):
jomacarpe20c13232019-06-12 21:20:51 +000046
47 """Constructor of VIM
48 Params:
49 'uuid': id asigned to this VIM
50 'name': name assigned to this VIM, can be used for logging
51 'tenant_id', 'tenant_name': (only one of them is mandatory) VIM tenant to be used
52 'url_admin': (optional), url used for administrative tasks
53 'user', 'passwd': credentials of the VIM user
54 'log_level': provider if it should use a different log_level than the general one
55 'config': dictionary with extra VIM information. This contains a consolidate version of general VIM config
56 at creation and particular VIM config at teh attachment
57 'persistent_info': dict where the class can store information that will be available among class
58 destroy/creation cycles. This info is unique per VIM/credential. At first call it will contain an
59 empty dict. Useful to store login/tokens information for speed up communication
60
61 Returns: Raise an exception is some needed parameter is missing, but it must not do any connectivity
62 check against the VIM
63 """
64
tierno72774862020-05-04 11:44:15 +000065 vimconn.VimConnector.__init__(self, uuid, name, tenant_id, tenant_name, url, url_admin, user, passwd, log_level,
jomacarpeea2a73e2018-02-27 13:48:22 +010066 config)
jomacarpeea2a73e2018-02-27 13:48:22 +010067
sousaedue493e9b2021-02-09 15:30:01 +010068 self.logger = logging.getLogger('ro.vim.openstack')
69
jomacarpe20c13232019-06-12 21:20:51 +000070 def _new_one_connection(self):
71 return pyone.OneServer(self.url, session=self.user + ':' + self.passwd)
jomacarpeea2a73e2018-02-27 13:48:22 +010072
73 def new_tenant(self, tenant_name, tenant_description):
74 # '''Adds a new tenant to VIM with this name and description, returns the tenant identifier'''
75 try:
76 client = oca.Client(self.user + ':' + self.passwd, self.url)
77 group_list = oca.GroupPool(client)
78 user_list = oca.UserPool(client)
79 group_list.info()
80 user_list.info()
81 create_primarygroup = 1
82 # create group-tenant
83 for group in group_list:
84 if str(group.name) == str(tenant_name):
85 create_primarygroup = 0
86 break
87 if create_primarygroup == 1:
88 oca.Group.allocate(client, tenant_name)
89 group_list.info()
90 # set to primary_group the tenant_group and oneadmin to secondary_group
91 for group in group_list:
92 if str(group.name) == str(tenant_name):
93 for user in user_list:
94 if str(user.name) == str(self.user):
95 if user.name == "oneadmin":
96 return str(0)
97 else:
98 self._add_secondarygroup(user.id, group.id)
99 user.chgrp(group.id)
100 return str(group.id)
101 except Exception as e:
102 self.logger.error("Create new tenant error: " + str(e))
tierno72774862020-05-04 11:44:15 +0000103 raise vimconn.VimConnException(e)
jomacarpeea2a73e2018-02-27 13:48:22 +0100104
jomacarpeea2a73e2018-02-27 13:48:22 +0100105 def delete_tenant(self, tenant_id):
106 """Delete a tenant from VIM. Returns the old tenant identifier"""
107 try:
108 client = oca.Client(self.user + ':' + self.passwd, self.url)
109 group_list = oca.GroupPool(client)
110 user_list = oca.UserPool(client)
111 group_list.info()
112 user_list.info()
113 for group in group_list:
114 if str(group.id) == str(tenant_id):
115 for user in user_list:
116 if str(user.name) == str(self.user):
117 self._delete_secondarygroup(user.id, group.id)
118 group.delete(client)
119 return None
tierno72774862020-05-04 11:44:15 +0000120 raise vimconn.VimConnNotFoundException("Group {} not found".format(tenant_id))
jomacarpeea2a73e2018-02-27 13:48:22 +0100121 except Exception as e:
122 self.logger.error("Delete tenant " + str(tenant_id) + " error: " + str(e))
tierno72774862020-05-04 11:44:15 +0000123 raise vimconn.VimConnException(e)
jomacarpeea2a73e2018-02-27 13:48:22 +0100124
jomacarpe20c13232019-06-12 21:20:51 +0000125 def _add_secondarygroup(self, id_user, id_group):
126 # change secondary_group to primary_group
127 params = '<?xml version="1.0"?> \
128 <methodCall>\
129 <methodName>one.user.addgroup</methodName>\
130 <params>\
131 <param>\
132 <value><string>{}:{}</string></value>\
133 </param>\
134 <param>\
135 <value><int>{}</int></value>\
136 </param>\
137 <param>\
138 <value><int>{}</int></value>\
139 </param>\
140 </params>\
141 </methodCall>'.format(self.user, self.passwd, (str(id_user)), (str(id_group)))
142 requests.post(self.url, params)
143
jomacarpeea2a73e2018-02-27 13:48:22 +0100144 def _delete_secondarygroup(self, id_user, id_group):
145 params = '<?xml version="1.0"?> \
146 <methodCall>\
147 <methodName>one.user.delgroup</methodName>\
148 <params>\
149 <param>\
150 <value><string>{}:{}</string></value>\
151 </param>\
152 <param>\
153 <value><int>{}</int></value>\
154 </param>\
155 <param>\
156 <value><int>{}</int></value>\
157 </param>\
158 </params>\
159 </methodCall>'.format(self.user, self.passwd, (str(id_user)), (str(id_group)))
160 requests.post(self.url, params)
161
tierno1ec592d2020-06-16 15:29:47 +0000162 def new_network(self, net_name, net_type, ip_profile=None, shared=False, provider_network_profile=None):
garciadeblasebd66722019-01-31 16:01:31 +0000163 """Adds a tenant network to VIM
164 Params:
165 'net_name': name of the network
166 'net_type': one of:
167 'bridge': overlay isolated network
168 'data': underlay E-LAN network for Passthrough and SRIOV interfaces
169 'ptp': underlay E-LINE network for Passthrough and SRIOV interfaces.
170 'ip_profile': is a dict containing the IP parameters of the network
171 'ip_version': can be "IPv4" or "IPv6" (Currently only IPv4 is implemented)
172 'subnet_address': ip_prefix_schema, that is X.X.X.X/Y
173 'gateway_address': (Optional) ip_schema, that is X.X.X.X
174 'dns_address': (Optional) comma separated list of ip_schema, e.g. X.X.X.X[,X,X,X,X]
175 'dhcp_enabled': True or False
176 'dhcp_start_address': ip_schema, first IP to grant
177 'dhcp_count': number of IPs to grant.
178 'shared': if this network can be seen/use by other tenants/organization
kbsuba85c54d2019-10-17 16:30:32 +0000179 'provider_network_profile': (optional) contains {segmentation-id: vlan, provider-network: vim_netowrk}
garciadeblasebd66722019-01-31 16:01:31 +0000180 Returns a tuple with the network identifier and created_items, or raises an exception on error
181 created_items can be None or a dictionary where this method can include key-values that will be passed to
182 the method delete_network. Can be used to store created segments, created l2gw connections, etc.
183 Format is vimconnector dependent, but do not use nested dictionaries and a value of None should be the same
184 as not present.
185 """
186
jomacarpeea2a73e2018-02-27 13:48:22 +0100187 # oca library method cannot be used in this case (problem with cluster parameters)
188 try:
kbsuba85c54d2019-10-17 16:30:32 +0000189 vlan = None
190 if provider_network_profile:
191 vlan = provider_network_profile.get("segmentation-id")
albertoflorez3ade00b2019-07-08 13:16:21 +0200192 created_items = {}
jomacarpe20c13232019-06-12 21:20:51 +0000193 one = self._new_one_connection()
jomacarpe20c13232019-06-12 21:20:51 +0000194 size = "254"
jomacarpeea2a73e2018-02-27 13:48:22 +0100195 if ip_profile is None:
jomacarpe20c13232019-06-12 21:20:51 +0000196 subnet_rand = random.randint(0, 255)
197 ip_start = "192.168.{}.1".format(subnet_rand)
jomacarpeea2a73e2018-02-27 13:48:22 +0100198 else:
199 index = ip_profile["subnet_address"].find("/")
200 ip_start = ip_profile["subnet_address"][:index]
tierno7d782ef2019-10-04 12:56:31 +0000201 if "dhcp_count" in ip_profile and ip_profile["dhcp_count"] is not None:
jomacarpeea2a73e2018-02-27 13:48:22 +0100202 size = str(ip_profile["dhcp_count"])
tierno7d782ef2019-10-04 12:56:31 +0000203 elif "dhcp_count" not in ip_profile and ip_profile["ip_version"] == "IPv4":
jomacarpeea2a73e2018-02-27 13:48:22 +0100204 prefix = ip_profile["subnet_address"][index + 1:]
205 size = int(math.pow(2, 32 - prefix))
tierno7d782ef2019-10-04 12:56:31 +0000206 if "dhcp_start_address" in ip_profile and ip_profile["dhcp_start_address"] is not None:
jomacarpeea2a73e2018-02-27 13:48:22 +0100207 ip_start = str(ip_profile["dhcp_start_address"])
tierno1ec592d2020-06-16 15:29:47 +0000208 # if ip_profile["ip_version"] == "IPv6":
209 # ip_prefix_type = "GLOBAL_PREFIX"
jomacarpeea2a73e2018-02-27 13:48:22 +0100210
jomacarpe20c13232019-06-12 21:20:51 +0000211 if vlan is not None:
212 vlan_id = vlan
213 else:
214 vlan_id = str(random.randint(100, 4095))
tierno1ec592d2020-06-16 15:29:47 +0000215 # if "internal" in net_name:
albertoflorez3ade00b2019-07-08 13:16:21 +0200216 # OpenNebula not support two networks with same name
217 random_net_name = str(random.randint(1, 1000000))
218 net_name = net_name + random_net_name
jomacarpe20c13232019-06-12 21:20:51 +0000219 net_id = one.vn.allocate({
tierno1ec592d2020-06-16 15:29:47 +0000220 'NAME': net_name,
221 'VN_MAD': '802.1Q',
222 'PHYDEV': self.config["network"]["phydev"],
223 'VLAN_ID': vlan_id
224 }, self.config["cluster"]["id"])
225 arpool = {
226 'AR_POOL': {
227 'AR': {
228 'TYPE': 'IP4',
229 'IP': ip_start,
230 'SIZE': size
jomacarpe20c13232019-06-12 21:20:51 +0000231 }
tierno1ec592d2020-06-16 15:29:47 +0000232 }
albertoflorez3ade00b2019-07-08 13:16:21 +0200233 }
234 one.vn.add_ar(net_id, arpool)
235 return net_id, created_items
jomacarpeea2a73e2018-02-27 13:48:22 +0100236 except Exception as e:
237 self.logger.error("Create new network error: " + str(e))
tierno72774862020-05-04 11:44:15 +0000238 raise vimconn.VimConnException(e)
jomacarpeea2a73e2018-02-27 13:48:22 +0100239
240 def get_network_list(self, filter_dict={}):
241 """Obtain tenant networks of VIM
tierno1ec592d2020-06-16 15:29:47 +0000242 :params filter_dict: (optional) contains entries to return only networks that matches ALL entries:
243 name: string => returns only networks with this name
244 id: string => returns networks with this VIM id, this imply returns one network at most
245 shared: boolean >= returns only networks that are (or are not) shared
246 tenant_id: sting => returns only networks that belong to this tenant/project
247 (not used yet) admin_state_up: boolean => returns only networks that are (or are not) in admin state active
248 (not used yet) status: 'ACTIVE','ERROR',... => filter networks that are on this status
jomacarpe20c13232019-06-12 21:20:51 +0000249 Returns the network list of dictionaries. each dictionary contains:
250 'id': (mandatory) VIM network id
251 'name': (mandatory) VIM network name
252 'status': (mandatory) can be 'ACTIVE', 'INACTIVE', 'DOWN', 'BUILD', 'ERROR', 'VIM_ERROR', 'OTHER'
253 'network_type': (optional) can be 'vxlan', 'vlan' or 'flat'
254 'segmentation_id': (optional) in case network_type is vlan or vxlan this field contains the segmentation id
255 'error_msg': (optional) text that explains the ERROR status
256 other VIM specific fields: (optional) whenever possible using the same naming of filter_dict param
257 List can be empty if no network map the filter_dict. Raise an exception only upon VIM connectivity,
258 authorization, or some other unspecific error
jomacarpeea2a73e2018-02-27 13:48:22 +0100259 """
jomacarpe20c13232019-06-12 21:20:51 +0000260
jomacarpeea2a73e2018-02-27 13:48:22 +0100261 try:
jomacarpe20c13232019-06-12 21:20:51 +0000262 one = self._new_one_connection()
263 net_pool = one.vnpool.info(-2, -1, -1).VNET
jomacarpeea2a73e2018-02-27 13:48:22 +0100264 response = []
tierno7d782ef2019-10-04 12:56:31 +0000265 if "name" in filter_dict:
jomacarpeea2a73e2018-02-27 13:48:22 +0100266 network_name_filter = filter_dict["name"]
267 else:
268 network_name_filter = None
tierno7d782ef2019-10-04 12:56:31 +0000269 if "id" in filter_dict:
jomacarpeea2a73e2018-02-27 13:48:22 +0100270 network_id_filter = filter_dict["id"]
271 else:
272 network_id_filter = None
jomacarpe20c13232019-06-12 21:20:51 +0000273 for network in net_pool:
274 if network.NAME == network_name_filter or str(network.ID) == str(network_id_filter):
275 net_dict = {"name": network.NAME, "id": str(network.ID), "status": "ACTIVE"}
jomacarpeea2a73e2018-02-27 13:48:22 +0100276 response.append(net_dict)
277 return response
278 except Exception as e:
279 self.logger.error("Get network list error: " + str(e))
tierno72774862020-05-04 11:44:15 +0000280 raise vimconn.VimConnException(e)
jomacarpeea2a73e2018-02-27 13:48:22 +0100281
282 def get_network(self, net_id):
jomacarpe20c13232019-06-12 21:20:51 +0000283 """Obtain network details from the 'net_id' VIM network
284 Return a dict that contains:
285 'id': (mandatory) VIM network id, that is, net_id
286 'name': (mandatory) VIM network name
287 'status': (mandatory) can be 'ACTIVE', 'INACTIVE', 'DOWN', 'BUILD', 'ERROR', 'VIM_ERROR', 'OTHER'
288 'error_msg': (optional) text that explains the ERROR status
289 other VIM specific fields: (optional) whenever possible using the same naming of filter_dict param
290 Raises an exception upon error or when network is not found
291 """
jomacarpeea2a73e2018-02-27 13:48:22 +0100292 try:
jomacarpe20c13232019-06-12 21:20:51 +0000293 one = self._new_one_connection()
294 net_pool = one.vnpool.info(-2, -1, -1).VNET
jomacarpeea2a73e2018-02-27 13:48:22 +0100295 net = {}
jomacarpe20c13232019-06-12 21:20:51 +0000296 for network in net_pool:
297 if str(network.ID) == str(net_id):
298 net['id'] = network.ID
299 net['name'] = network.NAME
jomacarpeea2a73e2018-02-27 13:48:22 +0100300 net['status'] = "ACTIVE"
301 break
302 if net:
303 return net
304 else:
tierno72774862020-05-04 11:44:15 +0000305 raise vimconn.VimConnNotFoundException("Network {} not found".format(net_id))
jomacarpeea2a73e2018-02-27 13:48:22 +0100306 except Exception as e:
jomacarpe20c13232019-06-12 21:20:51 +0000307 self.logger.error("Get network " + str(net_id) + " error): " + str(e))
tierno72774862020-05-04 11:44:15 +0000308 raise vimconn.VimConnException(e)
jomacarpeea2a73e2018-02-27 13:48:22 +0100309
garciadeblasebd66722019-01-31 16:01:31 +0000310 def delete_network(self, net_id, created_items=None):
311 """
312 Removes a tenant network from VIM and its associated elements
313 :param net_id: VIM identifier of the network, provided by method new_network
314 :param created_items: dictionary with extra items to be deleted. provided by method new_network
315 Returns the network identifier or raises an exception upon error or when network is not found
jomacarpeea2a73e2018-02-27 13:48:22 +0100316 """
317 try:
jomacarpeea2a73e2018-02-27 13:48:22 +0100318
jomacarpe20c13232019-06-12 21:20:51 +0000319 one = self._new_one_connection()
320 one.vn.delete(int(net_id))
321 return net_id
jomacarpeea2a73e2018-02-27 13:48:22 +0100322 except Exception as e:
jomacarpe20c13232019-06-12 21:20:51 +0000323 self.logger.error("Delete network " + str(net_id) + "error: network not found" + str(e))
tierno72774862020-05-04 11:44:15 +0000324 raise vimconn.VimConnException(e)
jomacarpeea2a73e2018-02-27 13:48:22 +0100325
jomacarpeea2a73e2018-02-27 13:48:22 +0100326 def refresh_nets_status(self, net_list):
327 """Get the status of the networks
jomacarpe20c13232019-06-12 21:20:51 +0000328 Params:
329 'net_list': a list with the VIM network id to be get the status
330 Returns a dictionary with:
331 'net_id': #VIM id of this network
332 status: #Mandatory. Text with one of:
333 # DELETED (not found at vim)
334 # VIM_ERROR (Cannot connect to VIM, authentication problems, VIM response error, ...)
335 # OTHER (Vim reported other status not understood)
336 # ERROR (VIM indicates an ERROR status)
337 # ACTIVE, INACTIVE, DOWN (admin down),
338 # BUILD (on building process)
339 error_msg: #Text with VIM error message, if any. Or the VIM connection ERROR
340 vim_info: #Text with plain information obtained from vim (yaml.safe_dump)
341 'net_id2': ...
jomacarpeea2a73e2018-02-27 13:48:22 +0100342 """
343 net_dict = {}
344 try:
345 for net_id in net_list:
346 net = {}
347 try:
348 net_vim = self.get_network(net_id)
349 net["status"] = net_vim["status"]
350 net["vim_info"] = None
tierno72774862020-05-04 11:44:15 +0000351 except vimconn.VimConnNotFoundException as e:
jomacarpeea2a73e2018-02-27 13:48:22 +0100352 self.logger.error("Exception getting net status: {}".format(str(e)))
353 net['status'] = "DELETED"
354 net['error_msg'] = str(e)
tierno72774862020-05-04 11:44:15 +0000355 except vimconn.VimConnException as e:
jomacarpeea2a73e2018-02-27 13:48:22 +0100356 self.logger.error(e)
357 net["status"] = "VIM_ERROR"
358 net["error_msg"] = str(e)
359 net_dict[net_id] = net
360 return net_dict
tierno72774862020-05-04 11:44:15 +0000361 except vimconn.VimConnException as e:
jomacarpeea2a73e2018-02-27 13:48:22 +0100362 self.logger.error(e)
363 for k in net_dict:
364 net_dict[k]["status"] = "VIM_ERROR"
365 net_dict[k]["error_msg"] = str(e)
366 return net_dict
367
jomacarpe20c13232019-06-12 21:20:51 +0000368 def get_flavor(self, flavor_id): # Esta correcto
369 """Obtain flavor details from the VIM
370 Returns the flavor dict details {'id':<>, 'name':<>, other vim specific }
371 Raises an exception upon error or if not found
372 """
373 try:
374
375 one = self._new_one_connection()
376 template = one.template.info(int(flavor_id))
377 if template is not None:
378 return {'id': template.ID, 'name': template.NAME}
tierno72774862020-05-04 11:44:15 +0000379 raise vimconn.VimConnNotFoundException("Flavor {} not found".format(flavor_id))
jomacarpe20c13232019-06-12 21:20:51 +0000380 except Exception as e:
381 self.logger.error("get flavor " + str(flavor_id) + " error: " + str(e))
tierno72774862020-05-04 11:44:15 +0000382 raise vimconn.VimConnException(e)
jomacarpe20c13232019-06-12 21:20:51 +0000383
384 def new_flavor(self, flavor_data):
385 """Adds a tenant flavor to VIM
386 flavor_data contains a dictionary with information, keys:
387 name: flavor name
388 ram: memory (cloud type) in MBytes
389 vpcus: cpus (cloud type)
390 extended: EPA parameters
391 - numas: #items requested in same NUMA
tierno1ec592d2020-06-16 15:29:47 +0000392 memory: number of 1G huge pages memory
393 paired-threads|cores|threads: number of paired hyperthreads, complete cores OR individual threads
394 interfaces: # passthrough(PT) or SRIOV interfaces attached to this numa
jomacarpe20c13232019-06-12 21:20:51 +0000395 - name: interface name
396 dedicated: yes|no|yes:sriov; for PT, SRIOV or only one SRIOV for the physical NIC
397 bandwidth: X Gbps; requested guarantee bandwidth
398 vpci: requested virtual PCI address
399 disk: disk size
400 is_public:
401 #TODO to concrete
402 Returns the flavor identifier"""
403
404 disk_size = str(int(flavor_data["disk"])*1024)
405
406 try:
407 one = self._new_one_connection()
408 template_id = one.template.allocate({
409 'TEMPLATE': {
410 'NAME': flavor_data["name"],
411 'CPU': flavor_data["vcpus"],
412 'VCPU': flavor_data["vcpus"],
413 'MEMORY': flavor_data["ram"],
414 'DISK': {
415 'SIZE': disk_size
416 },
417 'CONTEXT': {
418 'NETWORK': "YES",
419 'SSH_PUBLIC_KEY': '$USER[SSH_PUBLIC_KEY]'
420 },
421 'GRAPHICS': {
422 'LISTEN': '0.0.0.0',
423 'TYPE': 'VNC'
424 },
425 'CLUSTER_ID': self.config["cluster"]["id"]
426 }
427 })
428 return template_id
429
430 except Exception as e:
431 self.logger.error("Create new flavor error: " + str(e))
tierno72774862020-05-04 11:44:15 +0000432 raise vimconn.VimConnException(e)
jomacarpe20c13232019-06-12 21:20:51 +0000433
434 def delete_flavor(self, flavor_id):
435 """ Deletes a tenant flavor from VIM
436 Returns the old flavor_id
437 """
438 try:
439 one = self._new_one_connection()
440 one.template.delete(int(flavor_id), False)
441 return flavor_id
442 except Exception as e:
443 self.logger.error("Error deleting flavor " + str(flavor_id) + ". Flavor not found")
tierno72774862020-05-04 11:44:15 +0000444 raise vimconn.VimConnException(e)
jomacarpe20c13232019-06-12 21:20:51 +0000445
446 def get_image_list(self, filter_dict={}):
447 """Obtain tenant images from VIM
448 Filter_dict can be:
449 name: image name
450 id: image uuid
451 checksum: image checksum
452 location: image path
453 Returns the image list of dictionaries:
454 [{<the fields at Filter_dict plus some VIM specific>}, ...]
455 List can be empty
456 """
457 try:
458 one = self._new_one_connection()
459 image_pool = one.imagepool.info(-2, -1, -1).IMAGE
460 images = []
tierno7d782ef2019-10-04 12:56:31 +0000461 if "name" in filter_dict:
jomacarpe20c13232019-06-12 21:20:51 +0000462 image_name_filter = filter_dict["name"]
463 else:
464 image_name_filter = None
tierno7d782ef2019-10-04 12:56:31 +0000465 if "id" in filter_dict:
jomacarpe20c13232019-06-12 21:20:51 +0000466 image_id_filter = filter_dict["id"]
467 else:
468 image_id_filter = None
469 for image in image_pool:
470 if str(image_name_filter) == str(image.NAME) or str(image.ID) == str(image_id_filter):
471 images_dict = {"name": image.NAME, "id": str(image.ID)}
472 images.append(images_dict)
473 return images
474 except Exception as e:
475 self.logger.error("Get image list error: " + str(e))
tierno72774862020-05-04 11:44:15 +0000476 raise vimconn.VimConnException(e)
jomacarpe20c13232019-06-12 21:20:51 +0000477
478 def new_vminstance(self, name, description, start, image_id, flavor_id, net_list, cloud_config=None, disk_list=None,
479 availability_zone_index=None, availability_zone_list=None):
tierno1ec592d2020-06-16 15:29:47 +0000480 """
481 Adds a VM instance to VIM
482 :param name:
483 :param description:
484 :param start: (boolean) indicates if VM must start or created in pause mode.
485 :param image_id: image VIM id to use for the VM
486 :param flavor_id: flavor VIM id to use for the VM
487 :param net_list: list of interfaces, each one is a dictionary with:
488 'name': (optional) name for the interface.
489 'net_id': VIM network id where this interface must be connect to. Mandatory for type==virtual
490 'vpci': (optional) virtual vPCI address to assign at the VM. Can be ignored depending on VIM
491 capabilities
492 'model': (optional and only have sense for type==virtual) interface model: virtio, e1000, ...
493 'mac_address': (optional) mac address to assign to this interface
494 'ip_address': (optional) IP address to assign to this interface
495 #TODO: CHECK if an optional 'vlan' parameter is needed for VIMs when type if VF and net_id is not
496 provided, the VLAN tag to be used. In case net_id is provided, the internal network vlan is
497 used for tagging VF
498 'type': (mandatory) can be one of:
499 'virtual', in this case always connected to a network of type 'net_type=bridge'
500 'PCI-PASSTHROUGH' or 'PF' (passthrough): depending on VIM capabilities it can be connected to
501 a data/ptp network ot itcan created unconnected
502 'SR-IOV' or 'VF' (SRIOV with VLAN tag): same as PF for network connectivity.
503 'VFnotShared'(SRIOV without VLAN tag) same as PF for network connectivity. VF where no other VFs
504 are allocated on the same physical NIC
505 'bw': (optional) only for PF/VF/VFnotShared. Minimal Bandwidth required for the interface in GBPS
506 'port_security': (optional) If False it must avoid any traffic filtering at this interface. If missing
507 or True, it must apply the default VIM behaviour
508 After execution the method will add the key:
509 'vim_id': must be filled/added by this method with the VIM identifier generated by the VIM for this
510 interface. 'net_list' is modified
511 :param cloud_config: (optional) dictionary with:
512 'key-pairs': (optional) list of strings with the public key to be inserted to the default user
513 'users': (optional) list of users to be inserted, each item is a dict with:
514 'name': (mandatory) user name,
515 'key-pairs': (optional) list of strings with the public key to be inserted to the user
516 'user-data': (optional) can be a string with the text script to be passed directly to cloud-init,
517 or a list of strings, each one contains a script to be passed, usually with a MIMEmultipart file
518 'config-files': (optional). List of files to be transferred. Each item is a dict with:
519 'dest': (mandatory) string with the destination absolute path
520 'encoding': (optional, by default text). Can be one of:
521 'b64', 'base64', 'gz', 'gz+b64', 'gz+base64', 'gzip+b64', 'gzip+base64'
522 'content' (mandatory): string with the content of the file
523 'permissions': (optional) string with file permissions, typically octal notation '0644'
524 'owner': (optional) file owner, string with the format 'owner:group'
525 'boot-data-drive': boolean to indicate if user-data must be passed using a boot drive (hard disk)
526 :param disk_list: (optional) list with additional disks to the VM. Each item is a dict with:
527 'image_id': (optional). VIM id of an existing image. If not provided an empty disk must be mounted
528 'size': (mandatory) string with the size of the disk in GB
529 :param availability_zone_index: Index of availability_zone_list to use for this this VM. None if not AV
530 required
531 :param availability_zone_list: list of availability zones given by user in the VNFD descriptor. Ignore if
jomacarpe20c13232019-06-12 21:20:51 +0000532 availability_zone_index is None
tierno1ec592d2020-06-16 15:29:47 +0000533 :return: a tuple with the instance identifier and created_items or raises an exception on error
534 created_items can be None or a dictionary where this method can include key-values that will be passed to
535 the method delete_vminstance and action_vminstance. Can be used to store created ports, volumes, etc.
536 Format is vimconnector dependent, but do not use nested dictionaries and a value of None should be the same
537 as not present.
538 """
jomacarpe20c13232019-06-12 21:20:51 +0000539 self.logger.debug(
540 "new_vminstance input: image='{}' flavor='{}' nics='{}'".format(image_id, flavor_id, str(net_list)))
541 try:
542 one = self._new_one_connection()
543 template_vim = one.template.info(int(flavor_id), True)
544 disk_size = str(template_vim.TEMPLATE["DISK"]["SIZE"])
545
546 one = self._new_one_connection()
547 template_updated = ""
548 for net in net_list:
549 net_in_vim = one.vn.info(int(net["net_id"]))
550 net["vim_id"] = str(net_in_vim.ID)
551 network = 'NIC = [NETWORK = "{}",NETWORK_UNAME = "{}" ]'.format(
552 net_in_vim.NAME, net_in_vim.UNAME)
553 template_updated += network
554
555 template_updated += "DISK = [ IMAGE_ID = {},\n SIZE = {}]".format(image_id, disk_size)
556
557 if isinstance(cloud_config, dict):
558 if cloud_config.get("key-pairs"):
559 context = 'CONTEXT = [\n NETWORK = "YES",\n SSH_PUBLIC_KEY = "'
560 for key in cloud_config["key-pairs"]:
561 context += key + '\n'
562 # if False:
563 # context += '"\n USERNAME = '
564 context += '"]'
565 template_updated += context
566
567 vm_instance_id = one.template.instantiate(int(flavor_id), name, False, template_updated)
568 self.logger.info(
569 "Instanciating in OpenNebula a new VM name:{} id:{}".format(name, flavor_id))
570 return str(vm_instance_id), None
571 except pyone.OneNoExistsException as e:
572 self.logger.error("Network with id " + str(e) + " not found: " + str(e))
tierno72774862020-05-04 11:44:15 +0000573 raise vimconn.VimConnNotFoundException(e)
jomacarpe20c13232019-06-12 21:20:51 +0000574 except Exception as e:
575 self.logger.error("Create new vm instance error: " + str(e))
tierno72774862020-05-04 11:44:15 +0000576 raise vimconn.VimConnException(e)
jomacarpe20c13232019-06-12 21:20:51 +0000577
578 def get_vminstance(self, vm_id):
579 """Returns the VM instance information from VIM"""
580 try:
581 one = self._new_one_connection()
582 vm = one.vm.info(int(vm_id))
583 return vm
584 except Exception as e:
585 self.logger.error("Getting vm instance error: " + str(e) + ": VM Instance not found")
tierno72774862020-05-04 11:44:15 +0000586 raise vimconn.VimConnException(e)
jomacarpe20c13232019-06-12 21:20:51 +0000587
588 def delete_vminstance(self, vm_id, created_items=None):
589 """
590 Removes a VM instance from VIM and its associated elements
591 :param vm_id: VIM identifier of the VM, provided by method new_vminstance
592 :param created_items: dictionary with extra items to be deleted. provided by method new_vminstance and/or method
593 action_vminstance
594 :return: None or the same vm_id. Raises an exception on fail
595 """
596 try:
597 one = self._new_one_connection()
598 one.vm.recover(int(vm_id), 3)
599 vm = None
600 while True:
601 if vm is not None and vm.LCM_STATE == 0:
602 break
603 else:
604 vm = one.vm.info(int(vm_id))
605
tierno1ec592d2020-06-16 15:29:47 +0000606 except pyone.OneNoExistsException:
jomacarpe20c13232019-06-12 21:20:51 +0000607 self.logger.info("The vm " + str(vm_id) + " does not exist or is already deleted")
tierno72774862020-05-04 11:44:15 +0000608 raise vimconn.VimConnNotFoundException("The vm {} does not exist or is already deleted".format(vm_id))
jomacarpe20c13232019-06-12 21:20:51 +0000609 except Exception as e:
610 self.logger.error("Delete vm instance " + str(vm_id) + " error: " + str(e))
tierno72774862020-05-04 11:44:15 +0000611 raise vimconn.VimConnException(e)
jomacarpe20c13232019-06-12 21:20:51 +0000612
613 def refresh_vms_status(self, vm_list):
614 """Get the status of the virtual machines and their interfaces/ports
615 Params: the list of VM identifiers
616 Returns a dictionary with:
617 vm_id: #VIM id of this Virtual Machine
618 status: #Mandatory. Text with one of:
619 # DELETED (not found at vim)
620 # VIM_ERROR (Cannot connect to VIM, VIM response error, ...)
621 # OTHER (Vim reported other status not understood)
622 # ERROR (VIM indicates an ERROR status)
623 # ACTIVE, PAUSED, SUSPENDED, INACTIVE (not running),
624 # BUILD (on building process), ERROR
625 # ACTIVE:NoMgmtIP (Active but any of its interface has an IP address
626 #
627 error_msg: #Text with VIM error message, if any. Or the VIM connection ERROR
628 vim_info: #Text with plain information obtained from vim (yaml.safe_dump)
629 interfaces: list with interface info. Each item a dictionary with:
630 vim_info: #Text with plain information obtained from vim (yaml.safe_dump)
631 mac_address: #Text format XX:XX:XX:XX:XX:XX
632 vim_net_id: #network id where this interface is connected, if provided at creation
633 vim_interface_id: #interface/port VIM id
634 ip_address: #null, or text with IPv4, IPv6 address
635 compute_node: #identification of compute node where PF,VF interface is allocated
636 pci: #PCI address of the NIC that hosts the PF,VF
637 vlan: #physical VLAN used for VF
638 """
639 vm_dict = {}
640 try:
641 for vm_id in vm_list:
642 vm = {}
643 if self.get_vminstance(vm_id) is not None:
644 vm_element = self.get_vminstance(vm_id)
645 else:
646 self.logger.info("The vm " + str(vm_id) + " does not exist.")
647 vm['status'] = "DELETED"
648 vm['error_msg'] = ("The vm " + str(vm_id) + " does not exist.")
649 continue
650 vm["vim_info"] = None
651 vm_status = vm_element.LCM_STATE
652 if vm_status == 3:
653 vm['status'] = "ACTIVE"
654 elif vm_status == 36:
655 vm['status'] = "ERROR"
656 vm['error_msg'] = "VM failure"
657 else:
658 vm['status'] = "BUILD"
659
660 if vm_element is not None:
661 interfaces = self._get_networks_vm(vm_element)
662 vm["interfaces"] = interfaces
663 vm_dict[vm_id] = vm
664 return vm_dict
665 except Exception as e:
666 self.logger.error(e)
667 for k in vm_dict:
668 vm_dict[k]["status"] = "VIM_ERROR"
669 vm_dict[k]["error_msg"] = str(e)
670 return vm_dict
671
672 def _get_networks_vm(self, vm_element):
673 interfaces = []
674 try:
675 if isinstance(vm_element.TEMPLATE["NIC"], list):
676 for net in vm_element.TEMPLATE["NIC"]:
677 interface = {'vim_info': None, "mac_address": str(net["MAC"]), "vim_net_id": str(net["NETWORK_ID"]),
678 "vim_interface_id": str(net["NETWORK_ID"])}
679 # maybe it should be 2 different keys for ip_address if an interface has ipv4 and ipv6
tierno1ec592d2020-06-16 15:29:47 +0000680 if 'IP' in net:
jomacarpe20c13232019-06-12 21:20:51 +0000681 interface["ip_address"] = str(net["IP"])
tierno1ec592d2020-06-16 15:29:47 +0000682 if 'IP6_GLOBAL' in net:
jomacarpe20c13232019-06-12 21:20:51 +0000683 interface["ip_address"] = str(net["IP6_GLOBAL"])
684 interfaces.append(interface)
685 else:
686 net = vm_element.TEMPLATE["NIC"]
687 interface = {'vim_info': None, "mac_address": str(net["MAC"]), "vim_net_id": str(net["NETWORK_ID"]),
688 "vim_interface_id": str(net["NETWORK_ID"])}
689 # maybe it should be 2 different keys for ip_address if an interface has ipv4 and ipv6
tierno1ec592d2020-06-16 15:29:47 +0000690 if 'IP' in net:
jomacarpe20c13232019-06-12 21:20:51 +0000691 interface["ip_address"] = str(net["IP"])
tierno1ec592d2020-06-16 15:29:47 +0000692 if 'IP6_GLOBAL' in net:
jomacarpe20c13232019-06-12 21:20:51 +0000693 interface["ip_address"] = str(net["IP6_GLOBAL"])
694 interfaces.append(interface)
695 return interfaces
tierno1ec592d2020-06-16 15:29:47 +0000696 except Exception:
albertoflorez3ade00b2019-07-08 13:16:21 +0200697 self.logger.error("Error getting vm interface_information of vm_id: " + str(vm_element.ID))