blob: ad9ca29808a7f35864c0c680c1643d02bba66dae [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"""
sousaedu80135b92021-02-17 15:05:18 +010028__author__ = (
29 "Jose Maria Carmona Perez,Juan Antonio Hernando Labajo, Emilio Abraham Garrido Garcia,Alberto Florez "
30 "Pages, Andres Pozo Munoz, Santiago Perez Marin, Onlife Networks Telefonica I+D Product Innovation "
31)
jomacarpeea2a73e2018-02-27 13:48:22 +010032__date__ = "$13-dec-2017 11:09:29$"
sousaedue493e9b2021-02-09 15:30:01 +010033
tierno72774862020-05-04 11:44:15 +000034from osm_ro_plugin import vimconn
sousaedue493e9b2021-02-09 15:30:01 +010035import logging
jomacarpeea2a73e2018-02-27 13:48:22 +010036import requests
sousaedu80135b92021-02-17 15:05:18 +010037
tierno1ec592d2020-06-16 15:29:47 +000038# import logging
jomacarpeea2a73e2018-02-27 13:48:22 +010039import oca
sousaedu80135b92021-02-17 15:05:18 +010040
tierno1ec592d2020-06-16 15:29:47 +000041# import untangle
jomacarpeea2a73e2018-02-27 13:48:22 +010042import math
43import random
jomacarpe20c13232019-06-12 21:20:51 +000044import pyone
jomacarpeea2a73e2018-02-27 13:48:22 +010045
tierno1ec592d2020-06-16 15:29:47 +000046
tierno72774862020-05-04 11:44:15 +000047class vimconnector(vimconn.VimConnector):
sousaedu80135b92021-02-17 15:05:18 +010048 def __init__(
49 self,
50 uuid,
51 name,
52 tenant_id,
53 tenant_name,
54 url,
55 url_admin=None,
56 user=None,
57 passwd=None,
58 log_level="DEBUG",
59 config={},
60 persistent_info={},
61 ):
jomacarpe20c13232019-06-12 21:20:51 +000062 """Constructor of VIM
63 Params:
64 'uuid': id asigned to this VIM
65 'name': name assigned to this VIM, can be used for logging
66 'tenant_id', 'tenant_name': (only one of them is mandatory) VIM tenant to be used
67 'url_admin': (optional), url used for administrative tasks
68 'user', 'passwd': credentials of the VIM user
69 'log_level': provider if it should use a different log_level than the general one
70 'config': dictionary with extra VIM information. This contains a consolidate version of general VIM config
71 at creation and particular VIM config at teh attachment
72 'persistent_info': dict where the class can store information that will be available among class
73 destroy/creation cycles. This info is unique per VIM/credential. At first call it will contain an
74 empty dict. Useful to store login/tokens information for speed up communication
75
76 Returns: Raise an exception is some needed parameter is missing, but it must not do any connectivity
77 check against the VIM
78 """
sousaedu80135b92021-02-17 15:05:18 +010079 vimconn.VimConnector.__init__(
80 self,
81 uuid,
82 name,
83 tenant_id,
84 tenant_name,
85 url,
86 url_admin,
87 user,
88 passwd,
89 log_level,
90 config,
91 )
jomacarpe20c13232019-06-12 21:20:51 +000092
sousaedu80135b92021-02-17 15:05:18 +010093 self.logger = logging.getLogger("ro.vim.openstack")
sousaedue493e9b2021-02-09 15:30:01 +010094
jomacarpe20c13232019-06-12 21:20:51 +000095 def _new_one_connection(self):
sousaedu80135b92021-02-17 15:05:18 +010096 return pyone.OneServer(self.url, session=self.user + ":" + self.passwd)
jomacarpeea2a73e2018-02-27 13:48:22 +010097
98 def new_tenant(self, tenant_name, tenant_description):
sousaedu80135b92021-02-17 15:05:18 +010099 # """Adds a new tenant to VIM with this name and description, returns the tenant identifier"""
jomacarpeea2a73e2018-02-27 13:48:22 +0100100 try:
sousaedu80135b92021-02-17 15:05:18 +0100101 client = oca.Client(self.user + ":" + self.passwd, self.url)
jomacarpeea2a73e2018-02-27 13:48:22 +0100102 group_list = oca.GroupPool(client)
103 user_list = oca.UserPool(client)
104 group_list.info()
105 user_list.info()
106 create_primarygroup = 1
sousaedu80135b92021-02-17 15:05:18 +0100107
jomacarpeea2a73e2018-02-27 13:48:22 +0100108 # create group-tenant
109 for group in group_list:
110 if str(group.name) == str(tenant_name):
111 create_primarygroup = 0
112 break
sousaedu80135b92021-02-17 15:05:18 +0100113
jomacarpeea2a73e2018-02-27 13:48:22 +0100114 if create_primarygroup == 1:
115 oca.Group.allocate(client, tenant_name)
sousaedu80135b92021-02-17 15:05:18 +0100116
jomacarpeea2a73e2018-02-27 13:48:22 +0100117 group_list.info()
sousaedu80135b92021-02-17 15:05:18 +0100118
jomacarpeea2a73e2018-02-27 13:48:22 +0100119 # set to primary_group the tenant_group and oneadmin to secondary_group
120 for group in group_list:
121 if str(group.name) == str(tenant_name):
122 for user in user_list:
123 if str(user.name) == str(self.user):
124 if user.name == "oneadmin":
125 return str(0)
126 else:
127 self._add_secondarygroup(user.id, group.id)
128 user.chgrp(group.id)
sousaedu80135b92021-02-17 15:05:18 +0100129
jomacarpeea2a73e2018-02-27 13:48:22 +0100130 return str(group.id)
131 except Exception as e:
132 self.logger.error("Create new tenant error: " + str(e))
sousaedu80135b92021-02-17 15:05:18 +0100133
tierno72774862020-05-04 11:44:15 +0000134 raise vimconn.VimConnException(e)
jomacarpeea2a73e2018-02-27 13:48:22 +0100135
jomacarpeea2a73e2018-02-27 13:48:22 +0100136 def delete_tenant(self, tenant_id):
137 """Delete a tenant from VIM. Returns the old tenant identifier"""
138 try:
sousaedu80135b92021-02-17 15:05:18 +0100139 client = oca.Client(self.user + ":" + self.passwd, self.url)
jomacarpeea2a73e2018-02-27 13:48:22 +0100140 group_list = oca.GroupPool(client)
141 user_list = oca.UserPool(client)
142 group_list.info()
143 user_list.info()
sousaedu80135b92021-02-17 15:05:18 +0100144
jomacarpeea2a73e2018-02-27 13:48:22 +0100145 for group in group_list:
146 if str(group.id) == str(tenant_id):
147 for user in user_list:
148 if str(user.name) == str(self.user):
149 self._delete_secondarygroup(user.id, group.id)
150 group.delete(client)
sousaedu80135b92021-02-17 15:05:18 +0100151
jomacarpeea2a73e2018-02-27 13:48:22 +0100152 return None
sousaedu80135b92021-02-17 15:05:18 +0100153
154 raise vimconn.VimConnNotFoundException(
155 "Group {} not found".format(tenant_id)
156 )
jomacarpeea2a73e2018-02-27 13:48:22 +0100157 except Exception as e:
158 self.logger.error("Delete tenant " + str(tenant_id) + " error: " + str(e))
tierno72774862020-05-04 11:44:15 +0000159 raise vimconn.VimConnException(e)
jomacarpeea2a73e2018-02-27 13:48:22 +0100160
jomacarpe20c13232019-06-12 21:20:51 +0000161 def _add_secondarygroup(self, id_user, id_group):
162 # change secondary_group to primary_group
163 params = '<?xml version="1.0"?> \
164 <methodCall>\
165 <methodName>one.user.addgroup</methodName>\
166 <params>\
167 <param>\
168 <value><string>{}:{}</string></value>\
169 </param>\
170 <param>\
171 <value><int>{}</int></value>\
172 </param>\
173 <param>\
174 <value><int>{}</int></value>\
175 </param>\
176 </params>\
sousaedu80135b92021-02-17 15:05:18 +0100177 </methodCall>'.format(
178 self.user, self.passwd, (str(id_user)), (str(id_group))
179 )
jomacarpe20c13232019-06-12 21:20:51 +0000180 requests.post(self.url, params)
181
jomacarpeea2a73e2018-02-27 13:48:22 +0100182 def _delete_secondarygroup(self, id_user, id_group):
183 params = '<?xml version="1.0"?> \
184 <methodCall>\
185 <methodName>one.user.delgroup</methodName>\
186 <params>\
187 <param>\
188 <value><string>{}:{}</string></value>\
189 </param>\
190 <param>\
191 <value><int>{}</int></value>\
192 </param>\
193 <param>\
194 <value><int>{}</int></value>\
195 </param>\
196 </params>\
sousaedu80135b92021-02-17 15:05:18 +0100197 </methodCall>'.format(
198 self.user, self.passwd, (str(id_user)), (str(id_group))
199 )
jomacarpeea2a73e2018-02-27 13:48:22 +0100200 requests.post(self.url, params)
201
sousaedu80135b92021-02-17 15:05:18 +0100202 def new_network(
203 self,
204 net_name,
205 net_type,
206 ip_profile=None,
207 shared=False,
208 provider_network_profile=None,
209 ):
garciadeblasebd66722019-01-31 16:01:31 +0000210 """Adds a tenant network to VIM
211 Params:
212 'net_name': name of the network
213 'net_type': one of:
214 'bridge': overlay isolated network
215 'data': underlay E-LAN network for Passthrough and SRIOV interfaces
216 'ptp': underlay E-LINE network for Passthrough and SRIOV interfaces.
217 'ip_profile': is a dict containing the IP parameters of the network
218 'ip_version': can be "IPv4" or "IPv6" (Currently only IPv4 is implemented)
219 'subnet_address': ip_prefix_schema, that is X.X.X.X/Y
220 'gateway_address': (Optional) ip_schema, that is X.X.X.X
221 'dns_address': (Optional) comma separated list of ip_schema, e.g. X.X.X.X[,X,X,X,X]
222 'dhcp_enabled': True or False
223 'dhcp_start_address': ip_schema, first IP to grant
224 'dhcp_count': number of IPs to grant.
225 'shared': if this network can be seen/use by other tenants/organization
kbsuba85c54d2019-10-17 16:30:32 +0000226 'provider_network_profile': (optional) contains {segmentation-id: vlan, provider-network: vim_netowrk}
garciadeblasebd66722019-01-31 16:01:31 +0000227 Returns a tuple with the network identifier and created_items, or raises an exception on error
228 created_items can be None or a dictionary where this method can include key-values that will be passed to
229 the method delete_network. Can be used to store created segments, created l2gw connections, etc.
230 Format is vimconnector dependent, but do not use nested dictionaries and a value of None should be the same
231 as not present.
232 """
jomacarpeea2a73e2018-02-27 13:48:22 +0100233 # oca library method cannot be used in this case (problem with cluster parameters)
234 try:
kbsuba85c54d2019-10-17 16:30:32 +0000235 vlan = None
sousaedu80135b92021-02-17 15:05:18 +0100236
kbsuba85c54d2019-10-17 16:30:32 +0000237 if provider_network_profile:
238 vlan = provider_network_profile.get("segmentation-id")
sousaedu80135b92021-02-17 15:05:18 +0100239
albertoflorez3ade00b2019-07-08 13:16:21 +0200240 created_items = {}
jomacarpe20c13232019-06-12 21:20:51 +0000241 one = self._new_one_connection()
jomacarpe20c13232019-06-12 21:20:51 +0000242 size = "254"
sousaedu80135b92021-02-17 15:05:18 +0100243
jomacarpeea2a73e2018-02-27 13:48:22 +0100244 if ip_profile is None:
jomacarpe20c13232019-06-12 21:20:51 +0000245 subnet_rand = random.randint(0, 255)
246 ip_start = "192.168.{}.1".format(subnet_rand)
jomacarpeea2a73e2018-02-27 13:48:22 +0100247 else:
248 index = ip_profile["subnet_address"].find("/")
249 ip_start = ip_profile["subnet_address"][:index]
sousaedu80135b92021-02-17 15:05:18 +0100250
tierno7d782ef2019-10-04 12:56:31 +0000251 if "dhcp_count" in ip_profile and ip_profile["dhcp_count"] is not None:
jomacarpeea2a73e2018-02-27 13:48:22 +0100252 size = str(ip_profile["dhcp_count"])
sousaedu80135b92021-02-17 15:05:18 +0100253 elif (
254 "dhcp_count" not in ip_profile
255 and ip_profile["ip_version"] == "IPv4"
256 ):
257 prefix = ip_profile["subnet_address"][index + 1 :]
jomacarpeea2a73e2018-02-27 13:48:22 +0100258 size = int(math.pow(2, 32 - prefix))
sousaedu80135b92021-02-17 15:05:18 +0100259
260 if (
261 "dhcp_start_address" in ip_profile
262 and ip_profile["dhcp_start_address"] is not None
263 ):
jomacarpeea2a73e2018-02-27 13:48:22 +0100264 ip_start = str(ip_profile["dhcp_start_address"])
tierno1ec592d2020-06-16 15:29:47 +0000265 # if ip_profile["ip_version"] == "IPv6":
266 # ip_prefix_type = "GLOBAL_PREFIX"
jomacarpeea2a73e2018-02-27 13:48:22 +0100267
jomacarpe20c13232019-06-12 21:20:51 +0000268 if vlan is not None:
269 vlan_id = vlan
270 else:
271 vlan_id = str(random.randint(100, 4095))
sousaedu80135b92021-02-17 15:05:18 +0100272
tierno1ec592d2020-06-16 15:29:47 +0000273 # if "internal" in net_name:
albertoflorez3ade00b2019-07-08 13:16:21 +0200274 # OpenNebula not support two networks with same name
275 random_net_name = str(random.randint(1, 1000000))
276 net_name = net_name + random_net_name
sousaedu80135b92021-02-17 15:05:18 +0100277 net_id = one.vn.allocate(
278 {
279 "NAME": net_name,
280 "VN_MAD": "802.1Q",
281 "PHYDEV": self.config["network"]["phydev"],
282 "VLAN_ID": vlan_id,
283 },
284 self.config["cluster"]["id"],
285 )
286 arpool = {"AR_POOL": {"AR": {"TYPE": "IP4", "IP": ip_start, "SIZE": size}}}
albertoflorez3ade00b2019-07-08 13:16:21 +0200287 one.vn.add_ar(net_id, arpool)
sousaedu80135b92021-02-17 15:05:18 +0100288
albertoflorez3ade00b2019-07-08 13:16:21 +0200289 return net_id, created_items
jomacarpeea2a73e2018-02-27 13:48:22 +0100290 except Exception as e:
291 self.logger.error("Create new network error: " + str(e))
sousaedu80135b92021-02-17 15:05:18 +0100292
tierno72774862020-05-04 11:44:15 +0000293 raise vimconn.VimConnException(e)
jomacarpeea2a73e2018-02-27 13:48:22 +0100294
295 def get_network_list(self, filter_dict={}):
296 """Obtain tenant networks of VIM
tierno1ec592d2020-06-16 15:29:47 +0000297 :params filter_dict: (optional) contains entries to return only networks that matches ALL entries:
298 name: string => returns only networks with this name
299 id: string => returns networks with this VIM id, this imply returns one network at most
300 shared: boolean >= returns only networks that are (or are not) shared
301 tenant_id: sting => returns only networks that belong to this tenant/project
302 (not used yet) admin_state_up: boolean => returns only networks that are (or are not) in admin state active
303 (not used yet) status: 'ACTIVE','ERROR',... => filter networks that are on this status
jomacarpe20c13232019-06-12 21:20:51 +0000304 Returns the network list of dictionaries. each dictionary contains:
305 'id': (mandatory) VIM network id
306 'name': (mandatory) VIM network name
307 'status': (mandatory) can be 'ACTIVE', 'INACTIVE', 'DOWN', 'BUILD', 'ERROR', 'VIM_ERROR', 'OTHER'
308 'network_type': (optional) can be 'vxlan', 'vlan' or 'flat'
309 'segmentation_id': (optional) in case network_type is vlan or vxlan this field contains the segmentation id
310 'error_msg': (optional) text that explains the ERROR status
311 other VIM specific fields: (optional) whenever possible using the same naming of filter_dict param
312 List can be empty if no network map the filter_dict. Raise an exception only upon VIM connectivity,
313 authorization, or some other unspecific error
jomacarpeea2a73e2018-02-27 13:48:22 +0100314 """
315 try:
jomacarpe20c13232019-06-12 21:20:51 +0000316 one = self._new_one_connection()
317 net_pool = one.vnpool.info(-2, -1, -1).VNET
jomacarpeea2a73e2018-02-27 13:48:22 +0100318 response = []
sousaedu80135b92021-02-17 15:05:18 +0100319
tierno7d782ef2019-10-04 12:56:31 +0000320 if "name" in filter_dict:
jomacarpeea2a73e2018-02-27 13:48:22 +0100321 network_name_filter = filter_dict["name"]
322 else:
323 network_name_filter = None
sousaedu80135b92021-02-17 15:05:18 +0100324
tierno7d782ef2019-10-04 12:56:31 +0000325 if "id" in filter_dict:
jomacarpeea2a73e2018-02-27 13:48:22 +0100326 network_id_filter = filter_dict["id"]
327 else:
328 network_id_filter = None
sousaedu80135b92021-02-17 15:05:18 +0100329
jomacarpe20c13232019-06-12 21:20:51 +0000330 for network in net_pool:
sousaedu80135b92021-02-17 15:05:18 +0100331 if network.NAME == network_name_filter or str(network.ID) == str(
332 network_id_filter
333 ):
334 net_dict = {
335 "name": network.NAME,
336 "id": str(network.ID),
337 "status": "ACTIVE",
338 }
jomacarpeea2a73e2018-02-27 13:48:22 +0100339 response.append(net_dict)
sousaedu80135b92021-02-17 15:05:18 +0100340
jomacarpeea2a73e2018-02-27 13:48:22 +0100341 return response
342 except Exception as e:
343 self.logger.error("Get network list error: " + str(e))
sousaedu80135b92021-02-17 15:05:18 +0100344
tierno72774862020-05-04 11:44:15 +0000345 raise vimconn.VimConnException(e)
jomacarpeea2a73e2018-02-27 13:48:22 +0100346
347 def get_network(self, net_id):
jomacarpe20c13232019-06-12 21:20:51 +0000348 """Obtain network details from the 'net_id' VIM network
349 Return a dict that contains:
350 'id': (mandatory) VIM network id, that is, net_id
351 'name': (mandatory) VIM network name
352 'status': (mandatory) can be 'ACTIVE', 'INACTIVE', 'DOWN', 'BUILD', 'ERROR', 'VIM_ERROR', 'OTHER'
353 'error_msg': (optional) text that explains the ERROR status
354 other VIM specific fields: (optional) whenever possible using the same naming of filter_dict param
355 Raises an exception upon error or when network is not found
356 """
jomacarpeea2a73e2018-02-27 13:48:22 +0100357 try:
jomacarpe20c13232019-06-12 21:20:51 +0000358 one = self._new_one_connection()
359 net_pool = one.vnpool.info(-2, -1, -1).VNET
jomacarpeea2a73e2018-02-27 13:48:22 +0100360 net = {}
sousaedu80135b92021-02-17 15:05:18 +0100361
jomacarpe20c13232019-06-12 21:20:51 +0000362 for network in net_pool:
363 if str(network.ID) == str(net_id):
sousaedu80135b92021-02-17 15:05:18 +0100364 net["id"] = network.ID
365 net["name"] = network.NAME
366 net["status"] = "ACTIVE"
jomacarpeea2a73e2018-02-27 13:48:22 +0100367 break
sousaedu80135b92021-02-17 15:05:18 +0100368
jomacarpeea2a73e2018-02-27 13:48:22 +0100369 if net:
370 return net
371 else:
sousaedu80135b92021-02-17 15:05:18 +0100372 raise vimconn.VimConnNotFoundException(
373 "Network {} not found".format(net_id)
374 )
jomacarpeea2a73e2018-02-27 13:48:22 +0100375 except Exception as e:
jomacarpe20c13232019-06-12 21:20:51 +0000376 self.logger.error("Get network " + str(net_id) + " error): " + str(e))
sousaedu80135b92021-02-17 15:05:18 +0100377
tierno72774862020-05-04 11:44:15 +0000378 raise vimconn.VimConnException(e)
jomacarpeea2a73e2018-02-27 13:48:22 +0100379
garciadeblasebd66722019-01-31 16:01:31 +0000380 def delete_network(self, net_id, created_items=None):
381 """
382 Removes a tenant network from VIM and its associated elements
383 :param net_id: VIM identifier of the network, provided by method new_network
384 :param created_items: dictionary with extra items to be deleted. provided by method new_network
385 Returns the network identifier or raises an exception upon error or when network is not found
jomacarpeea2a73e2018-02-27 13:48:22 +0100386 """
387 try:
jomacarpe20c13232019-06-12 21:20:51 +0000388 one = self._new_one_connection()
389 one.vn.delete(int(net_id))
sousaedu80135b92021-02-17 15:05:18 +0100390
jomacarpe20c13232019-06-12 21:20:51 +0000391 return net_id
jomacarpeea2a73e2018-02-27 13:48:22 +0100392 except Exception as e:
sousaedu80135b92021-02-17 15:05:18 +0100393 self.logger.error(
394 "Delete network " + str(net_id) + "error: network not found" + str(e)
395 )
396
tierno72774862020-05-04 11:44:15 +0000397 raise vimconn.VimConnException(e)
jomacarpeea2a73e2018-02-27 13:48:22 +0100398
jomacarpeea2a73e2018-02-27 13:48:22 +0100399 def refresh_nets_status(self, net_list):
400 """Get the status of the networks
jomacarpe20c13232019-06-12 21:20:51 +0000401 Params:
402 'net_list': a list with the VIM network id to be get the status
403 Returns a dictionary with:
404 'net_id': #VIM id of this network
405 status: #Mandatory. Text with one of:
406 # DELETED (not found at vim)
407 # VIM_ERROR (Cannot connect to VIM, authentication problems, VIM response error, ...)
408 # OTHER (Vim reported other status not understood)
409 # ERROR (VIM indicates an ERROR status)
410 # ACTIVE, INACTIVE, DOWN (admin down),
411 # BUILD (on building process)
412 error_msg: #Text with VIM error message, if any. Or the VIM connection ERROR
413 vim_info: #Text with plain information obtained from vim (yaml.safe_dump)
414 'net_id2': ...
jomacarpeea2a73e2018-02-27 13:48:22 +0100415 """
416 net_dict = {}
417 try:
418 for net_id in net_list:
419 net = {}
sousaedu80135b92021-02-17 15:05:18 +0100420
jomacarpeea2a73e2018-02-27 13:48:22 +0100421 try:
422 net_vim = self.get_network(net_id)
423 net["status"] = net_vim["status"]
424 net["vim_info"] = None
tierno72774862020-05-04 11:44:15 +0000425 except vimconn.VimConnNotFoundException as e:
jomacarpeea2a73e2018-02-27 13:48:22 +0100426 self.logger.error("Exception getting net status: {}".format(str(e)))
sousaedu80135b92021-02-17 15:05:18 +0100427 net["status"] = "DELETED"
428 net["error_msg"] = str(e)
tierno72774862020-05-04 11:44:15 +0000429 except vimconn.VimConnException as e:
jomacarpeea2a73e2018-02-27 13:48:22 +0100430 self.logger.error(e)
431 net["status"] = "VIM_ERROR"
432 net["error_msg"] = str(e)
sousaedu80135b92021-02-17 15:05:18 +0100433
jomacarpeea2a73e2018-02-27 13:48:22 +0100434 net_dict[net_id] = net
sousaedu80135b92021-02-17 15:05:18 +0100435
jomacarpeea2a73e2018-02-27 13:48:22 +0100436 return net_dict
tierno72774862020-05-04 11:44:15 +0000437 except vimconn.VimConnException as e:
jomacarpeea2a73e2018-02-27 13:48:22 +0100438 self.logger.error(e)
sousaedu80135b92021-02-17 15:05:18 +0100439
jomacarpeea2a73e2018-02-27 13:48:22 +0100440 for k in net_dict:
441 net_dict[k]["status"] = "VIM_ERROR"
442 net_dict[k]["error_msg"] = str(e)
sousaedu80135b92021-02-17 15:05:18 +0100443
jomacarpeea2a73e2018-02-27 13:48:22 +0100444 return net_dict
445
jomacarpe20c13232019-06-12 21:20:51 +0000446 def get_flavor(self, flavor_id): # Esta correcto
447 """Obtain flavor details from the VIM
448 Returns the flavor dict details {'id':<>, 'name':<>, other vim specific }
449 Raises an exception upon error or if not found
450 """
451 try:
jomacarpe20c13232019-06-12 21:20:51 +0000452 one = self._new_one_connection()
453 template = one.template.info(int(flavor_id))
sousaedu80135b92021-02-17 15:05:18 +0100454
jomacarpe20c13232019-06-12 21:20:51 +0000455 if template is not None:
sousaedu80135b92021-02-17 15:05:18 +0100456 return {"id": template.ID, "name": template.NAME}
457
458 raise vimconn.VimConnNotFoundException(
459 "Flavor {} not found".format(flavor_id)
460 )
jomacarpe20c13232019-06-12 21:20:51 +0000461 except Exception as e:
462 self.logger.error("get flavor " + str(flavor_id) + " error: " + str(e))
sousaedu80135b92021-02-17 15:05:18 +0100463
tierno72774862020-05-04 11:44:15 +0000464 raise vimconn.VimConnException(e)
jomacarpe20c13232019-06-12 21:20:51 +0000465
466 def new_flavor(self, flavor_data):
467 """Adds a tenant flavor to VIM
468 flavor_data contains a dictionary with information, keys:
469 name: flavor name
470 ram: memory (cloud type) in MBytes
471 vpcus: cpus (cloud type)
472 extended: EPA parameters
473 - numas: #items requested in same NUMA
tierno1ec592d2020-06-16 15:29:47 +0000474 memory: number of 1G huge pages memory
475 paired-threads|cores|threads: number of paired hyperthreads, complete cores OR individual threads
476 interfaces: # passthrough(PT) or SRIOV interfaces attached to this numa
jomacarpe20c13232019-06-12 21:20:51 +0000477 - name: interface name
478 dedicated: yes|no|yes:sriov; for PT, SRIOV or only one SRIOV for the physical NIC
479 bandwidth: X Gbps; requested guarantee bandwidth
480 vpci: requested virtual PCI address
481 disk: disk size
482 is_public:
483 #TODO to concrete
484 Returns the flavor identifier"""
sousaedu80135b92021-02-17 15:05:18 +0100485 disk_size = str(int(flavor_data["disk"]) * 1024)
jomacarpe20c13232019-06-12 21:20:51 +0000486
487 try:
488 one = self._new_one_connection()
sousaedu80135b92021-02-17 15:05:18 +0100489 template_id = one.template.allocate(
490 {
491 "TEMPLATE": {
492 "NAME": flavor_data["name"],
493 "CPU": flavor_data["vcpus"],
494 "VCPU": flavor_data["vcpus"],
495 "MEMORY": flavor_data["ram"],
496 "DISK": {"SIZE": disk_size},
497 "CONTEXT": {
498 "NETWORK": "YES",
499 "SSH_PUBLIC_KEY": "$USER[SSH_PUBLIC_KEY]",
500 },
501 "GRAPHICS": {"LISTEN": "0.0.0.0", "TYPE": "VNC"},
502 "CLUSTER_ID": self.config["cluster"]["id"],
503 }
jomacarpe20c13232019-06-12 21:20:51 +0000504 }
sousaedu80135b92021-02-17 15:05:18 +0100505 )
jomacarpe20c13232019-06-12 21:20:51 +0000506
sousaedu80135b92021-02-17 15:05:18 +0100507 return template_id
jomacarpe20c13232019-06-12 21:20:51 +0000508 except Exception as e:
509 self.logger.error("Create new flavor error: " + str(e))
sousaedu80135b92021-02-17 15:05:18 +0100510
tierno72774862020-05-04 11:44:15 +0000511 raise vimconn.VimConnException(e)
jomacarpe20c13232019-06-12 21:20:51 +0000512
513 def delete_flavor(self, flavor_id):
sousaedu80135b92021-02-17 15:05:18 +0100514 """Deletes a tenant flavor from VIM
515 Returns the old flavor_id
jomacarpe20c13232019-06-12 21:20:51 +0000516 """
517 try:
518 one = self._new_one_connection()
519 one.template.delete(int(flavor_id), False)
sousaedu80135b92021-02-17 15:05:18 +0100520
jomacarpe20c13232019-06-12 21:20:51 +0000521 return flavor_id
522 except Exception as e:
sousaedu80135b92021-02-17 15:05:18 +0100523 self.logger.error(
524 "Error deleting flavor " + str(flavor_id) + ". Flavor not found"
525 )
526
tierno72774862020-05-04 11:44:15 +0000527 raise vimconn.VimConnException(e)
jomacarpe20c13232019-06-12 21:20:51 +0000528
529 def get_image_list(self, filter_dict={}):
530 """Obtain tenant images from VIM
531 Filter_dict can be:
532 name: image name
533 id: image uuid
534 checksum: image checksum
535 location: image path
536 Returns the image list of dictionaries:
537 [{<the fields at Filter_dict plus some VIM specific>}, ...]
538 List can be empty
539 """
540 try:
541 one = self._new_one_connection()
542 image_pool = one.imagepool.info(-2, -1, -1).IMAGE
543 images = []
sousaedu80135b92021-02-17 15:05:18 +0100544
tierno7d782ef2019-10-04 12:56:31 +0000545 if "name" in filter_dict:
jomacarpe20c13232019-06-12 21:20:51 +0000546 image_name_filter = filter_dict["name"]
547 else:
548 image_name_filter = None
sousaedu80135b92021-02-17 15:05:18 +0100549
tierno7d782ef2019-10-04 12:56:31 +0000550 if "id" in filter_dict:
jomacarpe20c13232019-06-12 21:20:51 +0000551 image_id_filter = filter_dict["id"]
552 else:
553 image_id_filter = None
sousaedu80135b92021-02-17 15:05:18 +0100554
jomacarpe20c13232019-06-12 21:20:51 +0000555 for image in image_pool:
sousaedu80135b92021-02-17 15:05:18 +0100556 if str(image_name_filter) == str(image.NAME) or str(image.ID) == str(
557 image_id_filter
558 ):
jomacarpe20c13232019-06-12 21:20:51 +0000559 images_dict = {"name": image.NAME, "id": str(image.ID)}
560 images.append(images_dict)
sousaedu80135b92021-02-17 15:05:18 +0100561
jomacarpe20c13232019-06-12 21:20:51 +0000562 return images
563 except Exception as e:
564 self.logger.error("Get image list error: " + str(e))
tierno72774862020-05-04 11:44:15 +0000565 raise vimconn.VimConnException(e)
jomacarpe20c13232019-06-12 21:20:51 +0000566
sousaedu80135b92021-02-17 15:05:18 +0100567 def new_vminstance(
568 self,
569 name,
570 description,
571 start,
572 image_id,
573 flavor_id,
574 net_list,
575 cloud_config=None,
576 disk_list=None,
577 availability_zone_index=None,
578 availability_zone_list=None,
579 ):
tierno1ec592d2020-06-16 15:29:47 +0000580 """
581 Adds a VM instance to VIM
582 :param name:
583 :param description:
584 :param start: (boolean) indicates if VM must start or created in pause mode.
585 :param image_id: image VIM id to use for the VM
586 :param flavor_id: flavor VIM id to use for the VM
587 :param net_list: list of interfaces, each one is a dictionary with:
588 'name': (optional) name for the interface.
589 'net_id': VIM network id where this interface must be connect to. Mandatory for type==virtual
590 'vpci': (optional) virtual vPCI address to assign at the VM. Can be ignored depending on VIM
591 capabilities
592 'model': (optional and only have sense for type==virtual) interface model: virtio, e1000, ...
593 'mac_address': (optional) mac address to assign to this interface
594 'ip_address': (optional) IP address to assign to this interface
595 #TODO: CHECK if an optional 'vlan' parameter is needed for VIMs when type if VF and net_id is not
596 provided, the VLAN tag to be used. In case net_id is provided, the internal network vlan is
597 used for tagging VF
598 'type': (mandatory) can be one of:
599 'virtual', in this case always connected to a network of type 'net_type=bridge'
600 'PCI-PASSTHROUGH' or 'PF' (passthrough): depending on VIM capabilities it can be connected to
601 a data/ptp network ot itcan created unconnected
602 'SR-IOV' or 'VF' (SRIOV with VLAN tag): same as PF for network connectivity.
603 'VFnotShared'(SRIOV without VLAN tag) same as PF for network connectivity. VF where no other VFs
604 are allocated on the same physical NIC
605 'bw': (optional) only for PF/VF/VFnotShared. Minimal Bandwidth required for the interface in GBPS
606 'port_security': (optional) If False it must avoid any traffic filtering at this interface. If missing
607 or True, it must apply the default VIM behaviour
608 After execution the method will add the key:
609 'vim_id': must be filled/added by this method with the VIM identifier generated by the VIM for this
610 interface. 'net_list' is modified
611 :param cloud_config: (optional) dictionary with:
612 'key-pairs': (optional) list of strings with the public key to be inserted to the default user
613 'users': (optional) list of users to be inserted, each item is a dict with:
614 'name': (mandatory) user name,
615 'key-pairs': (optional) list of strings with the public key to be inserted to the user
616 'user-data': (optional) can be a string with the text script to be passed directly to cloud-init,
617 or a list of strings, each one contains a script to be passed, usually with a MIMEmultipart file
618 'config-files': (optional). List of files to be transferred. Each item is a dict with:
619 'dest': (mandatory) string with the destination absolute path
620 'encoding': (optional, by default text). Can be one of:
621 'b64', 'base64', 'gz', 'gz+b64', 'gz+base64', 'gzip+b64', 'gzip+base64'
622 'content' (mandatory): string with the content of the file
623 'permissions': (optional) string with file permissions, typically octal notation '0644'
624 'owner': (optional) file owner, string with the format 'owner:group'
625 'boot-data-drive': boolean to indicate if user-data must be passed using a boot drive (hard disk)
626 :param disk_list: (optional) list with additional disks to the VM. Each item is a dict with:
627 'image_id': (optional). VIM id of an existing image. If not provided an empty disk must be mounted
628 'size': (mandatory) string with the size of the disk in GB
629 :param availability_zone_index: Index of availability_zone_list to use for this this VM. None if not AV
630 required
631 :param availability_zone_list: list of availability zones given by user in the VNFD descriptor. Ignore if
jomacarpe20c13232019-06-12 21:20:51 +0000632 availability_zone_index is None
tierno1ec592d2020-06-16 15:29:47 +0000633 :return: a tuple with the instance identifier and created_items or raises an exception on error
634 created_items can be None or a dictionary where this method can include key-values that will be passed to
635 the method delete_vminstance and action_vminstance. Can be used to store created ports, volumes, etc.
636 Format is vimconnector dependent, but do not use nested dictionaries and a value of None should be the same
637 as not present.
638 """
jomacarpe20c13232019-06-12 21:20:51 +0000639 self.logger.debug(
sousaedu80135b92021-02-17 15:05:18 +0100640 "new_vminstance input: image='{}' flavor='{}' nics='{}'".format(
641 image_id, flavor_id, str(net_list)
642 )
643 )
644
jomacarpe20c13232019-06-12 21:20:51 +0000645 try:
646 one = self._new_one_connection()
647 template_vim = one.template.info(int(flavor_id), True)
648 disk_size = str(template_vim.TEMPLATE["DISK"]["SIZE"])
649
650 one = self._new_one_connection()
651 template_updated = ""
sousaedu80135b92021-02-17 15:05:18 +0100652
jomacarpe20c13232019-06-12 21:20:51 +0000653 for net in net_list:
654 net_in_vim = one.vn.info(int(net["net_id"]))
655 net["vim_id"] = str(net_in_vim.ID)
656 network = 'NIC = [NETWORK = "{}",NETWORK_UNAME = "{}" ]'.format(
sousaedu80135b92021-02-17 15:05:18 +0100657 net_in_vim.NAME, net_in_vim.UNAME
658 )
jomacarpe20c13232019-06-12 21:20:51 +0000659 template_updated += network
660
sousaedu80135b92021-02-17 15:05:18 +0100661 template_updated += "DISK = [ IMAGE_ID = {},\n SIZE = {}]".format(
662 image_id, disk_size
663 )
jomacarpe20c13232019-06-12 21:20:51 +0000664
665 if isinstance(cloud_config, dict):
666 if cloud_config.get("key-pairs"):
667 context = 'CONTEXT = [\n NETWORK = "YES",\n SSH_PUBLIC_KEY = "'
sousaedu80135b92021-02-17 15:05:18 +0100668
jomacarpe20c13232019-06-12 21:20:51 +0000669 for key in cloud_config["key-pairs"]:
sousaedu80135b92021-02-17 15:05:18 +0100670 context += key + "\n"
671
jomacarpe20c13232019-06-12 21:20:51 +0000672 # if False:
673 # context += '"\n USERNAME = '
674 context += '"]'
675 template_updated += context
676
sousaedu80135b92021-02-17 15:05:18 +0100677 vm_instance_id = one.template.instantiate(
678 int(flavor_id), name, False, template_updated
679 )
jomacarpe20c13232019-06-12 21:20:51 +0000680 self.logger.info(
sousaedu80135b92021-02-17 15:05:18 +0100681 "Instanciating in OpenNebula a new VM name:{} id:{}".format(
682 name, flavor_id
683 )
684 )
685
jomacarpe20c13232019-06-12 21:20:51 +0000686 return str(vm_instance_id), None
687 except pyone.OneNoExistsException as e:
688 self.logger.error("Network with id " + str(e) + " not found: " + str(e))
sousaedu80135b92021-02-17 15:05:18 +0100689
tierno72774862020-05-04 11:44:15 +0000690 raise vimconn.VimConnNotFoundException(e)
jomacarpe20c13232019-06-12 21:20:51 +0000691 except Exception as e:
692 self.logger.error("Create new vm instance error: " + str(e))
sousaedu80135b92021-02-17 15:05:18 +0100693
tierno72774862020-05-04 11:44:15 +0000694 raise vimconn.VimConnException(e)
jomacarpe20c13232019-06-12 21:20:51 +0000695
696 def get_vminstance(self, vm_id):
697 """Returns the VM instance information from VIM"""
698 try:
699 one = self._new_one_connection()
700 vm = one.vm.info(int(vm_id))
sousaedu80135b92021-02-17 15:05:18 +0100701
jomacarpe20c13232019-06-12 21:20:51 +0000702 return vm
703 except Exception as e:
sousaedu80135b92021-02-17 15:05:18 +0100704 self.logger.error(
705 "Getting vm instance error: " + str(e) + ": VM Instance not found"
706 )
707
tierno72774862020-05-04 11:44:15 +0000708 raise vimconn.VimConnException(e)
jomacarpe20c13232019-06-12 21:20:51 +0000709
710 def delete_vminstance(self, vm_id, created_items=None):
711 """
712 Removes a VM instance from VIM and its associated elements
713 :param vm_id: VIM identifier of the VM, provided by method new_vminstance
714 :param created_items: dictionary with extra items to be deleted. provided by method new_vminstance and/or method
715 action_vminstance
716 :return: None or the same vm_id. Raises an exception on fail
717 """
718 try:
719 one = self._new_one_connection()
720 one.vm.recover(int(vm_id), 3)
721 vm = None
sousaedu80135b92021-02-17 15:05:18 +0100722
jomacarpe20c13232019-06-12 21:20:51 +0000723 while True:
724 if vm is not None and vm.LCM_STATE == 0:
725 break
726 else:
727 vm = one.vm.info(int(vm_id))
tierno1ec592d2020-06-16 15:29:47 +0000728 except pyone.OneNoExistsException:
sousaedu80135b92021-02-17 15:05:18 +0100729 self.logger.info(
730 "The vm " + str(vm_id) + " does not exist or is already deleted"
731 )
732
733 raise vimconn.VimConnNotFoundException(
734 "The vm {} does not exist or is already deleted".format(vm_id)
735 )
jomacarpe20c13232019-06-12 21:20:51 +0000736 except Exception as e:
737 self.logger.error("Delete vm instance " + str(vm_id) + " error: " + str(e))
tierno72774862020-05-04 11:44:15 +0000738 raise vimconn.VimConnException(e)
jomacarpe20c13232019-06-12 21:20:51 +0000739
740 def refresh_vms_status(self, vm_list):
741 """Get the status of the virtual machines and their interfaces/ports
sousaedu80135b92021-02-17 15:05:18 +0100742 Params: the list of VM identifiers
743 Returns a dictionary with:
744 vm_id: #VIM id of this Virtual Machine
745 status: #Mandatory. Text with one of:
746 # DELETED (not found at vim)
747 # VIM_ERROR (Cannot connect to VIM, VIM response error, ...)
748 # OTHER (Vim reported other status not understood)
749 # ERROR (VIM indicates an ERROR status)
750 # ACTIVE, PAUSED, SUSPENDED, INACTIVE (not running),
751 # BUILD (on building process), ERROR
752 # ACTIVE:NoMgmtIP (Active but any of its interface has an IP address
753 #
754 error_msg: #Text with VIM error message, if any. Or the VIM connection ERROR
755 vim_info: #Text with plain information obtained from vim (yaml.safe_dump)
756 interfaces: list with interface info. Each item a dictionary with:
757 vim_info: #Text with plain information obtained from vim (yaml.safe_dump)
758 mac_address: #Text format XX:XX:XX:XX:XX:XX
759 vim_net_id: #network id where this interface is connected, if provided at creation
760 vim_interface_id: #interface/port VIM id
761 ip_address: #null, or text with IPv4, IPv6 address
762 compute_node: #identification of compute node where PF,VF interface is allocated
763 pci: #PCI address of the NIC that hosts the PF,VF
764 vlan: #physical VLAN used for VF
jomacarpe20c13232019-06-12 21:20:51 +0000765 """
766 vm_dict = {}
767 try:
768 for vm_id in vm_list:
769 vm = {}
sousaedu80135b92021-02-17 15:05:18 +0100770
jomacarpe20c13232019-06-12 21:20:51 +0000771 if self.get_vminstance(vm_id) is not None:
772 vm_element = self.get_vminstance(vm_id)
773 else:
774 self.logger.info("The vm " + str(vm_id) + " does not exist.")
sousaedu80135b92021-02-17 15:05:18 +0100775 vm["status"] = "DELETED"
776 vm["error_msg"] = "The vm " + str(vm_id) + " does not exist."
jomacarpe20c13232019-06-12 21:20:51 +0000777 continue
sousaedu80135b92021-02-17 15:05:18 +0100778
jomacarpe20c13232019-06-12 21:20:51 +0000779 vm["vim_info"] = None
780 vm_status = vm_element.LCM_STATE
sousaedu80135b92021-02-17 15:05:18 +0100781
jomacarpe20c13232019-06-12 21:20:51 +0000782 if vm_status == 3:
sousaedu80135b92021-02-17 15:05:18 +0100783 vm["status"] = "ACTIVE"
jomacarpe20c13232019-06-12 21:20:51 +0000784 elif vm_status == 36:
sousaedu80135b92021-02-17 15:05:18 +0100785 vm["status"] = "ERROR"
786 vm["error_msg"] = "VM failure"
jomacarpe20c13232019-06-12 21:20:51 +0000787 else:
sousaedu80135b92021-02-17 15:05:18 +0100788 vm["status"] = "BUILD"
jomacarpe20c13232019-06-12 21:20:51 +0000789
790 if vm_element is not None:
791 interfaces = self._get_networks_vm(vm_element)
792 vm["interfaces"] = interfaces
sousaedu80135b92021-02-17 15:05:18 +0100793
jomacarpe20c13232019-06-12 21:20:51 +0000794 vm_dict[vm_id] = vm
sousaedu80135b92021-02-17 15:05:18 +0100795
jomacarpe20c13232019-06-12 21:20:51 +0000796 return vm_dict
797 except Exception as e:
798 self.logger.error(e)
799 for k in vm_dict:
800 vm_dict[k]["status"] = "VIM_ERROR"
801 vm_dict[k]["error_msg"] = str(e)
sousaedu80135b92021-02-17 15:05:18 +0100802
jomacarpe20c13232019-06-12 21:20:51 +0000803 return vm_dict
804
805 def _get_networks_vm(self, vm_element):
806 interfaces = []
807 try:
808 if isinstance(vm_element.TEMPLATE["NIC"], list):
809 for net in vm_element.TEMPLATE["NIC"]:
sousaedu80135b92021-02-17 15:05:18 +0100810 interface = {
811 "vim_info": None,
812 "mac_address": str(net["MAC"]),
813 "vim_net_id": str(net["NETWORK_ID"]),
814 "vim_interface_id": str(net["NETWORK_ID"]),
815 }
816
jomacarpe20c13232019-06-12 21:20:51 +0000817 # maybe it should be 2 different keys for ip_address if an interface has ipv4 and ipv6
sousaedu80135b92021-02-17 15:05:18 +0100818 if "IP" in net:
jomacarpe20c13232019-06-12 21:20:51 +0000819 interface["ip_address"] = str(net["IP"])
sousaedu80135b92021-02-17 15:05:18 +0100820
821 if "IP6_GLOBAL" in net:
jomacarpe20c13232019-06-12 21:20:51 +0000822 interface["ip_address"] = str(net["IP6_GLOBAL"])
sousaedu80135b92021-02-17 15:05:18 +0100823
jomacarpe20c13232019-06-12 21:20:51 +0000824 interfaces.append(interface)
825 else:
826 net = vm_element.TEMPLATE["NIC"]
sousaedu80135b92021-02-17 15:05:18 +0100827 interface = {
828 "vim_info": None,
829 "mac_address": str(net["MAC"]),
830 "vim_net_id": str(net["NETWORK_ID"]),
831 "vim_interface_id": str(net["NETWORK_ID"]),
832 }
833
jomacarpe20c13232019-06-12 21:20:51 +0000834 # maybe it should be 2 different keys for ip_address if an interface has ipv4 and ipv6
sousaedu80135b92021-02-17 15:05:18 +0100835 if "IP" in net:
jomacarpe20c13232019-06-12 21:20:51 +0000836 interface["ip_address"] = str(net["IP"])
sousaedu80135b92021-02-17 15:05:18 +0100837
838 if "IP6_GLOBAL" in net:
jomacarpe20c13232019-06-12 21:20:51 +0000839 interface["ip_address"] = str(net["IP6_GLOBAL"])
sousaedu80135b92021-02-17 15:05:18 +0100840
jomacarpe20c13232019-06-12 21:20:51 +0000841 interfaces.append(interface)
842 return interfaces
tierno1ec592d2020-06-16 15:29:47 +0000843 except Exception:
sousaedu80135b92021-02-17 15:05:18 +0100844 self.logger.error(
845 "Error getting vm interface_information of vm_id: " + str(vm_element.ID)
846 )