| jomacarpe | ea2a73e | 2018-02-27 13:48:22 +0100 | [diff] [blame] | 1 | # -*- coding: utf-8 -*- |
| 2 | |
| 3 | ## |
| tierno | 9202102 | 2018-09-12 16:29:23 +0200 | [diff] [blame] | 4 | # Copyright 2017 Telefonica Digital Spain S.L.U. |
| jomacarpe | ea2a73e | 2018-02-27 13:48:22 +0100 | [diff] [blame] | 5 | # 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 | """ |
| 26 | vimconnector 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 " \ |
| tierno | 9202102 | 2018-09-12 16:29:23 +0200 | [diff] [blame] | 29 | "Pages, Andres Pozo Munoz, Santiago Perez Marin, Onlife Networks Telefonica I+D Product Innovation " |
| jomacarpe | ea2a73e | 2018-02-27 13:48:22 +0100 | [diff] [blame] | 30 | __date__ = "$13-dec-2017 11:09:29$" |
| 31 | import vimconn |
| 32 | import requests |
| 33 | import logging |
| 34 | import oca |
| 35 | import untangle |
| 36 | import math |
| 37 | import random |
| jomacarpe | 20c1323 | 2019-06-12 21:20:51 +0000 | [diff] [blame] | 38 | import pyone |
| jomacarpe | ea2a73e | 2018-02-27 13:48:22 +0100 | [diff] [blame] | 39 | |
| 40 | class vimconnector(vimconn.vimconnector): |
| 41 | def __init__(self, uuid, name, tenant_id, tenant_name, url, url_admin=None, user=None, passwd=None, |
| 42 | log_level="DEBUG", config={}, persistent_info={}): |
| jomacarpe | 20c1323 | 2019-06-12 21:20:51 +0000 | [diff] [blame] | 43 | |
| 44 | """Constructor of VIM |
| 45 | Params: |
| 46 | 'uuid': id asigned to this VIM |
| 47 | 'name': name assigned to this VIM, can be used for logging |
| 48 | 'tenant_id', 'tenant_name': (only one of them is mandatory) VIM tenant to be used |
| 49 | 'url_admin': (optional), url used for administrative tasks |
| 50 | 'user', 'passwd': credentials of the VIM user |
| 51 | 'log_level': provider if it should use a different log_level than the general one |
| 52 | 'config': dictionary with extra VIM information. This contains a consolidate version of general VIM config |
| 53 | at creation and particular VIM config at teh attachment |
| 54 | 'persistent_info': dict where the class can store information that will be available among class |
| 55 | destroy/creation cycles. This info is unique per VIM/credential. At first call it will contain an |
| 56 | empty dict. Useful to store login/tokens information for speed up communication |
| 57 | |
| 58 | Returns: Raise an exception is some needed parameter is missing, but it must not do any connectivity |
| 59 | check against the VIM |
| 60 | """ |
| 61 | |
| jomacarpe | ea2a73e | 2018-02-27 13:48:22 +0100 | [diff] [blame] | 62 | vimconn.vimconnector.__init__(self, uuid, name, tenant_id, tenant_name, url, url_admin, user, passwd, log_level, |
| 63 | config) |
| jomacarpe | ea2a73e | 2018-02-27 13:48:22 +0100 | [diff] [blame] | 64 | |
| jomacarpe | 20c1323 | 2019-06-12 21:20:51 +0000 | [diff] [blame] | 65 | def _new_one_connection(self): |
| 66 | return pyone.OneServer(self.url, session=self.user + ':' + self.passwd) |
| jomacarpe | ea2a73e | 2018-02-27 13:48:22 +0100 | [diff] [blame] | 67 | |
| 68 | def new_tenant(self, tenant_name, tenant_description): |
| 69 | # '''Adds a new tenant to VIM with this name and description, returns the tenant identifier''' |
| 70 | try: |
| 71 | client = oca.Client(self.user + ':' + self.passwd, self.url) |
| 72 | group_list = oca.GroupPool(client) |
| 73 | user_list = oca.UserPool(client) |
| 74 | group_list.info() |
| 75 | user_list.info() |
| 76 | create_primarygroup = 1 |
| 77 | # create group-tenant |
| 78 | for group in group_list: |
| 79 | if str(group.name) == str(tenant_name): |
| 80 | create_primarygroup = 0 |
| 81 | break |
| 82 | if create_primarygroup == 1: |
| 83 | oca.Group.allocate(client, tenant_name) |
| 84 | group_list.info() |
| 85 | # set to primary_group the tenant_group and oneadmin to secondary_group |
| 86 | for group in group_list: |
| 87 | if str(group.name) == str(tenant_name): |
| 88 | for user in user_list: |
| 89 | if str(user.name) == str(self.user): |
| 90 | if user.name == "oneadmin": |
| 91 | return str(0) |
| 92 | else: |
| 93 | self._add_secondarygroup(user.id, group.id) |
| 94 | user.chgrp(group.id) |
| 95 | return str(group.id) |
| 96 | except Exception as e: |
| 97 | self.logger.error("Create new tenant error: " + str(e)) |
| 98 | raise vimconn.vimconnException(e) |
| 99 | |
| jomacarpe | ea2a73e | 2018-02-27 13:48:22 +0100 | [diff] [blame] | 100 | def delete_tenant(self, tenant_id): |
| 101 | """Delete a tenant from VIM. Returns the old tenant identifier""" |
| 102 | try: |
| 103 | client = oca.Client(self.user + ':' + self.passwd, self.url) |
| 104 | group_list = oca.GroupPool(client) |
| 105 | user_list = oca.UserPool(client) |
| 106 | group_list.info() |
| 107 | user_list.info() |
| 108 | for group in group_list: |
| 109 | if str(group.id) == str(tenant_id): |
| 110 | for user in user_list: |
| 111 | if str(user.name) == str(self.user): |
| 112 | self._delete_secondarygroup(user.id, group.id) |
| 113 | group.delete(client) |
| 114 | return None |
| 115 | raise vimconn.vimconnNotFoundException("Group {} not found".format(tenant_id)) |
| 116 | except Exception as e: |
| 117 | self.logger.error("Delete tenant " + str(tenant_id) + " error: " + str(e)) |
| 118 | raise vimconn.vimconnException(e) |
| 119 | |
| jomacarpe | 20c1323 | 2019-06-12 21:20:51 +0000 | [diff] [blame] | 120 | def _add_secondarygroup(self, id_user, id_group): |
| 121 | # change secondary_group to primary_group |
| 122 | params = '<?xml version="1.0"?> \ |
| 123 | <methodCall>\ |
| 124 | <methodName>one.user.addgroup</methodName>\ |
| 125 | <params>\ |
| 126 | <param>\ |
| 127 | <value><string>{}:{}</string></value>\ |
| 128 | </param>\ |
| 129 | <param>\ |
| 130 | <value><int>{}</int></value>\ |
| 131 | </param>\ |
| 132 | <param>\ |
| 133 | <value><int>{}</int></value>\ |
| 134 | </param>\ |
| 135 | </params>\ |
| 136 | </methodCall>'.format(self.user, self.passwd, (str(id_user)), (str(id_group))) |
| 137 | requests.post(self.url, params) |
| 138 | |
| jomacarpe | ea2a73e | 2018-02-27 13:48:22 +0100 | [diff] [blame] | 139 | def _delete_secondarygroup(self, id_user, id_group): |
| 140 | params = '<?xml version="1.0"?> \ |
| 141 | <methodCall>\ |
| 142 | <methodName>one.user.delgroup</methodName>\ |
| 143 | <params>\ |
| 144 | <param>\ |
| 145 | <value><string>{}:{}</string></value>\ |
| 146 | </param>\ |
| 147 | <param>\ |
| 148 | <value><int>{}</int></value>\ |
| 149 | </param>\ |
| 150 | <param>\ |
| 151 | <value><int>{}</int></value>\ |
| 152 | </param>\ |
| 153 | </params>\ |
| 154 | </methodCall>'.format(self.user, self.passwd, (str(id_user)), (str(id_group))) |
| 155 | requests.post(self.url, params) |
| 156 | |
| jomacarpe | ea2a73e | 2018-02-27 13:48:22 +0100 | [diff] [blame] | 157 | def new_network(self, net_name, net_type, ip_profile=None, shared=False, vlan=None): # , **vim_specific): |
| garciadeblas | ebd6672 | 2019-01-31 16:01:31 +0000 | [diff] [blame] | 158 | """Adds a tenant network to VIM |
| 159 | Params: |
| 160 | 'net_name': name of the network |
| 161 | 'net_type': one of: |
| 162 | 'bridge': overlay isolated network |
| 163 | 'data': underlay E-LAN network for Passthrough and SRIOV interfaces |
| 164 | 'ptp': underlay E-LINE network for Passthrough and SRIOV interfaces. |
| 165 | 'ip_profile': is a dict containing the IP parameters of the network |
| 166 | 'ip_version': can be "IPv4" or "IPv6" (Currently only IPv4 is implemented) |
| 167 | 'subnet_address': ip_prefix_schema, that is X.X.X.X/Y |
| 168 | 'gateway_address': (Optional) ip_schema, that is X.X.X.X |
| 169 | 'dns_address': (Optional) comma separated list of ip_schema, e.g. X.X.X.X[,X,X,X,X] |
| 170 | 'dhcp_enabled': True or False |
| 171 | 'dhcp_start_address': ip_schema, first IP to grant |
| 172 | 'dhcp_count': number of IPs to grant. |
| 173 | 'shared': if this network can be seen/use by other tenants/organization |
| 174 | 'vlan': in case of a data or ptp net_type, the intended vlan tag to be used for the network |
| 175 | Returns a tuple with the network identifier and created_items, or raises an exception on error |
| 176 | created_items can be None or a dictionary where this method can include key-values that will be passed to |
| 177 | the method delete_network. Can be used to store created segments, created l2gw connections, etc. |
| 178 | Format is vimconnector dependent, but do not use nested dictionaries and a value of None should be the same |
| 179 | as not present. |
| 180 | """ |
| 181 | |
| jomacarpe | ea2a73e | 2018-02-27 13:48:22 +0100 | [diff] [blame] | 182 | # oca library method cannot be used in this case (problem with cluster parameters) |
| 183 | try: |
| albertoflorez | 3ade00b | 2019-07-08 13:16:21 +0200 | [diff] [blame] | 184 | created_items = {} |
| jomacarpe | 20c1323 | 2019-06-12 21:20:51 +0000 | [diff] [blame] | 185 | one = self._new_one_connection() |
| jomacarpe | 20c1323 | 2019-06-12 21:20:51 +0000 | [diff] [blame] | 186 | size = "254" |
| jomacarpe | ea2a73e | 2018-02-27 13:48:22 +0100 | [diff] [blame] | 187 | if ip_profile is None: |
| jomacarpe | 20c1323 | 2019-06-12 21:20:51 +0000 | [diff] [blame] | 188 | subnet_rand = random.randint(0, 255) |
| 189 | ip_start = "192.168.{}.1".format(subnet_rand) |
| jomacarpe | ea2a73e | 2018-02-27 13:48:22 +0100 | [diff] [blame] | 190 | else: |
| 191 | index = ip_profile["subnet_address"].find("/") |
| 192 | ip_start = ip_profile["subnet_address"][:index] |
| 193 | if "dhcp_count" in ip_profile.keys() and ip_profile["dhcp_count"] is not None: |
| 194 | size = str(ip_profile["dhcp_count"]) |
| 195 | elif not ("dhcp_count" in ip_profile.keys()) and ip_profile["ip_version"] == "IPv4": |
| 196 | prefix = ip_profile["subnet_address"][index + 1:] |
| 197 | size = int(math.pow(2, 32 - prefix)) |
| 198 | if "dhcp_start_address" in ip_profile.keys() and ip_profile["dhcp_start_address"] is not None: |
| 199 | ip_start = str(ip_profile["dhcp_start_address"]) |
| 200 | if ip_profile["ip_version"] == "IPv6": |
| jomacarpe | 20c1323 | 2019-06-12 21:20:51 +0000 | [diff] [blame] | 201 | ip_prefix_type = "GLOBAL_PREFIX" |
| jomacarpe | ea2a73e | 2018-02-27 13:48:22 +0100 | [diff] [blame] | 202 | |
| jomacarpe | 20c1323 | 2019-06-12 21:20:51 +0000 | [diff] [blame] | 203 | if vlan is not None: |
| 204 | vlan_id = vlan |
| 205 | else: |
| 206 | vlan_id = str(random.randint(100, 4095)) |
| albertoflorez | 3ade00b | 2019-07-08 13:16:21 +0200 | [diff] [blame] | 207 | #if "internal" in net_name: |
| 208 | # OpenNebula not support two networks with same name |
| 209 | random_net_name = str(random.randint(1, 1000000)) |
| 210 | net_name = net_name + random_net_name |
| jomacarpe | 20c1323 | 2019-06-12 21:20:51 +0000 | [diff] [blame] | 211 | net_id = one.vn.allocate({ |
| 212 | 'NAME': net_name, |
| 213 | 'VN_MAD': '802.1Q', |
| 214 | 'PHYDEV': self.config["network"]["phydev"], |
| 215 | 'VLAN_ID': vlan_id |
| 216 | }, self.config["cluster"]["id"]) |
| albertoflorez | 3ade00b | 2019-07-08 13:16:21 +0200 | [diff] [blame] | 217 | arpool = {'AR_POOL': { |
| 218 | 'AR': { |
| 219 | 'TYPE': 'IP4', |
| 220 | 'IP': ip_start, |
| 221 | 'SIZE': size |
| 222 | } |
| jomacarpe | 20c1323 | 2019-06-12 21:20:51 +0000 | [diff] [blame] | 223 | } |
| albertoflorez | 3ade00b | 2019-07-08 13:16:21 +0200 | [diff] [blame] | 224 | } |
| 225 | one.vn.add_ar(net_id, arpool) |
| 226 | return net_id, created_items |
| jomacarpe | ea2a73e | 2018-02-27 13:48:22 +0100 | [diff] [blame] | 227 | except Exception as e: |
| 228 | self.logger.error("Create new network error: " + str(e)) |
| 229 | raise vimconn.vimconnException(e) |
| 230 | |
| 231 | def get_network_list(self, filter_dict={}): |
| 232 | """Obtain tenant networks of VIM |
| jomacarpe | 20c1323 | 2019-06-12 21:20:51 +0000 | [diff] [blame] | 233 | Params: |
| 234 | 'filter_dict' (optional) contains entries to return only networks that matches ALL entries: |
| 235 | name: string => returns only networks with this name |
| 236 | id: string => returns networks with this VIM id, this imply returns one network at most |
| 237 | shared: boolean >= returns only networks that are (or are not) shared |
| 238 | tenant_id: sting => returns only networks that belong to this tenant/project |
| 239 | ,#(not used yet) admin_state_up: boolean => returns only networks that are (or are not) in admin state active |
| 240 | #(not used yet) status: 'ACTIVE','ERROR',... => filter networks that are on this status |
| 241 | Returns the network list of dictionaries. each dictionary contains: |
| 242 | 'id': (mandatory) VIM network id |
| 243 | 'name': (mandatory) VIM network name |
| 244 | 'status': (mandatory) can be 'ACTIVE', 'INACTIVE', 'DOWN', 'BUILD', 'ERROR', 'VIM_ERROR', 'OTHER' |
| 245 | 'network_type': (optional) can be 'vxlan', 'vlan' or 'flat' |
| 246 | 'segmentation_id': (optional) in case network_type is vlan or vxlan this field contains the segmentation id |
| 247 | 'error_msg': (optional) text that explains the ERROR status |
| 248 | other VIM specific fields: (optional) whenever possible using the same naming of filter_dict param |
| 249 | List can be empty if no network map the filter_dict. Raise an exception only upon VIM connectivity, |
| 250 | authorization, or some other unspecific error |
| jomacarpe | ea2a73e | 2018-02-27 13:48:22 +0100 | [diff] [blame] | 251 | """ |
| jomacarpe | 20c1323 | 2019-06-12 21:20:51 +0000 | [diff] [blame] | 252 | |
| jomacarpe | ea2a73e | 2018-02-27 13:48:22 +0100 | [diff] [blame] | 253 | try: |
| jomacarpe | 20c1323 | 2019-06-12 21:20:51 +0000 | [diff] [blame] | 254 | one = self._new_one_connection() |
| 255 | net_pool = one.vnpool.info(-2, -1, -1).VNET |
| jomacarpe | ea2a73e | 2018-02-27 13:48:22 +0100 | [diff] [blame] | 256 | response = [] |
| 257 | if "name" in filter_dict.keys(): |
| 258 | network_name_filter = filter_dict["name"] |
| 259 | else: |
| 260 | network_name_filter = None |
| 261 | if "id" in filter_dict.keys(): |
| 262 | network_id_filter = filter_dict["id"] |
| 263 | else: |
| 264 | network_id_filter = None |
| jomacarpe | 20c1323 | 2019-06-12 21:20:51 +0000 | [diff] [blame] | 265 | for network in net_pool: |
| 266 | if network.NAME == network_name_filter or str(network.ID) == str(network_id_filter): |
| 267 | net_dict = {"name": network.NAME, "id": str(network.ID), "status": "ACTIVE"} |
| jomacarpe | ea2a73e | 2018-02-27 13:48:22 +0100 | [diff] [blame] | 268 | response.append(net_dict) |
| 269 | return response |
| 270 | except Exception as e: |
| 271 | self.logger.error("Get network list error: " + str(e)) |
| 272 | raise vimconn.vimconnException(e) |
| 273 | |
| 274 | def get_network(self, net_id): |
| jomacarpe | 20c1323 | 2019-06-12 21:20:51 +0000 | [diff] [blame] | 275 | """Obtain network details from the 'net_id' VIM network |
| 276 | Return a dict that contains: |
| 277 | 'id': (mandatory) VIM network id, that is, net_id |
| 278 | 'name': (mandatory) VIM network name |
| 279 | 'status': (mandatory) can be 'ACTIVE', 'INACTIVE', 'DOWN', 'BUILD', 'ERROR', 'VIM_ERROR', 'OTHER' |
| 280 | 'error_msg': (optional) text that explains the ERROR status |
| 281 | other VIM specific fields: (optional) whenever possible using the same naming of filter_dict param |
| 282 | Raises an exception upon error or when network is not found |
| 283 | """ |
| jomacarpe | ea2a73e | 2018-02-27 13:48:22 +0100 | [diff] [blame] | 284 | try: |
| jomacarpe | 20c1323 | 2019-06-12 21:20:51 +0000 | [diff] [blame] | 285 | one = self._new_one_connection() |
| 286 | net_pool = one.vnpool.info(-2, -1, -1).VNET |
| jomacarpe | ea2a73e | 2018-02-27 13:48:22 +0100 | [diff] [blame] | 287 | net = {} |
| jomacarpe | 20c1323 | 2019-06-12 21:20:51 +0000 | [diff] [blame] | 288 | for network in net_pool: |
| 289 | if str(network.ID) == str(net_id): |
| 290 | net['id'] = network.ID |
| 291 | net['name'] = network.NAME |
| jomacarpe | ea2a73e | 2018-02-27 13:48:22 +0100 | [diff] [blame] | 292 | net['status'] = "ACTIVE" |
| 293 | break |
| 294 | if net: |
| 295 | return net |
| 296 | else: |
| 297 | raise vimconn.vimconnNotFoundException("Network {} not found".format(net_id)) |
| 298 | except Exception as e: |
| jomacarpe | 20c1323 | 2019-06-12 21:20:51 +0000 | [diff] [blame] | 299 | self.logger.error("Get network " + str(net_id) + " error): " + str(e)) |
| 300 | raise vimconn.vimconnException(e) |
| jomacarpe | ea2a73e | 2018-02-27 13:48:22 +0100 | [diff] [blame] | 301 | |
| garciadeblas | ebd6672 | 2019-01-31 16:01:31 +0000 | [diff] [blame] | 302 | def delete_network(self, net_id, created_items=None): |
| 303 | """ |
| 304 | Removes a tenant network from VIM and its associated elements |
| 305 | :param net_id: VIM identifier of the network, provided by method new_network |
| 306 | :param created_items: dictionary with extra items to be deleted. provided by method new_network |
| 307 | Returns the network identifier or raises an exception upon error or when network is not found |
| jomacarpe | ea2a73e | 2018-02-27 13:48:22 +0100 | [diff] [blame] | 308 | """ |
| 309 | try: |
| jomacarpe | ea2a73e | 2018-02-27 13:48:22 +0100 | [diff] [blame] | 310 | |
| jomacarpe | 20c1323 | 2019-06-12 21:20:51 +0000 | [diff] [blame] | 311 | one = self._new_one_connection() |
| 312 | one.vn.delete(int(net_id)) |
| 313 | return net_id |
| jomacarpe | ea2a73e | 2018-02-27 13:48:22 +0100 | [diff] [blame] | 314 | except Exception as e: |
| jomacarpe | 20c1323 | 2019-06-12 21:20:51 +0000 | [diff] [blame] | 315 | self.logger.error("Delete network " + str(net_id) + "error: network not found" + str(e)) |
| jomacarpe | ea2a73e | 2018-02-27 13:48:22 +0100 | [diff] [blame] | 316 | raise vimconn.vimconnException(e) |
| 317 | |
| jomacarpe | ea2a73e | 2018-02-27 13:48:22 +0100 | [diff] [blame] | 318 | def refresh_nets_status(self, net_list): |
| 319 | """Get the status of the networks |
| jomacarpe | 20c1323 | 2019-06-12 21:20:51 +0000 | [diff] [blame] | 320 | Params: |
| 321 | 'net_list': a list with the VIM network id to be get the status |
| 322 | Returns a dictionary with: |
| 323 | 'net_id': #VIM id of this network |
| 324 | status: #Mandatory. Text with one of: |
| 325 | # DELETED (not found at vim) |
| 326 | # VIM_ERROR (Cannot connect to VIM, authentication problems, VIM response error, ...) |
| 327 | # OTHER (Vim reported other status not understood) |
| 328 | # ERROR (VIM indicates an ERROR status) |
| 329 | # ACTIVE, INACTIVE, DOWN (admin down), |
| 330 | # BUILD (on building process) |
| 331 | error_msg: #Text with VIM error message, if any. Or the VIM connection ERROR |
| 332 | vim_info: #Text with plain information obtained from vim (yaml.safe_dump) |
| 333 | 'net_id2': ... |
| jomacarpe | ea2a73e | 2018-02-27 13:48:22 +0100 | [diff] [blame] | 334 | """ |
| 335 | net_dict = {} |
| 336 | try: |
| 337 | for net_id in net_list: |
| 338 | net = {} |
| 339 | try: |
| 340 | net_vim = self.get_network(net_id) |
| 341 | net["status"] = net_vim["status"] |
| 342 | net["vim_info"] = None |
| 343 | except vimconn.vimconnNotFoundException as e: |
| 344 | self.logger.error("Exception getting net status: {}".format(str(e))) |
| 345 | net['status'] = "DELETED" |
| 346 | net['error_msg'] = str(e) |
| 347 | except vimconn.vimconnException as e: |
| 348 | self.logger.error(e) |
| 349 | net["status"] = "VIM_ERROR" |
| 350 | net["error_msg"] = str(e) |
| 351 | net_dict[net_id] = net |
| 352 | return net_dict |
| 353 | except vimconn.vimconnException as e: |
| 354 | self.logger.error(e) |
| 355 | for k in net_dict: |
| 356 | net_dict[k]["status"] = "VIM_ERROR" |
| 357 | net_dict[k]["error_msg"] = str(e) |
| 358 | return net_dict |
| 359 | |
| jomacarpe | 20c1323 | 2019-06-12 21:20:51 +0000 | [diff] [blame] | 360 | def get_flavor(self, flavor_id): # Esta correcto |
| 361 | """Obtain flavor details from the VIM |
| 362 | Returns the flavor dict details {'id':<>, 'name':<>, other vim specific } |
| 363 | Raises an exception upon error or if not found |
| 364 | """ |
| 365 | try: |
| 366 | |
| 367 | one = self._new_one_connection() |
| 368 | template = one.template.info(int(flavor_id)) |
| 369 | if template is not None: |
| 370 | return {'id': template.ID, 'name': template.NAME} |
| 371 | raise vimconn.vimconnNotFoundException("Flavor {} not found".format(flavor_id)) |
| 372 | except Exception as e: |
| 373 | self.logger.error("get flavor " + str(flavor_id) + " error: " + str(e)) |
| 374 | raise vimconn.vimconnException(e) |
| 375 | |
| 376 | def new_flavor(self, flavor_data): |
| 377 | """Adds a tenant flavor to VIM |
| 378 | flavor_data contains a dictionary with information, keys: |
| 379 | name: flavor name |
| 380 | ram: memory (cloud type) in MBytes |
| 381 | vpcus: cpus (cloud type) |
| 382 | extended: EPA parameters |
| 383 | - numas: #items requested in same NUMA |
| 384 | memory: number of 1G huge pages memory |
| 385 | paired-threads|cores|threads: number of paired hyperthreads, complete cores OR individual threads |
| 386 | interfaces: # passthrough(PT) or SRIOV interfaces attached to this numa |
| 387 | - name: interface name |
| 388 | dedicated: yes|no|yes:sriov; for PT, SRIOV or only one SRIOV for the physical NIC |
| 389 | bandwidth: X Gbps; requested guarantee bandwidth |
| 390 | vpci: requested virtual PCI address |
| 391 | disk: disk size |
| 392 | is_public: |
| 393 | #TODO to concrete |
| 394 | Returns the flavor identifier""" |
| 395 | |
| 396 | disk_size = str(int(flavor_data["disk"])*1024) |
| 397 | |
| 398 | try: |
| 399 | one = self._new_one_connection() |
| 400 | template_id = one.template.allocate({ |
| 401 | 'TEMPLATE': { |
| 402 | 'NAME': flavor_data["name"], |
| 403 | 'CPU': flavor_data["vcpus"], |
| 404 | 'VCPU': flavor_data["vcpus"], |
| 405 | 'MEMORY': flavor_data["ram"], |
| 406 | 'DISK': { |
| 407 | 'SIZE': disk_size |
| 408 | }, |
| 409 | 'CONTEXT': { |
| 410 | 'NETWORK': "YES", |
| 411 | 'SSH_PUBLIC_KEY': '$USER[SSH_PUBLIC_KEY]' |
| 412 | }, |
| 413 | 'GRAPHICS': { |
| 414 | 'LISTEN': '0.0.0.0', |
| 415 | 'TYPE': 'VNC' |
| 416 | }, |
| 417 | 'CLUSTER_ID': self.config["cluster"]["id"] |
| 418 | } |
| 419 | }) |
| 420 | return template_id |
| 421 | |
| 422 | except Exception as e: |
| 423 | self.logger.error("Create new flavor error: " + str(e)) |
| 424 | raise vimconn.vimconnException(e) |
| 425 | |
| 426 | def delete_flavor(self, flavor_id): |
| 427 | """ Deletes a tenant flavor from VIM |
| 428 | Returns the old flavor_id |
| 429 | """ |
| 430 | try: |
| 431 | one = self._new_one_connection() |
| 432 | one.template.delete(int(flavor_id), False) |
| 433 | return flavor_id |
| 434 | except Exception as e: |
| 435 | self.logger.error("Error deleting flavor " + str(flavor_id) + ". Flavor not found") |
| 436 | raise vimconn.vimconnException(e) |
| 437 | |
| 438 | def get_image_list(self, filter_dict={}): |
| 439 | """Obtain tenant images from VIM |
| 440 | Filter_dict can be: |
| 441 | name: image name |
| 442 | id: image uuid |
| 443 | checksum: image checksum |
| 444 | location: image path |
| 445 | Returns the image list of dictionaries: |
| 446 | [{<the fields at Filter_dict plus some VIM specific>}, ...] |
| 447 | List can be empty |
| 448 | """ |
| 449 | try: |
| 450 | one = self._new_one_connection() |
| 451 | image_pool = one.imagepool.info(-2, -1, -1).IMAGE |
| 452 | images = [] |
| 453 | if "name" in filter_dict.keys(): |
| 454 | image_name_filter = filter_dict["name"] |
| 455 | else: |
| 456 | image_name_filter = None |
| 457 | if "id" in filter_dict.keys(): |
| 458 | image_id_filter = filter_dict["id"] |
| 459 | else: |
| 460 | image_id_filter = None |
| 461 | for image in image_pool: |
| 462 | if str(image_name_filter) == str(image.NAME) or str(image.ID) == str(image_id_filter): |
| 463 | images_dict = {"name": image.NAME, "id": str(image.ID)} |
| 464 | images.append(images_dict) |
| 465 | return images |
| 466 | except Exception as e: |
| 467 | self.logger.error("Get image list error: " + str(e)) |
| 468 | raise vimconn.vimconnException(e) |
| 469 | |
| 470 | def new_vminstance(self, name, description, start, image_id, flavor_id, net_list, cloud_config=None, disk_list=None, |
| 471 | availability_zone_index=None, availability_zone_list=None): |
| 472 | |
| 473 | """Adds a VM instance to VIM |
| 474 | Params: |
| 475 | 'start': (boolean) indicates if VM must start or created in pause mode. |
| 476 | 'image_id','flavor_id': image and flavor VIM id to use for the VM |
| 477 | 'net_list': list of interfaces, each one is a dictionary with: |
| 478 | 'name': (optional) name for the interface. |
| 479 | 'net_id': VIM network id where this interface must be connect to. Mandatory for type==virtual |
| 480 | 'vpci': (optional) virtual vPCI address to assign at the VM. Can be ignored depending on VIM capabilities |
| 481 | 'model': (optional and only have sense for type==virtual) interface model: virtio, e1000, ... |
| 482 | 'mac_address': (optional) mac address to assign to this interface |
| 483 | 'ip_address': (optional) IP address to assign to this interface |
| 484 | #TODO: CHECK if an optional 'vlan' parameter is needed for VIMs when type if VF and net_id is not provided, |
| 485 | the VLAN tag to be used. In case net_id is provided, the internal network vlan is used for tagging VF |
| 486 | 'type': (mandatory) can be one of: |
| 487 | 'virtual', in this case always connected to a network of type 'net_type=bridge' |
| 488 | 'PCI-PASSTHROUGH' or 'PF' (passthrough): depending on VIM capabilities it can be connected to a data/ptp network ot it |
| 489 | can created unconnected |
| 490 | 'SR-IOV' or 'VF' (SRIOV with VLAN tag): same as PF for network connectivity. |
| 491 | 'VFnotShared'(SRIOV without VLAN tag) same as PF for network connectivity. VF where no other VFs |
| 492 | are allocated on the same physical NIC |
| 493 | 'bw': (optional) only for PF/VF/VFnotShared. Minimal Bandwidth required for the interface in GBPS |
| 494 | 'port_security': (optional) If False it must avoid any traffic filtering at this interface. If missing |
| 495 | or True, it must apply the default VIM behaviour |
| 496 | After execution the method will add the key: |
| 497 | 'vim_id': must be filled/added by this method with the VIM identifier generated by the VIM for this |
| 498 | interface. 'net_list' is modified |
| 499 | 'cloud_config': (optional) dictionary with: |
| 500 | 'key-pairs': (optional) list of strings with the public key to be inserted to the default user |
| 501 | 'users': (optional) list of users to be inserted, each item is a dict with: |
| 502 | 'name': (mandatory) user name, |
| 503 | 'key-pairs': (optional) list of strings with the public key to be inserted to the user |
| 504 | 'user-data': (optional) can be a string with the text script to be passed directly to cloud-init, |
| 505 | or a list of strings, each one contains a script to be passed, usually with a MIMEmultipart file |
| 506 | 'config-files': (optional). List of files to be transferred. Each item is a dict with: |
| 507 | 'dest': (mandatory) string with the destination absolute path |
| 508 | 'encoding': (optional, by default text). Can be one of: |
| 509 | 'b64', 'base64', 'gz', 'gz+b64', 'gz+base64', 'gzip+b64', 'gzip+base64' |
| 510 | 'content' (mandatory): string with the content of the file |
| 511 | 'permissions': (optional) string with file permissions, typically octal notation '0644' |
| 512 | 'owner': (optional) file owner, string with the format 'owner:group' |
| 513 | 'boot-data-drive': boolean to indicate if user-data must be passed using a boot drive (hard disk) |
| 514 | 'disk_list': (optional) list with additional disks to the VM. Each item is a dict with: |
| 515 | 'image_id': (optional). VIM id of an existing image. If not provided an empty disk must be mounted |
| 516 | 'size': (mandatory) string with the size of the disk in GB |
| 517 | availability_zone_index: Index of availability_zone_list to use for this this VM. None if not AV required |
| 518 | availability_zone_list: list of availability zones given by user in the VNFD descriptor. Ignore if |
| 519 | availability_zone_index is None |
| 520 | Returns a tuple with the instance identifier and created_items or raises an exception on error |
| 521 | created_items can be None or a dictionary where this method can include key-values that will be passed to |
| 522 | the method delete_vminstance and action_vminstance. Can be used to store created ports, volumes, etc. |
| 523 | Format is vimconnector dependent, but do not use nested dictionaries and a value of None should be the same |
| 524 | as not present. |
| 525 | """ |
| 526 | self.logger.debug( |
| 527 | "new_vminstance input: image='{}' flavor='{}' nics='{}'".format(image_id, flavor_id, str(net_list))) |
| 528 | try: |
| 529 | one = self._new_one_connection() |
| 530 | template_vim = one.template.info(int(flavor_id), True) |
| 531 | disk_size = str(template_vim.TEMPLATE["DISK"]["SIZE"]) |
| 532 | |
| 533 | one = self._new_one_connection() |
| 534 | template_updated = "" |
| 535 | for net in net_list: |
| 536 | net_in_vim = one.vn.info(int(net["net_id"])) |
| 537 | net["vim_id"] = str(net_in_vim.ID) |
| 538 | network = 'NIC = [NETWORK = "{}",NETWORK_UNAME = "{}" ]'.format( |
| 539 | net_in_vim.NAME, net_in_vim.UNAME) |
| 540 | template_updated += network |
| 541 | |
| 542 | template_updated += "DISK = [ IMAGE_ID = {},\n SIZE = {}]".format(image_id, disk_size) |
| 543 | |
| 544 | if isinstance(cloud_config, dict): |
| 545 | if cloud_config.get("key-pairs"): |
| 546 | context = 'CONTEXT = [\n NETWORK = "YES",\n SSH_PUBLIC_KEY = "' |
| 547 | for key in cloud_config["key-pairs"]: |
| 548 | context += key + '\n' |
| 549 | # if False: |
| 550 | # context += '"\n USERNAME = ' |
| 551 | context += '"]' |
| 552 | template_updated += context |
| 553 | |
| 554 | vm_instance_id = one.template.instantiate(int(flavor_id), name, False, template_updated) |
| 555 | self.logger.info( |
| 556 | "Instanciating in OpenNebula a new VM name:{} id:{}".format(name, flavor_id)) |
| 557 | return str(vm_instance_id), None |
| 558 | except pyone.OneNoExistsException as e: |
| 559 | self.logger.error("Network with id " + str(e) + " not found: " + str(e)) |
| 560 | raise vimconn.vimconnNotFoundException(e) |
| 561 | except Exception as e: |
| 562 | self.logger.error("Create new vm instance error: " + str(e)) |
| 563 | raise vimconn.vimconnException(e) |
| 564 | |
| 565 | def get_vminstance(self, vm_id): |
| 566 | """Returns the VM instance information from VIM""" |
| 567 | try: |
| 568 | one = self._new_one_connection() |
| 569 | vm = one.vm.info(int(vm_id)) |
| 570 | return vm |
| 571 | except Exception as e: |
| 572 | self.logger.error("Getting vm instance error: " + str(e) + ": VM Instance not found") |
| 573 | raise vimconn.vimconnException(e) |
| 574 | |
| 575 | def delete_vminstance(self, vm_id, created_items=None): |
| 576 | """ |
| 577 | Removes a VM instance from VIM and its associated elements |
| 578 | :param vm_id: VIM identifier of the VM, provided by method new_vminstance |
| 579 | :param created_items: dictionary with extra items to be deleted. provided by method new_vminstance and/or method |
| 580 | action_vminstance |
| 581 | :return: None or the same vm_id. Raises an exception on fail |
| 582 | """ |
| 583 | try: |
| 584 | one = self._new_one_connection() |
| 585 | one.vm.recover(int(vm_id), 3) |
| 586 | vm = None |
| 587 | while True: |
| 588 | if vm is not None and vm.LCM_STATE == 0: |
| 589 | break |
| 590 | else: |
| 591 | vm = one.vm.info(int(vm_id)) |
| 592 | |
| 593 | except pyone.OneNoExistsException as e: |
| 594 | self.logger.info("The vm " + str(vm_id) + " does not exist or is already deleted") |
| 595 | raise vimconn.vimconnNotFoundException("The vm {} does not exist or is already deleted".format(vm_id)) |
| 596 | except Exception as e: |
| 597 | self.logger.error("Delete vm instance " + str(vm_id) + " error: " + str(e)) |
| 598 | raise vimconn.vimconnException(e) |
| 599 | |
| 600 | def refresh_vms_status(self, vm_list): |
| 601 | """Get the status of the virtual machines and their interfaces/ports |
| 602 | Params: the list of VM identifiers |
| 603 | Returns a dictionary with: |
| 604 | vm_id: #VIM id of this Virtual Machine |
| 605 | status: #Mandatory. Text with one of: |
| 606 | # DELETED (not found at vim) |
| 607 | # VIM_ERROR (Cannot connect to VIM, VIM response error, ...) |
| 608 | # OTHER (Vim reported other status not understood) |
| 609 | # ERROR (VIM indicates an ERROR status) |
| 610 | # ACTIVE, PAUSED, SUSPENDED, INACTIVE (not running), |
| 611 | # BUILD (on building process), ERROR |
| 612 | # ACTIVE:NoMgmtIP (Active but any of its interface has an IP address |
| 613 | # |
| 614 | error_msg: #Text with VIM error message, if any. Or the VIM connection ERROR |
| 615 | vim_info: #Text with plain information obtained from vim (yaml.safe_dump) |
| 616 | interfaces: list with interface info. Each item a dictionary with: |
| 617 | vim_info: #Text with plain information obtained from vim (yaml.safe_dump) |
| 618 | mac_address: #Text format XX:XX:XX:XX:XX:XX |
| 619 | vim_net_id: #network id where this interface is connected, if provided at creation |
| 620 | vim_interface_id: #interface/port VIM id |
| 621 | ip_address: #null, or text with IPv4, IPv6 address |
| 622 | compute_node: #identification of compute node where PF,VF interface is allocated |
| 623 | pci: #PCI address of the NIC that hosts the PF,VF |
| 624 | vlan: #physical VLAN used for VF |
| 625 | """ |
| 626 | vm_dict = {} |
| 627 | try: |
| 628 | for vm_id in vm_list: |
| 629 | vm = {} |
| 630 | if self.get_vminstance(vm_id) is not None: |
| 631 | vm_element = self.get_vminstance(vm_id) |
| 632 | else: |
| 633 | self.logger.info("The vm " + str(vm_id) + " does not exist.") |
| 634 | vm['status'] = "DELETED" |
| 635 | vm['error_msg'] = ("The vm " + str(vm_id) + " does not exist.") |
| 636 | continue |
| 637 | vm["vim_info"] = None |
| 638 | vm_status = vm_element.LCM_STATE |
| 639 | if vm_status == 3: |
| 640 | vm['status'] = "ACTIVE" |
| 641 | elif vm_status == 36: |
| 642 | vm['status'] = "ERROR" |
| 643 | vm['error_msg'] = "VM failure" |
| 644 | else: |
| 645 | vm['status'] = "BUILD" |
| 646 | |
| 647 | if vm_element is not None: |
| 648 | interfaces = self._get_networks_vm(vm_element) |
| 649 | vm["interfaces"] = interfaces |
| 650 | vm_dict[vm_id] = vm |
| 651 | return vm_dict |
| 652 | except Exception as e: |
| 653 | self.logger.error(e) |
| 654 | for k in vm_dict: |
| 655 | vm_dict[k]["status"] = "VIM_ERROR" |
| 656 | vm_dict[k]["error_msg"] = str(e) |
| 657 | return vm_dict |
| 658 | |
| 659 | def _get_networks_vm(self, vm_element): |
| 660 | interfaces = [] |
| 661 | try: |
| 662 | if isinstance(vm_element.TEMPLATE["NIC"], list): |
| 663 | for net in vm_element.TEMPLATE["NIC"]: |
| 664 | interface = {'vim_info': None, "mac_address": str(net["MAC"]), "vim_net_id": str(net["NETWORK_ID"]), |
| 665 | "vim_interface_id": str(net["NETWORK_ID"])} |
| 666 | # maybe it should be 2 different keys for ip_address if an interface has ipv4 and ipv6 |
| 667 | if u'IP' in net: |
| 668 | interface["ip_address"] = str(net["IP"]) |
| 669 | if u'IP6_GLOBAL' in net: |
| 670 | interface["ip_address"] = str(net["IP6_GLOBAL"]) |
| 671 | interfaces.append(interface) |
| 672 | else: |
| 673 | net = vm_element.TEMPLATE["NIC"] |
| 674 | interface = {'vim_info': None, "mac_address": str(net["MAC"]), "vim_net_id": str(net["NETWORK_ID"]), |
| 675 | "vim_interface_id": str(net["NETWORK_ID"])} |
| 676 | # maybe it should be 2 different keys for ip_address if an interface has ipv4 and ipv6 |
| 677 | if u'IP' in net: |
| 678 | interface["ip_address"] = str(net["IP"]) |
| 679 | if u'IP6_GLOBAL' in net: |
| 680 | interface["ip_address"] = str(net["IP6_GLOBAL"]) |
| 681 | interfaces.append(interface) |
| 682 | return interfaces |
| 683 | except Exception as e: |
| albertoflorez | 3ade00b | 2019-07-08 13:16:21 +0200 | [diff] [blame] | 684 | self.logger.error("Error getting vm interface_information of vm_id: " + str(vm_element.ID)) |