Merge "Changes in vimconn_vmware.py: Fixed Bug ID 121: VMWARE Connector: Return Nic...
[osm/RO.git] / vimconn_vmware.py
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 from progressbar import Percentage, Bar, ETA, FileTransferSpeed, ProgressBar
29
30 import vimconn
31 import os
32 import traceback
33 import itertools
34 import requests
35
36 from xml.etree import ElementTree as XmlElementTree
37
38 import yaml
39 from pyvcloud import Http
40 from pyvcloud.vcloudair import VCA
41 from pyvcloud.schema.vcd.v1_5.schemas.vcloud import sessionType, organizationType, \
42 vAppType, organizationListType, vdcType, catalogType, queryRecordViewType, \
43 networkType, vcloudType, taskType, diskType, vmsType, vdcTemplateListType, mediaType
44 from xml.sax.saxutils import escape
45
46 from pyvcloud.schema.vcd.v1_5.schemas.admin.vCloudEntities import TaskType
47 from pyvcloud.schema.vcd.v1_5.schemas.vcloud.taskType import TaskType as GenericTask
48 from pyvcloud.schema.vcd.v1_5.schemas.vcloud.vAppType import TaskType as VappTask
49 from pyvcloud.schema.vcd.v1_5.schemas.admin.vCloudEntities import TasksInProgressType
50
51 import logging
52 import json
53 import vimconn
54 import time
55 import uuid
56 import httplib
57 import hashlib
58
59 # global variable for vcd connector type
60 STANDALONE = 'standalone'
61
62 # key for flavor dicts
63 FLAVOR_RAM_KEY = 'ram'
64 FLAVOR_VCPUS_KEY = 'vcpus'
65
66 # global variable for number of retry
67 DELETE_INSTANCE_RETRY = 3
68
69 VCAVERSION = '5.9'
70
71 __author__ = "Mustafa Bayramov"
72 __date__ = "$26-Aug-2016 11:09:29$"
73 __version__ = '0.1'
74
75 # -1: "Could not be created",
76 # 0: "Unresolved",
77 # 1: "Resolved",
78 # 2: "Deployed",
79 # 3: "Suspended",
80 # 4: "Powered on",
81 # 5: "Waiting for user input",
82 # 6: "Unknown state",
83 # 7: "Unrecognized state",
84 # 8: "Powered off",
85 # 9: "Inconsistent state",
86 # 10: "Children do not all have the same status",
87 # 11: "Upload initiated, OVF descriptor pending",
88 # 12: "Upload initiated, copying contents",
89 # 13: "Upload initiated , disk contents pending",
90 # 14: "Upload has been quarantined",
91 # 15: "Upload quarantine period has expired"
92
93 # mapping vCD status to MANO
94 vcdStatusCode2manoFormat = {4: 'ACTIVE',
95 7: 'PAUSED',
96 3: 'SUSPENDED',
97 8: 'INACTIVE',
98 12: 'BUILD',
99 -1: 'ERROR',
100 14: 'DELETED'}
101
102 #
103 netStatus2manoFormat = {'ACTIVE': 'ACTIVE', 'PAUSED': 'PAUSED', 'INACTIVE': 'INACTIVE', 'BUILD': 'BUILD',
104 'ERROR': 'ERROR', 'DELETED': 'DELETED'
105 }
106
107 # dict used to store flavor in memory
108 flavorlist = {}
109
110
111 class vimconnector(vimconn.vimconnector):
112 def __init__(self, uuid=None, name=None, tenant_id=None, tenant_name=None,
113 url=None, url_admin=None, user=None, passwd=None, log_level=None, config={}):
114 """
115 Constructor create vmware connector to vCloud director.
116
117 By default construct doesn't validate connection state. So client can create object with None arguments.
118 If client specified username , password and host and VDC name. Connector initialize other missing attributes.
119
120 a) It initialize organization UUID
121 b) Initialize tenant_id/vdc ID. (This information derived from tenant name)
122
123 Args:
124 uuid - is organization uuid.
125 name - is organization name that must be presented in vCloud director.
126 tenant_id - is VDC uuid it must be presented in vCloud director
127 tenant_name - is VDC name.
128 url - is hostname or ip address of vCloud director
129 url_admin - same as above.
130 user - is user that administrator for organization. Caller must make sure that
131 username has right privileges.
132
133 password - is password for a user.
134
135 VMware connector also requires PVDC administrative privileges and separate account.
136 This variables must be passed via config argument dict contains keys
137
138 dict['admin_username']
139 dict['admin_password']
140
141 Returns:
142 Nothing.
143 """
144
145 vimconn.vimconnector.__init__(self, uuid, name, tenant_id, tenant_name, url,
146 url_admin, user, passwd, log_level, config)
147
148 self.logger = logging.getLogger('openmano.vim.vmware')
149 self.logger.setLevel(10)
150
151 self.name = name
152 self.id = uuid
153 self.url = url
154 self.url_admin = url_admin
155 self.tenant_id = tenant_id
156 self.tenant_name = tenant_name
157 self.user = user
158 self.passwd = passwd
159 self.config = config
160 self.admin_password = None
161 self.admin_user = None
162 self.org_name = ""
163
164 if tenant_name is not None:
165 orgnameandtenant = tenant_name.split(":")
166 if len(orgnameandtenant) == 2:
167 self.tenant_name = orgnameandtenant[0]
168 self.org_name = orgnameandtenant[1]
169 else:
170 self.tenant_name = tenant_name
171 elif "orgname" in config:
172 self.org_name = config['orgname']
173 else:
174 raise vimconn.vimconnException(message="Please indicate valid organization name. "
175 "Either pass by org config attribute "
176 "or as tenant_name:tenant_id.")
177 if log_level:
178 self.logger.setLevel(getattr(logging, log_level))
179
180 try:
181 self.admin_user = config['admin_username']
182 self.admin_password = config['admin_password']
183 except KeyError:
184 raise vimconn.vimconnException(message="Error admin username or admin password is empty.")
185
186 self.org_uuid = None
187 self.vca = None
188
189 if not url:
190 raise vimconn.vimconnException('url param can not be NoneType')
191
192 if not self.url_admin: # try to use normal url
193 self.url_admin = self.url
194
195 logging.debug("UUID: {} name: {} tenant_id: {} tenant name {}".format(self.id, self.org_name,
196 self.tenant_id, self.tenant_name))
197 logging.debug("vcd url {} vcd username: {} vcd password: {}".format(self.url, self.user, self.passwd))
198 logging.debug("vcd admin username {} vcd admin passowrd {}".format(self.admin_user, self.admin_password))
199
200 # initialize organization
201 if self.user is not None and self.passwd is not None and self.url:
202 self.init_organization()
203
204 def __getitem__(self, index):
205 if index == 'name':
206 return self.name
207 if index == 'tenant_id':
208 return self.tenant_id
209 if index == 'tenant_name':
210 return self.tenant_name
211 elif index == 'id':
212 return self.id
213 elif index == 'org_name':
214 return self.org_name
215 elif index == 'org_uuid':
216 return self.org_uuid
217 elif index == 'user':
218 return self.user
219 elif index == 'passwd':
220 return self.passwd
221 elif index == 'url':
222 return self.url
223 elif index == 'url_admin':
224 return self.url_admin
225 elif index == "config":
226 return self.config
227 else:
228 raise KeyError("Invalid key '%s'" % str(index))
229
230 def __setitem__(self, index, value):
231 if index == 'name':
232 self.name = value
233 if index == 'tenant_id':
234 self.tenant_id = value
235 if index == 'tenant_name':
236 self.tenant_name = value
237 elif index == 'id':
238 self.id = value
239 elif index == 'org_name':
240 self.org_name = value
241 elif index == 'org_uuid':
242 self.org_uuid = value
243 elif index == 'user':
244 self.user = value
245 elif index == 'passwd':
246 self.passwd = value
247 elif index == 'url':
248 self.url = value
249 elif index == 'url_admin':
250 self.url_admin = value
251 else:
252 raise KeyError("Invalid key '%s'" % str(index))
253
254 def connect_as_admin(self):
255 """ Method connect as pvdc admin user to vCloud director.
256 There are certain action that can be done only by provider vdc admin user.
257 Organization creation / provider network creation etc.
258
259 Returns:
260 The return vca object that letter can be used to connect to vcloud direct as admin for provider vdc
261 """
262
263 self.logger.debug("Logging in to a vca {} as admin.".format(self.org_name))
264
265 vca_admin = VCA(host=self.url,
266 username=self.admin_user,
267 service_type=STANDALONE,
268 version=VCAVERSION,
269 verify=False,
270 log=False)
271 result = vca_admin.login(password=self.admin_password, org='System')
272 if not result:
273 raise vimconn.vimconnConnectionException(
274 "Can't connect to a vCloud director as: {}".format(self.admin_user))
275 result = vca_admin.login(token=vca_admin.token, org='System', org_url=vca_admin.vcloud_session.org_url)
276 if result is True:
277 self.logger.info(
278 "Successfully logged to a vcloud direct org: {} as user: {}".format('System', self.admin_user))
279
280 return vca_admin
281
282 def connect(self):
283 """ Method connect as normal user to vCloud director.
284
285 Returns:
286 The return vca object that letter can be used to connect to vCloud director as admin for VDC
287 """
288
289 try:
290 self.logger.debug("Logging in to a vca {} as {} to datacenter {}.".format(self.org_name,
291 self.user,
292 self.org_name))
293 vca = VCA(host=self.url,
294 username=self.user,
295 service_type=STANDALONE,
296 version=VCAVERSION,
297 verify=False,
298 log=False)
299
300 result = vca.login(password=self.passwd, org=self.org_name)
301 if not result:
302 raise vimconn.vimconnConnectionException("Can't connect to a vCloud director as: {}".format(self.user))
303 result = vca.login(token=vca.token, org=self.org_name, org_url=vca.vcloud_session.org_url)
304 if result is True:
305 self.logger.info(
306 "Successfully logged to a vcloud direct org: {} as user: {}".format(self.org_name, self.user))
307
308 except:
309 raise vimconn.vimconnConnectionException("Can't connect to a vCloud director org: "
310 "{} as user: {}".format(self.org_name, self.user))
311
312 return vca
313
314 def init_organization(self):
315 """ Method initialize organization UUID and VDC parameters.
316
317 At bare minimum client must provide organization name that present in vCloud director and VDC.
318
319 The VDC - UUID ( tenant_id) will be initialized at the run time if client didn't call constructor.
320 The Org - UUID will be initialized at the run time if data center present in vCloud director.
321
322 Returns:
323 The return vca object that letter can be used to connect to vcloud direct as admin
324 """
325 try:
326 if self.org_uuid is None:
327 org_dict = self.get_org_list()
328 for org in org_dict:
329 # we set org UUID at the init phase but we can do it only when we have valid credential.
330 if org_dict[org] == self.org_name:
331 self.org_uuid = org
332 self.logger.debug("Setting organization UUID {}".format(self.org_uuid))
333 break
334 else:
335 raise vimconn.vimconnException("Vcloud director organization {} not found".format(self.org_name))
336
337 # if well good we require for org details
338 org_details_dict = self.get_org(org_uuid=self.org_uuid)
339
340 # we have two case if we want to initialize VDC ID or VDC name at run time
341 # tenant_name provided but no tenant id
342 if self.tenant_id is None and self.tenant_name is not None and 'vdcs' in org_details_dict:
343 vdcs_dict = org_details_dict['vdcs']
344 for vdc in vdcs_dict:
345 if vdcs_dict[vdc] == self.tenant_name:
346 self.tenant_id = vdc
347 self.logger.debug("Setting vdc uuid {} for organization UUID {}".format(self.tenant_id,
348 self.org_name))
349 break
350 else:
351 raise vimconn.vimconnException("Tenant name indicated but not present in vcloud director.")
352 # case two we have tenant_id but we don't have tenant name so we find and set it.
353 if self.tenant_id is not None and self.tenant_name is None and 'vdcs' in org_details_dict:
354 vdcs_dict = org_details_dict['vdcs']
355 for vdc in vdcs_dict:
356 if vdc == self.tenant_id:
357 self.tenant_name = vdcs_dict[vdc]
358 self.logger.debug("Setting vdc uuid {} for organization UUID {}".format(self.tenant_id,
359 self.org_name))
360 break
361 else:
362 raise vimconn.vimconnException("Tenant id indicated but not present in vcloud director")
363 self.logger.debug("Setting organization uuid {}".format(self.org_uuid))
364 except:
365 self.logger.debug("Failed initialize organization UUID for org {}".format(self.org_name))
366 self.logger.debug(traceback.format_exc())
367 self.org_uuid = None
368
369 def new_tenant(self, tenant_name=None, tenant_description=None):
370 """ Method adds a new tenant to VIM with this name.
371 This action requires access to create VDC action in vCloud director.
372
373 Args:
374 tenant_name is tenant_name to be created.
375 tenant_description not used for this call
376
377 Return:
378 returns the tenant identifier in UUID format.
379 If action is failed method will throw vimconn.vimconnException method
380 """
381 vdc_task = self.create_vdc(vdc_name=tenant_name)
382 if vdc_task is not None:
383 vdc_uuid, value = vdc_task.popitem()
384 self.logger.info("Crated new vdc {} and uuid: {}".format(tenant_name, vdc_uuid))
385 return vdc_uuid
386 else:
387 raise vimconn.vimconnException("Failed create tenant {}".format(tenant_name))
388
389 def delete_tenant(self, tenant_id=None):
390 """Delete a tenant from VIM"""
391 'Returns the tenant identifier'
392 raise vimconn.vimconnNotImplemented("Should have implemented this")
393
394 def get_tenant_list(self, filter_dict={}):
395 """Obtain tenants of VIM
396 filter_dict can contain the following keys:
397 name: filter by tenant name
398 id: filter by tenant uuid/id
399 <other VIM specific>
400 Returns the tenant list of dictionaries:
401 [{'name':'<name>, 'id':'<id>, ...}, ...]
402
403 """
404 org_dict = self.get_org(self.org_uuid)
405 vdcs_dict = org_dict['vdcs']
406
407 vdclist = []
408 try:
409 for k in vdcs_dict:
410 entry = {'name': vdcs_dict[k], 'id': k}
411 # if caller didn't specify dictionary we return all tenants.
412 if filter_dict is not None and filter_dict:
413 filtered_entry = entry.copy()
414 filtered_dict = set(entry.keys()) - set(filter_dict)
415 for unwanted_key in filtered_dict: del entry[unwanted_key]
416 if filter_dict == entry:
417 vdclist.append(filtered_entry)
418 else:
419 vdclist.append(entry)
420 except:
421 self.logger.debug("Error in get_tenant_list()")
422 self.logger.debug(traceback.format_exc())
423 raise vimconn.vimconnException("Incorrect state. {}")
424
425 return vdclist
426
427 def new_network(self, net_name, net_type, ip_profile=None, shared=False):
428 """Adds a tenant network to VIM
429 net_name is the name
430 net_type can be 'bridge','data'.'ptp'. TODO: this need to be revised
431 ip_profile is a dict containing the IP parameters of the network
432 shared is a boolean
433 Returns the network identifier"""
434
435 self.logger.debug(
436 "new_network tenant {} net_type {} ip_profile {} shared {}".format(net_name, net_type, ip_profile, shared))
437
438 isshared = 'false'
439 if shared:
440 isshared = 'true'
441
442 network_uuid = self.create_network(network_name=net_name, isshared=isshared)
443 if network_uuid is not None:
444 return network_uuid
445 else:
446 raise vimconn.vimconnUnexpectedResponse("Failed create a new network {}".format(net_name))
447
448 def get_vcd_network_list(self):
449 """ Method available organization for a logged in tenant
450
451 Returns:
452 The return vca object that letter can be used to connect to vcloud direct as admin
453 """
454
455 self.logger.debug("get_vcd_network_list(): retrieving network list for vcd {}".format(self.tenant_name))
456 vca = self.connect()
457 if not vca:
458 raise vimconn.vimconnConnectionException("self.connect() is failed.")
459
460 if not self.tenant_name:
461 raise vimconn.vimconnConnectionException("Tenant name is empty.")
462
463 vdc = vca.get_vdc(self.tenant_name)
464 if vdc is None:
465 raise vimconn.vimconnConnectionException("Can't retrieve information for a VDC {}".format(self.tenant_name))
466
467 vdc_uuid = vdc.get_id().split(":")[3]
468 networks = vca.get_networks(vdc.get_name())
469 network_list = []
470 try:
471 for network in networks:
472 filter_dict = {}
473 netid = network.get_id().split(":")
474 if len(netid) != 4:
475 continue
476
477 filter_dict["name"] = network.get_name()
478 filter_dict["id"] = netid[3]
479 filter_dict["shared"] = network.get_IsShared()
480 filter_dict["tenant_id"] = vdc_uuid
481 if network.get_status() == 1:
482 filter_dict["admin_state_up"] = True
483 else:
484 filter_dict["admin_state_up"] = False
485 filter_dict["status"] = "ACTIVE"
486 filter_dict["type"] = "bridge"
487 network_list.append(filter_dict)
488 self.logger.debug("get_vcd_network_list adding entry {}".format(filter_dict))
489 except:
490 self.logger.debug("Error in get_vcd_network_list")
491 self.logger.debug(traceback.format_exc())
492 pass
493
494 self.logger.debug("get_vcd_network_list returning {}".format(network_list))
495 return network_list
496
497 def get_network_list(self, filter_dict={}):
498 """Obtain tenant networks of VIM
499 Filter_dict can be:
500 name: network name OR/AND
501 id: network uuid OR/AND
502 shared: boolean OR/AND
503 tenant_id: tenant OR/AND
504 admin_state_up: boolean
505 status: 'ACTIVE'
506
507 [{key : value , key : value}]
508
509 Returns the network list of dictionaries:
510 [{<the fields at Filter_dict plus some VIM specific>}, ...]
511 List can be empty
512 """
513
514 self.logger.debug("get_vcd_network_list(): retrieving network list for vcd {}".format(self.tenant_name))
515 vca = self.connect()
516 if not vca:
517 raise vimconn.vimconnConnectionException("self.connect() is failed.")
518
519 if not self.tenant_name:
520 raise vimconn.vimconnConnectionException("Tenant name is empty.")
521
522 vdc = vca.get_vdc(self.tenant_name)
523 if vdc is None:
524 raise vimconn.vimconnConnectionException("Can't retrieve information for a VDC {}.".format(self.tenant_name))
525
526 vdcid = vdc.get_id().split(":")[3]
527 networks = vca.get_networks(vdc.get_name())
528 network_list = []
529
530 try:
531 for network in networks:
532 filter_entry = {}
533 net_uuid = network.get_id().split(":")
534 if len(net_uuid) != 4:
535 continue
536 else:
537 net_uuid = net_uuid[3]
538 # create dict entry
539 self.logger.debug("Adding {} to a list vcd id {} network {}".format(net_uuid,
540 vdcid,
541 network.get_name()))
542 filter_entry["name"] = network.get_name()
543 filter_entry["id"] = net_uuid
544 filter_entry["shared"] = network.get_IsShared()
545 filter_entry["tenant_id"] = vdcid
546 if network.get_status() == 1:
547 filter_entry["admin_state_up"] = True
548 else:
549 filter_entry["admin_state_up"] = False
550 filter_entry["status"] = "ACTIVE"
551 filter_entry["type"] = "bridge"
552 filtered_entry = filter_entry.copy()
553
554 if filter_dict is not None and filter_dict:
555 # we remove all the key : value we don't care and match only
556 # respected field
557 filtered_dict = set(filter_entry.keys()) - set(filter_dict)
558 for unwanted_key in filtered_dict: del filter_entry[unwanted_key]
559 if filter_dict == filter_entry:
560 network_list.append(filtered_entry)
561 else:
562 network_list.append(filtered_entry)
563 except:
564 self.logger.debug("Error in get_vcd_network_list")
565 self.logger.debug(traceback.format_exc())
566
567 self.logger.debug("Returning {}".format(network_list))
568 return network_list
569
570 def get_network(self, net_id):
571 """Method obtains network details of net_id VIM network
572 Return a dict with the fields at filter_dict (see get_network_list) plus some VIM specific>}, ...]"""
573
574 vca = self.connect()
575 if not vca:
576 raise vimconn.vimconnConnectionException("self.connect() is failed")
577
578 vdc = vca.get_vdc(self.tenant_name)
579 vdc_id = vdc.get_id().split(":")[3]
580
581 networks = vca.get_networks(vdc.get_name())
582 filter_dict = {}
583
584 try:
585 for network in networks:
586 vdc_network_id = network.get_id().split(":")
587 if len(vdc_network_id) == 4 and vdc_network_id[3] == net_id:
588 filter_dict["name"] = network.get_name()
589 filter_dict["id"] = vdc_network_id[3]
590 filter_dict["shared"] = network.get_IsShared()
591 filter_dict["tenant_id"] = vdc_id
592 if network.get_status() == 1:
593 filter_dict["admin_state_up"] = True
594 else:
595 filter_dict["admin_state_up"] = False
596 filter_dict["status"] = "ACTIVE"
597 filter_dict["type"] = "bridge"
598 self.logger.debug("Returning {}".format(filter_dict))
599 return filter_dict
600 except:
601 self.logger.debug("Error in get_network")
602 self.logger.debug(traceback.format_exc())
603
604 return filter_dict
605
606 def delete_network(self, net_id):
607 """
608 Method Deletes a tenant network from VIM, provide the network id.
609
610 Returns the network identifier or raise an exception
611 """
612
613 vca = self.connect()
614 if not vca:
615 raise vimconn.vimconnConnectionException("self.connect() for tenant {} is failed.".format(self.tenant_name))
616
617 vcd_network = self.get_vcd_network(network_uuid=net_id)
618 if vcd_network is not None and vcd_network:
619 if self.delete_network_action(network_uuid=net_id):
620 return net_id
621 else:
622 raise vimconn.vimconnNotFoundException("Network {} not found".format(net_id))
623
624 def refresh_nets_status(self, net_list):
625 """Get the status of the networks
626 Params: the list of network identifiers
627 Returns a dictionary with:
628 net_id: #VIM id of this network
629 status: #Mandatory. Text with one of:
630 # DELETED (not found at vim)
631 # VIM_ERROR (Cannot connect to VIM, VIM response error, ...)
632 # OTHER (Vim reported other status not understood)
633 # ERROR (VIM indicates an ERROR status)
634 # ACTIVE, INACTIVE, DOWN (admin down),
635 # BUILD (on building process)
636 #
637 error_msg: #Text with VIM error message, if any. Or the VIM connection ERROR
638 vim_info: #Text with plain information obtained from vim (yaml.safe_dump)
639
640 """
641
642 vca = self.connect()
643 if not vca:
644 raise vimconn.vimconnConnectionException("self.connect() is failed")
645
646 dict_entry = {}
647 try:
648 for net in net_list:
649 errormsg = ''
650 vcd_network = self.get_vcd_network(network_uuid=net)
651 if vcd_network is not None and vcd_network:
652 if vcd_network['status'] == '1':
653 status = 'ACTIVE'
654 else:
655 status = 'DOWN'
656 else:
657 status = 'DELETED'
658 errormsg = 'Network not found.'
659
660 dict_entry[net] = {'status': status, 'error_msg': errormsg,
661 'vim_info': yaml.safe_dump(vcd_network)}
662 except:
663 self.logger.debug("Error in refresh_nets_status")
664 self.logger.debug(traceback.format_exc())
665
666 return dict_entry
667
668 def get_flavor(self, flavor_id):
669 """Obtain flavor details from the VIM
670 Returns the flavor dict details {'id':<>, 'name':<>, other vim specific } #TODO to concrete
671 """
672 if flavor_id not in flavorlist:
673 raise vimconn.vimconnNotFoundException("Flavor not found.")
674 return flavorlist[flavor_id]
675
676 def new_flavor(self, flavor_data):
677 """Adds a tenant flavor to VIM
678 flavor_data contains a dictionary with information, keys:
679 name: flavor name
680 ram: memory (cloud type) in MBytes
681 vpcus: cpus (cloud type)
682 extended: EPA parameters
683 - numas: #items requested in same NUMA
684 memory: number of 1G huge pages memory
685 paired-threads|cores|threads: number of paired hyperthreads, complete cores OR individual threads
686 interfaces: # passthrough(PT) or SRIOV interfaces attached to this numa
687 - name: interface name
688 dedicated: yes|no|yes:sriov; for PT, SRIOV or only one SRIOV for the physical NIC
689 bandwidth: X Gbps; requested guarantee bandwidth
690 vpci: requested virtual PCI address
691 disk: disk size
692 is_public:
693
694
695
696 #TODO to concrete
697 Returns the flavor identifier"""
698
699 # generate a new uuid put to internal dict and return it.
700 flavor_id = uuid.uuid4()
701 flavorlist[str(flavor_id)] = flavor_data
702
703 return str(flavor_id)
704
705 def delete_flavor(self, flavor_id):
706 """Deletes a tenant flavor from VIM identify by its id
707
708 Returns the used id or raise an exception
709 """
710 if flavor_id not in flavorlist:
711 raise vimconn.vimconnNotFoundException("Flavor not found.")
712
713 flavorlist.pop(flavor_id, None)
714 return flavor_id
715
716 def new_image(self, image_dict):
717 """
718 Adds a tenant image to VIM
719 Returns:
720 200, image-id if the image is created
721 <0, message if there is an error
722 """
723
724 return self.get_image_id_from_path(image_dict['location'])
725
726 def delete_image(self, image_id):
727 """
728
729 :param image_id:
730 :return:
731 """
732
733 raise vimconn.vimconnNotImplemented("Should have implemented this")
734
735 def catalog_exists(self, catalog_name, catalogs):
736 """
737
738 :param catalog_name:
739 :param catalogs:
740 :return:
741 """
742 for catalog in catalogs:
743 if catalog.name == catalog_name:
744 return True
745 return False
746
747 def create_vimcatalog(self, vca=None, catalog_name=None):
748 """ Create new catalog entry in vCloud director.
749
750 Args
751 vca: vCloud director.
752 catalog_name catalog that client wish to create. Note no validation done for a name.
753 Client must make sure that provide valid string representation.
754
755 Return (bool) True if catalog created.
756
757 """
758 try:
759 task = vca.create_catalog(catalog_name, catalog_name)
760 result = vca.block_until_completed(task)
761 if not result:
762 return False
763 catalogs = vca.get_catalogs()
764 except:
765 return False
766 return self.catalog_exists(catalog_name, catalogs)
767
768 # noinspection PyIncorrectDocstring
769 def upload_ovf(self, vca=None, catalog_name=None, image_name=None, media_file_name=None,
770 description='', progress=False, chunk_bytes=128 * 1024):
771 """
772 Uploads a OVF file to a vCloud catalog
773
774 :param chunk_bytes:
775 :param progress:
776 :param description:
777 :param image_name:
778 :param vca:
779 :param catalog_name: (str): The name of the catalog to upload the media.
780 :param media_file_name: (str): The name of the local media file to upload.
781 :return: (bool) True if the media file was successfully uploaded, false otherwise.
782 """
783 os.path.isfile(media_file_name)
784 statinfo = os.stat(media_file_name)
785
786 # find a catalog entry where we upload OVF.
787 # create vApp Template and check the status if vCD able to read OVF it will respond with appropirate
788 # status change.
789 # if VCD can parse OVF we upload VMDK file
790 for catalog in vca.get_catalogs():
791 if catalog_name != catalog.name:
792 continue
793 link = filter(lambda link: link.get_type() == "application/vnd.vmware.vcloud.media+xml" and
794 link.get_rel() == 'add', catalog.get_Link())
795 assert len(link) == 1
796 data = """
797 <UploadVAppTemplateParams name="%s" xmlns="http://www.vmware.com/vcloud/v1.5" xmlns:ovf="http://schemas.dmtf.org/ovf/envelope/1"><Description>%s vApp Template</Description></UploadVAppTemplateParams>
798 """ % (escape(catalog_name), escape(description))
799 headers = vca.vcloud_session.get_vcloud_headers()
800 headers['Content-Type'] = 'application/vnd.vmware.vcloud.uploadVAppTemplateParams+xml'
801 response = Http.post(link[0].get_href(), headers=headers, data=data, verify=vca.verify, logger=self.logger)
802 if response.status_code == requests.codes.created:
803 catalogItem = XmlElementTree.fromstring(response.content)
804 entity = [child for child in catalogItem if
805 child.get("type") == "application/vnd.vmware.vcloud.vAppTemplate+xml"][0]
806 href = entity.get('href')
807 template = href
808 response = Http.get(href, headers=vca.vcloud_session.get_vcloud_headers(),
809 verify=vca.verify, logger=self.logger)
810
811 if response.status_code == requests.codes.ok:
812 media = mediaType.parseString(response.content, True)
813 link = filter(lambda link: link.get_rel() == 'upload:default',
814 media.get_Files().get_File()[0].get_Link())[0]
815 headers = vca.vcloud_session.get_vcloud_headers()
816 headers['Content-Type'] = 'Content-Type text/xml'
817 response = Http.put(link.get_href(),
818 data=open(media_file_name, 'rb'),
819 headers=headers,
820 verify=vca.verify, logger=self.logger)
821 if response.status_code != requests.codes.ok:
822 self.logger.debug(
823 "Failed create vApp template for catalog name {} and image {}".format(catalog_name,
824 media_file_name))
825 return False
826
827 # TODO fix this with aync block
828 time.sleep(5)
829
830 self.logger.debug("vApp template for catalog name {} and image {}".format(catalog_name, media_file_name))
831
832 # uploading VMDK file
833 # check status of OVF upload and upload remaining files.
834 response = Http.get(template,
835 headers=vca.vcloud_session.get_vcloud_headers(),
836 verify=vca.verify,
837 logger=self.logger)
838
839 if response.status_code == requests.codes.ok:
840 media = mediaType.parseString(response.content, True)
841 number_of_files = len(media.get_Files().get_File())
842 for index in xrange(0, number_of_files):
843 links_list = filter(lambda link: link.get_rel() == 'upload:default',
844 media.get_Files().get_File()[index].get_Link())
845 for link in links_list:
846 # we skip ovf since it already uploaded.
847 if 'ovf' in link.get_href():
848 continue
849 # The OVF file and VMDK must be in a same directory
850 head, tail = os.path.split(media_file_name)
851 file_vmdk = head + '/' + link.get_href().split("/")[-1]
852 if not os.path.isfile(file_vmdk):
853 return False
854 statinfo = os.stat(file_vmdk)
855 if statinfo.st_size == 0:
856 return False
857 hrefvmdk = link.get_href()
858
859 if progress:
860 print("Uploading file: {}".format(file_vmdk))
861 if progress:
862 widgets = ['Uploading file: ', Percentage(), ' ', Bar(), ' ', ETA(), ' ',
863 FileTransferSpeed()]
864 progress_bar = ProgressBar(widgets=widgets, maxval=statinfo.st_size).start()
865
866 bytes_transferred = 0
867 f = open(file_vmdk, 'rb')
868 while bytes_transferred < statinfo.st_size:
869 my_bytes = f.read(chunk_bytes)
870 if len(my_bytes) <= chunk_bytes:
871 headers = vca.vcloud_session.get_vcloud_headers()
872 headers['Content-Range'] = 'bytes %s-%s/%s' % (
873 bytes_transferred, len(my_bytes) - 1, statinfo.st_size)
874 headers['Content-Length'] = str(len(my_bytes))
875 response = Http.put(hrefvmdk,
876 headers=headers,
877 data=my_bytes,
878 verify=vca.verify,
879 logger=None)
880
881 if response.status_code == requests.codes.ok:
882 bytes_transferred += len(my_bytes)
883 if progress:
884 progress_bar.update(bytes_transferred)
885 else:
886 self.logger.debug(
887 'file upload failed with error: [%s] %s' % (response.status_code,
888 response.content))
889
890 f.close()
891 return False
892 f.close()
893 if progress:
894 progress_bar.finish()
895 return True
896 else:
897 self.logger.debug("Failed retrieve vApp template for catalog name {} for OVF {}".
898 format(catalog_name, media_file_name))
899 return False
900
901 self.logger.debug("Failed retrieve catalog name {} for OVF file {}".format(catalog_name, media_file_name))
902 return False
903
904 def upload_vimimage(self, vca=None, catalog_name=None, media_name=None, medial_file_name=None, progress=False):
905 """Upload media file"""
906 # TODO add named parameters for readability
907
908 return self.upload_ovf(vca=vca, catalog_name=catalog_name, image_name=media_name.split(".")[0],
909 media_file_name=medial_file_name, description='medial_file_name', progress=progress)
910
911 def validate_uuid4(self, uuid_string=None):
912 """ Method validate correct format of UUID.
913
914 Return: true if string represent valid uuid
915 """
916 try:
917 val = uuid.UUID(uuid_string, version=4)
918 except ValueError:
919 return False
920 return True
921
922 def get_catalogid(self, catalog_name=None, catalogs=None):
923 """ Method check catalog and return catalog ID in UUID format.
924
925 Args
926 catalog_name: catalog name as string
927 catalogs: list of catalogs.
928
929 Return: catalogs uuid
930 """
931
932 for catalog in catalogs:
933 if catalog.name == catalog_name:
934 catalog_id = catalog.get_id().split(":")
935 return catalog_id[3]
936 return None
937
938 def get_catalogbyid(self, catalog_uuid=None, catalogs=None):
939 """ Method check catalog and return catalog name lookup done by catalog UUID.
940
941 Args
942 catalog_name: catalog name as string
943 catalogs: list of catalogs.
944
945 Return: catalogs name or None
946 """
947
948 if not self.validate_uuid4(uuid_string=catalog_uuid):
949 return None
950
951 for catalog in catalogs:
952 catalog_id = catalog.get_id().split(":")[3]
953 if catalog_id == catalog_uuid:
954 return catalog.name
955 return None
956
957 def get_image_id_from_path(self, path=None, progress=False):
958 """ Method upload OVF image to vCloud director.
959
960 Each OVF image represented as single catalog entry in vcloud director.
961 The method check for existing catalog entry. The check done by file name without file extension.
962
963 if given catalog name already present method will respond with existing catalog uuid otherwise
964 it will create new catalog entry and upload OVF file to newly created catalog.
965
966 If method can't create catalog entry or upload a file it will throw exception.
967
968 Method accept boolean flag progress that will output progress bar. It useful method
969 for standalone upload use case. In case to test large file upload.
970
971 Args
972 path: - valid path to OVF file.
973 progress - boolean progress bar show progress bar.
974
975 Return: if image uploaded correct method will provide image catalog UUID.
976 """
977 vca = self.connect()
978 if not vca:
979 raise vimconn.vimconnConnectionException("self.connect() is failed.")
980
981 if not path:
982 raise vimconn.vimconnException("Image path can't be None.")
983
984 if not os.path.isfile(path):
985 raise vimconn.vimconnException("Can't read file. File not found.")
986
987 if not os.access(path, os.R_OK):
988 raise vimconn.vimconnException("Can't read file. Check file permission to read.")
989
990 self.logger.debug("get_image_id_from_path() client requesting {} ".format(path))
991
992 dirpath, filename = os.path.split(path)
993 flname, file_extension = os.path.splitext(path)
994 if file_extension != '.ovf':
995 self.logger.debug("Wrong file extension {} connector support only OVF container.".format(file_extension))
996 raise vimconn.vimconnException("Wrong container. vCloud director supports only OVF.")
997
998 catalog_name = os.path.splitext(filename)[0]
999 catalog_md5_name = hashlib.md5(path).hexdigest()
1000 self.logger.debug("File name {} Catalog Name {} file path {} "
1001 "vdc catalog name {}".format(filename, catalog_name, path, catalog_md5_name))
1002
1003 catalogs = vca.get_catalogs()
1004 if len(catalogs) == 0:
1005 self.logger.info("Creating a new catalog entry {} in vcloud director".format(catalog_name))
1006 result = self.create_vimcatalog(vca, catalog_md5_name)
1007 if not result:
1008 raise vimconn.vimconnException("Failed create new catalog {} ".format(catalog_md5_name))
1009 result = self.upload_vimimage(vca=vca, catalog_name=catalog_md5_name,
1010 media_name=filename, medial_file_name=path, progress=progress)
1011 if not result:
1012 raise vimconn.vimconnException("Failed create vApp template for catalog {} ".format(catalog_name))
1013 return self.get_catalogid(catalog_name, vca.get_catalogs())
1014 else:
1015 for catalog in catalogs:
1016 # search for existing catalog if we find same name we return ID
1017 # TODO optimize this
1018 if catalog.name == catalog_md5_name:
1019 self.logger.debug("Found existing catalog entry for {} "
1020 "catalog id {}".format(catalog_name,
1021 self.get_catalogid(catalog_md5_name, catalogs)))
1022 return self.get_catalogid(catalog_md5_name, vca.get_catalogs())
1023
1024 # if we didn't find existing catalog we create a new one and upload image.
1025 self.logger.debug("Creating new catalog entry {} - {}".format(catalog_name, catalog_md5_name))
1026 result = self.create_vimcatalog(vca, catalog_md5_name)
1027 if not result:
1028 raise vimconn.vimconnException("Failed create new catalog {} ".format(catalog_md5_name))
1029
1030 result = self.upload_vimimage(vca=vca, catalog_name=catalog_md5_name,
1031 media_name=filename, medial_file_name=path, progress=progress)
1032 if not result:
1033 raise vimconn.vimconnException("Failed create vApp template for catalog {} ".format(catalog_md5_name))
1034
1035 return self.get_catalogid(catalog_md5_name, vca.get_catalogs())
1036
1037 def get_vappid(self, vdc=None, vapp_name=None):
1038 """ Method takes vdc object and vApp name and returns vapp uuid or None
1039
1040 Args:
1041 vdc: The VDC object.
1042 vapp_name: is application vappp name identifier
1043
1044 Returns:
1045 The return vApp name otherwise None
1046 """
1047 if vdc is None or vapp_name is None:
1048 return None
1049 # UUID has following format https://host/api/vApp/vapp-30da58a3-e7c7-4d09-8f68-d4c8201169cf
1050 try:
1051 refs = filter(lambda ref: ref.name == vapp_name and ref.type_ == 'application/vnd.vmware.vcloud.vApp+xml',
1052 vdc.ResourceEntities.ResourceEntity)
1053 if len(refs) == 1:
1054 return refs[0].href.split("vapp")[1][1:]
1055 except Exception as e:
1056 self.logger.exception(e)
1057 return False
1058 return None
1059
1060 def check_vapp(self, vdc=None, vapp_uuid=None):
1061 """ Method Method returns True or False if vapp deployed in vCloud director
1062
1063 Args:
1064 vca: Connector to VCA
1065 vdc: The VDC object.
1066 vappid: vappid is application identifier
1067
1068 Returns:
1069 The return True if vApp deployed
1070 :param vdc:
1071 :param vapp_uuid:
1072 """
1073 try:
1074 refs = filter(lambda ref:
1075 ref.type_ == 'application/vnd.vmware.vcloud.vApp+xml',
1076 vdc.ResourceEntities.ResourceEntity)
1077 for ref in refs:
1078 vappid = ref.href.split("vapp")[1][1:]
1079 # find vapp with respected vapp uuid
1080 if vappid == vapp_uuid:
1081 return True
1082 except Exception as e:
1083 self.logger.exception(e)
1084 return False
1085 return False
1086
1087 def get_namebyvappid(self, vca=None, vdc=None, vapp_uuid=None):
1088 """Method returns vApp name from vCD and lookup done by vapp_id.
1089
1090 Args:
1091 vca: Connector to VCA
1092 vdc: The VDC object.
1093 vapp_uuid: vappid is application identifier
1094
1095 Returns:
1096 The return vApp name otherwise None
1097 """
1098
1099 try:
1100 refs = filter(lambda ref: ref.type_ == 'application/vnd.vmware.vcloud.vApp+xml',
1101 vdc.ResourceEntities.ResourceEntity)
1102 for ref in refs:
1103 # we care only about UUID the rest doesn't matter
1104 vappid = ref.href.split("vapp")[1][1:]
1105 if vappid == vapp_uuid:
1106 response = Http.get(ref.href, headers=vca.vcloud_session.get_vcloud_headers(), verify=vca.verify,
1107 logger=self.logger)
1108 tree = XmlElementTree.fromstring(response.content)
1109 return tree.attrib['name']
1110 except Exception as e:
1111 self.logger.exception(e)
1112 return None
1113 return None
1114
1115 def new_vminstance(self, name=None, description="", start=False, image_id=None, flavor_id=None, net_list={},
1116 cloud_config=None, disk_list=None):
1117 """Adds a VM instance to VIM
1118 Params:
1119 start: indicates if VM must start or boot in pause mode. Ignored
1120 image_id,flavor_id: image and flavor uuid
1121 net_list: list of interfaces, each one is a dictionary with:
1122 name:
1123 net_id: network uuid to connect
1124 vpci: virtual vcpi to assign
1125 model: interface model, virtio, e2000, ...
1126 mac_address:
1127 use: 'data', 'bridge', 'mgmt'
1128 type: 'virtual', 'PF', 'VF', 'VFnotShared'
1129 vim_id: filled/added by this function
1130 cloud_config: can be a text script to be passed directly to cloud-init,
1131 or an object to inject users and ssh keys with format:
1132 key-pairs: [] list of keys to install to the default user
1133 users: [{ name, key-pairs: []}] list of users to add with their key-pair
1134 #TODO ip, security groups
1135 Returns >=0, the instance identifier
1136 <0, error_text
1137 """
1138
1139 self.logger.info("Creating new instance for entry {}".format(name))
1140 self.logger.debug("desc {} boot {} image_id: {} flavor_id: {} net_list: {} cloud_config {}".
1141 format(description, start, image_id, flavor_id, net_list, cloud_config))
1142 vca = self.connect()
1143 if not vca:
1144 raise vimconn.vimconnConnectionException("self.connect() is failed.")
1145
1146 #new vm name = vmname + tenant_id + uuid
1147 new_vm_name = [name, '-', str(uuid.uuid4())]
1148 vmname_andid = ''.join(new_vm_name)
1149
1150 # if vm already deployed we return existing uuid
1151 # vapp_uuid = self.get_vappid(vca.get_vdc(self.tenant_name), name)
1152 # if vapp_uuid is not None:
1153 # return vapp_uuid
1154
1155 # we check for presence of VDC, Catalog entry and Flavor.
1156 vdc = vca.get_vdc(self.tenant_name)
1157 if vdc is None:
1158 raise vimconn.vimconnNotFoundException(
1159 "new_vminstance(): Failed create vApp {}: (Failed retrieve VDC information)".format(name))
1160 catalogs = vca.get_catalogs()
1161 if catalogs is None:
1162 raise vimconn.vimconnNotFoundException(
1163 "new_vminstance(): Failed create vApp {}: (Failed retrieve catalogs list)".format(name))
1164
1165 catalog_hash_name = self.get_catalogbyid(catalog_uuid=image_id, catalogs=catalogs)
1166 if catalog_hash_name:
1167 self.logger.info("Found catalog entry {} for image id {}".format(catalog_hash_name, image_id))
1168 else:
1169 raise vimconn.vimconnNotFoundException("new_vminstance(): Failed create vApp {}: "
1170 "(Failed retrieve catalog information {})".format(name, image_id))
1171
1172
1173 # Set vCPU and Memory based on flavor.
1174 #
1175 vm_cpus = None
1176 vm_memory = None
1177 if flavor_id is not None:
1178 if flavor_id not in flavorlist:
1179 raise vimconn.vimconnNotFoundException("new_vminstance(): Failed create vApp {}: "
1180 "Failed retrieve flavor information "
1181 "flavor id {}".format(name, flavor_id))
1182 else:
1183 try:
1184 flavor = flavorlist[flavor_id]
1185 vm_cpus = flavor[FLAVOR_VCPUS_KEY]
1186 vm_memory = flavor[FLAVOR_RAM_KEY]
1187 except KeyError:
1188 raise vimconn.vimconnException("Corrupted flavor. {}".format(flavor_id))
1189
1190 # image upload creates template name as catalog name space Template.
1191 templateName = self.get_catalogbyid(catalog_uuid=image_id, catalogs=catalogs)
1192 power_on = 'false'
1193 if start:
1194 power_on = 'true'
1195
1196 # client must provide at least one entry in net_list if not we report error
1197 #
1198 primary_netname = None
1199 network_mode = 'bridged'
1200 if net_list is not None and len(net_list) > 0:
1201 primary_net = net_list[0]
1202 if primary_net is None:
1203 raise vimconn.vimconnUnexpectedResponse("new_vminstance(): Failed network list is empty.".format(name))
1204 else:
1205 try:
1206 primary_net_id = primary_net['net_id']
1207 network_dict = self.get_vcd_network(network_uuid=primary_net_id)
1208 if 'name' in network_dict:
1209 primary_netname = network_dict['name']
1210 self.logger.info("Connecting VM to a network name {} "
1211 " network id {}".format(primary_netname, primary_net_id))
1212 if 'use' in primary_net:
1213 if primary_net['use'] == 'bridge':
1214 network_mode = 'bridged'
1215 except KeyError:
1216 raise vimconn.vimconnException("Corrupted flavor. {}".format(primary_net))
1217
1218 # use: 'data', 'bridge', 'mgmt'
1219 # create vApp. Set vcpu and ram based on flavor id.
1220 vapptask = vca.create_vapp(self.tenant_name, vmname_andid, templateName,
1221 self.get_catalogbyid(image_id, catalogs),
1222 network_name=primary_netname, # can be None if net_list None
1223 network_mode=network_mode,
1224 vm_name=vmname_andid,
1225 vm_cpus=vm_cpus, # can be None if flavor is None
1226 vm_memory=vm_memory) # can be None if flavor is None
1227
1228 if vapptask is None or vapptask is False:
1229 raise vimconn.vimconnUnexpectedResponse("new_vminstance(): failed deploy vApp {}".format(vmname_andid))
1230 if type(vapptask) is VappTask:
1231 vca.block_until_completed(vapptask)
1232
1233 # we should have now vapp in undeployed state.
1234 vapp = vca.get_vapp(vca.get_vdc(self.tenant_name), vmname_andid)
1235 if vapp is None:
1236 raise vimconn.vimconnUnexpectedResponse(
1237 "new_vminstance(): Failed failed retrieve vApp {} after we deployed".format(vmname_andid))
1238
1239 # add first NIC
1240 try:
1241 self.logger.info("Request to connect VM to a network: {}".format(net_list))
1242 nicIndex = 0
1243 for net in net_list:
1244 # openmano uses network id in UUID format.
1245 # vCloud Director need a name so we do reverse operation from provided UUID we lookup a name
1246 # [{'use': 'bridge', 'net_id': '527d4bf7-566a-41e7-a9e7-ca3cdd9cef4f', 'type': 'virtual',
1247 # 'vpci': '0000:00:11.0', 'name': 'eth0'}]
1248
1249 if 'net_id' not in net:
1250 continue
1251
1252 interface_net_id = net['net_id']
1253 interface_net_name = self.get_network_name_by_id(network_uuid=interface_net_id)
1254 interface_network_mode = net['use']
1255
1256 """- POOL (A static IP address is allocated automatically from a pool of addresses.)
1257 - DHCP (The IP address is obtained from a DHCP service.)
1258 - MANUAL (The IP address is assigned manually in the IpAddress element.)
1259 - NONE (No IP addressing mode specified.)"""
1260
1261 if primary_netname is not None:
1262 nets = filter(lambda n: n.name == interface_net_name, vca.get_networks(self.tenant_name))
1263 if len(nets) == 1:
1264 self.logger.info("Found requested network: {}".format(nets[0].name))
1265 task = vapp.connect_to_network(nets[0].name, nets[0].href)
1266 if type(task) is GenericTask:
1267 vca.block_until_completed(task)
1268 # connect network to VM
1269 # TODO figure out mapping between openmano representation to vCloud director.
1270 # one idea use first nic as management DHCP all remaining in bridge mode
1271 self.logger.info("Connecting VM to a network network {}".format(nets[0].name))
1272 task = vapp.connect_vms(nets[0].name,
1273 connection_index=nicIndex,
1274 connections_primary_index=nicIndex,
1275 ip_allocation_mode='DHCP')
1276 if type(task) is GenericTask:
1277 vca.block_until_completed(task)
1278 nicIndex += 1
1279 except KeyError:
1280 # it might be a case if specific mandatory entry in dict is empty
1281 self.logger.debug("Key error {}".format(KeyError.message))
1282 raise vimconn.vimconnUnexpectedResponse("new_vminstance(): Failed create new vm instance {}".format(name))
1283
1284 # deploy and power on vm
1285 task = vapp.poweron()
1286 if type(task) is TaskType:
1287 vca.block_until_completed(task)
1288 deploytask = vapp.deploy(powerOn='True')
1289 if type(task) is TaskType:
1290 vca.block_until_completed(deploytask)
1291
1292 # check if vApp deployed and if that the case return vApp UUID otherwise -1
1293 vapp_uuid = self.get_vappid(vca.get_vdc(self.tenant_name), vmname_andid)
1294 if vapp_uuid is not None:
1295 return vapp_uuid
1296 else:
1297 raise vimconn.vimconnUnexpectedResponse("new_vminstance(): Failed create new vm instance {}".format(name))
1298
1299 ##
1300 ##
1301 ## based on current discussion
1302 ##
1303 ##
1304 ## server:
1305 # created: '2016-09-08T11:51:58'
1306 # description: simple-instance.linux1.1
1307 # flavor: ddc6776e-75a9-11e6-ad5f-0800273e724c
1308 # hostId: e836c036-74e7-11e6-b249-0800273e724c
1309 # image: dde30fe6-75a9-11e6-ad5f-0800273e724c
1310 # status: ACTIVE
1311 # error_msg:
1312 # interfaces: …
1313 #
1314 def get_vminstance(self, vim_vm_uuid=None):
1315 """Returns the VM instance information from VIM"""
1316
1317 self.logger.debug("Client requesting vm instance {} ".format(vim_vm_uuid))
1318 vca = self.connect()
1319 if not vca:
1320 raise vimconn.vimconnConnectionException("self.connect() is failed.")
1321
1322 vdc = vca.get_vdc(self.tenant_name)
1323 if vdc is None:
1324 raise vimconn.vimconnConnectionException(
1325 "Failed to get a reference of VDC for a tenant {}".format(self.tenant_name))
1326
1327 vm_info_dict = self.get_vapp_details_rest(vapp_uuid=vim_vm_uuid)
1328 if not vm_info_dict:
1329 self.logger.debug("get_vminstance(): Failed to get vApp name by UUID {}".format(vim_vm_uuid))
1330 raise vimconn.vimconnNotFoundException("Failed to get vApp name by UUID {}".format(vim_vm_uuid))
1331
1332 status_key = vm_info_dict['status']
1333 error = ''
1334 try:
1335 vm_dict = {'created': vm_info_dict['created'],
1336 'description': vm_info_dict['name'],
1337 'status': vcdStatusCode2manoFormat[int(status_key)],
1338 'hostId': vm_info_dict['vmuuid'],
1339 'error_msg': error,
1340 'vim_info': yaml.safe_dump(vm_info_dict), 'interfaces': []}
1341
1342 if 'interfaces' in vm_info_dict:
1343 vm_dict['interfaces'] = vm_info_dict['interfaces']
1344 else:
1345 vm_dict['interfaces'] = []
1346 except KeyError:
1347 vm_dict = {'created': '',
1348 'description': '',
1349 'status': vcdStatusCode2manoFormat[int(-1)],
1350 'hostId': vm_info_dict['vmuuid'],
1351 'error_msg': "Inconsistency state",
1352 'vim_info': yaml.safe_dump(vm_info_dict), 'interfaces': []}
1353
1354 return vm_dict
1355
1356 def delete_vminstance(self, vm__vim_uuid):
1357 """Method poweroff and remove VM instance from vcloud director network.
1358
1359 Args:
1360 vm__vim_uuid: VM UUID
1361
1362 Returns:
1363 Returns the instance identifier
1364 """
1365
1366 self.logger.debug("Client requesting delete vm instance {} ".format(vm__vim_uuid))
1367 vca = self.connect()
1368 if not vca:
1369 raise vimconn.vimconnConnectionException("self.connect() is failed.")
1370
1371 vdc = vca.get_vdc(self.tenant_name)
1372 if vdc is None:
1373 self.logger.debug("delete_vminstance(): Failed to get a reference of VDC for a tenant {}".format(
1374 self.tenant_name))
1375 raise vimconn.vimconnException(
1376 "delete_vminstance(): Failed to get a reference of VDC for a tenant {}".format(self.tenant_name))
1377
1378 try:
1379 vapp_name = self.get_namebyvappid(vca, vdc, vm__vim_uuid)
1380 if vapp_name is None:
1381 self.logger.debug("delete_vminstance(): Failed to get vm by given {} vm uuid".format(vm__vim_uuid))
1382 return -1, "delete_vminstance(): Failed to get vm by given {} vm uuid".format(vm__vim_uuid)
1383 else:
1384 self.logger.info("Deleting vApp {} and UUID {}".format(vapp_name, vm__vim_uuid))
1385
1386 # Delete vApp and wait for status change if task executed and vApp is None.
1387 # We successfully delete vApp from vCloud
1388 vapp = vca.get_vapp(vca.get_vdc(self.tenant_name), vapp_name)
1389 # poweroff vapp / undeploy and delete
1390 power_off_task = vapp.poweroff()
1391 if type(power_off_task) is GenericTask:
1392 vca.block_until_completed(power_off_task)
1393 else:
1394 if not power_off_task:
1395 self.logger.debug("delete_vminstance(): Failed power off VM uuid {} ".format(vm__vim_uuid))
1396
1397 # refresh status
1398 if vapp.me.deployed:
1399 undeploy_task = vapp.undeploy()
1400 if type(undeploy_task) is GenericTask:
1401 retry = 0
1402 while retry <= DELETE_INSTANCE_RETRY:
1403 result = vca.block_until_completed(undeploy_task)
1404 if result:
1405 break
1406 retry += 1
1407 else:
1408 return -1
1409
1410 # delete vapp
1411 vapp = vca.get_vapp(vca.get_vdc(self.tenant_name), vapp_name)
1412 if vapp is not None:
1413 delete_task = vapp.delete()
1414 retry = 0
1415 while retry <= DELETE_INSTANCE_RETRY:
1416 task = vapp.delete()
1417 if type(task) is GenericTask:
1418 vca.block_until_completed(delete_task)
1419 if not delete_task:
1420 self.logger.debug("delete_vminstance(): Failed delete uuid {} ".format(vm__vim_uuid))
1421 retry += 1
1422
1423 except:
1424 self.logger.debug(traceback.format_exc())
1425 raise vimconn.vimconnException("delete_vminstance(): Failed delete vm instance {}".format(vm__vim_uuid))
1426
1427 if vca.get_vapp(vca.get_vdc(self.tenant_name), vapp_name) is None:
1428 return vm__vim_uuid
1429 else:
1430 raise vimconn.vimconnException("delete_vminstance(): Failed delete vm instance {}".format(vm__vim_uuid))
1431
1432 def refresh_vms_status(self, vm_list):
1433 """Get the status of the virtual machines and their interfaces/ports
1434 Params: the list of VM identifiers
1435 Returns a dictionary with:
1436 vm_id: #VIM id of this Virtual Machine
1437 status: #Mandatory. Text with one of:
1438 # DELETED (not found at vim)
1439 # VIM_ERROR (Cannot connect to VIM, VIM response error, ...)
1440 # OTHER (Vim reported other status not understood)
1441 # ERROR (VIM indicates an ERROR status)
1442 # ACTIVE, PAUSED, SUSPENDED, INACTIVE (not running),
1443 # CREATING (on building process), ERROR
1444 # ACTIVE:NoMgmtIP (Active but any of its interface has an IP address
1445 #
1446 error_msg: #Text with VIM error message, if any. Or the VIM connection ERROR
1447 vim_info: #Text with plain information obtained from vim (yaml.safe_dump)
1448 interfaces:
1449 - vim_info: #Text with plain information obtained from vim (yaml.safe_dump)
1450 mac_address: #Text format XX:XX:XX:XX:XX:XX
1451 vim_net_id: #network id where this interface is connected
1452 vim_interface_id: #interface/port VIM id
1453 ip_address: #null, or text with IPv4, IPv6 address
1454 """
1455
1456 self.logger.debug("Client requesting refresh vm status for {} ".format(vm_list))
1457 vca = self.connect()
1458 if not vca:
1459 raise vimconn.vimconnConnectionException("self.connect() is failed.")
1460
1461 vdc = vca.get_vdc(self.tenant_name)
1462 if vdc is None:
1463 raise vimconn.vimconnException("Failed to get a reference of VDC for a tenant {}".format(self.tenant_name))
1464
1465 vms_dict = {}
1466 for vmuuid in vm_list:
1467 vmname = self.get_namebyvappid(vca, vdc, vmuuid)
1468 if vmname is not None:
1469
1470 the_vapp = vca.get_vapp(vdc, vmname)
1471 vm_info = the_vapp.get_vms_details()
1472 vm_status = vm_info[0]['status']
1473
1474 vm_dict = {'status': vcdStatusCode2manoFormat[the_vapp.me.get_status()],
1475 'error_msg': vcdStatusCode2manoFormat[the_vapp.me.get_status()],
1476 'vim_info': yaml.safe_dump(the_vapp.get_vms_details()), 'interfaces': []}
1477
1478 # get networks
1479 try:
1480 vm_app_networks = the_vapp.get_vms_network_info()
1481 for vapp_network in vm_app_networks:
1482 for vm_network in vapp_network:
1483 if vm_network['name'] == vmname:
1484 interface = {"mac_address": vm_network['mac'],
1485 "vim_net_id": self.get_network_id_by_name(vm_network['network_name']),
1486 "vim_interface_id": self.get_network_id_by_name(vm_network['network_name']),
1487 'ip_address': vm_network['ip']}
1488 # interface['vim_info'] = yaml.safe_dump(vm_network)
1489 vm_dict["interfaces"].append(interface)
1490 # add a vm to vm dict
1491 vms_dict.setdefault(vmuuid, vm_dict)
1492 except KeyError:
1493 self.logger.debug("Error in respond {}".format(KeyError.message))
1494 self.logger.debug(traceback.format_exc())
1495
1496 return vms_dict
1497
1498 def action_vminstance(self, vm__vim_uuid=None, action_dict=None):
1499 """Send and action over a VM instance from VIM
1500 Returns the vm_id if the action was successfully sent to the VIM"""
1501
1502 self.logger.debug("Received action for vm {} and action dict {}".format(vm__vim_uuid, action_dict))
1503 if vm__vim_uuid is None or action_dict is None:
1504 raise vimconn.vimconnException("Invalid request. VM id or action is None.")
1505
1506 vca = self.connect()
1507 if not vca:
1508 raise vimconn.vimconnConnectionException("self.connect() is failed.")
1509
1510 vdc = vca.get_vdc(self.tenant_name)
1511 if vdc is None:
1512 return -1, "Failed to get a reference of VDC for a tenant {}".format(self.tenant_name)
1513
1514 vapp_name = self.get_namebyvappid(vca, vdc, vm__vim_uuid)
1515 if vapp_name is None:
1516 self.logger.debug("action_vminstance(): Failed to get vm by given {} vm uuid".format(vm__vim_uuid))
1517 raise vimconn.vimconnException("Failed to get vm by given {} vm uuid".format(vm__vim_uuid))
1518 else:
1519 self.logger.info("Action_vminstance vApp {} and UUID {}".format(vapp_name, vm__vim_uuid))
1520
1521 try:
1522 the_vapp = vca.get_vapp(vdc, vapp_name)
1523 # TODO fix all status
1524 if "start" in action_dict:
1525 if action_dict["start"] == "rebuild":
1526 the_vapp.deploy(powerOn=True)
1527 else:
1528 vm_info = the_vapp.get_vms_details()
1529 vm_status = vm_info[0]['status']
1530 if vm_status == "Suspended":
1531 the_vapp.poweron()
1532 elif vm_status.status == "Powered off":
1533 the_vapp.poweron()
1534 elif "pause" in action_dict:
1535 pass
1536 ## server.pause()
1537 elif "resume" in action_dict:
1538 pass
1539 ## server.resume()
1540 elif "shutoff" in action_dict or "shutdown" in action_dict:
1541 the_vapp.shutdown()
1542 elif "forceOff" in action_dict:
1543 the_vapp.reset()
1544 elif "terminate" in action_dict:
1545 the_vapp.delete()
1546 # elif "createImage" in action_dict:
1547 # server.create_image()
1548 else:
1549 pass
1550 except:
1551 pass
1552
1553 def get_vminstance_console(self, vm_id, console_type="vnc"):
1554 """
1555 Get a console for the virtual machine
1556 Params:
1557 vm_id: uuid of the VM
1558 console_type, can be:
1559 "novnc" (by default), "xvpvnc" for VNC types,
1560 "rdp-html5" for RDP types, "spice-html5" for SPICE types
1561 Returns dict with the console parameters:
1562 protocol: ssh, ftp, http, https, ...
1563 server: usually ip address
1564 port: the http, ssh, ... port
1565 suffix: extra text, e.g. the http path and query string
1566 """
1567 raise vimconn.vimconnNotImplemented("Should have implemented this")
1568
1569 # NOT USED METHODS in current version
1570
1571 def host_vim2gui(self, host, server_dict):
1572 """Transform host dictionary from VIM format to GUI format,
1573 and append to the server_dict
1574 """
1575 raise vimconn.vimconnNotImplemented("Should have implemented this")
1576
1577 def get_hosts_info(self):
1578 """Get the information of deployed hosts
1579 Returns the hosts content"""
1580 raise vimconn.vimconnNotImplemented("Should have implemented this")
1581
1582 def get_hosts(self, vim_tenant):
1583 """Get the hosts and deployed instances
1584 Returns the hosts content"""
1585 raise vimconn.vimconnNotImplemented("Should have implemented this")
1586
1587 def get_processor_rankings(self):
1588 """Get the processor rankings in the VIM database"""
1589 raise vimconn.vimconnNotImplemented("Should have implemented this")
1590
1591 def new_host(self, host_data):
1592 """Adds a new host to VIM"""
1593 '''Returns status code of the VIM response'''
1594 raise vimconn.vimconnNotImplemented("Should have implemented this")
1595
1596 def new_external_port(self, port_data):
1597 """Adds a external port to VIM"""
1598 '''Returns the port identifier'''
1599 raise vimconn.vimconnNotImplemented("Should have implemented this")
1600
1601 def new_external_network(self, net_name, net_type):
1602 """Adds a external network to VIM (shared)"""
1603 '''Returns the network identifier'''
1604 raise vimconn.vimconnNotImplemented("Should have implemented this")
1605
1606 def connect_port_network(self, port_id, network_id, admin=False):
1607 """Connects a external port to a network"""
1608 '''Returns status code of the VIM response'''
1609 raise vimconn.vimconnNotImplemented("Should have implemented this")
1610
1611 def new_vminstancefromJSON(self, vm_data):
1612 """Adds a VM instance to VIM"""
1613 '''Returns the instance identifier'''
1614 raise vimconn.vimconnNotImplemented("Should have implemented this")
1615
1616 def get_network_name_by_id(self, network_uuid=None):
1617 """Method gets vcloud director network named based on supplied uuid.
1618
1619 Args:
1620 network_uuid: network_id
1621
1622 Returns:
1623 The return network name.
1624 """
1625
1626 vca = self.connect()
1627 if not vca:
1628 raise vimconn.vimconnConnectionException("self.connect() is failed.")
1629
1630 if not network_uuid:
1631 return None
1632
1633 try:
1634 org_dict = self.get_org(self.org_uuid)
1635 if 'networks' in org_dict:
1636 org_network_dict = org_dict['networks']
1637 for net_uuid in org_network_dict:
1638 if net_uuid == network_uuid:
1639 return org_network_dict[net_uuid]
1640 except:
1641 self.logger.debug("Exception in get_network_name_by_id")
1642 self.logger.debug(traceback.format_exc())
1643
1644 return None
1645
1646 def get_network_id_by_name(self, network_name=None):
1647 """Method gets vcloud director network uuid based on supplied name.
1648
1649 Args:
1650 network_name: network_name
1651 Returns:
1652 The return network uuid.
1653 network_uuid: network_id
1654 """
1655
1656 vca = self.connect()
1657 if not vca:
1658 raise vimconn.vimconnConnectionException("self.connect() is failed.")
1659
1660 if not network_name:
1661 self.logger.debug("get_network_id_by_name() : Network name is empty")
1662 return None
1663
1664 try:
1665 org_dict = self.get_org(self.org_uuid)
1666 if org_dict and 'networks' in org_dict:
1667 org_network_dict = org_dict['networks']
1668 for net_uuid,net_name in org_network_dict.iteritems():
1669 if net_name == network_name:
1670 return net_uuid
1671
1672 except KeyError as exp:
1673 self.logger.debug("get_network_id_by_name() : KeyError- {} ".format(exp))
1674
1675 return None
1676
1677 def list_org_action(self):
1678 """
1679 Method leverages vCloud director and query for available organization for particular user
1680
1681 Args:
1682 vca - is active VCA connection.
1683 vdc_name - is a vdc name that will be used to query vms action
1684
1685 Returns:
1686 The return XML respond
1687 """
1688
1689 vca = self.connect()
1690 if not vca:
1691 raise vimconn.vimconnConnectionException("self.connect() is failed")
1692
1693 url_list = [vca.host, '/api/org']
1694 vm_list_rest_call = ''.join(url_list)
1695
1696 if not (not vca.vcloud_session or not vca.vcloud_session.organization):
1697 response = Http.get(url=vm_list_rest_call,
1698 headers=vca.vcloud_session.get_vcloud_headers(),
1699 verify=vca.verify,
1700 logger=vca.logger)
1701 if response.status_code == requests.codes.ok:
1702 return response.content
1703
1704 return None
1705
1706 def get_org_action(self, org_uuid=None):
1707 """
1708 Method leverages vCloud director and retrieve available object fdr organization.
1709
1710 Args:
1711 vca - is active VCA connection.
1712 vdc_name - is a vdc name that will be used to query vms action
1713
1714 Returns:
1715 The return XML respond
1716 """
1717
1718 vca = self.connect()
1719 if not vca:
1720 raise vimconn.vimconnConnectionException("self.connect() is failed")
1721
1722 if org_uuid is None:
1723 return None
1724
1725 url_list = [vca.host, '/api/org/', org_uuid]
1726 vm_list_rest_call = ''.join(url_list)
1727
1728 if not (not vca.vcloud_session or not vca.vcloud_session.organization):
1729 response = Http.get(url=vm_list_rest_call,
1730 headers=vca.vcloud_session.get_vcloud_headers(),
1731 verify=vca.verify,
1732 logger=vca.logger)
1733 if response.status_code == requests.codes.ok:
1734 return response.content
1735
1736 return None
1737
1738 def get_org(self, org_uuid=None):
1739 """
1740 Method retrieves available organization in vCloud Director
1741
1742 Args:
1743 org_uuid - is a organization uuid.
1744
1745 Returns:
1746 The return dictionary with following key
1747 "network" - for network list under the org
1748 "catalogs" - for network list under the org
1749 "vdcs" - for vdc list under org
1750 """
1751
1752 org_dict = {}
1753 vca = self.connect()
1754 if not vca:
1755 raise vimconn.vimconnConnectionException("self.connect() is failed")
1756
1757 if org_uuid is None:
1758 return org_dict
1759
1760 content = self.get_org_action(org_uuid=org_uuid)
1761 try:
1762 vdc_list = {}
1763 network_list = {}
1764 catalog_list = {}
1765 vm_list_xmlroot = XmlElementTree.fromstring(content)
1766 for child in vm_list_xmlroot:
1767 if child.attrib['type'] == 'application/vnd.vmware.vcloud.vdc+xml':
1768 vdc_list[child.attrib['href'].split("/")[-1:][0]] = child.attrib['name']
1769 org_dict['vdcs'] = vdc_list
1770 if child.attrib['type'] == 'application/vnd.vmware.vcloud.orgNetwork+xml':
1771 network_list[child.attrib['href'].split("/")[-1:][0]] = child.attrib['name']
1772 org_dict['networks'] = network_list
1773 if child.attrib['type'] == 'application/vnd.vmware.vcloud.catalog+xml':
1774 catalog_list[child.attrib['href'].split("/")[-1:][0]] = child.attrib['name']
1775 org_dict['catalogs'] = catalog_list
1776 except:
1777 pass
1778
1779 return org_dict
1780
1781 def get_org_list(self):
1782 """
1783 Method retrieves available organization in vCloud Director
1784
1785 Args:
1786 vca - is active VCA connection.
1787
1788 Returns:
1789 The return dictionary and key for each entry VDC UUID
1790 """
1791
1792 org_dict = {}
1793 vca = self.connect()
1794 if not vca:
1795 raise vimconn.vimconnConnectionException("self.connect() is failed")
1796
1797 content = self.list_org_action()
1798 try:
1799 vm_list_xmlroot = XmlElementTree.fromstring(content)
1800 for vm_xml in vm_list_xmlroot:
1801 if vm_xml.tag.split("}")[1] == 'Org':
1802 org_uuid = vm_xml.attrib['href'].split('/')[-1:]
1803 org_dict[org_uuid[0]] = vm_xml.attrib['name']
1804 except:
1805 pass
1806
1807 return org_dict
1808
1809 def vms_view_action(self, vdc_name=None):
1810 """ Method leverages vCloud director vms query call
1811
1812 Args:
1813 vca - is active VCA connection.
1814 vdc_name - is a vdc name that will be used to query vms action
1815
1816 Returns:
1817 The return XML respond
1818 """
1819 vca = self.connect()
1820 if vdc_name is None:
1821 return None
1822
1823 url_list = [vca.host, '/api/vms/query']
1824 vm_list_rest_call = ''.join(url_list)
1825
1826 if not (not vca.vcloud_session or not vca.vcloud_session.organization):
1827 refs = filter(lambda ref: ref.name == vdc_name and ref.type_ == 'application/vnd.vmware.vcloud.vdc+xml',
1828 vca.vcloud_session.organization.Link)
1829 if len(refs) == 1:
1830 response = Http.get(url=vm_list_rest_call,
1831 headers=vca.vcloud_session.get_vcloud_headers(),
1832 verify=vca.verify,
1833 logger=vca.logger)
1834 if response.status_code == requests.codes.ok:
1835 return response.content
1836
1837 return None
1838
1839 def get_vapp_list(self, vdc_name=None):
1840 """
1841 Method retrieves vApp list deployed vCloud director and returns a dictionary
1842 contains a list of all vapp deployed for queried VDC.
1843 The key for a dictionary is vApp UUID
1844
1845
1846 Args:
1847 vca - is active VCA connection.
1848 vdc_name - is a vdc name that will be used to query vms action
1849
1850 Returns:
1851 The return dictionary and key for each entry vapp UUID
1852 """
1853
1854 vapp_dict = {}
1855 if vdc_name is None:
1856 return vapp_dict
1857
1858 content = self.vms_view_action(vdc_name=vdc_name)
1859 try:
1860 vm_list_xmlroot = XmlElementTree.fromstring(content)
1861 for vm_xml in vm_list_xmlroot:
1862 if vm_xml.tag.split("}")[1] == 'VMRecord':
1863 if vm_xml.attrib['isVAppTemplate'] == 'true':
1864 rawuuid = vm_xml.attrib['container'].split('/')[-1:]
1865 if 'vappTemplate-' in rawuuid[0]:
1866 # vm in format vappTemplate-e63d40e7-4ff5-4c6d-851f-96c1e4da86a5 we remove
1867 # vm and use raw UUID as key
1868 vapp_dict[rawuuid[0][13:]] = vm_xml.attrib
1869 except:
1870 pass
1871
1872 return vapp_dict
1873
1874 def get_vm_list(self, vdc_name=None):
1875 """
1876 Method retrieves VM's list deployed vCloud director. It returns a dictionary
1877 contains a list of all VM's deployed for queried VDC.
1878 The key for a dictionary is VM UUID
1879
1880
1881 Args:
1882 vca - is active VCA connection.
1883 vdc_name - is a vdc name that will be used to query vms action
1884
1885 Returns:
1886 The return dictionary and key for each entry vapp UUID
1887 """
1888 vm_dict = {}
1889
1890 if vdc_name is None:
1891 return vm_dict
1892
1893 content = self.vms_view_action(vdc_name=vdc_name)
1894 try:
1895 vm_list_xmlroot = XmlElementTree.fromstring(content)
1896 for vm_xml in vm_list_xmlroot:
1897 if vm_xml.tag.split("}")[1] == 'VMRecord':
1898 if vm_xml.attrib['isVAppTemplate'] == 'false':
1899 rawuuid = vm_xml.attrib['href'].split('/')[-1:]
1900 if 'vm-' in rawuuid[0]:
1901 # vm in format vm-e63d40e7-4ff5-4c6d-851f-96c1e4da86a5 we remove
1902 # vm and use raw UUID as key
1903 vm_dict[rawuuid[0][3:]] = vm_xml.attrib
1904 except:
1905 pass
1906
1907 return vm_dict
1908
1909 def get_vapp(self, vdc_name=None, vapp_name=None, isuuid=False):
1910 """
1911 Method retrieves VM deployed vCloud director. It returns VM attribute as dictionary
1912 contains a list of all VM's deployed for queried VDC.
1913 The key for a dictionary is VM UUID
1914
1915
1916 Args:
1917 vca - is active VCA connection.
1918 vdc_name - is a vdc name that will be used to query vms action
1919
1920 Returns:
1921 The return dictionary and key for each entry vapp UUID
1922 """
1923 vm_dict = {}
1924 vca = self.connect()
1925 if not vca:
1926 raise vimconn.vimconnConnectionException("self.connect() is failed")
1927
1928 if vdc_name is None:
1929 return vm_dict
1930
1931 content = self.vms_view_action(vdc_name=vdc_name)
1932 try:
1933 vm_list_xmlroot = XmlElementTree.fromstring(content)
1934 for vm_xml in vm_list_xmlroot:
1935 if vm_xml.tag.split("}")[1] == 'VMRecord' and vm_xml.attrib['isVAppTemplate'] == 'false':
1936 # lookup done by UUID
1937 if isuuid:
1938 if vapp_name in vm_xml.attrib['container']:
1939 rawuuid = vm_xml.attrib['href'].split('/')[-1:]
1940 if 'vm-' in rawuuid[0]:
1941 vm_dict[rawuuid[0][3:]] = vm_xml.attrib
1942 break
1943 # lookup done by Name
1944 else:
1945 if vapp_name in vm_xml.attrib['name']:
1946 rawuuid = vm_xml.attrib['href'].split('/')[-1:]
1947 if 'vm-' in rawuuid[0]:
1948 vm_dict[rawuuid[0][3:]] = vm_xml.attrib
1949 break
1950 except:
1951 pass
1952
1953 return vm_dict
1954
1955 def get_network_action(self, network_uuid=None):
1956 """
1957 Method leverages vCloud director and query network based on network uuid
1958
1959 Args:
1960 vca - is active VCA connection.
1961 network_uuid - is a network uuid
1962
1963 Returns:
1964 The return XML respond
1965 """
1966
1967 vca = self.connect()
1968 if not vca:
1969 raise vimconn.vimconnConnectionException("self.connect() is failed")
1970
1971 if network_uuid is None:
1972 return None
1973
1974 url_list = [vca.host, '/api/network/', network_uuid]
1975 vm_list_rest_call = ''.join(url_list)
1976
1977 if not (not vca.vcloud_session or not vca.vcloud_session.organization):
1978 response = Http.get(url=vm_list_rest_call,
1979 headers=vca.vcloud_session.get_vcloud_headers(),
1980 verify=vca.verify,
1981 logger=vca.logger)
1982 if response.status_code == requests.codes.ok:
1983 return response.content
1984
1985 return None
1986
1987 def get_vcd_network(self, network_uuid=None):
1988 """
1989 Method retrieves available network from vCloud Director
1990
1991 Args:
1992 network_uuid - is VCD network UUID
1993
1994 Each element serialized as key : value pair
1995
1996 Following keys available for access. network_configuration['Gateway'}
1997 <Configuration>
1998 <IpScopes>
1999 <IpScope>
2000 <IsInherited>true</IsInherited>
2001 <Gateway>172.16.252.100</Gateway>
2002 <Netmask>255.255.255.0</Netmask>
2003 <Dns1>172.16.254.201</Dns1>
2004 <Dns2>172.16.254.202</Dns2>
2005 <DnsSuffix>vmwarelab.edu</DnsSuffix>
2006 <IsEnabled>true</IsEnabled>
2007 <IpRanges>
2008 <IpRange>
2009 <StartAddress>172.16.252.1</StartAddress>
2010 <EndAddress>172.16.252.99</EndAddress>
2011 </IpRange>
2012 </IpRanges>
2013 </IpScope>
2014 </IpScopes>
2015 <FenceMode>bridged</FenceMode>
2016
2017 Returns:
2018 The return dictionary and key for each entry vapp UUID
2019 """
2020
2021 network_configuration = {}
2022 if network_uuid is None:
2023 return network_uuid
2024
2025 content = self.get_network_action(network_uuid=network_uuid)
2026 try:
2027 vm_list_xmlroot = XmlElementTree.fromstring(content)
2028
2029 network_configuration['status'] = vm_list_xmlroot.get("status")
2030 network_configuration['name'] = vm_list_xmlroot.get("name")
2031 network_configuration['uuid'] = vm_list_xmlroot.get("id").split(":")[3]
2032
2033 for child in vm_list_xmlroot:
2034 if child.tag.split("}")[1] == 'IsShared':
2035 network_configuration['isShared'] = child.text.strip()
2036 if child.tag.split("}")[1] == 'Configuration':
2037 for configuration in child.iter():
2038 tagKey = configuration.tag.split("}")[1].strip()
2039 if tagKey != "":
2040 network_configuration[tagKey] = configuration.text.strip()
2041 return network_configuration
2042 except:
2043 pass
2044
2045 return network_configuration
2046
2047 def delete_network_action(self, network_uuid=None):
2048 """
2049 Method delete given network from vCloud director
2050
2051 Args:
2052 network_uuid - is a network uuid that client wish to delete
2053
2054 Returns:
2055 The return None or XML respond or false
2056 """
2057
2058 vca = self.connect_as_admin()
2059 if not vca:
2060 raise vimconn.vimconnConnectionException("self.connect() is failed")
2061 if network_uuid is None:
2062 return False
2063
2064 url_list = [vca.host, '/api/admin/network/', network_uuid]
2065 vm_list_rest_call = ''.join(url_list)
2066
2067 if not (not vca.vcloud_session or not vca.vcloud_session.organization):
2068 response = Http.delete(url=vm_list_rest_call,
2069 headers=vca.vcloud_session.get_vcloud_headers(),
2070 verify=vca.verify,
2071 logger=vca.logger)
2072
2073 if response.status_code == 202:
2074 return True
2075
2076 return False
2077
2078 def create_network(self, network_name=None, parent_network_uuid=None, isshared='true'):
2079 """
2080 Method create network in vCloud director
2081
2082 Args:
2083 network_name - is network name to be created.
2084 parent_network_uuid - is parent provider vdc network that will be used for mapping.
2085 It optional attribute. by default if no parent network indicate the first available will be used.
2086
2087 Returns:
2088 The return network uuid or return None
2089 """
2090
2091 new_network_name = [network_name, '-', str(uuid.uuid4())]
2092 content = self.create_network_rest(network_name=''.join(new_network_name),
2093 parent_network_uuid=parent_network_uuid,
2094 isshared=isshared)
2095 if content is None:
2096 self.logger.debug("Failed create network {}.".format(network_name))
2097 return None
2098
2099 try:
2100 vm_list_xmlroot = XmlElementTree.fromstring(content)
2101 vcd_uuid = vm_list_xmlroot.get('id').split(":")
2102 if len(vcd_uuid) == 4:
2103 self.logger.info("Create new network name: {} uuid: {}".format(network_name, vcd_uuid[3]))
2104 return vcd_uuid[3]
2105 except:
2106 self.logger.debug("Failed create network {}".format(network_name))
2107 return None
2108
2109 def create_network_rest(self, network_name=None, parent_network_uuid=None, isshared='true'):
2110 """
2111 Method create network in vCloud director
2112
2113 Args:
2114 network_name - is network name to be created.
2115 parent_network_uuid - is parent provider vdc network that will be used for mapping.
2116 It optional attribute. by default if no parent network indicate the first available will be used.
2117
2118 Returns:
2119 The return network uuid or return None
2120 """
2121
2122 vca = self.connect_as_admin()
2123 if not vca:
2124 raise vimconn.vimconnConnectionException("self.connect() is failed.")
2125 if network_name is None:
2126 return None
2127
2128 url_list = [vca.host, '/api/admin/vdc/', self.tenant_id]
2129 vm_list_rest_call = ''.join(url_list)
2130 if not (not vca.vcloud_session or not vca.vcloud_session.organization):
2131 response = Http.get(url=vm_list_rest_call,
2132 headers=vca.vcloud_session.get_vcloud_headers(),
2133 verify=vca.verify,
2134 logger=vca.logger)
2135
2136 provider_network = None
2137 available_networks = None
2138 add_vdc_rest_url = None
2139
2140 if response.status_code != requests.codes.ok:
2141 self.logger.debug("REST API call {} failed. Return status code {}".format(vm_list_rest_call,
2142 response.status_code))
2143 return None
2144 else:
2145 try:
2146 vm_list_xmlroot = XmlElementTree.fromstring(response.content)
2147 for child in vm_list_xmlroot:
2148 if child.tag.split("}")[1] == 'ProviderVdcReference':
2149 provider_network = child.attrib.get('href')
2150 # application/vnd.vmware.admin.providervdc+xml
2151 if child.tag.split("}")[1] == 'Link':
2152 if child.attrib.get('type') == 'application/vnd.vmware.vcloud.orgVdcNetwork+xml' \
2153 and child.attrib.get('rel') == 'add':
2154 add_vdc_rest_url = child.attrib.get('href')
2155 except:
2156 self.logger.debug("Failed parse respond for rest api call {}".format(vm_list_rest_call))
2157 self.logger.debug("Respond body {}".format(response.content))
2158 return None
2159
2160 # find pvdc provided available network
2161 response = Http.get(url=provider_network,
2162 headers=vca.vcloud_session.get_vcloud_headers(),
2163 verify=vca.verify,
2164 logger=vca.logger)
2165 if response.status_code != requests.codes.ok:
2166 self.logger.debug("REST API call {} failed. Return status code {}".format(vm_list_rest_call,
2167 response.status_code))
2168 return None
2169
2170 # available_networks.split("/")[-1]
2171
2172 if parent_network_uuid is None:
2173 try:
2174 vm_list_xmlroot = XmlElementTree.fromstring(response.content)
2175 for child in vm_list_xmlroot.iter():
2176 if child.tag.split("}")[1] == 'AvailableNetworks':
2177 for networks in child.iter():
2178 # application/vnd.vmware.admin.network+xml
2179 if networks.attrib.get('href') is not None:
2180 available_networks = networks.attrib.get('href')
2181 break
2182 except:
2183 return None
2184
2185 # either use client provided UUID or search for a first available
2186 # if both are not defined we return none
2187 if parent_network_uuid is not None:
2188 url_list = [vca.host, '/api/admin/network/', parent_network_uuid]
2189 add_vdc_rest_url = ''.join(url_list)
2190
2191 # return response.content
2192 data = """ <OrgVdcNetwork name="{0:s}" xmlns="http://www.vmware.com/vcloud/v1.5">
2193 <Description>Openmano created</Description>
2194 <Configuration>
2195 <ParentNetwork href="{1:s}"/>
2196 <FenceMode>{2:s}</FenceMode>
2197 </Configuration>
2198 <IsShared>{3:s}</IsShared>
2199 </OrgVdcNetwork> """.format(escape(network_name), available_networks, "bridged", isshared)
2200
2201 headers = vca.vcloud_session.get_vcloud_headers()
2202 headers['Content-Type'] = 'application/vnd.vmware.vcloud.orgVdcNetwork+xml'
2203 response = Http.post(url=add_vdc_rest_url, headers=headers, data=data, verify=vca.verify, logger=vca.logger)
2204
2205 # if we all ok we respond with content otherwise by default None
2206 if response.status_code == 201:
2207 return response.content
2208 return None
2209
2210 def get_provider_rest(self, vca=None):
2211 """
2212 Method gets provider vdc view from vcloud director
2213
2214 Args:
2215 network_name - is network name to be created.
2216 parent_network_uuid - is parent provider vdc network that will be used for mapping.
2217 It optional attribute. by default if no parent network indicate the first available will be used.
2218
2219 Returns:
2220 The return xml content of respond or None
2221 """
2222
2223 url_list = [vca.host, '/api/admin']
2224 response = Http.get(url=''.join(url_list),
2225 headers=vca.vcloud_session.get_vcloud_headers(),
2226 verify=vca.verify,
2227 logger=vca.logger)
2228
2229 if response.status_code == requests.codes.ok:
2230 return response.content
2231 return None
2232
2233 def create_vdc(self, vdc_name=None):
2234
2235 vdc_dict = {}
2236
2237 xml_content = self.create_vdc_from_tmpl_rest(vdc_name=vdc_name)
2238 if xml_content is not None:
2239 try:
2240 task_resp_xmlroot = XmlElementTree.fromstring(xml_content)
2241 for child in task_resp_xmlroot:
2242 if child.tag.split("}")[1] == 'Owner':
2243 vdc_id = child.attrib.get('href').split("/")[-1]
2244 vdc_dict[vdc_id] = task_resp_xmlroot.get('href')
2245 return vdc_dict
2246 except:
2247 self.logger.debug("Respond body {}".format(xml_content))
2248
2249 return None
2250
2251 def create_vdc_from_tmpl_rest(self, vdc_name=None):
2252 """
2253 Method create vdc in vCloud director based on VDC template.
2254 it uses pre-defined template that must be named openmano
2255
2256 Args:
2257 vdc_name - name of a new vdc.
2258
2259 Returns:
2260 The return xml content of respond or None
2261 """
2262
2263 self.logger.info("Creating new vdc {}".format(vdc_name))
2264 vca = self.connect()
2265 if not vca:
2266 raise vimconn.vimconnConnectionException("self.connect() is failed")
2267 if vdc_name is None:
2268 return None
2269
2270 url_list = [vca.host, '/api/vdcTemplates']
2271 vm_list_rest_call = ''.join(url_list)
2272 response = Http.get(url=vm_list_rest_call,
2273 headers=vca.vcloud_session.get_vcloud_headers(),
2274 verify=vca.verify,
2275 logger=vca.logger)
2276
2277 # container url to a template
2278 vdc_template_ref = None
2279 try:
2280 vm_list_xmlroot = XmlElementTree.fromstring(response.content)
2281 for child in vm_list_xmlroot:
2282 # application/vnd.vmware.admin.providervdc+xml
2283 # we need find a template from witch we instantiate VDC
2284 if child.tag.split("}")[1] == 'VdcTemplate':
2285 if child.attrib.get('type') == 'application/vnd.vmware.admin.vdcTemplate+xml' and child.attrib.get(
2286 'name') == 'openmano':
2287 vdc_template_ref = child.attrib.get('href')
2288 except:
2289 self.logger.debug("Failed parse respond for rest api call {}".format(vm_list_rest_call))
2290 self.logger.debug("Respond body {}".format(response.content))
2291 return None
2292
2293 # if we didn't found required pre defined template we return None
2294 if vdc_template_ref is None:
2295 return None
2296
2297 try:
2298 # instantiate vdc
2299 url_list = [vca.host, '/api/org/', self.org_uuid, '/action/instantiate']
2300 vm_list_rest_call = ''.join(url_list)
2301 data = """<InstantiateVdcTemplateParams name="{0:s}" xmlns="http://www.vmware.com/vcloud/v1.5">
2302 <Source href="{1:s}"></Source>
2303 <Description>opnemano</Description>
2304 </InstantiateVdcTemplateParams>""".format(vdc_name, vdc_template_ref)
2305 headers = vca.vcloud_session.get_vcloud_headers()
2306 headers['Content-Type'] = 'application/vnd.vmware.vcloud.instantiateVdcTemplateParams+xml'
2307 response = Http.post(url=vm_list_rest_call, headers=headers, data=data, verify=vca.verify,
2308 logger=vca.logger)
2309 # if we all ok we respond with content otherwise by default None
2310 if response.status_code >= 200 and response.status_code < 300:
2311 return response.content
2312 return None
2313 except:
2314 self.logger.debug("Failed parse respond for rest api call {}".format(vm_list_rest_call))
2315 self.logger.debug("Respond body {}".format(response.content))
2316
2317 return None
2318
2319 def create_vdc_rest(self, vdc_name=None):
2320 """
2321 Method create network in vCloud director
2322
2323 Args:
2324 network_name - is network name to be created.
2325 parent_network_uuid - is parent provider vdc network that will be used for mapping.
2326 It optional attribute. by default if no parent network indicate the first available will be used.
2327
2328 Returns:
2329 The return network uuid or return None
2330 """
2331
2332 self.logger.info("Creating new vdc {}".format(vdc_name))
2333
2334 vca = self.connect_as_admin()
2335 if not vca:
2336 raise vimconn.vimconnConnectionException("self.connect() is failed")
2337 if vdc_name is None:
2338 return None
2339
2340 url_list = [vca.host, '/api/admin/org/', self.org_uuid]
2341 vm_list_rest_call = ''.join(url_list)
2342 if not (not vca.vcloud_session or not vca.vcloud_session.organization):
2343 response = Http.get(url=vm_list_rest_call,
2344 headers=vca.vcloud_session.get_vcloud_headers(),
2345 verify=vca.verify,
2346 logger=vca.logger)
2347
2348 provider_vdc_ref = None
2349 add_vdc_rest_url = None
2350 available_networks = None
2351
2352 if response.status_code != requests.codes.ok:
2353 self.logger.debug("REST API call {} failed. Return status code {}".format(vm_list_rest_call,
2354 response.status_code))
2355 return None
2356 else:
2357 try:
2358 vm_list_xmlroot = XmlElementTree.fromstring(response.content)
2359 for child in vm_list_xmlroot:
2360 # application/vnd.vmware.admin.providervdc+xml
2361 if child.tag.split("}")[1] == 'Link':
2362 if child.attrib.get('type') == 'application/vnd.vmware.admin.createVdcParams+xml' \
2363 and child.attrib.get('rel') == 'add':
2364 add_vdc_rest_url = child.attrib.get('href')
2365 except:
2366 self.logger.debug("Failed parse respond for rest api call {}".format(vm_list_rest_call))
2367 self.logger.debug("Respond body {}".format(response.content))
2368 return None
2369
2370 response = self.get_provider_rest(vca=vca)
2371 try:
2372 vm_list_xmlroot = XmlElementTree.fromstring(response)
2373 for child in vm_list_xmlroot:
2374 if child.tag.split("}")[1] == 'ProviderVdcReferences':
2375 for sub_child in child:
2376 provider_vdc_ref = sub_child.attrib.get('href')
2377 except:
2378 self.logger.debug("Failed parse respond for rest api call {}".format(vm_list_rest_call))
2379 self.logger.debug("Respond body {}".format(response))
2380 return None
2381
2382 if add_vdc_rest_url is not None and provider_vdc_ref is not None:
2383 data = """ <CreateVdcParams name="{0:s}" xmlns="http://www.vmware.com/vcloud/v1.5"><Description>{1:s}</Description>
2384 <AllocationModel>ReservationPool</AllocationModel>
2385 <ComputeCapacity><Cpu><Units>MHz</Units><Allocated>2048</Allocated><Limit>2048</Limit></Cpu>
2386 <Memory><Units>MB</Units><Allocated>2048</Allocated><Limit>2048</Limit></Memory>
2387 </ComputeCapacity><NicQuota>0</NicQuota><NetworkQuota>100</NetworkQuota>
2388 <VdcStorageProfile><Enabled>true</Enabled><Units>MB</Units><Limit>20480</Limit><Default>true</Default></VdcStorageProfile>
2389 <ProviderVdcReference
2390 name="Main Provider"
2391 href="{2:s}" />
2392 <UsesFastProvisioning>true</UsesFastProvisioning></CreateVdcParams>""".format(escape(vdc_name),
2393 escape(vdc_name),
2394 provider_vdc_ref)
2395
2396 headers = vca.vcloud_session.get_vcloud_headers()
2397 headers['Content-Type'] = 'application/vnd.vmware.admin.createVdcParams+xml'
2398 response = Http.post(url=add_vdc_rest_url, headers=headers, data=data, verify=vca.verify,
2399 logger=vca.logger)
2400
2401 # if we all ok we respond with content otherwise by default None
2402 if response.status_code == 201:
2403 return response.content
2404 return None
2405
2406 def get_vapp_details_rest(self, vapp_uuid=None):
2407 """
2408 Method retrieve vapp detail from vCloud director
2409
2410 Args:
2411 vapp_uuid - is vapp identifier.
2412
2413 Returns:
2414 The return network uuid or return None
2415 """
2416
2417 parsed_respond = {}
2418
2419 vca = self.connect()
2420 if not vca:
2421 raise vimconn.vimconnConnectionException("self.connect() is failed")
2422 if vapp_uuid is None:
2423 return None
2424
2425 url_list = [vca.host, '/api/vApp/vapp-', vapp_uuid]
2426 get_vapp_restcall = ''.join(url_list)
2427 if not (not vca.vcloud_session or not vca.vcloud_session.organization):
2428 response = Http.get(url=get_vapp_restcall,
2429 headers=vca.vcloud_session.get_vcloud_headers(),
2430 verify=vca.verify,
2431 logger=vca.logger)
2432
2433 if response.status_code != requests.codes.ok:
2434 self.logger.debug("REST API call {} failed. Return status code {}".format(get_vapp_restcall,
2435 response.status_code))
2436 return parsed_respond
2437
2438 try:
2439 xmlroot_respond = XmlElementTree.fromstring(response.content)
2440 parsed_respond['ovfDescriptorUploaded'] = xmlroot_respond.attrib['ovfDescriptorUploaded']
2441
2442 namespaces_ovf = {'ovf': 'http://schemas.dmtf.org/ovf/envelope/1'}
2443 namespace_vmm = {'vmw': 'http://www.vmware.com/schema/ovf'}
2444 namespace_vm = {'vm': 'http://www.vmware.com/vcloud/v1.5'}
2445
2446 created_section = xmlroot_respond.find('vm:DateCreated', namespace_vm)
2447 if created_section is not None:
2448 parsed_respond['created'] = created_section.text
2449
2450 network_section = xmlroot_respond.find('vm:NetworkConfigSection/vm:NetworkConfig', namespace_vm)
2451 if network_section is not None and 'networkName' in network_section.attrib:
2452 parsed_respond['networkname'] = network_section.attrib['networkName']
2453
2454 ipscopes_section = \
2455 xmlroot_respond.find('vm:NetworkConfigSection/vm:NetworkConfig/vm:Configuration/vm:IpScopes',
2456 namespace_vm)
2457 if ipscopes_section is not None:
2458 for ipscope in ipscopes_section:
2459 for scope in ipscope:
2460 tag_key = scope.tag.split("}")[1]
2461 if tag_key == 'IpRanges':
2462 ip_ranges = scope.getchildren()
2463 for ipblock in ip_ranges:
2464 for block in ipblock:
2465 parsed_respond[block.tag.split("}")[1]] = block.text
2466 else:
2467 parsed_respond[tag_key] = scope.text
2468
2469 # parse children section for other attrib
2470 children_section = xmlroot_respond.find('vm:Children/', namespace_vm)
2471 if children_section is not None:
2472 parsed_respond['name'] = children_section.attrib['name']
2473 parsed_respond['nestedHypervisorEnabled'] = children_section.attrib['nestedHypervisorEnabled']
2474 parsed_respond['deployed'] = children_section.attrib['deployed']
2475 parsed_respond['status'] = children_section.attrib['status']
2476 parsed_respond['vmuuid'] = children_section.attrib['id'].split(":")[-1]
2477 network_adapter = children_section.find('vm:NetworkConnectionSection', namespace_vm)
2478 nic_list = []
2479 for adapters in network_adapter:
2480 adapter_key = adapters.tag.split("}")[1]
2481 if adapter_key == 'PrimaryNetworkConnectionIndex':
2482 parsed_respond['primarynetwork'] = adapters.text
2483 if adapter_key == 'NetworkConnection':
2484 vnic = {}
2485 if 'network' in adapters.attrib:
2486 vnic['network'] = adapters.attrib['network']
2487 for adapter in adapters:
2488 setting_key = adapter.tag.split("}")[1]
2489 vnic[setting_key] = adapter.text
2490 nic_list.append(vnic)
2491
2492 for link in children_section:
2493 if link.tag.split("}")[1] == 'Link' and 'rel' in link.attrib:
2494 if link.attrib['rel'] == 'screen:acquireTicket':
2495 parsed_respond['acquireTicket'] = link.attrib
2496 if link.attrib['rel'] == 'screen:acquireMksTicket':
2497 parsed_respond['acquireMksTicket'] = link.attrib
2498
2499 parsed_respond['interfaces'] = nic_list
2500 except:
2501 pass
2502
2503 return parsed_respond
2504
2505 def acuire_console(self, vm_uuid=None):
2506
2507 vca = self.connect()
2508 if not vca:
2509 raise vimconn.vimconnConnectionException("self.connect() is failed")
2510 if vm_uuid is None:
2511 return None
2512
2513 if not (not vca.vcloud_session or not vca.vcloud_session.organization):
2514 vm_dict = self.get_vapp_details_rest(self, vapp_uuid=vm_uuid)
2515 console_dict = vm_dict['acquireTicket']
2516 console_rest_call = console_dict['href']
2517
2518 response = Http.post(url=console_rest_call,
2519 headers=vca.vcloud_session.get_vcloud_headers(),
2520 verify=vca.verify,
2521 logger=vca.logger)
2522
2523 if response.status_code == requests.codes.ok:
2524 return response.content
2525
2526 return None