| bayramov | 325fa1c | 2016-09-08 01:42:46 -0700 | [diff] [blame^] | 1 | # -*- coding: utf-8 -*- |
| 2 | |
| 3 | ## |
| 4 | # Copyright 2015 Telefónica Investigación y Desarrollo, S.A.U. |
| 5 | # This file is part of openmano |
| 6 | # All Rights Reserved. |
| 7 | # |
| 8 | # Licensed under the Apache License, Version 2.0 (the "License"); you may |
| 9 | # not use this file except in compliance with the License. You may obtain |
| 10 | # a copy of the License at |
| 11 | # |
| 12 | # http://www.apache.org/licenses/LICENSE-2.0 |
| 13 | # |
| 14 | # Unless required by applicable law or agreed to in writing, software |
| 15 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT |
| 16 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the |
| 17 | # License for the specific language governing permissions and limitations |
| 18 | # under the License. |
| 19 | # |
| 20 | # For those usages not covered by the Apache License, Version 2.0 please |
| 21 | # contact with: nfvlabs@tid.es |
| 22 | ## |
| 23 | |
| 24 | ''' |
| 25 | vimconn_vmware implementation an Abstract class in order to interact with VMware vCloud Director. |
| 26 | mbayramov@vmware.com |
| 27 | ''' |
| 28 | import os |
| 29 | import requests |
| 30 | |
| 31 | |
| 32 | from xml.etree import ElementTree as ET |
| 33 | |
| 34 | from pyvcloud import Http |
| 35 | from pyvcloud.vcloudair import VCA |
| 36 | from pyvcloud.schema.vcd.v1_5.schemas.vcloud import sessionType, organizationType, \ |
| 37 | vAppType, organizationListType, vdcType, catalogType, queryRecordViewType, \ |
| 38 | networkType, vcloudType, taskType, diskType, vmsType, vdcTemplateListType, mediaType |
| 39 | from xml.sax.saxutils import escape |
| 40 | |
| 41 | import logging |
| 42 | import json |
| 43 | import vimconn |
| 44 | import time |
| 45 | import uuid |
| 46 | import httplib |
| 47 | |
| 48 | |
| 49 | __author__="Mustafa Bayramov" |
| 50 | __date__ ="$26-Aug-2016 11:09:29$" |
| 51 | |
| 52 | |
| 53 | #Error variables |
| 54 | HTTP_Bad_Request = 400 |
| 55 | HTTP_Unauthorized = 401 |
| 56 | HTTP_Not_Found = 404 |
| 57 | HTTP_Method_Not_Allowed = 405 |
| 58 | HTTP_Request_Timeout = 408 |
| 59 | HTTP_Conflict = 409 |
| 60 | HTTP_Not_Implemented = 501 |
| 61 | HTTP_Service_Unavailable = 503 |
| 62 | HTTP_Internal_Server_Error = 500 |
| 63 | |
| 64 | class vimconnException(Exception): |
| 65 | '''Common and base class Exception for all vimconnector exceptions''' |
| 66 | def __init__(self, message, http_code=HTTP_Bad_Request): |
| 67 | Exception.__init__(self, message) |
| 68 | self.http_code = http_code |
| 69 | |
| 70 | class vimconnConnectionException(vimconnException): |
| 71 | '''Connectivity error with the VIM''' |
| 72 | def __init__(self, message, http_code=HTTP_Service_Unavailable): |
| 73 | vimconnException.__init__(self, message, http_code) |
| 74 | |
| 75 | class vimconnUnexpectedResponse(vimconnException): |
| 76 | '''Get an wrong response from VIM''' |
| 77 | def __init__(self, message, http_code=HTTP_Service_Unavailable): |
| 78 | vimconnException.__init__(self, message, http_code) |
| 79 | |
| 80 | class vimconnAuthException(vimconnException): |
| 81 | '''Invalid credentials or authorization to perform this action over the VIM''' |
| 82 | def __init__(self, message, http_code=HTTP_Unauthorized): |
| 83 | vimconnException.__init__(self, message, http_code) |
| 84 | |
| 85 | class vimconnNotFoundException(vimconnException): |
| 86 | '''The item is not found at VIM''' |
| 87 | def __init__(self, message, http_code=HTTP_Not_Found): |
| 88 | vimconnException.__init__(self, message, http_code) |
| 89 | |
| 90 | class vimconnConflictException(vimconnException): |
| 91 | '''There is a conflict, e.g. more item found than one''' |
| 92 | def __init__(self, message, http_code=HTTP_Conflict): |
| 93 | vimconnException.__init__(self, message, http_code) |
| 94 | |
| 95 | class vimconnNotImplemented(vimconnException): |
| 96 | '''The method is not implemented by the connected''' |
| 97 | def __init__(self, message, http_code=HTTP_Not_Implemented): |
| 98 | vimconnException.__init__(self, message, http_code) |
| 99 | |
| 100 | |
| 101 | flavorlist = {} |
| 102 | |
| 103 | class vimconnector(): |
| 104 | '''Vmware VIM Connector base class |
| 105 | ''' |
| 106 | def __init__(self, uuid, name, tenant_id, tenant_name, url, url_admin=None, user=None, passwd=None, log_level="ERROR", config={}): |
| 107 | self.id = uuid |
| 108 | self.name = name |
| 109 | self.url = url |
| 110 | self.url_admin = url_admin |
| 111 | self.tenant_id = tenant_id |
| 112 | self.tenant_name = tenant_name |
| 113 | self.user = user |
| 114 | self.passwd = passwd |
| 115 | self.config = config |
| 116 | self.logger = logging.getLogger('mano.vim.vmware') |
| 117 | |
| 118 | formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') |
| 119 | ch = logging.StreamHandler() |
| 120 | ch.setLevel(log_level) |
| 121 | ch.setFormatter(formatter) |
| 122 | self.logger.addHandler(ch) |
| 123 | self.logger.setLevel( getattr(logging, log_level)) |
| 124 | |
| 125 | # self.logger = logging.getLogger('mano.vim.vmware') |
| 126 | |
| 127 | self.logger.debug("Vmware tenant from VIM filter: '%s'", user) |
| 128 | self.logger.debug("Vmware tenant from VIM filter: '%s'", passwd) |
| 129 | |
| 130 | if not url: |
| 131 | raise TypeError, 'url param can not be NoneType' |
| 132 | |
| 133 | if not self.url_admin: #try to use normal url |
| 134 | self.url_admin = self.url |
| 135 | |
| 136 | self.vcaversion = '5.6' |
| 137 | |
| 138 | print "Calling constructor with following paramters" |
| 139 | print " UUID: {} ".format(uuid) |
| 140 | print " name: {} ".format(name) |
| 141 | print " tenant_id: {} ".format(tenant_id) |
| 142 | print " tenant_name: {} ".format(tenant_name) |
| 143 | print " url: {} ".format(url) |
| 144 | print " url_admin: {} ".format(url_admin) |
| 145 | print " user: {} ".format(user) |
| 146 | print " passwd: {} ".format(passwd) |
| 147 | print " debug: {} ".format(log_level) |
| 148 | |
| 149 | def __getitem__(self,index): |
| 150 | if index=='tenant_id': |
| 151 | return self.tenant_id |
| 152 | if index=='tenant_name': |
| 153 | return self.tenant_name |
| 154 | elif index=='id': |
| 155 | return self.id |
| 156 | elif index=='name': |
| 157 | return self.name |
| 158 | elif index=='user': |
| 159 | return self.user |
| 160 | elif index=='passwd': |
| 161 | return self.passwd |
| 162 | elif index=='url': |
| 163 | return self.url |
| 164 | elif index=='url_admin': |
| 165 | return self.url_admin |
| 166 | elif index=="config": |
| 167 | return self.config |
| 168 | else: |
| 169 | raise KeyError("Invalid key '%s'" %str(index)) |
| 170 | |
| 171 | def __setitem__(self,index, value): |
| 172 | if index=='tenant_id': |
| 173 | self.tenant_id = value |
| 174 | if index=='tenant_name': |
| 175 | self.tenant_name = value |
| 176 | elif index=='id': |
| 177 | self.id = value |
| 178 | elif index=='name': |
| 179 | self.name = value |
| 180 | elif index=='user': |
| 181 | self.user = value |
| 182 | elif index=='passwd': |
| 183 | self.passwd = value |
| 184 | elif index=='url': |
| 185 | self.url = value |
| 186 | elif index=='url_admin': |
| 187 | self.url_admin = value |
| 188 | else: |
| 189 | raise KeyError("Invalid key '%s'" %str(index)) |
| 190 | |
| 191 | def connect(self): |
| 192 | |
| 193 | service_type = 'standalone' |
| 194 | version = '5.6' |
| 195 | |
| 196 | self.logger.debug("Logging in to a VCA '%s'", self.name) |
| 197 | |
| 198 | vca = VCA(host=self.url, username=self.user, service_type=service_type, version=version, verify=False, log=True) |
| 199 | result = vca.login(password=self.passwd, org=self.name) |
| 200 | if not result: |
| 201 | raise KeyError("Can't connect to a vCloud director.") |
| 202 | result = vca.login(token=vca.token, org=self.name, org_url=vca.vcloud_session.org_url) |
| 203 | if result is True: |
| 204 | self.logger.debug("Successfully logged to a VCA '%s'", self.name) |
| 205 | |
| 206 | # vca = VCA(host='172.16.254.206', username=self.user, service_type='standalone', version='5.6', verify=False, log=True) |
| 207 | # vca.login(password=self.passwd, org=self.name) |
| 208 | # vca.login(token=vca.token, org=self.name, org_url=vca.vcloud_session.org_url) |
| 209 | |
| 210 | # if not result: |
| 211 | # result = vca.login(token=vca.token, org=self.name, org_url=vca.vcloud_session.org_url) |
| 212 | # if not result: |
| 213 | # raise KeyError("Can't connect to a vcloud director") |
| 214 | # else: |
| 215 | # print "Logged to VCA via existing token" |
| 216 | # else: |
| 217 | # print "Logged to VCA" |
| 218 | |
| 219 | return vca |
| 220 | |
| 221 | |
| 222 | def new_tenant(self,tenant_name,tenant_description): |
| 223 | '''Adds a new tenant to VIM with this name and description, |
| 224 | returns the tenant identifier''' |
| 225 | raise vimconnNotImplemented( "Should have implemented this" ) |
| 226 | |
| 227 | def delete_tenant(self,tenant_id,): |
| 228 | '''Delete a tenant from VIM''' |
| 229 | '''Returns the tenant identifier''' |
| 230 | raise vimconnNotImplemented( "Should have implemented this" ) |
| 231 | |
| 232 | def get_tenant_list(self, filter_dict={}): |
| 233 | '''Obtain tenants of VIM |
| 234 | filter_dict can contain the following keys: |
| 235 | name: filter by tenant name |
| 236 | id: filter by tenant uuid/id |
| 237 | <other VIM specific> |
| 238 | Returns the tenant list of dictionaries: |
| 239 | [{'name':'<name>, 'id':'<id>, ...}, ...] |
| 240 | ''' |
| 241 | raise vimconnNotImplemented( "Should have implemented this" ) |
| 242 | |
| 243 | def new_network(self,net_name, net_type, ip_profile=None, shared=False): |
| 244 | '''Adds a tenant network to VIM |
| 245 | net_name is the name |
| 246 | net_type can be 'bridge','data'.'ptp'. TODO: this need to be revised |
| 247 | ip_profile is a dict containing the IP parameters of the network |
| 248 | shared is a boolean |
| 249 | Returns the network identifier''' |
| 250 | |
| 251 | self.logger.debug("Vmware tenant from VIM filter: '%s'", net_name) |
| 252 | self.logger.debug("Vmware tenant from VIM filter: '%s'", net_type) |
| 253 | self.logger.debug("Vmware tenant from VIM filter: '%s'", ip_profile) |
| 254 | self.logger.debug("Vmware tenant from VIM filter: '%s'", shared) |
| 255 | |
| 256 | raise vimconnNotImplemented( "Should have implemented this" ) |
| 257 | |
| 258 | def get_network_list(self, filter_dict={}): |
| 259 | '''Obtain tenant networks of VIM |
| 260 | Filter_dict can be: |
| 261 | name: network name |
| 262 | id: network uuid |
| 263 | shared: boolean |
| 264 | tenant_id: tenant |
| 265 | admin_state_up: boolean |
| 266 | status: 'ACTIVE' |
| 267 | Returns the network list of dictionaries: |
| 268 | [{<the fields at Filter_dict plus some VIM specific>}, ...] |
| 269 | List can be empty |
| 270 | ''' |
| 271 | |
| 272 | vca = self.connect() |
| 273 | if not vca: |
| 274 | raise vimconn.vimconnConnectionException("self.connect() is failed") |
| 275 | |
| 276 | vdc = vca.get_vdc(self.tenant_name) |
| 277 | vdcid = vdc.get_id().split(":") |
| 278 | |
| 279 | networks = vca.get_networks(vdc.get_name()) |
| 280 | network_list = [] |
| 281 | for network in networks: |
| 282 | filter_dict = {} |
| 283 | netid = network.get_id().split(":") |
| 284 | self.logger.debug ("Adding {} to a list".format(netid[3])) |
| 285 | self.logger.debug ("VDC ID {} to a list".format(vdcid[3])) |
| 286 | self.logger.debug ("Network {} to a list".format(network.get_name())) |
| 287 | |
| 288 | filter_dict["name"] = network.get_name() |
| 289 | filter_dict["id"] = netid[3] |
| 290 | filter_dict["shared"] = network.get_IsShared() |
| 291 | filter_dict["tenant_id"] = vdcid[3] |
| 292 | if network.get_status() == 1: |
| 293 | filter_dict["admin_state_up"] = True |
| 294 | else: |
| 295 | filter_dict["admin_state_up"] = False |
| 296 | filter_dict["status"] = "ACTIVE" |
| 297 | filter_dict["type"] = "bridge" |
| 298 | network_list.append(filter_dict) |
| 299 | |
| 300 | self.logger.debug("Returning {}".format(network_list)) |
| 301 | return network_list |
| 302 | |
| 303 | def get_network(self, net_id): |
| 304 | '''Obtain network details of net_id VIM network' |
| 305 | Return a dict with the fields at filter_dict (see get_network_list) plus some VIM specific>}, ...]''' |
| 306 | raise vimconnNotImplemented( "Should have implemented this" ) |
| 307 | |
| 308 | def delete_network(self, net_id): |
| 309 | '''Deletes a tenant network from VIM, provide the network id. |
| 310 | Returns the network identifier or raise an exception''' |
| 311 | raise vimconnNotImplemented( "Should have implemented this" ) |
| 312 | |
| 313 | def refresh_nets_status(self, net_list): |
| 314 | '''Get the status of the networks |
| 315 | Params: the list of network identifiers |
| 316 | Returns a dictionary with: |
| 317 | net_id: #VIM id of this network |
| 318 | status: #Mandatory. Text with one of: |
| 319 | # DELETED (not found at vim) |
| 320 | # VIM_ERROR (Cannot connect to VIM, VIM response error, ...) |
| 321 | # OTHER (Vim reported other status not understood) |
| 322 | # ERROR (VIM indicates an ERROR status) |
| 323 | # ACTIVE, INACTIVE, DOWN (admin down), |
| 324 | # BUILD (on building process) |
| 325 | # |
| 326 | error_msg: #Text with VIM error message, if any. Or the VIM connection ERROR |
| 327 | vim_info: #Text with plain information obtained from vim (yaml.safe_dump) |
| 328 | |
| 329 | ''' |
| 330 | raise vimconnNotImplemented( "Should have implemented this" ) |
| 331 | |
| 332 | def get_flavor(self, flavor_id): |
| 333 | '''Obtain flavor details from the VIM |
| 334 | Returns the flavor dict details {'id':<>, 'name':<>, other vim specific } #TODO to concrete |
| 335 | ''' |
| 336 | |
| 337 | print " get_flavor contains {}".format(flavor_id) |
| 338 | |
| 339 | vca = self.connect() |
| 340 | if not vca: |
| 341 | raise vimconn.vimconnConnectionException("self.connect() is failed.") |
| 342 | |
| 343 | def new_flavor(self, flavor_data): |
| 344 | '''Adds a tenant flavor to VIM |
| 345 | flavor_data contains a dictionary with information, keys: |
| 346 | name: flavor name |
| 347 | ram: memory (cloud type) in MBytes |
| 348 | vpcus: cpus (cloud type) |
| 349 | extended: EPA parameters |
| 350 | - numas: #items requested in same NUMA |
| 351 | memory: number of 1G huge pages memory |
| 352 | paired-threads|cores|threads: number of paired hyperthreads, complete cores OR individual threads |
| 353 | interfaces: # passthrough(PT) or SRIOV interfaces attached to this numa |
| 354 | - name: interface name |
| 355 | dedicated: yes|no|yes:sriov; for PT, SRIOV or only one SRIOV for the physical NIC |
| 356 | bandwidth: X Gbps; requested guarantee bandwidth |
| 357 | vpci: requested virtual PCI address |
| 358 | disk: disk size |
| 359 | is_public: |
| 360 | |
| 361 | |
| 362 | |
| 363 | #TODO to concrete |
| 364 | Returns the flavor identifier''' |
| 365 | |
| 366 | flavor_uuid = uuid.uuid4() |
| 367 | flavorlist[flavor_uuid] = flavor_data |
| 368 | |
| 369 | print " new_flavor contains {}".format(flavor_data) |
| 370 | print " flavor list contains {}".format(flavorlist) |
| 371 | |
| 372 | return flavor_uuid |
| 373 | |
| 374 | def delete_flavor(self, flavor_id): |
| 375 | '''Deletes a tenant flavor from VIM identify by its id |
| 376 | Returns the used id or raise an exception''' |
| 377 | raise vimconnNotImplemented( "Should have implemented this" ) |
| 378 | |
| 379 | def new_image(self,image_dict): |
| 380 | ''' |
| 381 | Adds a tenant image to VIM |
| 382 | Returns: |
| 383 | 200, image-id if the image is created |
| 384 | <0, message if there is an error |
| 385 | ''' |
| 386 | |
| 387 | print " ################################################################### " |
| 388 | print " new_image contains {}".format(image_dict) |
| 389 | print " ################################################################### " |
| 390 | |
| 391 | |
| 392 | def delete_image(self, image_id): |
| 393 | '''Deletes a tenant image from VIM''' |
| 394 | '''Returns the HTTP response code and a message indicating details of the success or fail''' |
| 395 | |
| 396 | print " ################################################################### " |
| 397 | print " delete_image contains {}".format(image_id) |
| 398 | print " ################################################################### " |
| 399 | |
| 400 | raise vimconnNotImplemented( "Should have implemented this" ) |
| 401 | |
| 402 | def catalog_exists(self, catalog_name, catalogs): |
| 403 | for catalog in catalogs: |
| 404 | if catalog.name == catalog_name: |
| 405 | return True |
| 406 | return False |
| 407 | |
| 408 | def create_vimcatalog(self, vca, catalog_name): |
| 409 | """Create Catalog entry in VIM""" |
| 410 | task = vca.create_catalog(catalog_name, catalog_name) |
| 411 | result = vca.block_until_completed(task) |
| 412 | if not result: |
| 413 | return False |
| 414 | catalogs = vca.get_catalogs() |
| 415 | return self.catalog_exists(catalog_name, catalogs) |
| 416 | |
| 417 | |
| 418 | def upload_ovf(self, vca, catalog_name, item_name, media_file_name, description='', display_progress=False, |
| 419 | chunk_bytes=128 * 1024): |
| 420 | """ |
| 421 | Uploads a OVF file to a vCloud catalog |
| 422 | |
| 423 | :param catalog_name: (str): The name of the catalog to upload the media. |
| 424 | :param item_name: (str): The name of the media file in the catalog. |
| 425 | :param media_file_name: (str): The name of the local media file to upload. |
| 426 | :return: (bool) True if the media file was successfully uploaded, false otherwise. |
| 427 | """ |
| 428 | os.path.isfile(media_file_name) |
| 429 | statinfo = os.stat(media_file_name) |
| 430 | statinfo.st_size |
| 431 | |
| 432 | # find a catalog entry where we upload OVF. |
| 433 | # create vApp Template and check the status if vCD able to read OVF it will respond with appropirate |
| 434 | # status change. |
| 435 | # if VCD can parse OVF we upload VMDK file |
| 436 | for catalog in vca.get_catalogs(): |
| 437 | if catalog_name != catalog.name: |
| 438 | continue |
| 439 | link = filter(lambda link: link.get_type() == "application/vnd.vmware.vcloud.media+xml" and |
| 440 | link.get_rel() == 'add', catalog.get_Link()) |
| 441 | assert len(link) == 1 |
| 442 | data = """ |
| 443 | <UploadVAppTemplateParams name="%s Template" xmlns="http://www.vmware.com/vcloud/v1.5" xmlns:ovf="http://schemas.dmtf.org/ovf/envelope/1"><Description>%s vApp Template</Description></UploadVAppTemplateParams> |
| 444 | """ % (escape(item_name), escape(description)) |
| 445 | headers = vca.vcloud_session.get_vcloud_headers() |
| 446 | headers['Content-Type'] = 'application/vnd.vmware.vcloud.uploadVAppTemplateParams+xml' |
| 447 | response = Http.post(link[0].get_href(), headers=headers, data=data, verify=vca.verify, logger=self.logger) |
| 448 | if response.status_code == requests.codes.created: |
| 449 | catalogItem = ET.fromstring(response.content) |
| 450 | entity = [child for child in catalogItem if |
| 451 | child.get("type") == "application/vnd.vmware.vcloud.vAppTemplate+xml"][0] |
| 452 | href = entity.get('href') |
| 453 | template = href |
| 454 | response = Http.get(href, headers=vca.vcloud_session.get_vcloud_headers(), |
| 455 | verify=vca.verify, logger=self.logger) |
| 456 | |
| 457 | if response.status_code == requests.codes.ok: |
| 458 | media = mediaType.parseString(response.content, True) |
| 459 | link = filter(lambda link: link.get_rel() == 'upload:default', media.get_Files().get_File()[0].get_Link())[0] |
| 460 | headers = vca.vcloud_session.get_vcloud_headers() |
| 461 | headers['Content-Type'] = 'Content-Type text/xml' |
| 462 | response = Http.put(link.get_href(), data=open(media_file_name, 'rb'), headers=headers, verify=vca.verify,logger=self.logger) |
| 463 | if response.status_code != requests.codes.ok: |
| 464 | self.logger.debug("Failed create vApp template for catalog name {} and image {}".format(catalog_name, media_file_name)) |
| 465 | return False |
| 466 | |
| 467 | time.sleep(5) |
| 468 | |
| 469 | self.logger.debug("Failed create vApp template for catalog name {} and image {}". |
| 470 | format(catalog_name, media_file_name)) |
| 471 | |
| 472 | # uploading VMDK file |
| 473 | # check status of OVF upload |
| 474 | response = Http.get(template, headers=vca.vcloud_session.get_vcloud_headers(), verify=vca.verify, logger=self.logger) |
| 475 | if response.status_code == requests.codes.ok: |
| 476 | media = mediaType.parseString(response.content, True) |
| 477 | link = filter(lambda link: link.get_rel() == 'upload:default', media.get_Files().get_File()[0].get_Link())[0] |
| 478 | |
| 479 | # The OVF file and VMDK must be in a same directory |
| 480 | head, tail = os.path.split(media_file_name) |
| 481 | filevmdk = head + '/' + os.path.basename(link.get_href()) |
| 482 | |
| 483 | os.path.isfile(filevmdk) |
| 484 | statinfo = os.stat(filevmdk) |
| 485 | |
| 486 | # TODO debug output remove it |
| 487 | #print media.get_Files().get_File()[0].get_Link()[0].get_href() |
| 488 | #print media.get_Files().get_File()[1].get_Link()[0].get_href() |
| 489 | #print link.get_href() |
| 490 | |
| 491 | # in case first element is pointer to OVF. |
| 492 | hrefvmdk = link.get_href().replace("descriptor.ovf","Cirros-disk1.vmdk") |
| 493 | |
| 494 | f = open(filevmdk, 'rb') |
| 495 | bytes_transferred = 0 |
| 496 | while bytes_transferred < statinfo.st_size: |
| 497 | my_bytes = f.read(chunk_bytes) |
| 498 | if len(my_bytes) <= chunk_bytes: |
| 499 | headers = vca.vcloud_session.get_vcloud_headers() |
| 500 | headers['Content-Range'] = 'bytes %s-%s/%s' % (bytes_transferred, len(my_bytes) - 1, statinfo.st_size) |
| 501 | headers['Content-Length'] = str(len(my_bytes)) |
| 502 | response = Http.put(hrefvmdk, headers=headers, data=my_bytes, verify=vca.verify,logger=None) |
| 503 | if response.status_code == requests.codes.ok: |
| 504 | bytes_transferred += len(my_bytes) |
| 505 | self.logger.debug('transferred %s of %s bytes' % (str(bytes_transferred), |
| 506 | str(statinfo.st_size))) |
| 507 | else: |
| 508 | self.logger.debug('file upload failed with error: [%s] %s' % (response.status_code, |
| 509 | response.content)) |
| 510 | return False |
| 511 | f.close() |
| 512 | return True |
| 513 | else: |
| 514 | self.logger.debug("Failed retrieve vApp template for catalog name {} for OVF {}". |
| 515 | format(catalog_name, media_file_name)) |
| 516 | return False |
| 517 | |
| 518 | self.logger.debug("Failed retrieve catalog name {} for OVF file {}".format(catalog_name, media_file_name)) |
| 519 | return False |
| 520 | |
| 521 | def upload_vimimage(self,vca, catalog_name, media_name, medial_file_name): |
| 522 | """Upload media file""" |
| 523 | return self.upload_ovf(vca, catalog_name, media_name.split(".")[0], medial_file_name, medial_file_name, True) |
| 524 | |
| 525 | def get_catalogid(self, catalog_name, catalogs): |
| 526 | for catalog in catalogs: |
| 527 | if catalog.name == catalog_name: |
| 528 | print catalog.name |
| 529 | catalog_id = catalog.get_id().split(":") |
| 530 | return catalog_id[3] |
| 531 | return None |
| 532 | |
| 533 | def get_catalogbyid(self, catalog_id, catalogs): |
| 534 | for catalog in catalogs: |
| 535 | catalogid = catalog.get_id().split(":")[3] |
| 536 | if catalogid == catalog_id: |
| 537 | return catalog.name |
| 538 | return None |
| 539 | |
| 540 | def get_image_id_from_path(self, path): |
| 541 | '''Get the image id from image path in the VIM database''' |
| 542 | '''Returns: |
| 543 | 0,"Image not found" if there are no images with that path |
| 544 | 1,image-id if there is one image with that path |
| 545 | <0,message if there was an error (Image not found, error contacting VIM, more than 1 image with that path, etc.) |
| 546 | ''' |
| 547 | |
| 548 | vca = self.connect() |
| 549 | if not vca: |
| 550 | raise vimconn.vimconnConnectionException("self.connect() is failed") |
| 551 | |
| 552 | self.logger.debug("get_image_id_from_path path {}".format(path)) |
| 553 | |
| 554 | dirpath, filename = os.path.split(path) |
| 555 | flname, file_extension = os.path.splitext(path) |
| 556 | if file_extension != '.ovf': |
| 557 | self.logger.debug("Wrong file extension {}".format(file_extension)) |
| 558 | return -1, "Wrong container. vCloud director supports only OVF." |
| 559 | catalog_name = os.path.splitext(filename)[0] |
| 560 | |
| 561 | self.logger.debug("File name {} Catalog Name {} file path {}".format(filename, catalog_name, path)) |
| 562 | self.logger.debug("Catalog name {}".format(catalog_name)) |
| 563 | |
| 564 | catalogs = vca.get_catalogs() |
| 565 | if len(catalogs) == 0: |
| 566 | self.logger.info("Creating new catalog entry {} in vcloud director".format(catalog_name)) |
| 567 | result = self.create_vimcatalog(vca, catalog_name) |
| 568 | if not result: |
| 569 | return -1, "Failed create new catalog {} ".format(catalog_name) |
| 570 | result = self.upload_vimimage(vca, catalog_name, filename, path) |
| 571 | if not result: |
| 572 | return -1, "Failed create vApp template for catalog {} ".format(catalog_name) |
| 573 | return self.get_catalogid(catalog_name, vca.get_catalogs()) |
| 574 | else: |
| 575 | for catalog in catalogs: |
| 576 | # search for existing catalog if we find same name we return ID |
| 577 | # TODO optimize this |
| 578 | if catalog.name == catalog_name: |
| 579 | self.logger.debug("Found existing catalog entry for {} catalog id {}".format(catalog_name, self.get_catalogid(catalog_name, catalogs))) |
| 580 | return self.get_catalogid(catalog_name, vca.get_catalogs()) |
| 581 | |
| 582 | # if we didn't find existing catalog we create a new one. |
| 583 | self.logger.debug("Creating new catalog entry".format(catalog_name)) |
| 584 | result = self.create_vimcatalog(vca, catalog_name) |
| 585 | if not result: |
| 586 | return -1, "Failed create new catalog {} ".format(catalog_name) |
| 587 | result = self.upload_vimimage(vca, catalog_name, filename, path) |
| 588 | if not result: |
| 589 | return -1, "Failed create vApp template for catalog {} ".format(catalog_name) |
| 590 | |
| 591 | return self.get_catalogid(catalog_name, vca.get_catalogs()) |
| 592 | |
| 593 | def get_vappid(self, vdc, vapp_name): |
| 594 | """ Take vdc object and vApp name and returns vapp uuid or None |
| 595 | """ |
| 596 | #UUID has following format https://host/api/vApp/vapp-30da58a3-e7c7-4d09-8f68-d4c8201169cf |
| 597 | |
| 598 | try: |
| 599 | refs = filter(lambda ref: ref.name == vapp_name and ref.type_ == 'application/vnd.vmware.vcloud.vApp+xml', |
| 600 | vdc.ResourceEntities.ResourceEntity) |
| 601 | |
| 602 | if len(refs) == 1: |
| 603 | return refs[0].href.split("vapp")[1][1:] |
| 604 | except: |
| 605 | return None |
| 606 | return None |
| 607 | |
| 608 | def get_vappbyid(self, vdc, vapp_id): |
| 609 | refs = filter(lambda ref: ref.type_ == 'application/vnd.vmware.vcloud.vApp+xml', |
| 610 | vdc.ResourceEntities.ResourceEntity) |
| 611 | for ref in refs: |
| 612 | print ref.href |
| 613 | |
| 614 | if len(refs) == 1: |
| 615 | return refs[0].href.split("vapp")[1][1:] |
| 616 | |
| 617 | def new_vminstance(self,name,description,start,image_id,flavor_id,net_list,cloud_config=None): |
| 618 | """Adds a VM instance to VIM |
| 619 | Params: |
| 620 | start: indicates if VM must start or boot in pause mode. Ignored |
| 621 | image_id,flavor_id: image and flavor uuid |
| 622 | net_list: list of interfaces, each one is a dictionary with: |
| 623 | name: |
| 624 | net_id: network uuid to connect |
| 625 | vpci: virtual vcpi to assign |
| 626 | model: interface model, virtio, e2000, ... |
| 627 | mac_address: |
| 628 | use: 'data', 'bridge', 'mgmt' |
| 629 | type: 'virtual', 'PF', 'VF', 'VFnotShared' |
| 630 | vim_id: filled/added by this function |
| 631 | cloud_config: can be a text script to be passed directly to cloud-init, |
| 632 | or an object to inject users and ssh keys with format: |
| 633 | key-pairs: [] list of keys to install to the default user |
| 634 | users: [{ name, key-pairs: []}] list of users to add with their key-pair |
| 635 | #TODO ip, security groups |
| 636 | Returns >=0, the instance identifier |
| 637 | <0, error_text |
| 638 | """ |
| 639 | |
| 640 | self.logger.info("Creating new instance for entry".format(name)) |
| 641 | self.logger.debug("desc {} boot {} image_id: {} flavor_id: {} net_list: {} cloud_config {}". |
| 642 | format(description, start, image_id, flavor_id, net_list, cloud_config)) |
| 643 | vca = self.connect() |
| 644 | if not vca: |
| 645 | raise vimconn.vimconnConnectionException("self.connect() is failed.") |
| 646 | |
| 647 | # TODO following attribute need be featched from flavor / OVF file must contain same data. |
| 648 | # task = self.vca.create_vapp(vdc_name, vapp_name, template, catalog, |
| 649 | # vm_name=vm_name, |
| 650 | # vm_cpus=cpu, |
| 651 | # vm_memory=memory) |
| 652 | # |
| 653 | |
| 654 | catalogs = vca.get_catalogs() |
| 655 | #image upload creates template name as catalog name space Template. |
| 656 | templateName = self.get_catalogbyid(image_id, catalogs) + ' Template' |
| 657 | task = vca.create_vapp(self.tenant_name, name, templateName, self.get_catalogbyid(image_id,catalogs), vm_name=name) |
| 658 | if task is False: |
| 659 | return -1, " Failed deploy vApp {}".format(name) |
| 660 | |
| 661 | result = vca.block_until_completed(task) |
| 662 | if result: |
| 663 | vappID = self.get_vappid(vca.get_vdc(self.tenant_name), name) |
| 664 | if vappID is None: |
| 665 | return -1, " Failed featch UUID for vApp {}".format(name) |
| 666 | else: |
| 667 | return vappID |
| 668 | |
| 669 | return -1, " Failed create vApp {}".format(name) |
| 670 | |
| 671 | def get_vminstance(self,vm_id): |
| 672 | '''Returns the VM instance information from VIM''' |
| 673 | |
| 674 | vca = self.connect() |
| 675 | if not vca: |
| 676 | raise vimconn.vimconnConnectionException("self.connect() is failed.") |
| 677 | |
| 678 | |
| 679 | raise vimconnNotImplemented( "Should have implemented this" ) |
| 680 | |
| 681 | def delete_vminstance(self, vm_id): |
| 682 | '''Removes a VM instance from VIM''' |
| 683 | '''Returns the instance identifier''' |
| 684 | |
| 685 | print " ###### {} ".format(vm_id) |
| 686 | |
| 687 | vca = self.connect() |
| 688 | if not vca: |
| 689 | raise vimconn.vimconnConnectionException("self.connect() is failed.") |
| 690 | |
| 691 | thevdc = vca.get_vdc(self.tenant_name) |
| 692 | self.get_vappid(vca.get_vdc(self.tenant_name), name) |
| 693 | |
| 694 | |
| 695 | |
| 696 | |
| 697 | def refresh_vms_status(self, vm_list): |
| 698 | '''Get the status of the virtual machines and their interfaces/ports |
| 699 | Params: the list of VM identifiers |
| 700 | Returns a dictionary with: |
| 701 | vm_id: #VIM id of this Virtual Machine |
| 702 | status: #Mandatory. Text with one of: |
| 703 | # DELETED (not found at vim) |
| 704 | # VIM_ERROR (Cannot connect to VIM, VIM response error, ...) |
| 705 | # OTHER (Vim reported other status not understood) |
| 706 | # ERROR (VIM indicates an ERROR status) |
| 707 | # ACTIVE, PAUSED, SUSPENDED, INACTIVE (not running), |
| 708 | # CREATING (on building process), ERROR |
| 709 | # ACTIVE:NoMgmtIP (Active but any of its interface has an IP address |
| 710 | # |
| 711 | error_msg: #Text with VIM error message, if any. Or the VIM connection ERROR |
| 712 | vim_info: #Text with plain information obtained from vim (yaml.safe_dump) |
| 713 | interfaces: |
| 714 | - vim_info: #Text with plain information obtained from vim (yaml.safe_dump) |
| 715 | mac_address: #Text format XX:XX:XX:XX:XX:XX |
| 716 | vim_net_id: #network id where this interface is connected |
| 717 | vim_interface_id: #interface/port VIM id |
| 718 | ip_address: #null, or text with IPv4, IPv6 address |
| 719 | ''' |
| 720 | raise vimconnNotImplemented( "Should have implemented this" ) |
| 721 | |
| 722 | def action_vminstance(self, vm_id, action_dict): |
| 723 | '''Send and action over a VM instance from VIM |
| 724 | Returns the vm_id if the action was successfully sent to the VIM''' |
| 725 | raise vimconnNotImplemented( "Should have implemented this" ) |
| 726 | |
| 727 | def get_vminstance_console(self,vm_id, console_type="vnc"): |
| 728 | ''' |
| 729 | Get a console for the virtual machine |
| 730 | Params: |
| 731 | vm_id: uuid of the VM |
| 732 | console_type, can be: |
| 733 | "novnc" (by default), "xvpvnc" for VNC types, |
| 734 | "rdp-html5" for RDP types, "spice-html5" for SPICE types |
| 735 | Returns dict with the console parameters: |
| 736 | protocol: ssh, ftp, http, https, ... |
| 737 | server: usually ip address |
| 738 | port: the http, ssh, ... port |
| 739 | suffix: extra text, e.g. the http path and query string |
| 740 | ''' |
| 741 | raise vimconnNotImplemented( "Should have implemented this" ) |
| 742 | |
| 743 | #NOT USED METHODS in current version |
| 744 | |
| 745 | def host_vim2gui(self, host, server_dict): |
| 746 | '''Transform host dictionary from VIM format to GUI format, |
| 747 | and append to the server_dict |
| 748 | ''' |
| 749 | raise vimconnNotImplemented( "Should have implemented this" ) |
| 750 | |
| 751 | def get_hosts_info(self): |
| 752 | '''Get the information of deployed hosts |
| 753 | Returns the hosts content''' |
| 754 | raise vimconnNotImplemented( "Should have implemented this" ) |
| 755 | |
| 756 | def get_hosts(self, vim_tenant): |
| 757 | '''Get the hosts and deployed instances |
| 758 | Returns the hosts content''' |
| 759 | raise vimconnNotImplemented( "Should have implemented this" ) |
| 760 | |
| 761 | def get_processor_rankings(self): |
| 762 | '''Get the processor rankings in the VIM database''' |
| 763 | raise vimconnNotImplemented( "Should have implemented this" ) |
| 764 | |
| 765 | def new_host(self, host_data): |
| 766 | '''Adds a new host to VIM''' |
| 767 | '''Returns status code of the VIM response''' |
| 768 | raise vimconnNotImplemented( "Should have implemented this" ) |
| 769 | |
| 770 | def new_external_port(self, port_data): |
| 771 | '''Adds a external port to VIM''' |
| 772 | '''Returns the port identifier''' |
| 773 | raise vimconnNotImplemented( "Should have implemented this" ) |
| 774 | |
| 775 | def new_external_network(self,net_name,net_type): |
| 776 | '''Adds a external network to VIM (shared)''' |
| 777 | '''Returns the network identifier''' |
| 778 | raise vimconnNotImplemented( "Should have implemented this" ) |
| 779 | |
| 780 | def connect_port_network(self, port_id, network_id, admin=False): |
| 781 | '''Connects a external port to a network''' |
| 782 | '''Returns status code of the VIM response''' |
| 783 | raise vimconnNotImplemented( "Should have implemented this" ) |
| 784 | |
| 785 | def new_vminstancefromJSON(self, vm_data): |
| 786 | '''Adds a VM instance to VIM''' |
| 787 | '''Returns the instance identifier''' |
| 788 | raise vimconnNotImplemented( "Should have implemented this" ) |
| 789 | |