Bug 642 - Need to update VCD VIMconnector for VCD 9.5
[osm/RO.git] / osm_ro / vimconn_vmware.py
1 # -*- coding: utf-8 -*-
2
3 ##
4 # Copyright 2016-2017 VMware Inc.
5 # This file is part of ETSI OSM
6 # All Rights Reserved.
7 #
8 # Licensed under the Apache License, Version 2.0 (the "License"); you may
9 # not use this file except in compliance with the License. You may obtain
10 # a copy of the License at
11 #
12 # http://www.apache.org/licenses/LICENSE-2.0
13 #
14 # Unless required by applicable law or agreed to in writing, software
15 # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
16 # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
17 # 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: osslegalrouting@vmware.com
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 import ssl
36 import atexit
37
38 from pyVmomi import vim, vmodl
39 from pyVim.connect import SmartConnect, Disconnect
40
41 from xml.etree import ElementTree as XmlElementTree
42 from lxml import etree as lxmlElementTree
43
44 import yaml
45 from pyvcloud.vcd.client import BasicLoginCredentials,Client,VcdTaskException
46 from pyvcloud.vcd.vdc import VDC
47 from pyvcloud.vcd.org import Org
48 import re
49 from pyvcloud.vcd.vapp import VApp
50 from xml.sax.saxutils import escape
51 import logging
52 import json
53 import time
54 import uuid
55 import httplib
56 #For python3
57 #import http.client
58 import hashlib
59 import socket
60 import struct
61 import netaddr
62 import random
63
64 # global variable for vcd connector type
65 STANDALONE = 'standalone'
66
67 # key for flavor dicts
68 FLAVOR_RAM_KEY = 'ram'
69 FLAVOR_VCPUS_KEY = 'vcpus'
70 FLAVOR_DISK_KEY = 'disk'
71 DEFAULT_IP_PROFILE = {'dhcp_count':50,
72 'dhcp_enabled':True,
73 'ip_version':"IPv4"
74 }
75 # global variable for wait time
76 INTERVAL_TIME = 5
77 MAX_WAIT_TIME = 1800
78
79 API_VERSION = '31.0'
80
81 __author__ = "Mustafa Bayramov, Arpita Kate, Sachin Bhangare, Prakash Kasar"
82 __date__ = "$09-Mar-2018 11:09:29$"
83 __version__ = '0.2'
84
85 # -1: "Could not be created",
86 # 0: "Unresolved",
87 # 1: "Resolved",
88 # 2: "Deployed",
89 # 3: "Suspended",
90 # 4: "Powered on",
91 # 5: "Waiting for user input",
92 # 6: "Unknown state",
93 # 7: "Unrecognized state",
94 # 8: "Powered off",
95 # 9: "Inconsistent state",
96 # 10: "Children do not all have the same status",
97 # 11: "Upload initiated, OVF descriptor pending",
98 # 12: "Upload initiated, copying contents",
99 # 13: "Upload initiated , disk contents pending",
100 # 14: "Upload has been quarantined",
101 # 15: "Upload quarantine period has expired"
102
103 # mapping vCD status to MANO
104 vcdStatusCode2manoFormat = {4: 'ACTIVE',
105 7: 'PAUSED',
106 3: 'SUSPENDED',
107 8: 'INACTIVE',
108 12: 'BUILD',
109 -1: 'ERROR',
110 14: 'DELETED'}
111
112 #
113 netStatus2manoFormat = {'ACTIVE': 'ACTIVE', 'PAUSED': 'PAUSED', 'INACTIVE': 'INACTIVE', 'BUILD': 'BUILD',
114 'ERROR': 'ERROR', 'DELETED': 'DELETED'
115 }
116
117 class vimconnector(vimconn.vimconnector):
118 # dict used to store flavor in memory
119 flavorlist = {}
120
121 def __init__(self, uuid=None, name=None, tenant_id=None, tenant_name=None,
122 url=None, url_admin=None, user=None, passwd=None, log_level=None, config={}, persistent_info={}):
123 """
124 Constructor create vmware connector to vCloud director.
125
126 By default construct doesn't validate connection state. So client can create object with None arguments.
127 If client specified username , password and host and VDC name. Connector initialize other missing attributes.
128
129 a) It initialize organization UUID
130 b) Initialize tenant_id/vdc ID. (This information derived from tenant name)
131
132 Args:
133 uuid - is organization uuid.
134 name - is organization name that must be presented in vCloud director.
135 tenant_id - is VDC uuid it must be presented in vCloud director
136 tenant_name - is VDC name.
137 url - is hostname or ip address of vCloud director
138 url_admin - same as above.
139 user - is user that administrator for organization. Caller must make sure that
140 username has right privileges.
141
142 password - is password for a user.
143
144 VMware connector also requires PVDC administrative privileges and separate account.
145 This variables must be passed via config argument dict contains keys
146
147 dict['admin_username']
148 dict['admin_password']
149 config - Provide NSX and vCenter information
150
151 Returns:
152 Nothing.
153 """
154
155 vimconn.vimconnector.__init__(self, uuid, name, tenant_id, tenant_name, url,
156 url_admin, user, passwd, log_level, config)
157
158 self.logger = logging.getLogger('openmano.vim.vmware')
159 self.logger.setLevel(10)
160 self.persistent_info = persistent_info
161
162 self.name = name
163 self.id = uuid
164 self.url = url
165 self.url_admin = url_admin
166 self.tenant_id = tenant_id
167 self.tenant_name = tenant_name
168 self.user = user
169 self.passwd = passwd
170 self.config = config
171 self.admin_password = None
172 self.admin_user = None
173 self.org_name = ""
174 self.nsx_manager = None
175 self.nsx_user = None
176 self.nsx_password = None
177 self.availability_zone = None
178
179 # Disable warnings from self-signed certificates.
180 requests.packages.urllib3.disable_warnings()
181
182 if tenant_name is not None:
183 orgnameandtenant = tenant_name.split(":")
184 if len(orgnameandtenant) == 2:
185 self.tenant_name = orgnameandtenant[1]
186 self.org_name = orgnameandtenant[0]
187 else:
188 self.tenant_name = tenant_name
189 if "orgname" in config:
190 self.org_name = config['orgname']
191
192 if log_level:
193 self.logger.setLevel(getattr(logging, log_level))
194
195 try:
196 self.admin_user = config['admin_username']
197 self.admin_password = config['admin_password']
198 except KeyError:
199 raise vimconn.vimconnException(message="Error admin username or admin password is empty.")
200
201 try:
202 self.nsx_manager = config['nsx_manager']
203 self.nsx_user = config['nsx_user']
204 self.nsx_password = config['nsx_password']
205 except KeyError:
206 raise vimconn.vimconnException(message="Error: nsx manager or nsx user or nsx password is empty in Config")
207
208 self.vcenter_ip = config.get("vcenter_ip", None)
209 self.vcenter_port = config.get("vcenter_port", None)
210 self.vcenter_user = config.get("vcenter_user", None)
211 self.vcenter_password = config.get("vcenter_password", None)
212
213 #Set availability zone for Affinity rules
214 self.availability_zone = self.set_availability_zones()
215
216 # ############# Stub code for SRIOV #################
217 # try:
218 # self.dvs_name = config['dv_switch_name']
219 # except KeyError:
220 # raise vimconn.vimconnException(message="Error: distributed virtaul switch name is empty in Config")
221 #
222 # self.vlanID_range = config.get("vlanID_range", None)
223
224 self.org_uuid = None
225 self.client = None
226
227 if not url:
228 raise vimconn.vimconnException('url param can not be NoneType')
229
230 if not self.url_admin: # try to use normal url
231 self.url_admin = self.url
232
233 logging.debug("UUID: {} name: {} tenant_id: {} tenant name {}".format(self.id, self.org_name,
234 self.tenant_id, self.tenant_name))
235 logging.debug("vcd url {} vcd username: {} vcd password: {}".format(self.url, self.user, self.passwd))
236 logging.debug("vcd admin username {} vcd admin passowrd {}".format(self.admin_user, self.admin_password))
237
238 # initialize organization
239 if self.user is not None and self.passwd is not None and self.url:
240 self.init_organization()
241
242 def __getitem__(self, index):
243 if index == 'name':
244 return self.name
245 if index == 'tenant_id':
246 return self.tenant_id
247 if index == 'tenant_name':
248 return self.tenant_name
249 elif index == 'id':
250 return self.id
251 elif index == 'org_name':
252 return self.org_name
253 elif index == 'org_uuid':
254 return self.org_uuid
255 elif index == 'user':
256 return self.user
257 elif index == 'passwd':
258 return self.passwd
259 elif index == 'url':
260 return self.url
261 elif index == 'url_admin':
262 return self.url_admin
263 elif index == "config":
264 return self.config
265 else:
266 raise KeyError("Invalid key '%s'" % str(index))
267
268 def __setitem__(self, index, value):
269 if index == 'name':
270 self.name = value
271 if index == 'tenant_id':
272 self.tenant_id = value
273 if index == 'tenant_name':
274 self.tenant_name = value
275 elif index == 'id':
276 self.id = value
277 elif index == 'org_name':
278 self.org_name = value
279 elif index == 'org_uuid':
280 self.org_uuid = value
281 elif index == 'user':
282 self.user = value
283 elif index == 'passwd':
284 self.passwd = value
285 elif index == 'url':
286 self.url = value
287 elif index == 'url_admin':
288 self.url_admin = value
289 else:
290 raise KeyError("Invalid key '%s'" % str(index))
291
292 def connect_as_admin(self):
293 """ Method connect as pvdc admin user to vCloud director.
294 There are certain action that can be done only by provider vdc admin user.
295 Organization creation / provider network creation etc.
296
297 Returns:
298 The return client object that latter can be used to connect to vcloud director as admin for provider vdc
299 """
300 self.logger.debug("Logging into vCD {} as admin.".format(self.org_name))
301
302 try:
303 host = self.url
304 org = 'System'
305 client_as_admin = Client(host, verify_ssl_certs=False)
306 client_as_admin.set_highest_supported_version()
307 client_as_admin.set_credentials(BasicLoginCredentials(self.admin_user, org, self.admin_password))
308 except Exception as e:
309 raise vimconn.vimconnException(
310 "Can't connect to a vCloud director as: {} with exception {}".format(self.admin_user, e))
311
312 return client_as_admin
313
314 def connect(self):
315 """ Method connect as normal user to vCloud director.
316
317 Returns:
318 The return client object that latter can be used to connect to vCloud director as admin for VDC
319 """
320 try:
321 self.logger.debug("Logging into vCD {} as {} to datacenter {}.".format(self.org_name,
322 self.user,
323 self.org_name))
324 host = self.url
325 client = Client(host, verify_ssl_certs=False)
326 client.set_highest_supported_version()
327 client.set_credentials(BasicLoginCredentials(self.user, self.org_name, self.passwd))
328 except:
329 raise vimconn.vimconnConnectionException("Can't connect to a vCloud director org: "
330 "{} as user: {}".format(self.org_name, self.user))
331
332 return client
333
334 def init_organization(self):
335 """ Method initialize organization UUID and VDC parameters.
336
337 At bare minimum client must provide organization name that present in vCloud director and VDC.
338
339 The VDC - UUID ( tenant_id) will be initialized at the run time if client didn't call constructor.
340 The Org - UUID will be initialized at the run time if data center present in vCloud director.
341
342 Returns:
343 The return vca object that letter can be used to connect to vcloud direct as admin
344 """
345 client = self.connect()
346 if not client:
347 raise vimconn.vimconnConnectionException("Failed to connect vCD.")
348
349 self.client = client
350 try:
351 if self.org_uuid is None:
352 org_list = client.get_org_list()
353 for org in org_list.Org:
354 # we set org UUID at the init phase but we can do it only when we have valid credential.
355 if org.get('name') == self.org_name:
356 self.org_uuid = org.get('href').split('/')[-1]
357 self.logger.debug("Setting organization UUID {}".format(self.org_uuid))
358 break
359 else:
360 raise vimconn.vimconnException("Vcloud director organization {} not found".format(self.org_name))
361
362 # if well good we require for org details
363 org_details_dict = self.get_org(org_uuid=self.org_uuid)
364
365 # we have two case if we want to initialize VDC ID or VDC name at run time
366 # tenant_name provided but no tenant id
367 if self.tenant_id is None and self.tenant_name is not None and 'vdcs' in org_details_dict:
368 vdcs_dict = org_details_dict['vdcs']
369 for vdc in vdcs_dict:
370 if vdcs_dict[vdc] == self.tenant_name:
371 self.tenant_id = vdc
372 self.logger.debug("Setting vdc uuid {} for organization UUID {}".format(self.tenant_id,
373 self.org_name))
374 break
375 else:
376 raise vimconn.vimconnException("Tenant name indicated but not present in vcloud director.")
377 # case two we have tenant_id but we don't have tenant name so we find and set it.
378 if self.tenant_id is not None and self.tenant_name is None and 'vdcs' in org_details_dict:
379 vdcs_dict = org_details_dict['vdcs']
380 for vdc in vdcs_dict:
381 if vdc == self.tenant_id:
382 self.tenant_name = vdcs_dict[vdc]
383 self.logger.debug("Setting vdc uuid {} for organization UUID {}".format(self.tenant_id,
384 self.org_name))
385 break
386 else:
387 raise vimconn.vimconnException("Tenant id indicated but not present in vcloud director")
388 self.logger.debug("Setting organization uuid {}".format(self.org_uuid))
389 except:
390 self.logger.debug("Failed initialize organization UUID for org {}".format(self.org_name))
391 self.logger.debug(traceback.format_exc())
392 self.org_uuid = None
393
394 def new_tenant(self, tenant_name=None, tenant_description=None):
395 """ Method adds a new tenant to VIM with this name.
396 This action requires access to create VDC action in vCloud director.
397
398 Args:
399 tenant_name is tenant_name to be created.
400 tenant_description not used for this call
401
402 Return:
403 returns the tenant identifier in UUID format.
404 If action is failed method will throw vimconn.vimconnException method
405 """
406 vdc_task = self.create_vdc(vdc_name=tenant_name)
407 if vdc_task is not None:
408 vdc_uuid, value = vdc_task.popitem()
409 self.logger.info("Created new vdc {} and uuid: {}".format(tenant_name, vdc_uuid))
410 return vdc_uuid
411 else:
412 raise vimconn.vimconnException("Failed create tenant {}".format(tenant_name))
413
414 def delete_tenant(self, tenant_id=None):
415 """ Delete a tenant from VIM
416 Args:
417 tenant_id is tenant_id to be deleted.
418
419 Return:
420 returns the tenant identifier in UUID format.
421 If action is failed method will throw exception
422 """
423 vca = self.connect_as_admin()
424 if not vca:
425 raise vimconn.vimconnConnectionException("Failed to connect vCD")
426
427 if tenant_id is not None:
428 if vca._session:
429 #Get OrgVDC
430 url_list = [self.url, '/api/vdc/', tenant_id]
431 orgvdc_herf = ''.join(url_list)
432
433 headers = {'Accept':'application/*+xml;version=' + API_VERSION,
434 'x-vcloud-authorization': vca._session.headers['x-vcloud-authorization']}
435 response = self.perform_request(req_type='GET',
436 url=orgvdc_herf,
437 headers=headers)
438
439 if response.status_code != requests.codes.ok:
440 self.logger.debug("delete_tenant():GET REST API call {} failed. "\
441 "Return status code {}".format(orgvdc_herf,
442 response.status_code))
443 raise vimconn.vimconnNotFoundException("Fail to get tenant {}".format(tenant_id))
444
445 lxmlroot_respond = lxmlElementTree.fromstring(response.content)
446 namespaces = {prefix:uri for prefix,uri in lxmlroot_respond.nsmap.iteritems() if prefix}
447 #For python3
448 #namespaces = {prefix:uri for prefix,uri in lxmlroot_respond.nsmap.items() if prefix}
449 namespaces["xmlns"]= "http://www.vmware.com/vcloud/v1.5"
450 vdc_remove_href = lxmlroot_respond.find("xmlns:Link[@rel='remove']",namespaces).attrib['href']
451 vdc_remove_href = vdc_remove_href + '?recursive=true&force=true'
452
453 response = self.perform_request(req_type='DELETE',
454 url=vdc_remove_href,
455 headers=headers)
456
457 if response.status_code == 202:
458 time.sleep(5)
459 return tenant_id
460 else:
461 self.logger.debug("delete_tenant(): DELETE REST API call {} failed. "\
462 "Return status code {}".format(vdc_remove_href,
463 response.status_code))
464 raise vimconn.vimconnException("Fail to delete tenant with ID {}".format(tenant_id))
465 else:
466 self.logger.debug("delete_tenant():Incorrect tenant ID {}".format(tenant_id))
467 raise vimconn.vimconnNotFoundException("Fail to get tenant {}".format(tenant_id))
468
469
470 def get_tenant_list(self, filter_dict={}):
471 """Obtain tenants of VIM
472 filter_dict can contain the following keys:
473 name: filter by tenant name
474 id: filter by tenant uuid/id
475 <other VIM specific>
476 Returns the tenant list of dictionaries:
477 [{'name':'<name>, 'id':'<id>, ...}, ...]
478
479 """
480 org_dict = self.get_org(self.org_uuid)
481 vdcs_dict = org_dict['vdcs']
482
483 vdclist = []
484 try:
485 for k in vdcs_dict:
486 entry = {'name': vdcs_dict[k], 'id': k}
487 # if caller didn't specify dictionary we return all tenants.
488 if filter_dict is not None and filter_dict:
489 filtered_entry = entry.copy()
490 filtered_dict = set(entry.keys()) - set(filter_dict)
491 for unwanted_key in filtered_dict: del entry[unwanted_key]
492 if filter_dict == entry:
493 vdclist.append(filtered_entry)
494 else:
495 vdclist.append(entry)
496 except:
497 self.logger.debug("Error in get_tenant_list()")
498 self.logger.debug(traceback.format_exc())
499 raise vimconn.vimconnException("Incorrect state. {}")
500
501 return vdclist
502
503 def new_network(self, net_name, net_type, ip_profile=None, shared=False, vlan=None):
504 """Adds a tenant network to VIM
505 Params:
506 'net_name': name of the network
507 'net_type': one of:
508 'bridge': overlay isolated network
509 'data': underlay E-LAN network for Passthrough and SRIOV interfaces
510 'ptp': underlay E-LINE network for Passthrough and SRIOV interfaces.
511 'ip_profile': is a dict containing the IP parameters of the network
512 'ip_version': can be "IPv4" or "IPv6" (Currently only IPv4 is implemented)
513 'subnet_address': ip_prefix_schema, that is X.X.X.X/Y
514 'gateway_address': (Optional) ip_schema, that is X.X.X.X
515 'dns_address': (Optional) comma separated list of ip_schema, e.g. X.X.X.X[,X,X,X,X]
516 'dhcp_enabled': True or False
517 'dhcp_start_address': ip_schema, first IP to grant
518 'dhcp_count': number of IPs to grant.
519 'shared': if this network can be seen/use by other tenants/organization
520 'vlan': in case of a data or ptp net_type, the intended vlan tag to be used for the network
521 Returns a tuple with the network identifier and created_items, or raises an exception on error
522 created_items can be None or a dictionary where this method can include key-values that will be passed to
523 the method delete_network. Can be used to store created segments, created l2gw connections, etc.
524 Format is vimconnector dependent, but do not use nested dictionaries and a value of None should be the same
525 as not present.
526 """
527
528 self.logger.debug("new_network tenant {} net_type {} ip_profile {} shared {}"
529 .format(net_name, net_type, ip_profile, shared))
530
531 created_items = {}
532 isshared = 'false'
533 if shared:
534 isshared = 'true'
535
536 # ############# Stub code for SRIOV #################
537 # if net_type == "data" or net_type == "ptp":
538 # if self.config.get('dv_switch_name') == None:
539 # raise vimconn.vimconnConflictException("You must provide 'dv_switch_name' at config value")
540 # network_uuid = self.create_dvPort_group(net_name)
541
542 network_uuid = self.create_network(network_name=net_name, net_type=net_type,
543 ip_profile=ip_profile, isshared=isshared)
544 if network_uuid is not None:
545 return network_uuid, created_items
546 else:
547 raise vimconn.vimconnUnexpectedResponse("Failed create a new network {}".format(net_name))
548
549 def get_vcd_network_list(self):
550 """ Method available organization for a logged in tenant
551
552 Returns:
553 The return vca object that letter can be used to connect to vcloud direct as admin
554 """
555
556 self.logger.debug("get_vcd_network_list(): retrieving network list for vcd {}".format(self.tenant_name))
557
558 if not self.tenant_name:
559 raise vimconn.vimconnConnectionException("Tenant name is empty.")
560
561 org, vdc = self.get_vdc_details()
562 if vdc is None:
563 raise vimconn.vimconnConnectionException("Can't retrieve information for a VDC {}".format(self.tenant_name))
564
565 vdc_uuid = vdc.get('id').split(":")[3]
566 if self.client._session:
567 headers = {'Accept':'application/*+xml;version=' + API_VERSION,
568 'x-vcloud-authorization': self.client._session.headers['x-vcloud-authorization']}
569 response = self.perform_request(req_type='GET',
570 url=vdc.get('href'),
571 headers=headers)
572 if response.status_code != 200:
573 self.logger.error("Failed to get vdc content")
574 raise vimconn.vimconnNotFoundException("Failed to get vdc content")
575 else:
576 content = XmlElementTree.fromstring(response.content)
577
578 network_list = []
579 try:
580 for item in content:
581 if item.tag.split('}')[-1] == 'AvailableNetworks':
582 for net in item:
583 response = self.perform_request(req_type='GET',
584 url=net.get('href'),
585 headers=headers)
586
587 if response.status_code != 200:
588 self.logger.error("Failed to get network content")
589 raise vimconn.vimconnNotFoundException("Failed to get network content")
590 else:
591 net_details = XmlElementTree.fromstring(response.content)
592
593 filter_dict = {}
594 net_uuid = net_details.get('id').split(":")
595 if len(net_uuid) != 4:
596 continue
597 else:
598 net_uuid = net_uuid[3]
599 # create dict entry
600 self.logger.debug("get_vcd_network_list(): Adding network {} "
601 "to a list vcd id {} network {}".format(net_uuid,
602 vdc_uuid,
603 net_details.get('name')))
604 filter_dict["name"] = net_details.get('name')
605 filter_dict["id"] = net_uuid
606 if [i.text for i in net_details if i.tag.split('}')[-1] == 'IsShared'][0] == 'true':
607 shared = True
608 else:
609 shared = False
610 filter_dict["shared"] = shared
611 filter_dict["tenant_id"] = vdc_uuid
612 if int(net_details.get('status')) == 1:
613 filter_dict["admin_state_up"] = True
614 else:
615 filter_dict["admin_state_up"] = False
616 filter_dict["status"] = "ACTIVE"
617 filter_dict["type"] = "bridge"
618 network_list.append(filter_dict)
619 self.logger.debug("get_vcd_network_list adding entry {}".format(filter_dict))
620 except:
621 self.logger.debug("Error in get_vcd_network_list", exc_info=True)
622 pass
623
624 self.logger.debug("get_vcd_network_list returning {}".format(network_list))
625 return network_list
626
627 def get_network_list(self, filter_dict={}):
628 """Obtain tenant networks of VIM
629 Filter_dict can be:
630 name: network name OR/AND
631 id: network uuid OR/AND
632 shared: boolean OR/AND
633 tenant_id: tenant OR/AND
634 admin_state_up: boolean
635 status: 'ACTIVE'
636
637 [{key : value , key : value}]
638
639 Returns the network list of dictionaries:
640 [{<the fields at Filter_dict plus some VIM specific>}, ...]
641 List can be empty
642 """
643
644 self.logger.debug("get_network_list(): retrieving network list for vcd {}".format(self.tenant_name))
645
646 if not self.tenant_name:
647 raise vimconn.vimconnConnectionException("Tenant name is empty.")
648
649 org, vdc = self.get_vdc_details()
650 if vdc is None:
651 raise vimconn.vimconnConnectionException("Can't retrieve information for a VDC {}.".format(self.tenant_name))
652
653 try:
654 vdcid = vdc.get('id').split(":")[3]
655
656 if self.client._session:
657 headers = {'Accept':'application/*+xml;version=' + API_VERSION,
658 'x-vcloud-authorization': self.client._session.headers['x-vcloud-authorization']}
659 response = self.perform_request(req_type='GET',
660 url=vdc.get('href'),
661 headers=headers)
662 if response.status_code != 200:
663 self.logger.error("Failed to get vdc content")
664 raise vimconn.vimconnNotFoundException("Failed to get vdc content")
665 else:
666 content = XmlElementTree.fromstring(response.content)
667
668 network_list = []
669 for item in content:
670 if item.tag.split('}')[-1] == 'AvailableNetworks':
671 for net in item:
672 response = self.perform_request(req_type='GET',
673 url=net.get('href'),
674 headers=headers)
675
676 if response.status_code != 200:
677 self.logger.error("Failed to get network content")
678 raise vimconn.vimconnNotFoundException("Failed to get network content")
679 else:
680 net_details = XmlElementTree.fromstring(response.content)
681
682 filter_entry = {}
683 net_uuid = net_details.get('id').split(":")
684 if len(net_uuid) != 4:
685 continue
686 else:
687 net_uuid = net_uuid[3]
688 # create dict entry
689 self.logger.debug("get_network_list(): Adding net {}"
690 " to a list vcd id {} network {}".format(net_uuid,
691 vdcid,
692 net_details.get('name')))
693 filter_entry["name"] = net_details.get('name')
694 filter_entry["id"] = net_uuid
695 if [i.text for i in net_details if i.tag.split('}')[-1] == 'IsShared'][0] == 'true':
696 shared = True
697 else:
698 shared = False
699 filter_entry["shared"] = shared
700 filter_entry["tenant_id"] = vdcid
701 if int(net_details.get('status')) == 1:
702 filter_entry["admin_state_up"] = True
703 else:
704 filter_entry["admin_state_up"] = False
705 filter_entry["status"] = "ACTIVE"
706 filter_entry["type"] = "bridge"
707 filtered_entry = filter_entry.copy()
708
709 if filter_dict is not None and filter_dict:
710 # we remove all the key : value we don't care and match only
711 # respected field
712 filtered_dict = set(filter_entry.keys()) - set(filter_dict)
713 for unwanted_key in filtered_dict: del filter_entry[unwanted_key]
714 if filter_dict == filter_entry:
715 network_list.append(filtered_entry)
716 else:
717 network_list.append(filtered_entry)
718 except Exception as e:
719 self.logger.debug("Error in get_network_list",exc_info=True)
720 if isinstance(e, vimconn.vimconnException):
721 raise
722 else:
723 raise vimconn.vimconnNotFoundException("Failed : Networks list not found {} ".format(e))
724
725 self.logger.debug("Returning {}".format(network_list))
726 return network_list
727
728 def get_network(self, net_id):
729 """Method obtains network details of net_id VIM network
730 Return a dict with the fields at filter_dict (see get_network_list) plus some VIM specific>}, ...]"""
731
732 try:
733 org, vdc = self.get_vdc_details()
734 vdc_id = vdc.get('id').split(":")[3]
735 if self.client._session:
736 headers = {'Accept':'application/*+xml;version=' + API_VERSION,
737 'x-vcloud-authorization': self.client._session.headers['x-vcloud-authorization']}
738 response = self.perform_request(req_type='GET',
739 url=vdc.get('href'),
740 headers=headers)
741 if response.status_code != 200:
742 self.logger.error("Failed to get vdc content")
743 raise vimconn.vimconnNotFoundException("Failed to get vdc content")
744 else:
745 content = XmlElementTree.fromstring(response.content)
746
747 filter_dict = {}
748
749 for item in content:
750 if item.tag.split('}')[-1] == 'AvailableNetworks':
751 for net in item:
752 response = self.perform_request(req_type='GET',
753 url=net.get('href'),
754 headers=headers)
755
756 if response.status_code != 200:
757 self.logger.error("Failed to get network content")
758 raise vimconn.vimconnNotFoundException("Failed to get network content")
759 else:
760 net_details = XmlElementTree.fromstring(response.content)
761
762 vdc_network_id = net_details.get('id').split(":")
763 if len(vdc_network_id) == 4 and vdc_network_id[3] == net_id:
764 filter_dict["name"] = net_details.get('name')
765 filter_dict["id"] = vdc_network_id[3]
766 if [i.text for i in net_details if i.tag.split('}')[-1] == 'IsShared'][0] == 'true':
767 shared = True
768 else:
769 shared = False
770 filter_dict["shared"] = shared
771 filter_dict["tenant_id"] = vdc_id
772 if int(net_details.get('status')) == 1:
773 filter_dict["admin_state_up"] = True
774 else:
775 filter_dict["admin_state_up"] = False
776 filter_dict["status"] = "ACTIVE"
777 filter_dict["type"] = "bridge"
778 self.logger.debug("Returning {}".format(filter_dict))
779 return filter_dict
780 else:
781 raise vimconn.vimconnNotFoundException("Network {} not found".format(net_id))
782 except Exception as e:
783 self.logger.debug("Error in get_network")
784 self.logger.debug(traceback.format_exc())
785 if isinstance(e, vimconn.vimconnException):
786 raise
787 else:
788 raise vimconn.vimconnNotFoundException("Failed : Network not found {} ".format(e))
789
790 return filter_dict
791
792 def delete_network(self, net_id, created_items=None):
793 """
794 Removes a tenant network from VIM and its associated elements
795 :param net_id: VIM identifier of the network, provided by method new_network
796 :param created_items: dictionary with extra items to be deleted. provided by method new_network
797 Returns the network identifier or raises an exception upon error or when network is not found
798 """
799
800 # ############# Stub code for SRIOV #################
801 # dvport_group = self.get_dvport_group(net_id)
802 # if dvport_group:
803 # #delete portgroup
804 # status = self.destroy_dvport_group(net_id)
805 # if status:
806 # # Remove vlanID from persistent info
807 # if net_id in self.persistent_info["used_vlanIDs"]:
808 # del self.persistent_info["used_vlanIDs"][net_id]
809 #
810 # return net_id
811
812 vcd_network = self.get_vcd_network(network_uuid=net_id)
813 if vcd_network is not None and vcd_network:
814 if self.delete_network_action(network_uuid=net_id):
815 return net_id
816 else:
817 raise vimconn.vimconnNotFoundException("Network {} not found".format(net_id))
818
819 def refresh_nets_status(self, net_list):
820 """Get the status of the networks
821 Params: the list of network identifiers
822 Returns a dictionary with:
823 net_id: #VIM id of this network
824 status: #Mandatory. Text with one of:
825 # DELETED (not found at vim)
826 # VIM_ERROR (Cannot connect to VIM, VIM response error, ...)
827 # OTHER (Vim reported other status not understood)
828 # ERROR (VIM indicates an ERROR status)
829 # ACTIVE, INACTIVE, DOWN (admin down),
830 # BUILD (on building process)
831 #
832 error_msg: #Text with VIM error message, if any. Or the VIM connection ERROR
833 vim_info: #Text with plain information obtained from vim (yaml.safe_dump)
834
835 """
836
837 dict_entry = {}
838 try:
839 for net in net_list:
840 errormsg = ''
841 vcd_network = self.get_vcd_network(network_uuid=net)
842 if vcd_network is not None and vcd_network:
843 if vcd_network['status'] == '1':
844 status = 'ACTIVE'
845 else:
846 status = 'DOWN'
847 else:
848 status = 'DELETED'
849 errormsg = 'Network not found.'
850
851 dict_entry[net] = {'status': status, 'error_msg': errormsg,
852 'vim_info': yaml.safe_dump(vcd_network)}
853 except:
854 self.logger.debug("Error in refresh_nets_status")
855 self.logger.debug(traceback.format_exc())
856
857 return dict_entry
858
859 def get_flavor(self, flavor_id):
860 """Obtain flavor details from the VIM
861 Returns the flavor dict details {'id':<>, 'name':<>, other vim specific } #TODO to concrete
862 """
863 if flavor_id not in vimconnector.flavorlist:
864 raise vimconn.vimconnNotFoundException("Flavor not found.")
865 return vimconnector.flavorlist[flavor_id]
866
867 def new_flavor(self, flavor_data):
868 """Adds a tenant flavor to VIM
869 flavor_data contains a dictionary with information, keys:
870 name: flavor name
871 ram: memory (cloud type) in MBytes
872 vpcus: cpus (cloud type)
873 extended: EPA parameters
874 - numas: #items requested in same NUMA
875 memory: number of 1G huge pages memory
876 paired-threads|cores|threads: number of paired hyperthreads, complete cores OR individual threads
877 interfaces: # passthrough(PT) or SRIOV interfaces attached to this numa
878 - name: interface name
879 dedicated: yes|no|yes:sriov; for PT, SRIOV or only one SRIOV for the physical NIC
880 bandwidth: X Gbps; requested guarantee bandwidth
881 vpci: requested virtual PCI address
882 disk: disk size
883 is_public:
884 #TODO to concrete
885 Returns the flavor identifier"""
886
887 # generate a new uuid put to internal dict and return it.
888 self.logger.debug("Creating new flavor - flavor_data: {}".format(flavor_data))
889 new_flavor=flavor_data
890 ram = flavor_data.get(FLAVOR_RAM_KEY, 1024)
891 cpu = flavor_data.get(FLAVOR_VCPUS_KEY, 1)
892 disk = flavor_data.get(FLAVOR_DISK_KEY, 0)
893
894 if not isinstance(ram, int):
895 raise vimconn.vimconnException("Non-integer value for ram")
896 elif not isinstance(cpu, int):
897 raise vimconn.vimconnException("Non-integer value for cpu")
898 elif not isinstance(disk, int):
899 raise vimconn.vimconnException("Non-integer value for disk")
900
901 extended_flv = flavor_data.get("extended")
902 if extended_flv:
903 numas=extended_flv.get("numas")
904 if numas:
905 for numa in numas:
906 #overwrite ram and vcpus
907 if 'memory' in numa:
908 ram = numa['memory']*1024
909 if 'paired-threads' in numa:
910 cpu = numa['paired-threads']*2
911 elif 'cores' in numa:
912 cpu = numa['cores']
913 elif 'threads' in numa:
914 cpu = numa['threads']
915
916 new_flavor[FLAVOR_RAM_KEY] = ram
917 new_flavor[FLAVOR_VCPUS_KEY] = cpu
918 new_flavor[FLAVOR_DISK_KEY] = disk
919 # generate a new uuid put to internal dict and return it.
920 flavor_id = uuid.uuid4()
921 vimconnector.flavorlist[str(flavor_id)] = new_flavor
922 self.logger.debug("Created flavor - {} : {}".format(flavor_id, new_flavor))
923
924 return str(flavor_id)
925
926 def delete_flavor(self, flavor_id):
927 """Deletes a tenant flavor from VIM identify by its id
928
929 Returns the used id or raise an exception
930 """
931 if flavor_id not in vimconnector.flavorlist:
932 raise vimconn.vimconnNotFoundException("Flavor not found.")
933
934 vimconnector.flavorlist.pop(flavor_id, None)
935 return flavor_id
936
937 def new_image(self, image_dict):
938 """
939 Adds a tenant image to VIM
940 Returns:
941 200, image-id if the image is created
942 <0, message if there is an error
943 """
944
945 return self.get_image_id_from_path(image_dict['location'])
946
947 def delete_image(self, image_id):
948 """
949 Deletes a tenant image from VIM
950 Args:
951 image_id is ID of Image to be deleted
952 Return:
953 returns the image identifier in UUID format or raises an exception on error
954 """
955 conn = self.connect_as_admin()
956 if not conn:
957 raise vimconn.vimconnConnectionException("Failed to connect vCD")
958 # Get Catalog details
959 url_list = [self.url, '/api/catalog/', image_id]
960 catalog_herf = ''.join(url_list)
961
962 headers = {'Accept':'application/*+xml;version=' + API_VERSION,
963 'x-vcloud-authorization': conn._session.headers['x-vcloud-authorization']}
964
965 response = self.perform_request(req_type='GET',
966 url=catalog_herf,
967 headers=headers)
968
969 if response.status_code != requests.codes.ok:
970 self.logger.debug("delete_image():GET REST API call {} failed. "\
971 "Return status code {}".format(catalog_herf,
972 response.status_code))
973 raise vimconn.vimconnNotFoundException("Fail to get image {}".format(image_id))
974
975 lxmlroot_respond = lxmlElementTree.fromstring(response.content)
976 namespaces = {prefix:uri for prefix,uri in lxmlroot_respond.nsmap.iteritems() if prefix}
977 #For python3
978 #namespaces = {prefix:uri for prefix,uri in lxmlroot_respond.nsmap.items() if prefix}
979 namespaces["xmlns"]= "http://www.vmware.com/vcloud/v1.5"
980
981 catalogItems_section = lxmlroot_respond.find("xmlns:CatalogItems",namespaces)
982 catalogItems = catalogItems_section.iterfind("xmlns:CatalogItem",namespaces)
983 for catalogItem in catalogItems:
984 catalogItem_href = catalogItem.attrib['href']
985
986 response = self.perform_request(req_type='GET',
987 url=catalogItem_href,
988 headers=headers)
989
990 if response.status_code != requests.codes.ok:
991 self.logger.debug("delete_image():GET REST API call {} failed. "\
992 "Return status code {}".format(catalog_herf,
993 response.status_code))
994 raise vimconn.vimconnNotFoundException("Fail to get catalogItem {} for catalog {}".format(
995 catalogItem,
996 image_id))
997
998 lxmlroot_respond = lxmlElementTree.fromstring(response.content)
999 namespaces = {prefix:uri for prefix,uri in lxmlroot_respond.nsmap.iteritems() if prefix}
1000 #For python3
1001 #namespaces = {prefix:uri for prefix,uri in lxmlroot_respond.nsmap.items() if prefix}
1002 namespaces["xmlns"]= "http://www.vmware.com/vcloud/v1.5"
1003 catalogitem_remove_href = lxmlroot_respond.find("xmlns:Link[@rel='remove']",namespaces).attrib['href']
1004
1005 #Remove catalogItem
1006 response = self.perform_request(req_type='DELETE',
1007 url=catalogitem_remove_href,
1008 headers=headers)
1009 if response.status_code == requests.codes.no_content:
1010 self.logger.debug("Deleted Catalog item {}".format(catalogItem))
1011 else:
1012 raise vimconn.vimconnException("Fail to delete Catalog Item {}".format(catalogItem))
1013
1014 #Remove catalog
1015 url_list = [self.url, '/api/admin/catalog/', image_id]
1016 catalog_remove_herf = ''.join(url_list)
1017 response = self.perform_request(req_type='DELETE',
1018 url=catalog_remove_herf,
1019 headers=headers)
1020
1021 if response.status_code == requests.codes.no_content:
1022 self.logger.debug("Deleted Catalog {}".format(image_id))
1023 return image_id
1024 else:
1025 raise vimconn.vimconnException("Fail to delete Catalog {}".format(image_id))
1026
1027
1028 def catalog_exists(self, catalog_name, catalogs):
1029 """
1030
1031 :param catalog_name:
1032 :param catalogs:
1033 :return:
1034 """
1035 for catalog in catalogs:
1036 if catalog['name'] == catalog_name:
1037 return True
1038 return False
1039
1040 def create_vimcatalog(self, vca=None, catalog_name=None):
1041 """ Create new catalog entry in vCloud director.
1042
1043 Args
1044 vca: vCloud director.
1045 catalog_name catalog that client wish to create. Note no validation done for a name.
1046 Client must make sure that provide valid string representation.
1047
1048 Return (bool) True if catalog created.
1049
1050 """
1051 try:
1052 result = vca.create_catalog(catalog_name, catalog_name)
1053 if result is not None:
1054 return True
1055 catalogs = vca.list_catalogs()
1056 except:
1057 return False
1058 return self.catalog_exists(catalog_name, catalogs)
1059
1060 # noinspection PyIncorrectDocstring
1061 def upload_ovf(self, vca=None, catalog_name=None, image_name=None, media_file_name=None,
1062 description='', progress=False, chunk_bytes=128 * 1024):
1063 """
1064 Uploads a OVF file to a vCloud catalog
1065
1066 :param chunk_bytes:
1067 :param progress:
1068 :param description:
1069 :param image_name:
1070 :param vca:
1071 :param catalog_name: (str): The name of the catalog to upload the media.
1072 :param media_file_name: (str): The name of the local media file to upload.
1073 :return: (bool) True if the media file was successfully uploaded, false otherwise.
1074 """
1075 os.path.isfile(media_file_name)
1076 statinfo = os.stat(media_file_name)
1077
1078 # find a catalog entry where we upload OVF.
1079 # create vApp Template and check the status if vCD able to read OVF it will respond with appropirate
1080 # status change.
1081 # if VCD can parse OVF we upload VMDK file
1082 try:
1083 for catalog in vca.list_catalogs():
1084 if catalog_name != catalog['name']:
1085 continue
1086 catalog_href = "{}/api/catalog/{}/action/upload".format(self.url, catalog['id'])
1087 data = """
1088 <UploadVAppTemplateParams name="{}" xmlns="http://www.vmware.com/vcloud/v1.5" xmlns:ovf="http://schemas.dmtf.org/ovf/envelope/1"><Description>{} vApp Template</Description></UploadVAppTemplateParams>
1089 """.format(catalog_name, description)
1090
1091 if self.client:
1092 headers = {'Accept':'application/*+xml;version=' + API_VERSION,
1093 'x-vcloud-authorization': self.client._session.headers['x-vcloud-authorization']}
1094 headers['Content-Type'] = 'application/vnd.vmware.vcloud.uploadVAppTemplateParams+xml'
1095
1096 response = self.perform_request(req_type='POST',
1097 url=catalog_href,
1098 headers=headers,
1099 data=data)
1100
1101 if response.status_code == requests.codes.created:
1102 catalogItem = XmlElementTree.fromstring(response.content)
1103 entity = [child for child in catalogItem if
1104 child.get("type") == "application/vnd.vmware.vcloud.vAppTemplate+xml"][0]
1105 href = entity.get('href')
1106 template = href
1107
1108 response = self.perform_request(req_type='GET',
1109 url=href,
1110 headers=headers)
1111
1112 if response.status_code == requests.codes.ok:
1113 headers['Content-Type'] = 'Content-Type text/xml'
1114 result = re.search('rel="upload:default"\shref="(.*?\/descriptor.ovf)"',response.content)
1115 if result:
1116 transfer_href = result.group(1)
1117
1118 response = self.perform_request(req_type='PUT',
1119 url=transfer_href,
1120 headers=headers,
1121 data=open(media_file_name, 'rb'))
1122 if response.status_code != requests.codes.ok:
1123 self.logger.debug(
1124 "Failed create vApp template for catalog name {} and image {}".format(catalog_name,
1125 media_file_name))
1126 return False
1127
1128 # TODO fix this with aync block
1129 time.sleep(5)
1130
1131 self.logger.debug("vApp template for catalog name {} and image {}".format(catalog_name, media_file_name))
1132
1133 # uploading VMDK file
1134 # check status of OVF upload and upload remaining files.
1135 response = self.perform_request(req_type='GET',
1136 url=template,
1137 headers=headers)
1138
1139 if response.status_code == requests.codes.ok:
1140 result = re.search('rel="upload:default"\s*href="(.*?vmdk)"',response.content)
1141 if result:
1142 link_href = result.group(1)
1143 # we skip ovf since it already uploaded.
1144 if 'ovf' in link_href:
1145 continue
1146 # The OVF file and VMDK must be in a same directory
1147 head, tail = os.path.split(media_file_name)
1148 file_vmdk = head + '/' + link_href.split("/")[-1]
1149 if not os.path.isfile(file_vmdk):
1150 return False
1151 statinfo = os.stat(file_vmdk)
1152 if statinfo.st_size == 0:
1153 return False
1154 hrefvmdk = link_href
1155
1156 if progress:
1157 widgets = ['Uploading file: ', Percentage(), ' ', Bar(), ' ', ETA(), ' ',
1158 FileTransferSpeed()]
1159 progress_bar = ProgressBar(widgets=widgets, maxval=statinfo.st_size).start()
1160
1161 bytes_transferred = 0
1162 f = open(file_vmdk, 'rb')
1163 while bytes_transferred < statinfo.st_size:
1164 my_bytes = f.read(chunk_bytes)
1165 if len(my_bytes) <= chunk_bytes:
1166 headers['Content-Range'] = 'bytes %s-%s/%s' % (
1167 bytes_transferred, len(my_bytes) - 1, statinfo.st_size)
1168 headers['Content-Length'] = str(len(my_bytes))
1169 response = requests.put(url=hrefvmdk,
1170 headers=headers,
1171 data=my_bytes,
1172 verify=False)
1173 if response.status_code == requests.codes.ok:
1174 bytes_transferred += len(my_bytes)
1175 if progress:
1176 progress_bar.update(bytes_transferred)
1177 else:
1178 self.logger.debug(
1179 'file upload failed with error: [%s] %s' % (response.status_code,
1180 response.content))
1181
1182 f.close()
1183 return False
1184 f.close()
1185 if progress:
1186 progress_bar.finish()
1187 time.sleep(10)
1188 return True
1189 else:
1190 self.logger.debug("Failed retrieve vApp template for catalog name {} for OVF {}".
1191 format(catalog_name, media_file_name))
1192 return False
1193 except Exception as exp:
1194 self.logger.debug("Failed while uploading OVF to catalog {} for OVF file {} with Exception {}"
1195 .format(catalog_name,media_file_name, exp))
1196 raise vimconn.vimconnException(
1197 "Failed while uploading OVF to catalog {} for OVF file {} with Exception {}"
1198 .format(catalog_name,media_file_name, exp))
1199
1200 self.logger.debug("Failed retrieve catalog name {} for OVF file {}".format(catalog_name, media_file_name))
1201 return False
1202
1203 def upload_vimimage(self, vca=None, catalog_name=None, media_name=None, medial_file_name=None, progress=False):
1204 """Upload media file"""
1205 # TODO add named parameters for readability
1206
1207 return self.upload_ovf(vca=vca, catalog_name=catalog_name, image_name=media_name.split(".")[0],
1208 media_file_name=medial_file_name, description='medial_file_name', progress=progress)
1209
1210 def validate_uuid4(self, uuid_string=None):
1211 """ Method validate correct format of UUID.
1212
1213 Return: true if string represent valid uuid
1214 """
1215 try:
1216 val = uuid.UUID(uuid_string, version=4)
1217 except ValueError:
1218 return False
1219 return True
1220
1221 def get_catalogid(self, catalog_name=None, catalogs=None):
1222 """ Method check catalog and return catalog ID in UUID format.
1223
1224 Args
1225 catalog_name: catalog name as string
1226 catalogs: list of catalogs.
1227
1228 Return: catalogs uuid
1229 """
1230
1231 for catalog in catalogs:
1232 if catalog['name'] == catalog_name:
1233 catalog_id = catalog['id']
1234 return catalog_id
1235 return None
1236
1237 def get_catalogbyid(self, catalog_uuid=None, catalogs=None):
1238 """ Method check catalog and return catalog name lookup done by catalog UUID.
1239
1240 Args
1241 catalog_name: catalog name as string
1242 catalogs: list of catalogs.
1243
1244 Return: catalogs name or None
1245 """
1246
1247 if not self.validate_uuid4(uuid_string=catalog_uuid):
1248 return None
1249
1250 for catalog in catalogs:
1251 catalog_id = catalog.get('id')
1252 if catalog_id == catalog_uuid:
1253 return catalog.get('name')
1254 return None
1255
1256 def get_catalog_obj(self, catalog_uuid=None, catalogs=None):
1257 """ Method check catalog and return catalog name lookup done by catalog UUID.
1258
1259 Args
1260 catalog_name: catalog name as string
1261 catalogs: list of catalogs.
1262
1263 Return: catalogs name or None
1264 """
1265
1266 if not self.validate_uuid4(uuid_string=catalog_uuid):
1267 return None
1268
1269 for catalog in catalogs:
1270 catalog_id = catalog.get('id')
1271 if catalog_id == catalog_uuid:
1272 return catalog
1273 return None
1274
1275 def get_image_id_from_path(self, path=None, progress=False):
1276 """ Method upload OVF image to vCloud director.
1277
1278 Each OVF image represented as single catalog entry in vcloud director.
1279 The method check for existing catalog entry. The check done by file name without file extension.
1280
1281 if given catalog name already present method will respond with existing catalog uuid otherwise
1282 it will create new catalog entry and upload OVF file to newly created catalog.
1283
1284 If method can't create catalog entry or upload a file it will throw exception.
1285
1286 Method accept boolean flag progress that will output progress bar. It useful method
1287 for standalone upload use case. In case to test large file upload.
1288
1289 Args
1290 path: - valid path to OVF file.
1291 progress - boolean progress bar show progress bar.
1292
1293 Return: if image uploaded correct method will provide image catalog UUID.
1294 """
1295
1296 if not path:
1297 raise vimconn.vimconnException("Image path can't be None.")
1298
1299 if not os.path.isfile(path):
1300 raise vimconn.vimconnException("Can't read file. File not found.")
1301
1302 if not os.access(path, os.R_OK):
1303 raise vimconn.vimconnException("Can't read file. Check file permission to read.")
1304
1305 self.logger.debug("get_image_id_from_path() client requesting {} ".format(path))
1306
1307 dirpath, filename = os.path.split(path)
1308 flname, file_extension = os.path.splitext(path)
1309 if file_extension != '.ovf':
1310 self.logger.debug("Wrong file extension {} connector support only OVF container.".format(file_extension))
1311 raise vimconn.vimconnException("Wrong container. vCloud director supports only OVF.")
1312
1313 catalog_name = os.path.splitext(filename)[0]
1314 catalog_md5_name = hashlib.md5(path).hexdigest()
1315 self.logger.debug("File name {} Catalog Name {} file path {} "
1316 "vdc catalog name {}".format(filename, catalog_name, path, catalog_md5_name))
1317
1318 try:
1319 org,vdc = self.get_vdc_details()
1320 catalogs = org.list_catalogs()
1321 except Exception as exp:
1322 self.logger.debug("Failed get catalogs() with Exception {} ".format(exp))
1323 raise vimconn.vimconnException("Failed get catalogs() with Exception {} ".format(exp))
1324
1325 if len(catalogs) == 0:
1326 self.logger.info("Creating a new catalog entry {} in vcloud director".format(catalog_name))
1327 result = self.create_vimcatalog(org, catalog_md5_name)
1328 if not result:
1329 raise vimconn.vimconnException("Failed create new catalog {} ".format(catalog_md5_name))
1330
1331 result = self.upload_vimimage(vca=org, catalog_name=catalog_md5_name,
1332 media_name=filename, medial_file_name=path, progress=progress)
1333 if not result:
1334 raise vimconn.vimconnException("Failed create vApp template for catalog {} ".format(catalog_name))
1335 return self.get_catalogid(catalog_name, catalogs)
1336 else:
1337 for catalog in catalogs:
1338 # search for existing catalog if we find same name we return ID
1339 # TODO optimize this
1340 if catalog['name'] == catalog_md5_name:
1341 self.logger.debug("Found existing catalog entry for {} "
1342 "catalog id {}".format(catalog_name,
1343 self.get_catalogid(catalog_md5_name, catalogs)))
1344 return self.get_catalogid(catalog_md5_name, catalogs)
1345
1346 # if we didn't find existing catalog we create a new one and upload image.
1347 self.logger.debug("Creating new catalog entry {} - {}".format(catalog_name, catalog_md5_name))
1348 result = self.create_vimcatalog(org, catalog_md5_name)
1349 if not result:
1350 raise vimconn.vimconnException("Failed create new catalog {} ".format(catalog_md5_name))
1351
1352 result = self.upload_vimimage(vca=org, catalog_name=catalog_md5_name,
1353 media_name=filename, medial_file_name=path, progress=progress)
1354 if not result:
1355 raise vimconn.vimconnException("Failed create vApp template for catalog {} ".format(catalog_md5_name))
1356
1357 return self.get_catalogid(catalog_md5_name, org.list_catalogs())
1358
1359 def get_image_list(self, filter_dict={}):
1360 '''Obtain tenant images from VIM
1361 Filter_dict can be:
1362 name: image name
1363 id: image uuid
1364 checksum: image checksum
1365 location: image path
1366 Returns the image list of dictionaries:
1367 [{<the fields at Filter_dict plus some VIM specific>}, ...]
1368 List can be empty
1369 '''
1370
1371 try:
1372 org, vdc = self.get_vdc_details()
1373 image_list = []
1374 catalogs = org.list_catalogs()
1375 if len(catalogs) == 0:
1376 return image_list
1377 else:
1378 for catalog in catalogs:
1379 catalog_uuid = catalog.get('id')
1380 name = catalog.get('name')
1381 filtered_dict = {}
1382 if filter_dict.get("name") and filter_dict["name"] != name:
1383 continue
1384 if filter_dict.get("id") and filter_dict["id"] != catalog_uuid:
1385 continue
1386 filtered_dict ["name"] = name
1387 filtered_dict ["id"] = catalog_uuid
1388 image_list.append(filtered_dict)
1389
1390 self.logger.debug("List of already created catalog items: {}".format(image_list))
1391 return image_list
1392 except Exception as exp:
1393 raise vimconn.vimconnException("Exception occured while retriving catalog items {}".format(exp))
1394
1395 def get_vappid(self, vdc=None, vapp_name=None):
1396 """ Method takes vdc object and vApp name and returns vapp uuid or None
1397
1398 Args:
1399 vdc: The VDC object.
1400 vapp_name: is application vappp name identifier
1401
1402 Returns:
1403 The return vApp name otherwise None
1404 """
1405 if vdc is None or vapp_name is None:
1406 return None
1407 # UUID has following format https://host/api/vApp/vapp-30da58a3-e7c7-4d09-8f68-d4c8201169cf
1408 try:
1409 refs = filter(lambda ref: ref.name == vapp_name and ref.type_ == 'application/vnd.vmware.vcloud.vApp+xml',
1410 vdc.ResourceEntities.ResourceEntity)
1411 #For python3
1412 #refs = [ref for ref in vdc.ResourceEntities.ResourceEntity\
1413 # if ref.name == vapp_name and ref.type_ == 'application/vnd.vmware.vcloud.vApp+xml']
1414 if len(refs) == 1:
1415 return refs[0].href.split("vapp")[1][1:]
1416 except Exception as e:
1417 self.logger.exception(e)
1418 return False
1419 return None
1420
1421 def check_vapp(self, vdc=None, vapp_uuid=None):
1422 """ Method Method returns True or False if vapp deployed in vCloud director
1423
1424 Args:
1425 vca: Connector to VCA
1426 vdc: The VDC object.
1427 vappid: vappid is application identifier
1428
1429 Returns:
1430 The return True if vApp deployed
1431 :param vdc:
1432 :param vapp_uuid:
1433 """
1434 try:
1435 refs = filter(lambda ref:
1436 ref.type_ == 'application/vnd.vmware.vcloud.vApp+xml',
1437 vdc.ResourceEntities.ResourceEntity)
1438 #For python3
1439 #refs = [ref for ref in vdc.ResourceEntities.ResourceEntity\
1440 # if ref.type_ == 'application/vnd.vmware.vcloud.vApp+xml']
1441 for ref in refs:
1442 vappid = ref.href.split("vapp")[1][1:]
1443 # find vapp with respected vapp uuid
1444 if vappid == vapp_uuid:
1445 return True
1446 except Exception as e:
1447 self.logger.exception(e)
1448 return False
1449 return False
1450
1451 def get_namebyvappid(self, vapp_uuid=None):
1452 """Method returns vApp name from vCD and lookup done by vapp_id.
1453
1454 Args:
1455 vapp_uuid: vappid is application identifier
1456
1457 Returns:
1458 The return vApp name otherwise None
1459 """
1460 try:
1461 if self.client and vapp_uuid:
1462 vapp_call = "{}/api/vApp/vapp-{}".format(self.url, vapp_uuid)
1463 headers = {'Accept':'application/*+xml;version=' + API_VERSION,
1464 'x-vcloud-authorization': self.client._session.headers['x-vcloud-authorization']}
1465
1466 response = self.perform_request(req_type='GET',
1467 url=vapp_call,
1468 headers=headers)
1469 #Retry login if session expired & retry sending request
1470 if response.status_code == 403:
1471 response = self.retry_rest('GET', vapp_call)
1472
1473 tree = XmlElementTree.fromstring(response.content)
1474 return tree.attrib['name']
1475 except Exception as e:
1476 self.logger.exception(e)
1477 return None
1478 return None
1479
1480 def new_vminstance(self, name=None, description="", start=False, image_id=None, flavor_id=None, net_list=[],
1481 cloud_config=None, disk_list=None, availability_zone_index=None, availability_zone_list=None):
1482 """Adds a VM instance to VIM
1483 Params:
1484 'start': (boolean) indicates if VM must start or created in pause mode.
1485 'image_id','flavor_id': image and flavor VIM id to use for the VM
1486 'net_list': list of interfaces, each one is a dictionary with:
1487 'name': (optional) name for the interface.
1488 'net_id': VIM network id where this interface must be connect to. Mandatory for type==virtual
1489 'vpci': (optional) virtual vPCI address to assign at the VM. Can be ignored depending on VIM capabilities
1490 'model': (optional and only have sense for type==virtual) interface model: virtio, e1000, ...
1491 'mac_address': (optional) mac address to assign to this interface
1492 #TODO: CHECK if an optional 'vlan' parameter is needed for VIMs when type if VF and net_id is not provided,
1493 the VLAN tag to be used. In case net_id is provided, the internal network vlan is used for tagging VF
1494 'type': (mandatory) can be one of:
1495 'virtual', in this case always connected to a network of type 'net_type=bridge'
1496 'PCI-PASSTHROUGH' or 'PF' (passthrough): depending on VIM capabilities it can be connected to a data/ptp network ot it
1497 can created unconnected
1498 'SR-IOV' or 'VF' (SRIOV with VLAN tag): same as PF for network connectivity.
1499 'VFnotShared'(SRIOV without VLAN tag) same as PF for network connectivity. VF where no other VFs
1500 are allocated on the same physical NIC
1501 'bw': (optional) only for PF/VF/VFnotShared. Minimal Bandwidth required for the interface in GBPS
1502 'port_security': (optional) If False it must avoid any traffic filtering at this interface. If missing
1503 or True, it must apply the default VIM behaviour
1504 After execution the method will add the key:
1505 'vim_id': must be filled/added by this method with the VIM identifier generated by the VIM for this
1506 interface. 'net_list' is modified
1507 'cloud_config': (optional) dictionary with:
1508 'key-pairs': (optional) list of strings with the public key to be inserted to the default user
1509 'users': (optional) list of users to be inserted, each item is a dict with:
1510 'name': (mandatory) user name,
1511 'key-pairs': (optional) list of strings with the public key to be inserted to the user
1512 'user-data': (optional) can be a string with the text script to be passed directly to cloud-init,
1513 or a list of strings, each one contains a script to be passed, usually with a MIMEmultipart file
1514 'config-files': (optional). List of files to be transferred. Each item is a dict with:
1515 'dest': (mandatory) string with the destination absolute path
1516 'encoding': (optional, by default text). Can be one of:
1517 'b64', 'base64', 'gz', 'gz+b64', 'gz+base64', 'gzip+b64', 'gzip+base64'
1518 'content' (mandatory): string with the content of the file
1519 'permissions': (optional) string with file permissions, typically octal notation '0644'
1520 'owner': (optional) file owner, string with the format 'owner:group'
1521 'boot-data-drive': boolean to indicate if user-data must be passed using a boot drive (hard disk)
1522 'disk_list': (optional) list with additional disks to the VM. Each item is a dict with:
1523 'image_id': (optional). VIM id of an existing image. If not provided an empty disk must be mounted
1524 'size': (mandatory) string with the size of the disk in GB
1525 availability_zone_index: Index of availability_zone_list to use for this this VM. None if not AV required
1526 availability_zone_list: list of availability zones given by user in the VNFD descriptor. Ignore if
1527 availability_zone_index is None
1528 Returns a tuple with the instance identifier and created_items or raises an exception on error
1529 created_items can be None or a dictionary where this method can include key-values that will be passed to
1530 the method delete_vminstance and action_vminstance. Can be used to store created ports, volumes, etc.
1531 Format is vimconnector dependent, but do not use nested dictionaries and a value of None should be the same
1532 as not present.
1533 """
1534 self.logger.info("Creating new instance for entry {}".format(name))
1535 self.logger.debug("desc {} boot {} image_id: {} flavor_id: {} net_list: {} cloud_config {} disk_list {} "\
1536 "availability_zone_index {} availability_zone_list {}"\
1537 .format(description, start, image_id, flavor_id, net_list, cloud_config, disk_list,\
1538 availability_zone_index, availability_zone_list))
1539
1540 #new vm name = vmname + tenant_id + uuid
1541 new_vm_name = [name, '-', str(uuid.uuid4())]
1542 vmname_andid = ''.join(new_vm_name)
1543
1544 for net in net_list:
1545 if net['type'] == "PCI-PASSTHROUGH":
1546 raise vimconn.vimconnNotSupportedException(
1547 "Current vCD version does not support type : {}".format(net['type']))
1548
1549 if len(net_list) > 10:
1550 raise vimconn.vimconnNotSupportedException(
1551 "The VM hardware versions 7 and above support upto 10 NICs only")
1552
1553 # if vm already deployed we return existing uuid
1554 # we check for presence of VDC, Catalog entry and Flavor.
1555 org, vdc = self.get_vdc_details()
1556 if vdc is None:
1557 raise vimconn.vimconnNotFoundException(
1558 "new_vminstance(): Failed create vApp {}: (Failed retrieve VDC information)".format(name))
1559 catalogs = org.list_catalogs()
1560 if catalogs is None:
1561 #Retry once, if failed by refreshing token
1562 self.get_token()
1563 org = Org(self.client, resource=self.client.get_org())
1564 catalogs = org.list_catalogs()
1565 if catalogs is None:
1566 raise vimconn.vimconnNotFoundException(
1567 "new_vminstance(): Failed create vApp {}: (Failed retrieve catalogs list)".format(name))
1568
1569 catalog_hash_name = self.get_catalogbyid(catalog_uuid=image_id, catalogs=catalogs)
1570 if catalog_hash_name:
1571 self.logger.info("Found catalog entry {} for image id {}".format(catalog_hash_name, image_id))
1572 else:
1573 raise vimconn.vimconnNotFoundException("new_vminstance(): Failed create vApp {}: "
1574 "(Failed retrieve catalog information {})".format(name, image_id))
1575
1576 # Set vCPU and Memory based on flavor.
1577 vm_cpus = None
1578 vm_memory = None
1579 vm_disk = None
1580 numas = None
1581
1582 if flavor_id is not None:
1583 if flavor_id not in vimconnector.flavorlist:
1584 raise vimconn.vimconnNotFoundException("new_vminstance(): Failed create vApp {}: "
1585 "Failed retrieve flavor information "
1586 "flavor id {}".format(name, flavor_id))
1587 else:
1588 try:
1589 flavor = vimconnector.flavorlist[flavor_id]
1590 vm_cpus = flavor[FLAVOR_VCPUS_KEY]
1591 vm_memory = flavor[FLAVOR_RAM_KEY]
1592 vm_disk = flavor[FLAVOR_DISK_KEY]
1593 extended = flavor.get("extended", None)
1594 if extended:
1595 numas=extended.get("numas", None)
1596
1597 except Exception as exp:
1598 raise vimconn.vimconnException("Corrupted flavor. {}.Exception: {}".format(flavor_id, exp))
1599
1600 # image upload creates template name as catalog name space Template.
1601 templateName = self.get_catalogbyid(catalog_uuid=image_id, catalogs=catalogs)
1602 power_on = 'false'
1603 if start:
1604 power_on = 'true'
1605
1606 # client must provide at least one entry in net_list if not we report error
1607 #If net type is mgmt, then configure it as primary net & use its NIC index as primary NIC
1608 #If no mgmt, then the 1st NN in netlist is considered as primary net.
1609 primary_net = None
1610 primary_netname = None
1611 primary_net_href = None
1612 network_mode = 'bridged'
1613 if net_list is not None and len(net_list) > 0:
1614 for net in net_list:
1615 if 'use' in net and net['use'] == 'mgmt' and not primary_net:
1616 primary_net = net
1617 if primary_net is None:
1618 primary_net = net_list[0]
1619
1620 try:
1621 primary_net_id = primary_net['net_id']
1622 url_list = [self.url, '/api/network/', primary_net_id]
1623 primary_net_href = ''.join(url_list)
1624 network_dict = self.get_vcd_network(network_uuid=primary_net_id)
1625 if 'name' in network_dict:
1626 primary_netname = network_dict['name']
1627
1628 except KeyError:
1629 raise vimconn.vimconnException("Corrupted flavor. {}".format(primary_net))
1630 else:
1631 raise vimconn.vimconnUnexpectedResponse("new_vminstance(): Failed network list is empty.".format(name))
1632
1633 # use: 'data', 'bridge', 'mgmt'
1634 # create vApp. Set vcpu and ram based on flavor id.
1635 try:
1636 vdc_obj = VDC(self.client, resource=org.get_vdc(self.tenant_name))
1637 if not vdc_obj:
1638 raise vimconn.vimconnNotFoundException("new_vminstance(): Failed to get VDC object")
1639
1640 for retry in (1,2):
1641 items = org.get_catalog_item(catalog_hash_name, catalog_hash_name)
1642 catalog_items = [items.attrib]
1643
1644 if len(catalog_items) == 1:
1645 if self.client:
1646 headers = {'Accept':'application/*+xml;version=' + API_VERSION,
1647 'x-vcloud-authorization': self.client._session.headers['x-vcloud-authorization']}
1648
1649 response = self.perform_request(req_type='GET',
1650 url=catalog_items[0].get('href'),
1651 headers=headers)
1652 catalogItem = XmlElementTree.fromstring(response.content)
1653 entity = [child for child in catalogItem if child.get("type") == "application/vnd.vmware.vcloud.vAppTemplate+xml"][0]
1654 vapp_tempalte_href = entity.get("href")
1655
1656 response = self.perform_request(req_type='GET',
1657 url=vapp_tempalte_href,
1658 headers=headers)
1659 if response.status_code != requests.codes.ok:
1660 self.logger.debug("REST API call {} failed. Return status code {}".format(vapp_tempalte_href,
1661 response.status_code))
1662 else:
1663 result = (response.content).replace("\n"," ")
1664
1665 src = re.search('<Vm goldMaster="false"\sstatus="\d+"\sname="(.*?)"\s'
1666 'id="(\w+:\w+:vm:.*?)"\shref="(.*?)"\s'
1667 'type="application/vnd\.vmware\.vcloud\.vm\+xml',result)
1668 if src:
1669 vm_name = src.group(1)
1670 vm_id = src.group(2)
1671 vm_href = src.group(3)
1672
1673 cpus = re.search('<rasd:Description>Number of Virtual CPUs</.*?>(\d+)</rasd:VirtualQuantity>',result).group(1)
1674 memory_mb = re.search('<rasd:Description>Memory Size</.*?>(\d+)</rasd:VirtualQuantity>',result).group(1)
1675 cores = re.search('<vmw:CoresPerSocket ovf:required.*?>(\d+)</vmw:CoresPerSocket>',result).group(1)
1676
1677 headers['Content-Type'] = 'application/vnd.vmware.vcloud.instantiateVAppTemplateParams+xml'
1678 vdc_id = vdc.get('id').split(':')[-1]
1679 instantiate_vapp_href = "{}/api/vdc/{}/action/instantiateVAppTemplate".format(self.url,
1680 vdc_id)
1681 data = """<?xml version="1.0" encoding="UTF-8"?>
1682 <InstantiateVAppTemplateParams
1683 xmlns="http://www.vmware.com/vcloud/v1.5"
1684 name="{}"
1685 deploy="false"
1686 powerOn="false"
1687 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
1688 xmlns:ovf="http://schemas.dmtf.org/ovf/envelope/1">
1689 <Description>Vapp instantiation</Description>
1690 <InstantiationParams>
1691 <NetworkConfigSection>
1692 <ovf:Info>Configuration parameters for logical networks</ovf:Info>
1693 <NetworkConfig networkName="{}">
1694 <Configuration>
1695 <ParentNetwork href="{}" />
1696 <FenceMode>bridged</FenceMode>
1697 </Configuration>
1698 </NetworkConfig>
1699 </NetworkConfigSection>
1700 <LeaseSettingsSection
1701 type="application/vnd.vmware.vcloud.leaseSettingsSection+xml">
1702 <ovf:Info>Lease Settings</ovf:Info>
1703 <StorageLeaseInSeconds>172800</StorageLeaseInSeconds>
1704 <StorageLeaseExpiration>2014-04-25T08:08:16.438-07:00</StorageLeaseExpiration>
1705 </LeaseSettingsSection>
1706 </InstantiationParams>
1707 <Source href="{}"/>
1708 <SourcedItem>
1709 <Source href="{}" id="{}" name="{}"
1710 type="application/vnd.vmware.vcloud.vm+xml"/>
1711 <VmGeneralParams>
1712 <NeedsCustomization>false</NeedsCustomization>
1713 </VmGeneralParams>
1714 <InstantiationParams>
1715 <NetworkConnectionSection>
1716 <ovf:Info>Specifies the available VM network connections</ovf:Info>
1717 <NetworkConnection network="{}">
1718 <NetworkConnectionIndex>0</NetworkConnectionIndex>
1719 <IsConnected>true</IsConnected>
1720 <IpAddressAllocationMode>DHCP</IpAddressAllocationMode>
1721 </NetworkConnection>
1722 </NetworkConnectionSection><ovf:VirtualHardwareSection>
1723 <ovf:Info>Virtual hardware requirements</ovf:Info>
1724 <ovf:Item xmlns:rasd="http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/CIM_ResourceAllocationSettingData"
1725 xmlns:vmw="http://www.vmware.com/schema/ovf">
1726 <rasd:AllocationUnits>hertz * 10^6</rasd:AllocationUnits>
1727 <rasd:Description>Number of Virtual CPUs</rasd:Description>
1728 <rasd:ElementName xmlns:py="http://codespeak.net/lxml/objectify/pytype" py:pytype="str">{cpu} virtual CPU(s)</rasd:ElementName>
1729 <rasd:InstanceID>4</rasd:InstanceID>
1730 <rasd:Reservation>0</rasd:Reservation>
1731 <rasd:ResourceType>3</rasd:ResourceType>
1732 <rasd:VirtualQuantity xmlns:py="http://codespeak.net/lxml/objectify/pytype" py:pytype="int">{cpu}</rasd:VirtualQuantity>
1733 <rasd:Weight>0</rasd:Weight>
1734 <vmw:CoresPerSocket ovf:required="false">{core}</vmw:CoresPerSocket>
1735 </ovf:Item><ovf:Item xmlns:rasd="http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/CIM_ResourceAllocationSettingData">
1736 <rasd:AllocationUnits>byte * 2^20</rasd:AllocationUnits>
1737 <rasd:Description>Memory Size</rasd:Description>
1738 <rasd:ElementName xmlns:py="http://codespeak.net/lxml/objectify/pytype" py:pytype="str">{memory} MB of memory</rasd:ElementName>
1739 <rasd:InstanceID>5</rasd:InstanceID>
1740 <rasd:Reservation>0</rasd:Reservation>
1741 <rasd:ResourceType>4</rasd:ResourceType>
1742 <rasd:VirtualQuantity xmlns:py="http://codespeak.net/lxml/objectify/pytype" py:pytype="int">{memory}</rasd:VirtualQuantity>
1743 <rasd:Weight>0</rasd:Weight>
1744 </ovf:Item>
1745 </ovf:VirtualHardwareSection>
1746 </InstantiationParams>
1747 </SourcedItem>
1748 <AllEULAsAccepted>false</AllEULAsAccepted>
1749 </InstantiateVAppTemplateParams>""".format(vmname_andid,
1750 primary_netname,
1751 primary_net_href,
1752 vapp_tempalte_href,
1753 vm_href,
1754 vm_id,
1755 vm_name,
1756 primary_netname,
1757 cpu=cpus,
1758 core=cores,
1759 memory=memory_mb)
1760
1761 response = self.perform_request(req_type='POST',
1762 url=instantiate_vapp_href,
1763 headers=headers,
1764 data=data)
1765
1766 if response.status_code != 201:
1767 self.logger.error("REST call {} failed reason : {}"\
1768 "status code : {}".format(instantiate_vapp_href,
1769 response.content,
1770 response.status_code))
1771 raise vimconn.vimconnException("new_vminstance(): Failed to create"\
1772 "vAapp {}".format(vmname_andid))
1773 else:
1774 vapptask = self.get_task_from_response(response.content)
1775
1776 if vapptask is None and retry==1:
1777 self.get_token() # Retry getting token
1778 continue
1779 else:
1780 break
1781
1782 if vapptask is None or vapptask is False:
1783 raise vimconn.vimconnUnexpectedResponse(
1784 "new_vminstance(): failed to create vApp {}".format(vmname_andid))
1785
1786 # wait for task to complete
1787 result = self.client.get_task_monitor().wait_for_success(task=vapptask)
1788
1789 if result.get('status') == 'success':
1790 self.logger.debug("new_vminstance(): Sucessfully created Vapp {}".format(vmname_andid))
1791 else:
1792 raise vimconn.vimconnUnexpectedResponse(
1793 "new_vminstance(): failed to create vApp {}".format(vmname_andid))
1794
1795 except Exception as exp:
1796 raise vimconn.vimconnUnexpectedResponse(
1797 "new_vminstance(): failed to create vApp {} with Exception:{}".format(vmname_andid, exp))
1798
1799 # we should have now vapp in undeployed state.
1800 try:
1801 vdc_obj = VDC(self.client, href=vdc.get('href'))
1802 vapp_resource = vdc_obj.get_vapp(vmname_andid)
1803 vapp_uuid = vapp_resource.get('id').split(':')[-1]
1804 vapp = VApp(self.client, resource=vapp_resource)
1805
1806 except Exception as exp:
1807 raise vimconn.vimconnUnexpectedResponse(
1808 "new_vminstance(): Failed to retrieve vApp {} after creation: Exception:{}"
1809 .format(vmname_andid, exp))
1810
1811 if vapp_uuid is None:
1812 raise vimconn.vimconnUnexpectedResponse(
1813 "new_vminstance(): Failed to retrieve vApp {} after creation".format(
1814 vmname_andid))
1815
1816 #Add PCI passthrough/SRIOV configrations
1817 vm_obj = None
1818 pci_devices_info = []
1819 reserve_memory = False
1820
1821 for net in net_list:
1822 if net["type"] == "PF" or net["type"] == "PCI-PASSTHROUGH":
1823 pci_devices_info.append(net)
1824 elif (net["type"] == "VF" or net["type"] == "SR-IOV" or net["type"] == "VFnotShared") and 'net_id'in net:
1825 reserve_memory = True
1826
1827 #Add PCI
1828 if len(pci_devices_info) > 0:
1829 self.logger.info("Need to add PCI devices {} into VM {}".format(pci_devices_info,
1830 vmname_andid ))
1831 PCI_devices_status, vm_obj, vcenter_conect = self.add_pci_devices(vapp_uuid,
1832 pci_devices_info,
1833 vmname_andid)
1834 if PCI_devices_status:
1835 self.logger.info("Added PCI devives {} to VM {}".format(
1836 pci_devices_info,
1837 vmname_andid)
1838 )
1839 reserve_memory = True
1840 else:
1841 self.logger.info("Fail to add PCI devives {} to VM {}".format(
1842 pci_devices_info,
1843 vmname_andid)
1844 )
1845
1846 # Modify vm disk
1847 if vm_disk:
1848 #Assuming there is only one disk in ovf and fast provisioning in organization vDC is disabled
1849 result = self.modify_vm_disk(vapp_uuid, vm_disk)
1850 if result :
1851 self.logger.debug("Modified Disk size of VM {} ".format(vmname_andid))
1852
1853 #Add new or existing disks to vApp
1854 if disk_list:
1855 added_existing_disk = False
1856 for disk in disk_list:
1857 if 'device_type' in disk and disk['device_type'] == 'cdrom':
1858 image_id = disk['image_id']
1859 # Adding CD-ROM to VM
1860 # will revisit code once specification ready to support this feature
1861 self.insert_media_to_vm(vapp, image_id)
1862 elif "image_id" in disk and disk["image_id"] is not None:
1863 self.logger.debug("Adding existing disk from image {} to vm {} ".format(
1864 disk["image_id"] , vapp_uuid))
1865 self.add_existing_disk(catalogs=catalogs,
1866 image_id=disk["image_id"],
1867 size = disk["size"],
1868 template_name=templateName,
1869 vapp_uuid=vapp_uuid
1870 )
1871 added_existing_disk = True
1872 else:
1873 #Wait till added existing disk gets reflected into vCD database/API
1874 if added_existing_disk:
1875 time.sleep(5)
1876 added_existing_disk = False
1877 self.add_new_disk(vapp_uuid, disk['size'])
1878
1879 if numas:
1880 # Assigning numa affinity setting
1881 for numa in numas:
1882 if 'paired-threads-id' in numa:
1883 paired_threads_id = numa['paired-threads-id']
1884 self.set_numa_affinity(vapp_uuid, paired_threads_id)
1885
1886 # add NICs & connect to networks in netlist
1887 try:
1888 vdc_obj = VDC(self.client, href=vdc.get('href'))
1889 vapp_resource = vdc_obj.get_vapp(vmname_andid)
1890 vapp = VApp(self.client, resource=vapp_resource)
1891 vapp_id = vapp_resource.get('id').split(':')[-1]
1892
1893 self.logger.info("Removing primary NIC: ")
1894 # First remove all NICs so that NIC properties can be adjusted as needed
1895 self.remove_primary_network_adapter_from_all_vms(vapp)
1896
1897 self.logger.info("Request to connect VM to a network: {}".format(net_list))
1898 primary_nic_index = 0
1899 nicIndex = 0
1900 for net in net_list:
1901 # openmano uses network id in UUID format.
1902 # vCloud Director need a name so we do reverse operation from provided UUID we lookup a name
1903 # [{'use': 'bridge', 'net_id': '527d4bf7-566a-41e7-a9e7-ca3cdd9cef4f', 'type': 'virtual',
1904 # 'vpci': '0000:00:11.0', 'name': 'eth0'}]
1905
1906 if 'net_id' not in net:
1907 continue
1908
1909 #Using net_id as a vim_id i.e. vim interface id, as do not have saperate vim interface id
1910 #Same will be returned in refresh_vms_status() as vim_interface_id
1911 net['vim_id'] = net['net_id'] # Provide the same VIM identifier as the VIM network
1912
1913 interface_net_id = net['net_id']
1914 interface_net_name = self.get_network_name_by_id(network_uuid=interface_net_id)
1915 interface_network_mode = net['use']
1916
1917 if interface_network_mode == 'mgmt':
1918 primary_nic_index = nicIndex
1919
1920 """- POOL (A static IP address is allocated automatically from a pool of addresses.)
1921 - DHCP (The IP address is obtained from a DHCP service.)
1922 - MANUAL (The IP address is assigned manually in the IpAddress element.)
1923 - NONE (No IP addressing mode specified.)"""
1924
1925 if primary_netname is not None:
1926 self.logger.debug("new_vminstance(): Filtering by net name {}".format(interface_net_name))
1927 nets = filter(lambda n: n.get('name') == interface_net_name, self.get_network_list())
1928 #For python3
1929 #nets = [n for n in self.get_network_list() if n.get('name') == interface_net_name]
1930 if len(nets) == 1:
1931 self.logger.info("new_vminstance(): Found requested network: {}".format(nets[0].get('name')))
1932
1933 if interface_net_name != primary_netname:
1934 # connect network to VM - with all DHCP by default
1935 self.logger.info("new_vminstance(): Attaching net {} to vapp".format(interface_net_name))
1936 self.connect_vapp_to_org_vdc_network(vapp_id, nets[0].get('name'))
1937
1938 type_list = ('PF', 'PCI-PASSTHROUGH', 'VFnotShared')
1939 nic_type = 'VMXNET3'
1940 if 'type' in net and net['type'] not in type_list:
1941 # fetching nic type from vnf
1942 if 'model' in net:
1943 if net['model'] is not None:
1944 if net['model'].lower() == 'paravirt' or net['model'].lower() == 'virtio':
1945 nic_type = 'VMXNET3'
1946 else:
1947 nic_type = net['model']
1948
1949 self.logger.info("new_vminstance(): adding network adapter "\
1950 "to a network {}".format(nets[0].get('name')))
1951 self.add_network_adapter_to_vms(vapp, nets[0].get('name'),
1952 primary_nic_index,
1953 nicIndex,
1954 net,
1955 nic_type=nic_type)
1956 else:
1957 self.logger.info("new_vminstance(): adding network adapter "\
1958 "to a network {}".format(nets[0].get('name')))
1959 if net['type'] in ['SR-IOV', 'VF']:
1960 nic_type = net['type']
1961 self.add_network_adapter_to_vms(vapp, nets[0].get('name'),
1962 primary_nic_index,
1963 nicIndex,
1964 net,
1965 nic_type=nic_type)
1966 nicIndex += 1
1967
1968 # cloud-init for ssh-key injection
1969 if cloud_config:
1970 self.cloud_init(vapp,cloud_config)
1971
1972 # If VM has PCI devices or SRIOV reserve memory for VM
1973 if reserve_memory:
1974 self.reserve_memory_for_all_vms(vapp, memory_mb)
1975
1976 self.logger.debug("new_vminstance(): starting power on vApp {} ".format(vmname_andid))
1977
1978 poweron_task = self.power_on_vapp(vapp_id, vmname_andid)
1979 result = self.client.get_task_monitor().wait_for_success(task=poweron_task)
1980 if result.get('status') == 'success':
1981 self.logger.info("new_vminstance(): Successfully power on "\
1982 "vApp {}".format(vmname_andid))
1983 else:
1984 self.logger.error("new_vminstance(): failed to power on vApp "\
1985 "{}".format(vmname_andid))
1986
1987 except Exception as exp :
1988 # it might be a case if specific mandatory entry in dict is empty or some other pyVcloud exception
1989 self.logger.error("new_vminstance(): Failed create new vm instance {} with exception {}"
1990 .format(name, exp))
1991 raise vimconn.vimconnException("new_vminstance(): Failed create new vm instance {} with exception {}"
1992 .format(name, exp))
1993
1994 # check if vApp deployed and if that the case return vApp UUID otherwise -1
1995 wait_time = 0
1996 vapp_uuid = None
1997 while wait_time <= MAX_WAIT_TIME:
1998 try:
1999 vapp_resource = vdc_obj.get_vapp(vmname_andid)
2000 vapp = VApp(self.client, resource=vapp_resource)
2001 except Exception as exp:
2002 raise vimconn.vimconnUnexpectedResponse(
2003 "new_vminstance(): Failed to retrieve vApp {} after creation: Exception:{}"
2004 .format(vmname_andid, exp))
2005
2006 #if vapp and vapp.me.deployed:
2007 if vapp and vapp_resource.get('deployed') == 'true':
2008 vapp_uuid = vapp_resource.get('id').split(':')[-1]
2009 break
2010 else:
2011 self.logger.debug("new_vminstance(): Wait for vApp {} to deploy".format(name))
2012 time.sleep(INTERVAL_TIME)
2013
2014 wait_time +=INTERVAL_TIME
2015
2016 #SET Affinity Rule for VM
2017 #Pre-requisites: User has created Hosh Groups in vCenter with respective Hosts to be used
2018 #While creating VIM account user has to pass the Host Group names in availability_zone list
2019 #"availability_zone" is a part of VIM "config" parameters
2020 #For example, in VIM config: "availability_zone":["HG_170","HG_174","HG_175"]
2021 #Host groups are referred as availability zones
2022 #With following procedure, deployed VM will be added into a VM group.
2023 #Then A VM to Host Affinity rule will be created using the VM group & Host group.
2024 if(availability_zone_list):
2025 self.logger.debug("Existing Host Groups in VIM {}".format(self.config.get('availability_zone')))
2026 #Admin access required for creating Affinity rules
2027 client = self.connect_as_admin()
2028 if not client:
2029 raise vimconn.vimconnConnectionException("Failed to connect vCD as admin")
2030 else:
2031 self.client = client
2032 if self.client:
2033 headers = {'Accept':'application/*+xml;version=27.0',
2034 'x-vcloud-authorization': self.client._session.headers['x-vcloud-authorization']}
2035 #Step1: Get provider vdc details from organization
2036 pvdc_href = self.get_pvdc_for_org(self.tenant_name, headers)
2037 if pvdc_href is not None:
2038 #Step2: Found required pvdc, now get resource pool information
2039 respool_href = self.get_resource_pool_details(pvdc_href, headers)
2040 if respool_href is None:
2041 #Raise error if respool_href not found
2042 msg = "new_vminstance():Error in finding resource pool details in pvdc {}"\
2043 .format(pvdc_href)
2044 self.log_message(msg)
2045
2046 #Step3: Verify requested availability zone(hostGroup) is present in vCD
2047 # get availability Zone
2048 vm_az = self.get_vm_availability_zone(availability_zone_index, availability_zone_list)
2049 # check if provided av zone(hostGroup) is present in vCD VIM
2050 status = self.check_availibility_zone(vm_az, respool_href, headers)
2051 if status is False:
2052 msg = "new_vminstance(): Error in finding availability zone(Host Group): {} in "\
2053 "resource pool {} status: {}".format(vm_az,respool_href,status)
2054 self.log_message(msg)
2055 else:
2056 self.logger.debug ("new_vminstance(): Availability zone {} found in VIM".format(vm_az))
2057
2058 #Step4: Find VM group references to create vm group
2059 vmgrp_href = self.find_vmgroup_reference(respool_href, headers)
2060 if vmgrp_href == None:
2061 msg = "new_vminstance(): No reference to VmGroup found in resource pool"
2062 self.log_message(msg)
2063
2064 #Step5: Create a VmGroup with name az_VmGroup
2065 vmgrp_name = vm_az + "_" + name #Formed VM Group name = Host Group name + VM name
2066 status = self.create_vmgroup(vmgrp_name, vmgrp_href, headers)
2067 if status is not True:
2068 msg = "new_vminstance(): Error in creating VM group {}".format(vmgrp_name)
2069 self.log_message(msg)
2070
2071 #VM Group url to add vms to vm group
2072 vmgrpname_url = self.url + "/api/admin/extension/vmGroup/name/"+ vmgrp_name
2073
2074 #Step6: Add VM to VM Group
2075 #Find VM uuid from vapp_uuid
2076 vm_details = self.get_vapp_details_rest(vapp_uuid)
2077 vm_uuid = vm_details['vmuuid']
2078
2079 status = self.add_vm_to_vmgroup(vm_uuid, vmgrpname_url, vmgrp_name, headers)
2080 if status is not True:
2081 msg = "new_vminstance(): Error in adding VM to VM group {}".format(vmgrp_name)
2082 self.log_message(msg)
2083
2084 #Step7: Create VM to Host affinity rule
2085 addrule_href = self.get_add_rule_reference (respool_href, headers)
2086 if addrule_href is None:
2087 msg = "new_vminstance(): Error in finding href to add rule in resource pool: {}"\
2088 .format(respool_href)
2089 self.log_message(msg)
2090
2091 status = self.create_vm_to_host_affinity_rule(addrule_href, vmgrp_name, vm_az, "Affinity", headers)
2092 if status is False:
2093 msg = "new_vminstance(): Error in creating affinity rule for VM {} in Host group {}"\
2094 .format(name, vm_az)
2095 self.log_message(msg)
2096 else:
2097 self.logger.debug("new_vminstance(): Affinity rule created successfully. Added {} in Host group {}"\
2098 .format(name, vm_az))
2099 #Reset token to a normal user to perform other operations
2100 self.get_token()
2101
2102 if vapp_uuid is not None:
2103 return vapp_uuid, None
2104 else:
2105 raise vimconn.vimconnUnexpectedResponse("new_vminstance(): Failed create new vm instance {}".format(name))
2106
2107
2108 def get_vcd_availibility_zones(self,respool_href, headers):
2109 """ Method to find presence of av zone is VIM resource pool
2110
2111 Args:
2112 respool_href - resource pool href
2113 headers - header information
2114
2115 Returns:
2116 vcd_az - list of azone present in vCD
2117 """
2118 vcd_az = []
2119 url=respool_href
2120 resp = self.perform_request(req_type='GET',url=respool_href, headers=headers)
2121
2122 if resp.status_code != requests.codes.ok:
2123 self.logger.debug ("REST API call {} failed. Return status code {}".format(url, resp.status_code))
2124 else:
2125 #Get the href to hostGroups and find provided hostGroup is present in it
2126 resp_xml = XmlElementTree.fromstring(resp.content)
2127 for child in resp_xml:
2128 if 'VMWProviderVdcResourcePool' in child.tag:
2129 for schild in child:
2130 if 'Link' in schild.tag:
2131 if schild.attrib.get('type') == "application/vnd.vmware.admin.vmwHostGroupsType+xml":
2132 hostGroup = schild.attrib.get('href')
2133 hg_resp = self.perform_request(req_type='GET',url=hostGroup, headers=headers)
2134 if hg_resp.status_code != requests.codes.ok:
2135 self.logger.debug ("REST API call {} failed. Return status code {}".format(hostGroup, hg_resp.status_code))
2136 else:
2137 hg_resp_xml = XmlElementTree.fromstring(hg_resp.content)
2138 for hostGroup in hg_resp_xml:
2139 if 'HostGroup' in hostGroup.tag:
2140 #append host group name to the list
2141 vcd_az.append(hostGroup.attrib.get("name"))
2142 return vcd_az
2143
2144
2145 def set_availability_zones(self):
2146 """
2147 Set vim availability zone
2148 """
2149
2150 vim_availability_zones = None
2151 availability_zone = None
2152 if 'availability_zone' in self.config:
2153 vim_availability_zones = self.config.get('availability_zone')
2154 if isinstance(vim_availability_zones, str):
2155 availability_zone = [vim_availability_zones]
2156 elif isinstance(vim_availability_zones, list):
2157 availability_zone = vim_availability_zones
2158 else:
2159 return availability_zone
2160
2161 return availability_zone
2162
2163
2164 def get_vm_availability_zone(self, availability_zone_index, availability_zone_list):
2165 """
2166 Return the availability zone to be used by the created VM.
2167 returns: The VIM availability zone to be used or None
2168 """
2169 if availability_zone_index is None:
2170 if not self.config.get('availability_zone'):
2171 return None
2172 elif isinstance(self.config.get('availability_zone'), str):
2173 return self.config['availability_zone']
2174 else:
2175 return self.config['availability_zone'][0]
2176
2177 vim_availability_zones = self.availability_zone
2178
2179 # check if VIM offer enough availability zones describe in the VNFD
2180 if vim_availability_zones and len(availability_zone_list) <= len(vim_availability_zones):
2181 # check if all the names of NFV AV match VIM AV names
2182 match_by_index = False
2183 for av in availability_zone_list:
2184 if av not in vim_availability_zones:
2185 match_by_index = True
2186 break
2187 if match_by_index:
2188 self.logger.debug("Required Availability zone or Host Group not found in VIM config")
2189 self.logger.debug("Input Availability zone list: {}".format(availability_zone_list))
2190 self.logger.debug("VIM configured Availability zones: {}".format(vim_availability_zones))
2191 self.logger.debug("VIM Availability zones will be used by index")
2192 return vim_availability_zones[availability_zone_index]
2193 else:
2194 return availability_zone_list[availability_zone_index]
2195 else:
2196 raise vimconn.vimconnConflictException("No enough availability zones at VIM for this deployment")
2197
2198
2199 def create_vm_to_host_affinity_rule(self, addrule_href, vmgrpname, hostgrpname, polarity, headers):
2200 """ Method to create VM to Host Affinity rule in vCD
2201
2202 Args:
2203 addrule_href - href to make a POST request
2204 vmgrpname - name of the VM group created
2205 hostgrpnmae - name of the host group created earlier
2206 polarity - Affinity or Anti-affinity (default: Affinity)
2207 headers - headers to make REST call
2208
2209 Returns:
2210 True- if rule is created
2211 False- Failed to create rule due to some error
2212
2213 """
2214 task_status = False
2215 rule_name = polarity + "_" + vmgrpname
2216 payload = """<?xml version="1.0" encoding="UTF-8"?>
2217 <vmext:VMWVmHostAffinityRule
2218 xmlns:vmext="http://www.vmware.com/vcloud/extension/v1.5"
2219 xmlns:vcloud="http://www.vmware.com/vcloud/v1.5"
2220 type="application/vnd.vmware.admin.vmwVmHostAffinityRule+xml">
2221 <vcloud:Name>{}</vcloud:Name>
2222 <vcloud:IsEnabled>true</vcloud:IsEnabled>
2223 <vcloud:IsMandatory>true</vcloud:IsMandatory>
2224 <vcloud:Polarity>{}</vcloud:Polarity>
2225 <vmext:HostGroupName>{}</vmext:HostGroupName>
2226 <vmext:VmGroupName>{}</vmext:VmGroupName>
2227 </vmext:VMWVmHostAffinityRule>""".format(rule_name, polarity, hostgrpname, vmgrpname)
2228
2229 resp = self.perform_request(req_type='POST',url=addrule_href, headers=headers, data=payload)
2230
2231 if resp.status_code != requests.codes.accepted:
2232 self.logger.debug ("REST API call {} failed. Return status code {}".format(addrule_href, resp.status_code))
2233 task_status = False
2234 return task_status
2235 else:
2236 affinity_task = self.get_task_from_response(resp.content)
2237 self.logger.debug ("affinity_task: {}".format(affinity_task))
2238 if affinity_task is None or affinity_task is False:
2239 raise vimconn.vimconnUnexpectedResponse("failed to find affinity task")
2240 # wait for task to complete
2241 result = self.client.get_task_monitor().wait_for_success(task=affinity_task)
2242 if result.get('status') == 'success':
2243 self.logger.debug("Successfully created affinity rule {}".format(rule_name))
2244 return True
2245 else:
2246 raise vimconn.vimconnUnexpectedResponse(
2247 "failed to create affinity rule {}".format(rule_name))
2248
2249
2250 def get_add_rule_reference (self, respool_href, headers):
2251 """ This method finds href to add vm to host affinity rule to vCD
2252
2253 Args:
2254 respool_href- href to resource pool
2255 headers- header information to make REST call
2256
2257 Returns:
2258 None - if no valid href to add rule found or
2259 addrule_href - href to add vm to host affinity rule of resource pool
2260 """
2261 addrule_href = None
2262 resp = self.perform_request(req_type='GET',url=respool_href, headers=headers)
2263
2264 if resp.status_code != requests.codes.ok:
2265 self.logger.debug ("REST API call {} failed. Return status code {}".format(respool_href, resp.status_code))
2266 else:
2267
2268 resp_xml = XmlElementTree.fromstring(resp.content)
2269 for child in resp_xml:
2270 if 'VMWProviderVdcResourcePool' in child.tag:
2271 for schild in child:
2272 if 'Link' in schild.tag:
2273 if schild.attrib.get('type') == "application/vnd.vmware.admin.vmwVmHostAffinityRule+xml" and \
2274 schild.attrib.get('rel') == "add":
2275 addrule_href = schild.attrib.get('href')
2276 break
2277
2278 return addrule_href
2279
2280
2281 def add_vm_to_vmgroup(self, vm_uuid, vmGroupNameURL, vmGroup_name, headers):
2282 """ Method to add deployed VM to newly created VM Group.
2283 This is required to create VM to Host affinity in vCD
2284
2285 Args:
2286 vm_uuid- newly created vm uuid
2287 vmGroupNameURL- URL to VM Group name
2288 vmGroup_name- Name of VM group created
2289 headers- Headers for REST request
2290
2291 Returns:
2292 True- if VM added to VM group successfully
2293 False- if any error encounter
2294 """
2295
2296 addvm_resp = self.perform_request(req_type='GET',url=vmGroupNameURL, headers=headers)#, data=payload)
2297
2298 if addvm_resp.status_code != requests.codes.ok:
2299 self.logger.debug ("REST API call to get VM Group Name url {} failed. Return status code {}"\
2300 .format(vmGroupNameURL, addvm_resp.status_code))
2301 return False
2302 else:
2303 resp_xml = XmlElementTree.fromstring(addvm_resp.content)
2304 for child in resp_xml:
2305 if child.tag.split('}')[1] == 'Link':
2306 if child.attrib.get("rel") == "addVms":
2307 addvmtogrpURL = child.attrib.get("href")
2308
2309 #Get vm details
2310 url_list = [self.url, '/api/vApp/vm-',vm_uuid]
2311 vmdetailsURL = ''.join(url_list)
2312
2313 resp = self.perform_request(req_type='GET',url=vmdetailsURL, headers=headers)
2314
2315 if resp.status_code != requests.codes.ok:
2316 self.logger.debug ("REST API call {} failed. Return status code {}".format(vmdetailsURL, resp.status_code))
2317 return False
2318
2319 #Parse VM details
2320 resp_xml = XmlElementTree.fromstring(resp.content)
2321 if resp_xml.tag.split('}')[1] == "Vm":
2322 vm_id = resp_xml.attrib.get("id")
2323 vm_name = resp_xml.attrib.get("name")
2324 vm_href = resp_xml.attrib.get("href")
2325 #print vm_id, vm_name, vm_href
2326 #Add VM into VMgroup
2327 payload = """<?xml version="1.0" encoding="UTF-8"?>\
2328 <ns2:Vms xmlns:ns2="http://www.vmware.com/vcloud/v1.5" \
2329 xmlns="http://www.vmware.com/vcloud/versions" \
2330 xmlns:ns3="http://schemas.dmtf.org/ovf/envelope/1" \
2331 xmlns:ns4="http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/CIM_VirtualSystemSettingData" \
2332 xmlns:ns5="http://schemas.dmtf.org/wbem/wscim/1/common" \
2333 xmlns:ns6="http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/CIM_ResourceAllocationSettingData" \
2334 xmlns:ns7="http://www.vmware.com/schema/ovf" \
2335 xmlns:ns8="http://schemas.dmtf.org/ovf/environment/1" \
2336 xmlns:ns9="http://www.vmware.com/vcloud/extension/v1.5">\
2337 <ns2:VmReference href="{}" id="{}" name="{}" \
2338 type="application/vnd.vmware.vcloud.vm+xml" />\
2339 </ns2:Vms>""".format(vm_href, vm_id, vm_name)
2340
2341 addvmtogrp_resp = self.perform_request(req_type='POST',url=addvmtogrpURL, headers=headers, data=payload)
2342
2343 if addvmtogrp_resp.status_code != requests.codes.accepted:
2344 self.logger.debug ("REST API call {} failed. Return status code {}".format(addvmtogrpURL, addvmtogrp_resp.status_code))
2345 return False
2346 else:
2347 self.logger.debug ("Done adding VM {} to VMgroup {}".format(vm_name, vmGroup_name))
2348 return True
2349
2350
2351 def create_vmgroup(self, vmgroup_name, vmgroup_href, headers):
2352 """Method to create a VM group in vCD
2353
2354 Args:
2355 vmgroup_name : Name of VM group to be created
2356 vmgroup_href : href for vmgroup
2357 headers- Headers for REST request
2358 """
2359 #POST to add URL with required data
2360 vmgroup_status = False
2361 payload = """<VMWVmGroup xmlns="http://www.vmware.com/vcloud/extension/v1.5" \
2362 xmlns:vcloud_v1.5="http://www.vmware.com/vcloud/v1.5" name="{}">\
2363 <vmCount>1</vmCount>\
2364 </VMWVmGroup>""".format(vmgroup_name)
2365 resp = self.perform_request(req_type='POST',url=vmgroup_href, headers=headers, data=payload)
2366
2367 if resp.status_code != requests.codes.accepted:
2368 self.logger.debug ("REST API call {} failed. Return status code {}".format(vmgroup_href, resp.status_code))
2369 return vmgroup_status
2370 else:
2371 vmgroup_task = self.get_task_from_response(resp.content)
2372 if vmgroup_task is None or vmgroup_task is False:
2373 raise vimconn.vimconnUnexpectedResponse(
2374 "create_vmgroup(): failed to create VM group {}".format(vmgroup_name))
2375
2376 # wait for task to complete
2377 result = self.client.get_task_monitor().wait_for_success(task=vmgroup_task)
2378
2379 if result.get('status') == 'success':
2380 self.logger.debug("create_vmgroup(): Successfully created VM group {}".format(vmgroup_name))
2381 #time.sleep(10)
2382 vmgroup_status = True
2383 return vmgroup_status
2384 else:
2385 raise vimconn.vimconnUnexpectedResponse(\
2386 "create_vmgroup(): failed to create VM group {}".format(vmgroup_name))
2387
2388
2389 def find_vmgroup_reference(self, url, headers):
2390 """ Method to create a new VMGroup which is required to add created VM
2391 Args:
2392 url- resource pool href
2393 headers- header information
2394
2395 Returns:
2396 returns href to VM group to create VM group
2397 """
2398 #Perform GET on resource pool to find 'add' link to create VMGroup
2399 #https://vcd-ip/api/admin/extension/providervdc/<providervdc id>/resourcePools
2400 vmgrp_href = None
2401 resp = self.perform_request(req_type='GET',url=url, headers=headers)
2402
2403 if resp.status_code != requests.codes.ok:
2404 self.logger.debug ("REST API call {} failed. Return status code {}".format(url, resp.status_code))
2405 else:
2406 #Get the href to add vmGroup to vCD
2407 resp_xml = XmlElementTree.fromstring(resp.content)
2408 for child in resp_xml:
2409 if 'VMWProviderVdcResourcePool' in child.tag:
2410 for schild in child:
2411 if 'Link' in schild.tag:
2412 #Find href with type VMGroup and rel with add
2413 if schild.attrib.get('type') == "application/vnd.vmware.admin.vmwVmGroupType+xml"\
2414 and schild.attrib.get('rel') == "add":
2415 vmgrp_href = schild.attrib.get('href')
2416 return vmgrp_href
2417
2418
2419 def check_availibility_zone(self, az, respool_href, headers):
2420 """ Method to verify requested av zone is present or not in provided
2421 resource pool
2422
2423 Args:
2424 az - name of hostgroup (availibility_zone)
2425 respool_href - Resource Pool href
2426 headers - Headers to make REST call
2427 Returns:
2428 az_found - True if availibility_zone is found else False
2429 """
2430 az_found = False
2431 headers['Accept']='application/*+xml;version=27.0'
2432 resp = self.perform_request(req_type='GET',url=respool_href, headers=headers)
2433
2434 if resp.status_code != requests.codes.ok:
2435 self.logger.debug ("REST API call {} failed. Return status code {}".format(respool_href, resp.status_code))
2436 else:
2437 #Get the href to hostGroups and find provided hostGroup is present in it
2438 resp_xml = XmlElementTree.fromstring(resp.content)
2439
2440 for child in resp_xml:
2441 if 'VMWProviderVdcResourcePool' in child.tag:
2442 for schild in child:
2443 if 'Link' in schild.tag:
2444 if schild.attrib.get('type') == "application/vnd.vmware.admin.vmwHostGroupsType+xml":
2445 hostGroup_href = schild.attrib.get('href')
2446 hg_resp = self.perform_request(req_type='GET',url=hostGroup_href, headers=headers)
2447 if hg_resp.status_code != requests.codes.ok:
2448 self.logger.debug ("REST API call {} failed. Return status code {}".format(hostGroup_href, hg_resp.status_code))
2449 else:
2450 hg_resp_xml = XmlElementTree.fromstring(hg_resp.content)
2451 for hostGroup in hg_resp_xml:
2452 if 'HostGroup' in hostGroup.tag:
2453 if hostGroup.attrib.get("name") == az:
2454 az_found = True
2455 break
2456 return az_found
2457
2458
2459 def get_pvdc_for_org(self, org_vdc, headers):
2460 """ This method gets provider vdc references from organisation
2461
2462 Args:
2463 org_vdc - name of the organisation VDC to find pvdc
2464 headers - headers to make REST call
2465
2466 Returns:
2467 None - if no pvdc href found else
2468 pvdc_href - href to pvdc
2469 """
2470
2471 #Get provider VDC references from vCD
2472 pvdc_href = None
2473 #url = '<vcd url>/api/admin/extension/providerVdcReferences'
2474 url_list = [self.url, '/api/admin/extension/providerVdcReferences']
2475 url = ''.join(url_list)
2476
2477 response = self.perform_request(req_type='GET',url=url, headers=headers)
2478 if response.status_code != requests.codes.ok:
2479 self.logger.debug ("REST API call {} failed. Return status code {}"\
2480 .format(url, response.status_code))
2481 else:
2482 xmlroot_response = XmlElementTree.fromstring(response.content)
2483 for child in xmlroot_response:
2484 if 'ProviderVdcReference' in child.tag:
2485 pvdc_href = child.attrib.get('href')
2486 #Get vdcReferences to find org
2487 pvdc_resp = self.perform_request(req_type='GET',url=pvdc_href, headers=headers)
2488 if pvdc_resp.status_code != requests.codes.ok:
2489 raise vimconn.vimconnException("REST API call {} failed. "\
2490 "Return status code {}"\
2491 .format(url, pvdc_resp.status_code))
2492
2493 pvdc_resp_xml = XmlElementTree.fromstring(pvdc_resp.content)
2494 for child in pvdc_resp_xml:
2495 if 'Link' in child.tag:
2496 if child.attrib.get('type') == "application/vnd.vmware.admin.vdcReferences+xml":
2497 vdc_href = child.attrib.get('href')
2498
2499 #Check if provided org is present in vdc
2500 vdc_resp = self.perform_request(req_type='GET',
2501 url=vdc_href,
2502 headers=headers)
2503 if vdc_resp.status_code != requests.codes.ok:
2504 raise vimconn.vimconnException("REST API call {} failed. "\
2505 "Return status code {}"\
2506 .format(url, vdc_resp.status_code))
2507 vdc_resp_xml = XmlElementTree.fromstring(vdc_resp.content)
2508 for child in vdc_resp_xml:
2509 if 'VdcReference' in child.tag:
2510 if child.attrib.get('name') == org_vdc:
2511 return pvdc_href
2512
2513
2514 def get_resource_pool_details(self, pvdc_href, headers):
2515 """ Method to get resource pool information.
2516 Host groups are property of resource group.
2517 To get host groups, we need to GET details of resource pool.
2518
2519 Args:
2520 pvdc_href: href to pvdc details
2521 headers: headers
2522
2523 Returns:
2524 respool_href - Returns href link reference to resource pool
2525 """
2526 respool_href = None
2527 resp = self.perform_request(req_type='GET',url=pvdc_href, headers=headers)
2528
2529 if resp.status_code != requests.codes.ok:
2530 self.logger.debug ("REST API call {} failed. Return status code {}"\
2531 .format(pvdc_href, resp.status_code))
2532 else:
2533 respool_resp_xml = XmlElementTree.fromstring(resp.content)
2534 for child in respool_resp_xml:
2535 if 'Link' in child.tag:
2536 if child.attrib.get('type') == "application/vnd.vmware.admin.vmwProviderVdcResourcePoolSet+xml":
2537 respool_href = child.attrib.get("href")
2538 break
2539 return respool_href
2540
2541
2542 def log_message(self, msg):
2543 """
2544 Method to log error messages related to Affinity rule creation
2545 in new_vminstance & raise Exception
2546 Args :
2547 msg - Error message to be logged
2548
2549 """
2550 #get token to connect vCD as a normal user
2551 self.get_token()
2552 self.logger.debug(msg)
2553 raise vimconn.vimconnException(msg)
2554
2555
2556 ##
2557 ##
2558 ## based on current discussion
2559 ##
2560 ##
2561 ## server:
2562 # created: '2016-09-08T11:51:58'
2563 # description: simple-instance.linux1.1
2564 # flavor: ddc6776e-75a9-11e6-ad5f-0800273e724c
2565 # hostId: e836c036-74e7-11e6-b249-0800273e724c
2566 # image: dde30fe6-75a9-11e6-ad5f-0800273e724c
2567 # status: ACTIVE
2568 # error_msg:
2569 # interfaces: …
2570 #
2571 def get_vminstance(self, vim_vm_uuid=None):
2572 """Returns the VM instance information from VIM"""
2573
2574 self.logger.debug("Client requesting vm instance {} ".format(vim_vm_uuid))
2575
2576 org, vdc = self.get_vdc_details()
2577 if vdc is None:
2578 raise vimconn.vimconnConnectionException(
2579 "Failed to get a reference of VDC for a tenant {}".format(self.tenant_name))
2580
2581 vm_info_dict = self.get_vapp_details_rest(vapp_uuid=vim_vm_uuid)
2582 if not vm_info_dict:
2583 self.logger.debug("get_vminstance(): Failed to get vApp name by UUID {}".format(vim_vm_uuid))
2584 raise vimconn.vimconnNotFoundException("Failed to get vApp name by UUID {}".format(vim_vm_uuid))
2585
2586 status_key = vm_info_dict['status']
2587 error = ''
2588 try:
2589 vm_dict = {'created': vm_info_dict['created'],
2590 'description': vm_info_dict['name'],
2591 'status': vcdStatusCode2manoFormat[int(status_key)],
2592 'hostId': vm_info_dict['vmuuid'],
2593 'error_msg': error,
2594 'vim_info': yaml.safe_dump(vm_info_dict), 'interfaces': []}
2595
2596 if 'interfaces' in vm_info_dict:
2597 vm_dict['interfaces'] = vm_info_dict['interfaces']
2598 else:
2599 vm_dict['interfaces'] = []
2600 except KeyError:
2601 vm_dict = {'created': '',
2602 'description': '',
2603 'status': vcdStatusCode2manoFormat[int(-1)],
2604 'hostId': vm_info_dict['vmuuid'],
2605 'error_msg': "Inconsistency state",
2606 'vim_info': yaml.safe_dump(vm_info_dict), 'interfaces': []}
2607
2608 return vm_dict
2609
2610 def delete_vminstance(self, vm__vim_uuid, created_items=None):
2611 """Method poweroff and remove VM instance from vcloud director network.
2612
2613 Args:
2614 vm__vim_uuid: VM UUID
2615
2616 Returns:
2617 Returns the instance identifier
2618 """
2619
2620 self.logger.debug("Client requesting delete vm instance {} ".format(vm__vim_uuid))
2621
2622 org, vdc = self.get_vdc_details()
2623 vdc_obj = VDC(self.client, href=vdc.get('href'))
2624 if vdc_obj is None:
2625 self.logger.debug("delete_vminstance(): Failed to get a reference of VDC for a tenant {}".format(
2626 self.tenant_name))
2627 raise vimconn.vimconnException(
2628 "delete_vminstance(): Failed to get a reference of VDC for a tenant {}".format(self.tenant_name))
2629
2630 try:
2631 vapp_name = self.get_namebyvappid(vm__vim_uuid)
2632 if vapp_name is None:
2633 self.logger.debug("delete_vminstance(): Failed to get vm by given {} vm uuid".format(vm__vim_uuid))
2634 return -1, "delete_vminstance(): Failed to get vm by given {} vm uuid".format(vm__vim_uuid)
2635 self.logger.info("Deleting vApp {} and UUID {}".format(vapp_name, vm__vim_uuid))
2636 vapp_resource = vdc_obj.get_vapp(vapp_name)
2637 vapp = VApp(self.client, resource=vapp_resource)
2638
2639 # Delete vApp and wait for status change if task executed and vApp is None.
2640
2641 if vapp:
2642 if vapp_resource.get('deployed') == 'true':
2643 self.logger.info("Powering off vApp {}".format(vapp_name))
2644 #Power off vApp
2645 powered_off = False
2646 wait_time = 0
2647 while wait_time <= MAX_WAIT_TIME:
2648 power_off_task = vapp.power_off()
2649 result = self.client.get_task_monitor().wait_for_success(task=power_off_task)
2650
2651 if result.get('status') == 'success':
2652 powered_off = True
2653 break
2654 else:
2655 self.logger.info("Wait for vApp {} to power off".format(vapp_name))
2656 time.sleep(INTERVAL_TIME)
2657
2658 wait_time +=INTERVAL_TIME
2659 if not powered_off:
2660 self.logger.debug("delete_vminstance(): Failed to power off VM instance {} ".format(vm__vim_uuid))
2661 else:
2662 self.logger.info("delete_vminstance(): Powered off VM instance {} ".format(vm__vim_uuid))
2663
2664 #Undeploy vApp
2665 self.logger.info("Undeploy vApp {}".format(vapp_name))
2666 wait_time = 0
2667 undeployed = False
2668 while wait_time <= MAX_WAIT_TIME:
2669 vapp = VApp(self.client, resource=vapp_resource)
2670 if not vapp:
2671 self.logger.debug("delete_vminstance(): Failed to get vm by given {} vm uuid".format(vm__vim_uuid))
2672 return -1, "delete_vminstance(): Failed to get vm by given {} vm uuid".format(vm__vim_uuid)
2673 undeploy_task = vapp.undeploy()
2674
2675 result = self.client.get_task_monitor().wait_for_success(task=undeploy_task)
2676 if result.get('status') == 'success':
2677 undeployed = True
2678 break
2679 else:
2680 self.logger.debug("Wait for vApp {} to undeploy".format(vapp_name))
2681 time.sleep(INTERVAL_TIME)
2682
2683 wait_time +=INTERVAL_TIME
2684
2685 if not undeployed:
2686 self.logger.debug("delete_vminstance(): Failed to undeploy vApp {} ".format(vm__vim_uuid))
2687
2688 # delete vapp
2689 self.logger.info("Start deletion of vApp {} ".format(vapp_name))
2690
2691 if vapp is not None:
2692 wait_time = 0
2693 result = False
2694
2695 while wait_time <= MAX_WAIT_TIME:
2696 vapp = VApp(self.client, resource=vapp_resource)
2697 if not vapp:
2698 self.logger.debug("delete_vminstance(): Failed to get vm by given {} vm uuid".format(vm__vim_uuid))
2699 return -1, "delete_vminstance(): Failed to get vm by given {} vm uuid".format(vm__vim_uuid)
2700
2701 delete_task = vdc_obj.delete_vapp(vapp.name, force=True)
2702
2703 result = self.client.get_task_monitor().wait_for_success(task=delete_task)
2704 if result.get('status') == 'success':
2705 break
2706 else:
2707 self.logger.debug("Wait for vApp {} to delete".format(vapp_name))
2708 time.sleep(INTERVAL_TIME)
2709
2710 wait_time +=INTERVAL_TIME
2711
2712 if result is None:
2713 self.logger.debug("delete_vminstance(): Failed delete uuid {} ".format(vm__vim_uuid))
2714 else:
2715 self.logger.info("Deleted vm instance {} sccessfully".format(vm__vim_uuid))
2716 return vm__vim_uuid
2717 except:
2718 self.logger.debug(traceback.format_exc())
2719 raise vimconn.vimconnException("delete_vminstance(): Failed delete vm instance {}".format(vm__vim_uuid))
2720
2721
2722 def refresh_vms_status(self, vm_list):
2723 """Get the status of the virtual machines and their interfaces/ports
2724 Params: the list of VM identifiers
2725 Returns a dictionary with:
2726 vm_id: #VIM id of this Virtual Machine
2727 status: #Mandatory. Text with one of:
2728 # DELETED (not found at vim)
2729 # VIM_ERROR (Cannot connect to VIM, VIM response error, ...)
2730 # OTHER (Vim reported other status not understood)
2731 # ERROR (VIM indicates an ERROR status)
2732 # ACTIVE, PAUSED, SUSPENDED, INACTIVE (not running),
2733 # CREATING (on building process), ERROR
2734 # ACTIVE:NoMgmtIP (Active but any of its interface has an IP address
2735 #
2736 error_msg: #Text with VIM error message, if any. Or the VIM connection ERROR
2737 vim_info: #Text with plain information obtained from vim (yaml.safe_dump)
2738 interfaces:
2739 - vim_info: #Text with plain information obtained from vim (yaml.safe_dump)
2740 mac_address: #Text format XX:XX:XX:XX:XX:XX
2741 vim_net_id: #network id where this interface is connected
2742 vim_interface_id: #interface/port VIM id
2743 ip_address: #null, or text with IPv4, IPv6 address
2744 """
2745
2746 self.logger.debug("Client requesting refresh vm status for {} ".format(vm_list))
2747
2748 org,vdc = self.get_vdc_details()
2749 if vdc is None:
2750 raise vimconn.vimconnException("Failed to get a reference of VDC for a tenant {}".format(self.tenant_name))
2751
2752 vms_dict = {}
2753 nsx_edge_list = []
2754 for vmuuid in vm_list:
2755 vapp_name = self.get_namebyvappid(vmuuid)
2756 if vapp_name is not None:
2757
2758 try:
2759 vm_pci_details = self.get_vm_pci_details(vmuuid)
2760 vdc_obj = VDC(self.client, href=vdc.get('href'))
2761 vapp_resource = vdc_obj.get_vapp(vapp_name)
2762 the_vapp = VApp(self.client, resource=vapp_resource)
2763
2764 vm_details = {}
2765 for vm in the_vapp.get_all_vms():
2766 headers = {'Accept':'application/*+xml;version=' + API_VERSION,
2767 'x-vcloud-authorization': self.client._session.headers['x-vcloud-authorization']}
2768 response = self.perform_request(req_type='GET',
2769 url=vm.get('href'),
2770 headers=headers)
2771
2772 if response.status_code != 200:
2773 self.logger.error("refresh_vms_status : REST call {} failed reason : {}"\
2774 "status code : {}".format(vm.get('href'),
2775 response.content,
2776 response.status_code))
2777 raise vimconn.vimconnException("refresh_vms_status : Failed to get "\
2778 "VM details")
2779 xmlroot = XmlElementTree.fromstring(response.content)
2780
2781
2782 result = response.content.replace("\n"," ")
2783 hdd_match = re.search('vcloud:capacity="(\d+)"\svcloud:storageProfileOverrideVmDefault=',result)
2784 if hdd_match:
2785 hdd_mb = hdd_match.group(1)
2786 vm_details['hdd_mb'] = int(hdd_mb) if hdd_mb else None
2787 cpus_match = re.search('<rasd:Description>Number of Virtual CPUs</.*?>(\d+)</rasd:VirtualQuantity>',result)
2788 if cpus_match:
2789 cpus = cpus_match.group(1)
2790 vm_details['cpus'] = int(cpus) if cpus else None
2791 memory_mb = re.search('<rasd:Description>Memory Size</.*?>(\d+)</rasd:VirtualQuantity>',result).group(1)
2792 vm_details['memory_mb'] = int(memory_mb) if memory_mb else None
2793 vm_details['status'] = vcdStatusCode2manoFormat[int(xmlroot.get('status'))]
2794 vm_details['id'] = xmlroot.get('id')
2795 vm_details['name'] = xmlroot.get('name')
2796 vm_info = [vm_details]
2797 if vm_pci_details:
2798 vm_info[0].update(vm_pci_details)
2799
2800 vm_dict = {'status': vcdStatusCode2manoFormat[int(vapp_resource.get('status'))],
2801 'error_msg': vcdStatusCode2manoFormat[int(vapp_resource.get('status'))],
2802 'vim_info': yaml.safe_dump(vm_info), 'interfaces': []}
2803
2804 # get networks
2805 vm_ip = None
2806 vm_mac = None
2807 networks = re.findall('<NetworkConnection needsCustomization=.*?</NetworkConnection>',result)
2808 for network in networks:
2809 mac_s = re.search('<MACAddress>(.*?)</MACAddress>',network)
2810 vm_mac = mac_s.group(1) if mac_s else None
2811 ip_s = re.search('<IpAddress>(.*?)</IpAddress>',network)
2812 vm_ip = ip_s.group(1) if ip_s else None
2813
2814 if vm_ip is None:
2815 if not nsx_edge_list:
2816 nsx_edge_list = self.get_edge_details()
2817 if nsx_edge_list is None:
2818 raise vimconn.vimconnException("refresh_vms_status:"\
2819 "Failed to get edge details from NSX Manager")
2820 if vm_mac is not None:
2821 vm_ip = self.get_ipaddr_from_NSXedge(nsx_edge_list, vm_mac)
2822
2823 net_s = re.search('network="(.*?)"',network)
2824 network_name = net_s.group(1) if net_s else None
2825
2826 vm_net_id = self.get_network_id_by_name(network_name)
2827 interface = {"mac_address": vm_mac,
2828 "vim_net_id": vm_net_id,
2829 "vim_interface_id": vm_net_id,
2830 "ip_address": vm_ip}
2831
2832 vm_dict["interfaces"].append(interface)
2833
2834 # add a vm to vm dict
2835 vms_dict.setdefault(vmuuid, vm_dict)
2836 self.logger.debug("refresh_vms_status : vm info {}".format(vm_dict))
2837 except Exception as exp:
2838 self.logger.debug("Error in response {}".format(exp))
2839 self.logger.debug(traceback.format_exc())
2840
2841 return vms_dict
2842
2843
2844 def get_edge_details(self):
2845 """Get the NSX edge list from NSX Manager
2846 Returns list of NSX edges
2847 """
2848 edge_list = []
2849 rheaders = {'Content-Type': 'application/xml'}
2850 nsx_api_url = '/api/4.0/edges'
2851
2852 self.logger.debug("Get edge details from NSX Manager {} {}".format(self.nsx_manager, nsx_api_url))
2853
2854 try:
2855 resp = requests.get(self.nsx_manager + nsx_api_url,
2856 auth = (self.nsx_user, self.nsx_password),
2857 verify = False, headers = rheaders)
2858 if resp.status_code == requests.codes.ok:
2859 paged_Edge_List = XmlElementTree.fromstring(resp.text)
2860 for edge_pages in paged_Edge_List:
2861 if edge_pages.tag == 'edgePage':
2862 for edge_summary in edge_pages:
2863 if edge_summary.tag == 'pagingInfo':
2864 for element in edge_summary:
2865 if element.tag == 'totalCount' and element.text == '0':
2866 raise vimconn.vimconnException("get_edge_details: No NSX edges details found: {}"
2867 .format(self.nsx_manager))
2868
2869 if edge_summary.tag == 'edgeSummary':
2870 for element in edge_summary:
2871 if element.tag == 'id':
2872 edge_list.append(element.text)
2873 else:
2874 raise vimconn.vimconnException("get_edge_details: No NSX edge details found: {}"
2875 .format(self.nsx_manager))
2876
2877 if not edge_list:
2878 raise vimconn.vimconnException("get_edge_details: "\
2879 "No NSX edge details found: {}"
2880 .format(self.nsx_manager))
2881 else:
2882 self.logger.debug("get_edge_details: Found NSX edges {}".format(edge_list))
2883 return edge_list
2884 else:
2885 self.logger.debug("get_edge_details: "
2886 "Failed to get NSX edge details from NSX Manager: {}"
2887 .format(resp.content))
2888 return None
2889
2890 except Exception as exp:
2891 self.logger.debug("get_edge_details: "\
2892 "Failed to get NSX edge details from NSX Manager: {}"
2893 .format(exp))
2894 raise vimconn.vimconnException("get_edge_details: "\
2895 "Failed to get NSX edge details from NSX Manager: {}"
2896 .format(exp))
2897
2898
2899 def get_ipaddr_from_NSXedge(self, nsx_edges, mac_address):
2900 """Get IP address details from NSX edges, using the MAC address
2901 PARAMS: nsx_edges : List of NSX edges
2902 mac_address : Find IP address corresponding to this MAC address
2903 Returns: IP address corrresponding to the provided MAC address
2904 """
2905
2906 ip_addr = None
2907 rheaders = {'Content-Type': 'application/xml'}
2908
2909 self.logger.debug("get_ipaddr_from_NSXedge: Finding IP addr from NSX edge")
2910
2911 try:
2912 for edge in nsx_edges:
2913 nsx_api_url = '/api/4.0/edges/'+ edge +'/dhcp/leaseInfo'
2914
2915 resp = requests.get(self.nsx_manager + nsx_api_url,
2916 auth = (self.nsx_user, self.nsx_password),
2917 verify = False, headers = rheaders)
2918
2919 if resp.status_code == requests.codes.ok:
2920 dhcp_leases = XmlElementTree.fromstring(resp.text)
2921 for child in dhcp_leases:
2922 if child.tag == 'dhcpLeaseInfo':
2923 dhcpLeaseInfo = child
2924 for leaseInfo in dhcpLeaseInfo:
2925 for elem in leaseInfo:
2926 if (elem.tag)=='macAddress':
2927 edge_mac_addr = elem.text
2928 if (elem.tag)=='ipAddress':
2929 ip_addr = elem.text
2930 if edge_mac_addr is not None:
2931 if edge_mac_addr == mac_address:
2932 self.logger.debug("Found ip addr {} for mac {} at NSX edge {}"
2933 .format(ip_addr, mac_address,edge))
2934 return ip_addr
2935 else:
2936 self.logger.debug("get_ipaddr_from_NSXedge: "\
2937 "Error occurred while getting DHCP lease info from NSX Manager: {}"
2938 .format(resp.content))
2939
2940 self.logger.debug("get_ipaddr_from_NSXedge: No IP addr found in any NSX edge")
2941 return None
2942
2943 except XmlElementTree.ParseError as Err:
2944 self.logger.debug("ParseError in response from NSX Manager {}".format(Err.message), exc_info=True)
2945
2946
2947 def action_vminstance(self, vm__vim_uuid=None, action_dict=None, created_items={}):
2948 """Send and action over a VM instance from VIM
2949 Returns the vm_id if the action was successfully sent to the VIM"""
2950
2951 self.logger.debug("Received action for vm {} and action dict {}".format(vm__vim_uuid, action_dict))
2952 if vm__vim_uuid is None or action_dict is None:
2953 raise vimconn.vimconnException("Invalid request. VM id or action is None.")
2954
2955 org, vdc = self.get_vdc_details()
2956 if vdc is None:
2957 raise vimconn.vimconnException("Failed to get a reference of VDC for a tenant {}".format(self.tenant_name))
2958
2959 vapp_name = self.get_namebyvappid(vm__vim_uuid)
2960 if vapp_name is None:
2961 self.logger.debug("action_vminstance(): Failed to get vm by given {} vm uuid".format(vm__vim_uuid))
2962 raise vimconn.vimconnException("Failed to get vm by given {} vm uuid".format(vm__vim_uuid))
2963 else:
2964 self.logger.info("Action_vminstance vApp {} and UUID {}".format(vapp_name, vm__vim_uuid))
2965
2966 try:
2967 vdc_obj = VDC(self.client, href=vdc.get('href'))
2968 vapp_resource = vdc_obj.get_vapp(vapp_name)
2969 vapp = VApp(self.client, resource=vapp_resource)
2970 if "start" in action_dict:
2971 self.logger.info("action_vminstance: Power on vApp: {}".format(vapp_name))
2972 poweron_task = self.power_on_vapp(vm__vim_uuid, vapp_name)
2973 result = self.client.get_task_monitor().wait_for_success(task=poweron_task)
2974 self.instance_actions_result("start", result, vapp_name)
2975 elif "rebuild" in action_dict:
2976 self.logger.info("action_vminstance: Rebuild vApp: {}".format(vapp_name))
2977 rebuild_task = vapp.deploy(power_on=True)
2978 result = self.client.get_task_monitor().wait_for_success(task=rebuild_task)
2979 self.instance_actions_result("rebuild", result, vapp_name)
2980 elif "pause" in action_dict:
2981 self.logger.info("action_vminstance: pause vApp: {}".format(vapp_name))
2982 pause_task = vapp.undeploy(action='suspend')
2983 result = self.client.get_task_monitor().wait_for_success(task=pause_task)
2984 self.instance_actions_result("pause", result, vapp_name)
2985 elif "resume" in action_dict:
2986 self.logger.info("action_vminstance: resume vApp: {}".format(vapp_name))
2987 poweron_task = self.power_on_vapp(vm__vim_uuid, vapp_name)
2988 result = self.client.get_task_monitor().wait_for_success(task=poweron_task)
2989 self.instance_actions_result("resume", result, vapp_name)
2990 elif "shutoff" in action_dict or "shutdown" in action_dict:
2991 action_name , value = action_dict.items()[0]
2992 #For python3
2993 #action_name , value = list(action_dict.items())[0]
2994 self.logger.info("action_vminstance: {} vApp: {}".format(action_name, vapp_name))
2995 shutdown_task = vapp.shutdown()
2996 result = self.client.get_task_monitor().wait_for_success(task=shutdown_task)
2997 if action_name == "shutdown":
2998 self.instance_actions_result("shutdown", result, vapp_name)
2999 else:
3000 self.instance_actions_result("shutoff", result, vapp_name)
3001 elif "forceOff" in action_dict:
3002 result = vapp.undeploy(action='powerOff')
3003 self.instance_actions_result("forceOff", result, vapp_name)
3004 elif "reboot" in action_dict:
3005 self.logger.info("action_vminstance: reboot vApp: {}".format(vapp_name))
3006 reboot_task = vapp.reboot()
3007 self.client.get_task_monitor().wait_for_success(task=reboot_task)
3008 else:
3009 raise vimconn.vimconnException("action_vminstance: Invalid action {} or action is None.".format(action_dict))
3010 return vm__vim_uuid
3011 except Exception as exp :
3012 self.logger.debug("action_vminstance: Failed with Exception {}".format(exp))
3013 raise vimconn.vimconnException("action_vminstance: Failed with Exception {}".format(exp))
3014
3015 def instance_actions_result(self, action, result, vapp_name):
3016 if result.get('status') == 'success':
3017 self.logger.info("action_vminstance: Sucessfully {} the vApp: {}".format(action, vapp_name))
3018 else:
3019 self.logger.error("action_vminstance: Failed to {} vApp: {}".format(action, vapp_name))
3020
3021 def get_vminstance_console(self, vm_id, console_type="vnc"):
3022 """
3023 Get a console for the virtual machine
3024 Params:
3025 vm_id: uuid of the VM
3026 console_type, can be:
3027 "novnc" (by default), "xvpvnc" for VNC types,
3028 "rdp-html5" for RDP types, "spice-html5" for SPICE types
3029 Returns dict with the console parameters:
3030 protocol: ssh, ftp, http, https, ...
3031 server: usually ip address
3032 port: the http, ssh, ... port
3033 suffix: extra text, e.g. the http path and query string
3034 """
3035 raise vimconn.vimconnNotImplemented("Should have implemented this")
3036
3037 # NOT USED METHODS in current version
3038
3039 def host_vim2gui(self, host, server_dict):
3040 """Transform host dictionary from VIM format to GUI format,
3041 and append to the server_dict
3042 """
3043 raise vimconn.vimconnNotImplemented("Should have implemented this")
3044
3045 def get_hosts_info(self):
3046 """Get the information of deployed hosts
3047 Returns the hosts content"""
3048 raise vimconn.vimconnNotImplemented("Should have implemented this")
3049
3050 def get_hosts(self, vim_tenant):
3051 """Get the hosts and deployed instances
3052 Returns the hosts content"""
3053 raise vimconn.vimconnNotImplemented("Should have implemented this")
3054
3055 def get_processor_rankings(self):
3056 """Get the processor rankings in the VIM database"""
3057 raise vimconn.vimconnNotImplemented("Should have implemented this")
3058
3059 def new_host(self, host_data):
3060 """Adds a new host to VIM"""
3061 '''Returns status code of the VIM response'''
3062 raise vimconn.vimconnNotImplemented("Should have implemented this")
3063
3064 def new_external_port(self, port_data):
3065 """Adds a external port to VIM"""
3066 '''Returns the port identifier'''
3067 raise vimconn.vimconnNotImplemented("Should have implemented this")
3068
3069 def new_external_network(self, net_name, net_type):
3070 """Adds a external network to VIM (shared)"""
3071 '''Returns the network identifier'''
3072 raise vimconn.vimconnNotImplemented("Should have implemented this")
3073
3074 def connect_port_network(self, port_id, network_id, admin=False):
3075 """Connects a external port to a network"""
3076 '''Returns status code of the VIM response'''
3077 raise vimconn.vimconnNotImplemented("Should have implemented this")
3078
3079 def new_vminstancefromJSON(self, vm_data):
3080 """Adds a VM instance to VIM"""
3081 '''Returns the instance identifier'''
3082 raise vimconn.vimconnNotImplemented("Should have implemented this")
3083
3084 def get_network_name_by_id(self, network_uuid=None):
3085 """Method gets vcloud director network named based on supplied uuid.
3086
3087 Args:
3088 network_uuid: network_id
3089
3090 Returns:
3091 The return network name.
3092 """
3093
3094 if not network_uuid:
3095 return None
3096
3097 try:
3098 org_dict = self.get_org(self.org_uuid)
3099 if 'networks' in org_dict:
3100 org_network_dict = org_dict['networks']
3101 for net_uuid in org_network_dict:
3102 if net_uuid == network_uuid:
3103 return org_network_dict[net_uuid]
3104 except:
3105 self.logger.debug("Exception in get_network_name_by_id")
3106 self.logger.debug(traceback.format_exc())
3107
3108 return None
3109
3110 def get_network_id_by_name(self, network_name=None):
3111 """Method gets vcloud director network uuid based on supplied name.
3112
3113 Args:
3114 network_name: network_name
3115 Returns:
3116 The return network uuid.
3117 network_uuid: network_id
3118 """
3119
3120 if not network_name:
3121 self.logger.debug("get_network_id_by_name() : Network name is empty")
3122 return None
3123
3124 try:
3125 org_dict = self.get_org(self.org_uuid)
3126 if org_dict and 'networks' in org_dict:
3127 org_network_dict = org_dict['networks']
3128 for net_uuid,net_name in org_network_dict.iteritems():
3129 #For python3
3130 #for net_uuid,net_name in org_network_dict.items():
3131 if net_name == network_name:
3132 return net_uuid
3133
3134 except KeyError as exp:
3135 self.logger.debug("get_network_id_by_name() : KeyError- {} ".format(exp))
3136
3137 return None
3138
3139 def list_org_action(self):
3140 """
3141 Method leverages vCloud director and query for available organization for particular user
3142
3143 Args:
3144 vca - is active VCA connection.
3145 vdc_name - is a vdc name that will be used to query vms action
3146
3147 Returns:
3148 The return XML respond
3149 """
3150 url_list = [self.url, '/api/org']
3151 vm_list_rest_call = ''.join(url_list)
3152
3153 if self.client._session:
3154 headers = {'Accept':'application/*+xml;version=' + API_VERSION,
3155 'x-vcloud-authorization': self.client._session.headers['x-vcloud-authorization']}
3156
3157 response = self.perform_request(req_type='GET',
3158 url=vm_list_rest_call,
3159 headers=headers)
3160
3161 if response.status_code == 403:
3162 response = self.retry_rest('GET', vm_list_rest_call)
3163
3164 if response.status_code == requests.codes.ok:
3165 return response.content
3166
3167 return None
3168
3169 def get_org_action(self, org_uuid=None):
3170 """
3171 Method leverages vCloud director and retrieve available object for organization.
3172
3173 Args:
3174 org_uuid - vCD organization uuid
3175 self.client - is active connection.
3176
3177 Returns:
3178 The return XML respond
3179 """
3180
3181 if org_uuid is None:
3182 return None
3183
3184 url_list = [self.url, '/api/org/', org_uuid]
3185 vm_list_rest_call = ''.join(url_list)
3186
3187 if self.client._session:
3188 headers = {'Accept':'application/*+xml;version=' + API_VERSION,
3189 'x-vcloud-authorization': self.client._session.headers['x-vcloud-authorization']}
3190
3191 #response = requests.get(vm_list_rest_call, headers=headers, verify=False)
3192 response = self.perform_request(req_type='GET',
3193 url=vm_list_rest_call,
3194 headers=headers)
3195 if response.status_code == 403:
3196 response = self.retry_rest('GET', vm_list_rest_call)
3197
3198 if response.status_code == requests.codes.ok:
3199 return response.content
3200 return None
3201
3202 def get_org(self, org_uuid=None):
3203 """
3204 Method retrieves available organization in vCloud Director
3205
3206 Args:
3207 org_uuid - is a organization uuid.
3208
3209 Returns:
3210 The return dictionary with following key
3211 "network" - for network list under the org
3212 "catalogs" - for network list under the org
3213 "vdcs" - for vdc list under org
3214 """
3215
3216 org_dict = {}
3217
3218 if org_uuid is None:
3219 return org_dict
3220
3221 content = self.get_org_action(org_uuid=org_uuid)
3222 try:
3223 vdc_list = {}
3224 network_list = {}
3225 catalog_list = {}
3226 vm_list_xmlroot = XmlElementTree.fromstring(content)
3227 for child in vm_list_xmlroot:
3228 if child.attrib['type'] == 'application/vnd.vmware.vcloud.vdc+xml':
3229 vdc_list[child.attrib['href'].split("/")[-1:][0]] = child.attrib['name']
3230 org_dict['vdcs'] = vdc_list
3231 if child.attrib['type'] == 'application/vnd.vmware.vcloud.orgNetwork+xml':
3232 network_list[child.attrib['href'].split("/")[-1:][0]] = child.attrib['name']
3233 org_dict['networks'] = network_list
3234 if child.attrib['type'] == 'application/vnd.vmware.vcloud.catalog+xml':
3235 catalog_list[child.attrib['href'].split("/")[-1:][0]] = child.attrib['name']
3236 org_dict['catalogs'] = catalog_list
3237 except:
3238 pass
3239
3240 return org_dict
3241
3242 def get_org_list(self):
3243 """
3244 Method retrieves available organization in vCloud Director
3245
3246 Args:
3247 vca - is active VCA connection.
3248
3249 Returns:
3250 The return dictionary and key for each entry VDC UUID
3251 """
3252
3253 org_dict = {}
3254
3255 content = self.list_org_action()
3256 try:
3257 vm_list_xmlroot = XmlElementTree.fromstring(content)
3258 for vm_xml in vm_list_xmlroot:
3259 if vm_xml.tag.split("}")[1] == 'Org':
3260 org_uuid = vm_xml.attrib['href'].split('/')[-1:]
3261 org_dict[org_uuid[0]] = vm_xml.attrib['name']
3262 except:
3263 pass
3264
3265 return org_dict
3266
3267 def vms_view_action(self, vdc_name=None):
3268 """ Method leverages vCloud director vms query call
3269
3270 Args:
3271 vca - is active VCA connection.
3272 vdc_name - is a vdc name that will be used to query vms action
3273
3274 Returns:
3275 The return XML respond
3276 """
3277 vca = self.connect()
3278 if vdc_name is None:
3279 return None
3280
3281 url_list = [vca.host, '/api/vms/query']
3282 vm_list_rest_call = ''.join(url_list)
3283
3284 if not (not vca.vcloud_session or not vca.vcloud_session.organization):
3285 refs = filter(lambda ref: ref.name == vdc_name and ref.type_ == 'application/vnd.vmware.vcloud.vdc+xml',
3286 vca.vcloud_session.organization.Link)
3287 #For python3
3288 #refs = [ref for ref in vca.vcloud_session.organization.Link if ref.name == vdc_name and\
3289 # ref.type_ == 'application/vnd.vmware.vcloud.vdc+xml']
3290 if len(refs) == 1:
3291 response = Http.get(url=vm_list_rest_call,
3292 headers=vca.vcloud_session.get_vcloud_headers(),
3293 verify=vca.verify,
3294 logger=vca.logger)
3295 if response.status_code == requests.codes.ok:
3296 return response.content
3297
3298 return None
3299
3300 def get_vapp_list(self, vdc_name=None):
3301 """
3302 Method retrieves vApp list deployed vCloud director and returns a dictionary
3303 contains a list of all vapp deployed for queried VDC.
3304 The key for a dictionary is vApp UUID
3305
3306
3307 Args:
3308 vca - is active VCA connection.
3309 vdc_name - is a vdc name that will be used to query vms action
3310
3311 Returns:
3312 The return dictionary and key for each entry vapp UUID
3313 """
3314
3315 vapp_dict = {}
3316 if vdc_name is None:
3317 return vapp_dict
3318
3319 content = self.vms_view_action(vdc_name=vdc_name)
3320 try:
3321 vm_list_xmlroot = XmlElementTree.fromstring(content)
3322 for vm_xml in vm_list_xmlroot:
3323 if vm_xml.tag.split("}")[1] == 'VMRecord':
3324 if vm_xml.attrib['isVAppTemplate'] == 'true':
3325 rawuuid = vm_xml.attrib['container'].split('/')[-1:]
3326 if 'vappTemplate-' in rawuuid[0]:
3327 # vm in format vappTemplate-e63d40e7-4ff5-4c6d-851f-96c1e4da86a5 we remove
3328 # vm and use raw UUID as key
3329 vapp_dict[rawuuid[0][13:]] = vm_xml.attrib
3330 except:
3331 pass
3332
3333 return vapp_dict
3334
3335 def get_vm_list(self, vdc_name=None):
3336 """
3337 Method retrieves VM's list deployed vCloud director. It returns a dictionary
3338 contains a list of all VM's deployed for queried VDC.
3339 The key for a dictionary is VM UUID
3340
3341
3342 Args:
3343 vca - is active VCA connection.
3344 vdc_name - is a vdc name that will be used to query vms action
3345
3346 Returns:
3347 The return dictionary and key for each entry vapp UUID
3348 """
3349 vm_dict = {}
3350
3351 if vdc_name is None:
3352 return vm_dict
3353
3354 content = self.vms_view_action(vdc_name=vdc_name)
3355 try:
3356 vm_list_xmlroot = XmlElementTree.fromstring(content)
3357 for vm_xml in vm_list_xmlroot:
3358 if vm_xml.tag.split("}")[1] == 'VMRecord':
3359 if vm_xml.attrib['isVAppTemplate'] == 'false':
3360 rawuuid = vm_xml.attrib['href'].split('/')[-1:]
3361 if 'vm-' in rawuuid[0]:
3362 # vm in format vm-e63d40e7-4ff5-4c6d-851f-96c1e4da86a5 we remove
3363 # vm and use raw UUID as key
3364 vm_dict[rawuuid[0][3:]] = vm_xml.attrib
3365 except:
3366 pass
3367
3368 return vm_dict
3369
3370 def get_vapp(self, vdc_name=None, vapp_name=None, isuuid=False):
3371 """
3372 Method retrieves VM deployed vCloud director. It returns VM attribute as dictionary
3373 contains a list of all VM's deployed for queried VDC.
3374 The key for a dictionary is VM UUID
3375
3376
3377 Args:
3378 vca - is active VCA connection.
3379 vdc_name - is a vdc name that will be used to query vms action
3380
3381 Returns:
3382 The return dictionary and key for each entry vapp UUID
3383 """
3384 vm_dict = {}
3385 vca = self.connect()
3386 if not vca:
3387 raise vimconn.vimconnConnectionException("self.connect() is failed")
3388
3389 if vdc_name is None:
3390 return vm_dict
3391
3392 content = self.vms_view_action(vdc_name=vdc_name)
3393 try:
3394 vm_list_xmlroot = XmlElementTree.fromstring(content)
3395 for vm_xml in vm_list_xmlroot:
3396 if vm_xml.tag.split("}")[1] == 'VMRecord' and vm_xml.attrib['isVAppTemplate'] == 'false':
3397 # lookup done by UUID
3398 if isuuid:
3399 if vapp_name in vm_xml.attrib['container']:
3400 rawuuid = vm_xml.attrib['href'].split('/')[-1:]
3401 if 'vm-' in rawuuid[0]:
3402 vm_dict[rawuuid[0][3:]] = vm_xml.attrib
3403 break
3404 # lookup done by Name
3405 else:
3406 if vapp_name in vm_xml.attrib['name']:
3407 rawuuid = vm_xml.attrib['href'].split('/')[-1:]
3408 if 'vm-' in rawuuid[0]:
3409 vm_dict[rawuuid[0][3:]] = vm_xml.attrib
3410 break
3411 except:
3412 pass
3413
3414 return vm_dict
3415
3416 def get_network_action(self, network_uuid=None):
3417 """
3418 Method leverages vCloud director and query network based on network uuid
3419
3420 Args:
3421 vca - is active VCA connection.
3422 network_uuid - is a network uuid
3423
3424 Returns:
3425 The return XML respond
3426 """
3427
3428 if network_uuid is None:
3429 return None
3430
3431 url_list = [self.url, '/api/network/', network_uuid]
3432 vm_list_rest_call = ''.join(url_list)
3433
3434 if self.client._session:
3435 headers = {'Accept':'application/*+xml;version=' + API_VERSION,
3436 'x-vcloud-authorization': self.client._session.headers['x-vcloud-authorization']}
3437
3438 response = self.perform_request(req_type='GET',
3439 url=vm_list_rest_call,
3440 headers=headers)
3441 #Retry login if session expired & retry sending request
3442 if response.status_code == 403:
3443 response = self.retry_rest('GET', vm_list_rest_call)
3444
3445 if response.status_code == requests.codes.ok:
3446 return response.content
3447
3448 return None
3449
3450 def get_vcd_network(self, network_uuid=None):
3451 """
3452 Method retrieves available network from vCloud Director
3453
3454 Args:
3455 network_uuid - is VCD network UUID
3456
3457 Each element serialized as key : value pair
3458
3459 Following keys available for access. network_configuration['Gateway'}
3460 <Configuration>
3461 <IpScopes>
3462 <IpScope>
3463 <IsInherited>true</IsInherited>
3464 <Gateway>172.16.252.100</Gateway>
3465 <Netmask>255.255.255.0</Netmask>
3466 <Dns1>172.16.254.201</Dns1>
3467 <Dns2>172.16.254.202</Dns2>
3468 <DnsSuffix>vmwarelab.edu</DnsSuffix>
3469 <IsEnabled>true</IsEnabled>
3470 <IpRanges>
3471 <IpRange>
3472 <StartAddress>172.16.252.1</StartAddress>
3473 <EndAddress>172.16.252.99</EndAddress>
3474 </IpRange>
3475 </IpRanges>
3476 </IpScope>
3477 </IpScopes>
3478 <FenceMode>bridged</FenceMode>
3479
3480 Returns:
3481 The return dictionary and key for each entry vapp UUID
3482 """
3483
3484 network_configuration = {}
3485 if network_uuid is None:
3486 return network_uuid
3487
3488 try:
3489 content = self.get_network_action(network_uuid=network_uuid)
3490 vm_list_xmlroot = XmlElementTree.fromstring(content)
3491
3492 network_configuration['status'] = vm_list_xmlroot.get("status")
3493 network_configuration['name'] = vm_list_xmlroot.get("name")
3494 network_configuration['uuid'] = vm_list_xmlroot.get("id").split(":")[3]
3495
3496 for child in vm_list_xmlroot:
3497 if child.tag.split("}")[1] == 'IsShared':
3498 network_configuration['isShared'] = child.text.strip()
3499 if child.tag.split("}")[1] == 'Configuration':
3500 for configuration in child.iter():
3501 tagKey = configuration.tag.split("}")[1].strip()
3502 if tagKey != "":
3503 network_configuration[tagKey] = configuration.text.strip()
3504 return network_configuration
3505 except Exception as exp :
3506 self.logger.debug("get_vcd_network: Failed with Exception {}".format(exp))
3507 raise vimconn.vimconnException("get_vcd_network: Failed with Exception {}".format(exp))
3508
3509 return network_configuration
3510
3511 def delete_network_action(self, network_uuid=None):
3512 """
3513 Method delete given network from vCloud director
3514
3515 Args:
3516 network_uuid - is a network uuid that client wish to delete
3517
3518 Returns:
3519 The return None or XML respond or false
3520 """
3521 client = self.connect_as_admin()
3522 if not client:
3523 raise vimconn.vimconnConnectionException("Failed to connect vCD as admin")
3524 if network_uuid is None:
3525 return False
3526
3527 url_list = [self.url, '/api/admin/network/', network_uuid]
3528 vm_list_rest_call = ''.join(url_list)
3529
3530 if client._session:
3531 headers = {'Accept':'application/*+xml;version=' + API_VERSION,
3532 'x-vcloud-authorization': client._session.headers['x-vcloud-authorization']}
3533 response = self.perform_request(req_type='DELETE',
3534 url=vm_list_rest_call,
3535 headers=headers)
3536 if response.status_code == 202:
3537 return True
3538
3539 return False
3540
3541 def create_network(self, network_name=None, net_type='bridge', parent_network_uuid=None,
3542 ip_profile=None, isshared='true'):
3543 """
3544 Method create network in vCloud director
3545
3546 Args:
3547 network_name - is network name to be created.
3548 net_type - can be 'bridge','data','ptp','mgmt'.
3549 ip_profile is a dict containing the IP parameters of the network
3550 isshared - is a boolean
3551 parent_network_uuid - is parent provider vdc network that will be used for mapping.
3552 It optional attribute. by default if no parent network indicate the first available will be used.
3553
3554 Returns:
3555 The return network uuid or return None
3556 """
3557
3558 new_network_name = [network_name, '-', str(uuid.uuid4())]
3559 content = self.create_network_rest(network_name=''.join(new_network_name),
3560 ip_profile=ip_profile,
3561 net_type=net_type,
3562 parent_network_uuid=parent_network_uuid,
3563 isshared=isshared)
3564 if content is None:
3565 self.logger.debug("Failed create network {}.".format(network_name))
3566 return None
3567
3568 try:
3569 vm_list_xmlroot = XmlElementTree.fromstring(content)
3570 vcd_uuid = vm_list_xmlroot.get('id').split(":")
3571 if len(vcd_uuid) == 4:
3572 self.logger.info("Created new network name: {} uuid: {}".format(network_name, vcd_uuid[3]))
3573 return vcd_uuid[3]
3574 except:
3575 self.logger.debug("Failed create network {}".format(network_name))
3576 return None
3577
3578 def create_network_rest(self, network_name=None, net_type='bridge', parent_network_uuid=None,
3579 ip_profile=None, isshared='true'):
3580 """
3581 Method create network in vCloud director
3582
3583 Args:
3584 network_name - is network name to be created.
3585 net_type - can be 'bridge','data','ptp','mgmt'.
3586 ip_profile is a dict containing the IP parameters of the network
3587 isshared - is a boolean
3588 parent_network_uuid - is parent provider vdc network that will be used for mapping.
3589 It optional attribute. by default if no parent network indicate the first available will be used.
3590
3591 Returns:
3592 The return network uuid or return None
3593 """
3594 client_as_admin = self.connect_as_admin()
3595 if not client_as_admin:
3596 raise vimconn.vimconnConnectionException("Failed to connect vCD.")
3597 if network_name is None:
3598 return None
3599
3600 url_list = [self.url, '/api/admin/vdc/', self.tenant_id]
3601 vm_list_rest_call = ''.join(url_list)
3602
3603 if client_as_admin._session:
3604 headers = {'Accept':'application/*+xml;version=' + API_VERSION,
3605 'x-vcloud-authorization': client_as_admin._session.headers['x-vcloud-authorization']}
3606
3607 response = self.perform_request(req_type='GET',
3608 url=vm_list_rest_call,
3609 headers=headers)
3610
3611 provider_network = None
3612 available_networks = None
3613 add_vdc_rest_url = None
3614
3615 if response.status_code != requests.codes.ok:
3616 self.logger.debug("REST API call {} failed. Return status code {}".format(vm_list_rest_call,
3617 response.status_code))
3618 return None
3619 else:
3620 try:
3621 vm_list_xmlroot = XmlElementTree.fromstring(response.content)
3622 for child in vm_list_xmlroot:
3623 if child.tag.split("}")[1] == 'ProviderVdcReference':
3624 provider_network = child.attrib.get('href')
3625 # application/vnd.vmware.admin.providervdc+xml
3626 if child.tag.split("}")[1] == 'Link':
3627 if child.attrib.get('type') == 'application/vnd.vmware.vcloud.orgVdcNetwork+xml' \
3628 and child.attrib.get('rel') == 'add':
3629 add_vdc_rest_url = child.attrib.get('href')
3630 except:
3631 self.logger.debug("Failed parse respond for rest api call {}".format(vm_list_rest_call))
3632 self.logger.debug("Respond body {}".format(response.content))
3633 return None
3634
3635 # find pvdc provided available network
3636 response = self.perform_request(req_type='GET',
3637 url=provider_network,
3638 headers=headers)
3639 if response.status_code != requests.codes.ok:
3640 self.logger.debug("REST API call {} failed. Return status code {}".format(vm_list_rest_call,
3641 response.status_code))
3642 return None
3643
3644 if parent_network_uuid is None:
3645 try:
3646 vm_list_xmlroot = XmlElementTree.fromstring(response.content)
3647 for child in vm_list_xmlroot.iter():
3648 if child.tag.split("}")[1] == 'AvailableNetworks':
3649 for networks in child.iter():
3650 # application/vnd.vmware.admin.network+xml
3651 if networks.attrib.get('href') is not None:
3652 available_networks = networks.attrib.get('href')
3653 break
3654 except:
3655 return None
3656
3657 try:
3658 #Configure IP profile of the network
3659 ip_profile = ip_profile if ip_profile is not None else DEFAULT_IP_PROFILE
3660
3661 if 'subnet_address' not in ip_profile or ip_profile['subnet_address'] is None:
3662 subnet_rand = random.randint(0, 255)
3663 ip_base = "192.168.{}.".format(subnet_rand)
3664 ip_profile['subnet_address'] = ip_base + "0/24"
3665 else:
3666 ip_base = ip_profile['subnet_address'].rsplit('.',1)[0] + '.'
3667
3668 if 'gateway_address' not in ip_profile or ip_profile['gateway_address'] is None:
3669 ip_profile['gateway_address']=ip_base + "1"
3670 if 'dhcp_count' not in ip_profile or ip_profile['dhcp_count'] is None:
3671 ip_profile['dhcp_count']=DEFAULT_IP_PROFILE['dhcp_count']
3672 if 'dhcp_enabled' not in ip_profile or ip_profile['dhcp_enabled'] is None:
3673 ip_profile['dhcp_enabled']=DEFAULT_IP_PROFILE['dhcp_enabled']
3674 if 'dhcp_start_address' not in ip_profile or ip_profile['dhcp_start_address'] is None:
3675 ip_profile['dhcp_start_address']=ip_base + "3"
3676 if 'ip_version' not in ip_profile or ip_profile['ip_version'] is None:
3677 ip_profile['ip_version']=DEFAULT_IP_PROFILE['ip_version']
3678 if 'dns_address' not in ip_profile or ip_profile['dns_address'] is None:
3679 ip_profile['dns_address']=ip_base + "2"
3680
3681 gateway_address=ip_profile['gateway_address']
3682 dhcp_count=int(ip_profile['dhcp_count'])
3683 subnet_address=self.convert_cidr_to_netmask(ip_profile['subnet_address'])
3684
3685 if ip_profile['dhcp_enabled']==True:
3686 dhcp_enabled='true'
3687 else:
3688 dhcp_enabled='false'
3689 dhcp_start_address=ip_profile['dhcp_start_address']
3690
3691 #derive dhcp_end_address from dhcp_start_address & dhcp_count
3692 end_ip_int = int(netaddr.IPAddress(dhcp_start_address))
3693 end_ip_int += dhcp_count - 1
3694 dhcp_end_address = str(netaddr.IPAddress(end_ip_int))
3695
3696 ip_version=ip_profile['ip_version']
3697 dns_address=ip_profile['dns_address']
3698 except KeyError as exp:
3699 self.logger.debug("Create Network REST: Key error {}".format(exp))
3700 raise vimconn.vimconnException("Create Network REST: Key error{}".format(exp))
3701
3702 # either use client provided UUID or search for a first available
3703 # if both are not defined we return none
3704 if parent_network_uuid is not None:
3705 provider_network = None
3706 available_networks = None
3707 add_vdc_rest_url = None
3708
3709 url_list = [self.url, '/api/admin/vdc/', self.tenant_id, '/networks']
3710 add_vdc_rest_url = ''.join(url_list)
3711
3712 url_list = [self.url, '/api/admin/network/', parent_network_uuid]
3713 available_networks = ''.join(url_list)
3714
3715 #Creating all networks as Direct Org VDC type networks.
3716 #Unused in case of Underlay (data/ptp) network interface.
3717 fence_mode="isolated"
3718 is_inherited='false'
3719 dns_list = dns_address.split(";")
3720 dns1 = dns_list[0]
3721 dns2_text = ""
3722 if len(dns_list) >= 2:
3723 dns2_text = "\n <Dns2>{}</Dns2>\n".format(dns_list[1])
3724 data = """ <OrgVdcNetwork name="{0:s}" xmlns="http://www.vmware.com/vcloud/v1.5">
3725 <Description>Openmano created</Description>
3726 <Configuration>
3727 <IpScopes>
3728 <IpScope>
3729 <IsInherited>{1:s}</IsInherited>
3730 <Gateway>{2:s}</Gateway>
3731 <Netmask>{3:s}</Netmask>
3732 <Dns1>{4:s}</Dns1>{5:s}
3733 <IsEnabled>{6:s}</IsEnabled>
3734 <IpRanges>
3735 <IpRange>
3736 <StartAddress>{7:s}</StartAddress>
3737 <EndAddress>{8:s}</EndAddress>
3738 </IpRange>
3739 </IpRanges>
3740 </IpScope>
3741 </IpScopes>
3742 <FenceMode>{9:s}</FenceMode>
3743 </Configuration>
3744 <IsShared>{10:s}</IsShared>
3745 </OrgVdcNetwork> """.format(escape(network_name), is_inherited, gateway_address,
3746 subnet_address, dns1, dns2_text, dhcp_enabled,
3747 dhcp_start_address, dhcp_end_address,
3748 fence_mode, isshared)
3749
3750 headers['Content-Type'] = 'application/vnd.vmware.vcloud.orgVdcNetwork+xml'
3751 try:
3752 response = self.perform_request(req_type='POST',
3753 url=add_vdc_rest_url,
3754 headers=headers,
3755 data=data)
3756
3757 if response.status_code != 201:
3758 self.logger.debug("Create Network POST REST API call failed. Return status code {}, Response content: {}"
3759 .format(response.status_code,response.content))
3760 else:
3761 network_task = self.get_task_from_response(response.content)
3762 self.logger.debug("Create Network REST : Waiting for Network creation complete")
3763 time.sleep(5)
3764 result = self.client.get_task_monitor().wait_for_success(task=network_task)
3765 if result.get('status') == 'success':
3766 return response.content
3767 else:
3768 self.logger.debug("create_network_rest task failed. Network Create response : {}"
3769 .format(response.content))
3770 except Exception as exp:
3771 self.logger.debug("create_network_rest : Exception : {} ".format(exp))
3772
3773 return None
3774
3775 def convert_cidr_to_netmask(self, cidr_ip=None):
3776 """
3777 Method sets convert CIDR netmask address to normal IP format
3778 Args:
3779 cidr_ip : CIDR IP address
3780 Returns:
3781 netmask : Converted netmask
3782 """
3783 if cidr_ip is not None:
3784 if '/' in cidr_ip:
3785 network, net_bits = cidr_ip.split('/')
3786 netmask = socket.inet_ntoa(struct.pack(">I", (0xffffffff << (32 - int(net_bits))) & 0xffffffff))
3787 else:
3788 netmask = cidr_ip
3789 return netmask
3790 return None
3791
3792 def get_provider_rest(self, vca=None):
3793 """
3794 Method gets provider vdc view from vcloud director
3795
3796 Args:
3797 network_name - is network name to be created.
3798 parent_network_uuid - is parent provider vdc network that will be used for mapping.
3799 It optional attribute. by default if no parent network indicate the first available will be used.
3800
3801 Returns:
3802 The return xml content of respond or None
3803 """
3804
3805 url_list = [self.url, '/api/admin']
3806 if vca:
3807 headers = {'Accept':'application/*+xml;version=' + API_VERSION,
3808 'x-vcloud-authorization': self.client._session.headers['x-vcloud-authorization']}
3809 response = self.perform_request(req_type='GET',
3810 url=''.join(url_list),
3811 headers=headers)
3812
3813 if response.status_code == requests.codes.ok:
3814 return response.content
3815 return None
3816
3817 def create_vdc(self, vdc_name=None):
3818
3819 vdc_dict = {}
3820
3821 xml_content = self.create_vdc_from_tmpl_rest(vdc_name=vdc_name)
3822 if xml_content is not None:
3823 try:
3824 task_resp_xmlroot = XmlElementTree.fromstring(xml_content)
3825 for child in task_resp_xmlroot:
3826 if child.tag.split("}")[1] == 'Owner':
3827 vdc_id = child.attrib.get('href').split("/")[-1]
3828 vdc_dict[vdc_id] = task_resp_xmlroot.get('href')
3829 return vdc_dict
3830 except:
3831 self.logger.debug("Respond body {}".format(xml_content))
3832
3833 return None
3834
3835 def create_vdc_from_tmpl_rest(self, vdc_name=None):
3836 """
3837 Method create vdc in vCloud director based on VDC template.
3838 it uses pre-defined template.
3839
3840 Args:
3841 vdc_name - name of a new vdc.
3842
3843 Returns:
3844 The return xml content of respond or None
3845 """
3846 # pre-requesite atleast one vdc template should be available in vCD
3847 self.logger.info("Creating new vdc {}".format(vdc_name))
3848 vca = self.connect_as_admin()
3849 if not vca:
3850 raise vimconn.vimconnConnectionException("Failed to connect vCD")
3851 if vdc_name is None:
3852 return None
3853
3854 url_list = [self.url, '/api/vdcTemplates']
3855 vm_list_rest_call = ''.join(url_list)
3856
3857 headers = {'Accept':'application/*+xml;version=' + API_VERSION,
3858 'x-vcloud-authorization': vca._session.headers['x-vcloud-authorization']}
3859 response = self.perform_request(req_type='GET',
3860 url=vm_list_rest_call,
3861 headers=headers)
3862
3863 # container url to a template
3864 vdc_template_ref = None
3865 try:
3866 vm_list_xmlroot = XmlElementTree.fromstring(response.content)
3867 for child in vm_list_xmlroot:
3868 # application/vnd.vmware.admin.providervdc+xml
3869 # we need find a template from witch we instantiate VDC
3870 if child.tag.split("}")[1] == 'VdcTemplate':
3871 if child.attrib.get('type') == 'application/vnd.vmware.admin.vdcTemplate+xml':
3872 vdc_template_ref = child.attrib.get('href')
3873 except:
3874 self.logger.debug("Failed parse respond for rest api call {}".format(vm_list_rest_call))
3875 self.logger.debug("Respond body {}".format(response.content))
3876 return None
3877
3878 # if we didn't found required pre defined template we return None
3879 if vdc_template_ref is None:
3880 return None
3881
3882 try:
3883 # instantiate vdc
3884 url_list = [self.url, '/api/org/', self.org_uuid, '/action/instantiate']
3885 vm_list_rest_call = ''.join(url_list)
3886 data = """<InstantiateVdcTemplateParams name="{0:s}" xmlns="http://www.vmware.com/vcloud/v1.5">
3887 <Source href="{1:s}"></Source>
3888 <Description>opnemano</Description>
3889 </InstantiateVdcTemplateParams>""".format(vdc_name, vdc_template_ref)
3890
3891 headers['Content-Type'] = 'application/vnd.vmware.vcloud.instantiateVdcTemplateParams+xml'
3892
3893 response = self.perform_request(req_type='POST',
3894 url=vm_list_rest_call,
3895 headers=headers,
3896 data=data)
3897
3898 vdc_task = self.get_task_from_response(response.content)
3899 self.client.get_task_monitor().wait_for_success(task=vdc_task)
3900
3901 # if we all ok we respond with content otherwise by default None
3902 if response.status_code >= 200 and response.status_code < 300:
3903 return response.content
3904 return None
3905 except:
3906 self.logger.debug("Failed parse respond for rest api call {}".format(vm_list_rest_call))
3907 self.logger.debug("Respond body {}".format(response.content))
3908
3909 return None
3910
3911 def create_vdc_rest(self, vdc_name=None):
3912 """
3913 Method create network in vCloud director
3914
3915 Args:
3916 vdc_name - vdc name to be created
3917 Returns:
3918 The return response
3919 """
3920
3921 self.logger.info("Creating new vdc {}".format(vdc_name))
3922
3923 vca = self.connect_as_admin()
3924 if not vca:
3925 raise vimconn.vimconnConnectionException("Failed to connect vCD")
3926 if vdc_name is None:
3927 return None
3928
3929 url_list = [self.url, '/api/admin/org/', self.org_uuid]
3930 vm_list_rest_call = ''.join(url_list)
3931
3932 if vca._session:
3933 headers = {'Accept':'application/*+xml;version=' + API_VERSION,
3934 'x-vcloud-authorization': self.client._session.headers['x-vcloud-authorization']}
3935 response = self.perform_request(req_type='GET',
3936 url=vm_list_rest_call,
3937 headers=headers)
3938
3939 provider_vdc_ref = None
3940 add_vdc_rest_url = None
3941 available_networks = None
3942
3943 if response.status_code != requests.codes.ok:
3944 self.logger.debug("REST API call {} failed. Return status code {}".format(vm_list_rest_call,
3945 response.status_code))
3946 return None
3947 else:
3948 try:
3949 vm_list_xmlroot = XmlElementTree.fromstring(response.content)
3950 for child in vm_list_xmlroot:
3951 # application/vnd.vmware.admin.providervdc+xml
3952 if child.tag.split("}")[1] == 'Link':
3953 if child.attrib.get('type') == 'application/vnd.vmware.admin.createVdcParams+xml' \
3954 and child.attrib.get('rel') == 'add':
3955 add_vdc_rest_url = child.attrib.get('href')
3956 except:
3957 self.logger.debug("Failed parse respond for rest api call {}".format(vm_list_rest_call))
3958 self.logger.debug("Respond body {}".format(response.content))
3959 return None
3960
3961 response = self.get_provider_rest(vca=vca)
3962 try:
3963 vm_list_xmlroot = XmlElementTree.fromstring(response)
3964 for child in vm_list_xmlroot:
3965 if child.tag.split("}")[1] == 'ProviderVdcReferences':
3966 for sub_child in child:
3967 provider_vdc_ref = sub_child.attrib.get('href')
3968 except:
3969 self.logger.debug("Failed parse respond for rest api call {}".format(vm_list_rest_call))
3970 self.logger.debug("Respond body {}".format(response))
3971 return None
3972
3973 if add_vdc_rest_url is not None and provider_vdc_ref is not None:
3974 data = """ <CreateVdcParams name="{0:s}" xmlns="http://www.vmware.com/vcloud/v1.5"><Description>{1:s}</Description>
3975 <AllocationModel>ReservationPool</AllocationModel>
3976 <ComputeCapacity><Cpu><Units>MHz</Units><Allocated>2048</Allocated><Limit>2048</Limit></Cpu>
3977 <Memory><Units>MB</Units><Allocated>2048</Allocated><Limit>2048</Limit></Memory>
3978 </ComputeCapacity><NicQuota>0</NicQuota><NetworkQuota>100</NetworkQuota>
3979 <VdcStorageProfile><Enabled>true</Enabled><Units>MB</Units><Limit>20480</Limit><Default>true</Default></VdcStorageProfile>
3980 <ProviderVdcReference
3981 name="Main Provider"
3982 href="{2:s}" />
3983 <UsesFastProvisioning>true</UsesFastProvisioning></CreateVdcParams>""".format(escape(vdc_name),
3984 escape(vdc_name),
3985 provider_vdc_ref)
3986
3987 headers['Content-Type'] = 'application/vnd.vmware.admin.createVdcParams+xml'
3988
3989 response = self.perform_request(req_type='POST',
3990 url=add_vdc_rest_url,
3991 headers=headers,
3992 data=data)
3993
3994 # if we all ok we respond with content otherwise by default None
3995 if response.status_code == 201:
3996 return response.content
3997 return None
3998
3999 def get_vapp_details_rest(self, vapp_uuid=None, need_admin_access=False):
4000 """
4001 Method retrieve vapp detail from vCloud director
4002
4003 Args:
4004 vapp_uuid - is vapp identifier.
4005
4006 Returns:
4007 The return network uuid or return None
4008 """
4009
4010 parsed_respond = {}
4011 vca = None
4012
4013 if need_admin_access:
4014 vca = self.connect_as_admin()
4015 else:
4016 vca = self.client
4017
4018 if not vca:
4019 raise vimconn.vimconnConnectionException("Failed to connect vCD")
4020 if vapp_uuid is None:
4021 return None
4022
4023 url_list = [self.url, '/api/vApp/vapp-', vapp_uuid]
4024 get_vapp_restcall = ''.join(url_list)
4025
4026 if vca._session:
4027 headers = {'Accept':'application/*+xml;version=' + API_VERSION,
4028 'x-vcloud-authorization': vca._session.headers['x-vcloud-authorization']}
4029 response = self.perform_request(req_type='GET',
4030 url=get_vapp_restcall,
4031 headers=headers)
4032
4033 if response.status_code == 403:
4034 if need_admin_access == False:
4035 response = self.retry_rest('GET', get_vapp_restcall)
4036
4037 if response.status_code != requests.codes.ok:
4038 self.logger.debug("REST API call {} failed. Return status code {}".format(get_vapp_restcall,
4039 response.status_code))
4040 return parsed_respond
4041
4042 try:
4043 xmlroot_respond = XmlElementTree.fromstring(response.content)
4044 parsed_respond['ovfDescriptorUploaded'] = xmlroot_respond.attrib['ovfDescriptorUploaded']
4045
4046 namespaces = {"vssd":"http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/CIM_VirtualSystemSettingData" ,
4047 'ovf': 'http://schemas.dmtf.org/ovf/envelope/1',
4048 'vmw': 'http://www.vmware.com/schema/ovf',
4049 'vm': 'http://www.vmware.com/vcloud/v1.5',
4050 'rasd':"http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/CIM_ResourceAllocationSettingData",
4051 "vmext":"http://www.vmware.com/vcloud/extension/v1.5",
4052 "xmlns":"http://www.vmware.com/vcloud/v1.5"
4053 }
4054
4055 created_section = xmlroot_respond.find('vm:DateCreated', namespaces)
4056 if created_section is not None:
4057 parsed_respond['created'] = created_section.text
4058
4059 network_section = xmlroot_respond.find('vm:NetworkConfigSection/vm:NetworkConfig', namespaces)
4060 if network_section is not None and 'networkName' in network_section.attrib:
4061 parsed_respond['networkname'] = network_section.attrib['networkName']
4062
4063 ipscopes_section = \
4064 xmlroot_respond.find('vm:NetworkConfigSection/vm:NetworkConfig/vm:Configuration/vm:IpScopes',
4065 namespaces)
4066 if ipscopes_section is not None:
4067 for ipscope in ipscopes_section:
4068 for scope in ipscope:
4069 tag_key = scope.tag.split("}")[1]
4070 if tag_key == 'IpRanges':
4071 ip_ranges = scope.getchildren()
4072 for ipblock in ip_ranges:
4073 for block in ipblock:
4074 parsed_respond[block.tag.split("}")[1]] = block.text
4075 else:
4076 parsed_respond[tag_key] = scope.text
4077
4078 # parse children section for other attrib
4079 children_section = xmlroot_respond.find('vm:Children/', namespaces)
4080 if children_section is not None:
4081 parsed_respond['name'] = children_section.attrib['name']
4082 parsed_respond['nestedHypervisorEnabled'] = children_section.attrib['nestedHypervisorEnabled'] \
4083 if "nestedHypervisorEnabled" in children_section.attrib else None
4084 parsed_respond['deployed'] = children_section.attrib['deployed']
4085 parsed_respond['status'] = children_section.attrib['status']
4086 parsed_respond['vmuuid'] = children_section.attrib['id'].split(":")[-1]
4087 network_adapter = children_section.find('vm:NetworkConnectionSection', namespaces)
4088 nic_list = []
4089 for adapters in network_adapter:
4090 adapter_key = adapters.tag.split("}")[1]
4091 if adapter_key == 'PrimaryNetworkConnectionIndex':
4092 parsed_respond['primarynetwork'] = adapters.text
4093 if adapter_key == 'NetworkConnection':
4094 vnic = {}
4095 if 'network' in adapters.attrib:
4096 vnic['network'] = adapters.attrib['network']
4097 for adapter in adapters:
4098 setting_key = adapter.tag.split("}")[1]
4099 vnic[setting_key] = adapter.text
4100 nic_list.append(vnic)
4101
4102 for link in children_section:
4103 if link.tag.split("}")[1] == 'Link' and 'rel' in link.attrib:
4104 if link.attrib['rel'] == 'screen:acquireTicket':
4105 parsed_respond['acquireTicket'] = link.attrib
4106 if link.attrib['rel'] == 'screen:acquireMksTicket':
4107 parsed_respond['acquireMksTicket'] = link.attrib
4108
4109 parsed_respond['interfaces'] = nic_list
4110 vCloud_extension_section = children_section.find('xmlns:VCloudExtension', namespaces)
4111 if vCloud_extension_section is not None:
4112 vm_vcenter_info = {}
4113 vim_info = vCloud_extension_section.find('vmext:VmVimInfo', namespaces)
4114 vmext = vim_info.find('vmext:VmVimObjectRef', namespaces)
4115 if vmext is not None:
4116 vm_vcenter_info["vm_moref_id"] = vmext.find('vmext:MoRef', namespaces).text
4117 parsed_respond["vm_vcenter_info"]= vm_vcenter_info
4118
4119 virtual_hardware_section = children_section.find('ovf:VirtualHardwareSection', namespaces)
4120 vm_virtual_hardware_info = {}
4121 if virtual_hardware_section is not None:
4122 for item in virtual_hardware_section.iterfind('ovf:Item',namespaces):
4123 if item.find("rasd:Description",namespaces).text == "Hard disk":
4124 disk_size = item.find("rasd:HostResource" ,namespaces
4125 ).attrib["{"+namespaces['vm']+"}capacity"]
4126
4127 vm_virtual_hardware_info["disk_size"]= disk_size
4128 break
4129
4130 for link in virtual_hardware_section:
4131 if link.tag.split("}")[1] == 'Link' and 'rel' in link.attrib:
4132 if link.attrib['rel'] == 'edit' and link.attrib['href'].endswith("/disks"):
4133 vm_virtual_hardware_info["disk_edit_href"] = link.attrib['href']
4134 break
4135
4136 parsed_respond["vm_virtual_hardware"]= vm_virtual_hardware_info
4137 except Exception as exp :
4138 self.logger.info("Error occurred calling rest api for getting vApp details {}".format(exp))
4139 return parsed_respond
4140
4141 def acquire_console(self, vm_uuid=None):
4142
4143 if vm_uuid is None:
4144 return None
4145 if self.client._session:
4146 headers = {'Accept':'application/*+xml;version=' + API_VERSION,
4147 'x-vcloud-authorization': self.client._session.headers['x-vcloud-authorization']}
4148 vm_dict = self.get_vapp_details_rest(vapp_uuid=vm_uuid)
4149 console_dict = vm_dict['acquireTicket']
4150 console_rest_call = console_dict['href']
4151
4152 response = self.perform_request(req_type='POST',
4153 url=console_rest_call,
4154 headers=headers)
4155
4156 if response.status_code == 403:
4157 response = self.retry_rest('POST', console_rest_call)
4158
4159 if response.status_code == requests.codes.ok:
4160 return response.content
4161
4162 return None
4163
4164 def modify_vm_disk(self, vapp_uuid, flavor_disk):
4165 """
4166 Method retrieve vm disk details
4167
4168 Args:
4169 vapp_uuid - is vapp identifier.
4170 flavor_disk - disk size as specified in VNFD (flavor)
4171
4172 Returns:
4173 The return network uuid or return None
4174 """
4175 status = None
4176 try:
4177 #Flavor disk is in GB convert it into MB
4178 flavor_disk = int(flavor_disk) * 1024
4179 vm_details = self.get_vapp_details_rest(vapp_uuid)
4180 if vm_details:
4181 vm_name = vm_details["name"]
4182 self.logger.info("VM: {} flavor_disk :{}".format(vm_name , flavor_disk))
4183
4184 if vm_details and "vm_virtual_hardware" in vm_details:
4185 vm_disk = int(vm_details["vm_virtual_hardware"]["disk_size"])
4186 disk_edit_href = vm_details["vm_virtual_hardware"]["disk_edit_href"]
4187
4188 self.logger.info("VM: {} VM_disk :{}".format(vm_name , vm_disk))
4189
4190 if flavor_disk > vm_disk:
4191 status = self.modify_vm_disk_rest(disk_edit_href ,flavor_disk)
4192 self.logger.info("Modify disk of VM {} from {} to {} MB".format(vm_name,
4193 vm_disk, flavor_disk ))
4194 else:
4195 status = True
4196 self.logger.info("No need to modify disk of VM {}".format(vm_name))
4197
4198 return status
4199 except Exception as exp:
4200 self.logger.info("Error occurred while modifing disk size {}".format(exp))
4201
4202
4203 def modify_vm_disk_rest(self, disk_href , disk_size):
4204 """
4205 Method retrieve modify vm disk size
4206
4207 Args:
4208 disk_href - vCD API URL to GET and PUT disk data
4209 disk_size - disk size as specified in VNFD (flavor)
4210
4211 Returns:
4212 The return network uuid or return None
4213 """
4214 if disk_href is None or disk_size is None:
4215 return None
4216
4217 if self.client._session:
4218 headers = {'Accept':'application/*+xml;version=' + API_VERSION,
4219 'x-vcloud-authorization': self.client._session.headers['x-vcloud-authorization']}
4220 response = self.perform_request(req_type='GET',
4221 url=disk_href,
4222 headers=headers)
4223
4224 if response.status_code == 403:
4225 response = self.retry_rest('GET', disk_href)
4226
4227 if response.status_code != requests.codes.ok:
4228 self.logger.debug("GET REST API call {} failed. Return status code {}".format(disk_href,
4229 response.status_code))
4230 return None
4231 try:
4232 lxmlroot_respond = lxmlElementTree.fromstring(response.content)
4233 namespaces = {prefix:uri for prefix,uri in lxmlroot_respond.nsmap.iteritems() if prefix}
4234 #For python3
4235 #namespaces = {prefix:uri for prefix,uri in lxmlroot_respond.nsmap.items() if prefix}
4236 namespaces["xmlns"]= "http://www.vmware.com/vcloud/v1.5"
4237
4238 for item in lxmlroot_respond.iterfind('xmlns:Item',namespaces):
4239 if item.find("rasd:Description",namespaces).text == "Hard disk":
4240 disk_item = item.find("rasd:HostResource" ,namespaces )
4241 if disk_item is not None:
4242 disk_item.attrib["{"+namespaces['xmlns']+"}capacity"] = str(disk_size)
4243 break
4244
4245 data = lxmlElementTree.tostring(lxmlroot_respond, encoding='utf8', method='xml',
4246 xml_declaration=True)
4247
4248 #Send PUT request to modify disk size
4249 headers['Content-Type'] = 'application/vnd.vmware.vcloud.rasdItemsList+xml; charset=ISO-8859-1'
4250
4251 response = self.perform_request(req_type='PUT',
4252 url=disk_href,
4253 headers=headers,
4254 data=data)
4255 if response.status_code == 403:
4256 add_headers = {'Content-Type': headers['Content-Type']}
4257 response = self.retry_rest('PUT', disk_href, add_headers, data)
4258
4259 if response.status_code != 202:
4260 self.logger.debug("PUT REST API call {} failed. Return status code {}".format(disk_href,
4261 response.status_code))
4262 else:
4263 modify_disk_task = self.get_task_from_response(response.content)
4264 result = self.client.get_task_monitor().wait_for_success(task=modify_disk_task)
4265 if result.get('status') == 'success':
4266 return True
4267 else:
4268 return False
4269 return None
4270
4271 except Exception as exp :
4272 self.logger.info("Error occurred calling rest api for modifing disk size {}".format(exp))
4273 return None
4274
4275 def add_pci_devices(self, vapp_uuid , pci_devices , vmname_andid):
4276 """
4277 Method to attach pci devices to VM
4278
4279 Args:
4280 vapp_uuid - uuid of vApp/VM
4281 pci_devices - pci devices infromation as specified in VNFD (flavor)
4282
4283 Returns:
4284 The status of add pci device task , vm object and
4285 vcenter_conect object
4286 """
4287 vm_obj = None
4288 self.logger.info("Add pci devices {} into vApp {}".format(pci_devices , vapp_uuid))
4289 vcenter_conect, content = self.get_vcenter_content()
4290 vm_moref_id = self.get_vm_moref_id(vapp_uuid)
4291
4292 if vm_moref_id:
4293 try:
4294 no_of_pci_devices = len(pci_devices)
4295 if no_of_pci_devices > 0:
4296 #Get VM and its host
4297 host_obj, vm_obj = self.get_vm_obj(content, vm_moref_id)
4298 self.logger.info("VM {} is currently on host {}".format(vm_obj, host_obj))
4299 if host_obj and vm_obj:
4300 #get PCI devies from host on which vapp is currently installed
4301 avilable_pci_devices = self.get_pci_devices(host_obj, no_of_pci_devices)
4302
4303 if avilable_pci_devices is None:
4304 #find other hosts with active pci devices
4305 new_host_obj , avilable_pci_devices = self.get_host_and_PCIdevices(
4306 content,
4307 no_of_pci_devices
4308 )
4309
4310 if new_host_obj is not None and avilable_pci_devices is not None and len(avilable_pci_devices)> 0:
4311 #Migrate vm to the host where PCI devices are availble
4312 self.logger.info("Relocate VM {} on new host {}".format(vm_obj, new_host_obj))
4313 task = self.relocate_vm(new_host_obj, vm_obj)
4314 if task is not None:
4315 result = self.wait_for_vcenter_task(task, vcenter_conect)
4316 self.logger.info("Migrate VM status: {}".format(result))
4317 host_obj = new_host_obj
4318 else:
4319 self.logger.info("Fail to migrate VM : {}".format(result))
4320 raise vimconn.vimconnNotFoundException(
4321 "Fail to migrate VM : {} to host {}".format(
4322 vmname_andid,
4323 new_host_obj)
4324 )
4325
4326 if host_obj is not None and avilable_pci_devices is not None and len(avilable_pci_devices)> 0:
4327 #Add PCI devices one by one
4328 for pci_device in avilable_pci_devices:
4329 task = self.add_pci_to_vm(host_obj, vm_obj, pci_device)
4330 if task:
4331 status= self.wait_for_vcenter_task(task, vcenter_conect)
4332 if status:
4333 self.logger.info("Added PCI device {} to VM {}".format(pci_device,str(vm_obj)))
4334 else:
4335 self.logger.error("Fail to add PCI device {} to VM {}".format(pci_device,str(vm_obj)))
4336 return True, vm_obj, vcenter_conect
4337 else:
4338 self.logger.error("Currently there is no host with"\
4339 " {} number of avaialble PCI devices required for VM {}".format(
4340 no_of_pci_devices,
4341 vmname_andid)
4342 )
4343 raise vimconn.vimconnNotFoundException(
4344 "Currently there is no host with {} "\
4345 "number of avaialble PCI devices required for VM {}".format(
4346 no_of_pci_devices,
4347 vmname_andid))
4348 else:
4349 self.logger.debug("No infromation about PCI devices {} ",pci_devices)
4350
4351 except vmodl.MethodFault as error:
4352 self.logger.error("Error occurred while adding PCI devices {} ",error)
4353 return None, vm_obj, vcenter_conect
4354
4355 def get_vm_obj(self, content, mob_id):
4356 """
4357 Method to get the vsphere VM object associated with a given morf ID
4358 Args:
4359 vapp_uuid - uuid of vApp/VM
4360 content - vCenter content object
4361 mob_id - mob_id of VM
4362
4363 Returns:
4364 VM and host object
4365 """
4366 vm_obj = None
4367 host_obj = None
4368 try :
4369 container = content.viewManager.CreateContainerView(content.rootFolder,
4370 [vim.VirtualMachine], True
4371 )
4372 for vm in container.view:
4373 mobID = vm._GetMoId()
4374 if mobID == mob_id:
4375 vm_obj = vm
4376 host_obj = vm_obj.runtime.host
4377 break
4378 except Exception as exp:
4379 self.logger.error("Error occurred while finding VM object : {}".format(exp))
4380 return host_obj, vm_obj
4381
4382 def get_pci_devices(self, host, need_devices):
4383 """
4384 Method to get the details of pci devices on given host
4385 Args:
4386 host - vSphere host object
4387 need_devices - number of pci devices needed on host
4388
4389 Returns:
4390 array of pci devices
4391 """
4392 all_devices = []
4393 all_device_ids = []
4394 used_devices_ids = []
4395
4396 try:
4397 if host:
4398 pciPassthruInfo = host.config.pciPassthruInfo
4399 pciDevies = host.hardware.pciDevice
4400
4401 for pci_status in pciPassthruInfo:
4402 if pci_status.passthruActive:
4403 for device in pciDevies:
4404 if device.id == pci_status.id:
4405 all_device_ids.append(device.id)
4406 all_devices.append(device)
4407
4408 #check if devices are in use
4409 avalible_devices = all_devices
4410 for vm in host.vm:
4411 if vm.runtime.powerState == vim.VirtualMachinePowerState.poweredOn:
4412 vm_devices = vm.config.hardware.device
4413 for device in vm_devices:
4414 if type(device) is vim.vm.device.VirtualPCIPassthrough:
4415 if device.backing.id in all_device_ids:
4416 for use_device in avalible_devices:
4417 if use_device.id == device.backing.id:
4418 avalible_devices.remove(use_device)
4419 used_devices_ids.append(device.backing.id)
4420 self.logger.debug("Device {} from devices {}"\
4421 "is in use".format(device.backing.id,
4422 device)
4423 )
4424 if len(avalible_devices) < need_devices:
4425 self.logger.debug("Host {} don't have {} number of active devices".format(host,
4426 need_devices))
4427 self.logger.debug("found only {} devives {}".format(len(avalible_devices),
4428 avalible_devices))
4429 return None
4430 else:
4431 required_devices = avalible_devices[:need_devices]
4432 self.logger.info("Found {} PCI devivces on host {} but required only {}".format(
4433 len(avalible_devices),
4434 host,
4435 need_devices))
4436 self.logger.info("Retruning {} devices as {}".format(need_devices,
4437 required_devices ))
4438 return required_devices
4439
4440 except Exception as exp:
4441 self.logger.error("Error {} occurred while finding pci devices on host: {}".format(exp, host))
4442
4443 return None
4444
4445 def get_host_and_PCIdevices(self, content, need_devices):
4446 """
4447 Method to get the details of pci devices infromation on all hosts
4448
4449 Args:
4450 content - vSphere host object
4451 need_devices - number of pci devices needed on host
4452
4453 Returns:
4454 array of pci devices and host object
4455 """
4456 host_obj = None
4457 pci_device_objs = None
4458 try:
4459 if content:
4460 container = content.viewManager.CreateContainerView(content.rootFolder,
4461 [vim.HostSystem], True)
4462 for host in container.view:
4463 devices = self.get_pci_devices(host, need_devices)
4464 if devices:
4465 host_obj = host
4466 pci_device_objs = devices
4467 break
4468 except Exception as exp:
4469 self.logger.error("Error {} occurred while finding pci devices on host: {}".format(exp, host_obj))
4470
4471 return host_obj,pci_device_objs
4472
4473 def relocate_vm(self, dest_host, vm) :
4474 """
4475 Method to get the relocate VM to new host
4476
4477 Args:
4478 dest_host - vSphere host object
4479 vm - vSphere VM object
4480
4481 Returns:
4482 task object
4483 """
4484 task = None
4485 try:
4486 relocate_spec = vim.vm.RelocateSpec(host=dest_host)
4487 task = vm.Relocate(relocate_spec)
4488 self.logger.info("Migrating {} to destination host {}".format(vm, dest_host))
4489 except Exception as exp:
4490 self.logger.error("Error occurred while relocate VM {} to new host {}: {}".format(
4491 dest_host, vm, exp))
4492 return task
4493
4494 def wait_for_vcenter_task(self, task, actionName='job', hideResult=False):
4495 """
4496 Waits and provides updates on a vSphere task
4497 """
4498 while task.info.state == vim.TaskInfo.State.running:
4499 time.sleep(2)
4500
4501 if task.info.state == vim.TaskInfo.State.success:
4502 if task.info.result is not None and not hideResult:
4503 self.logger.info('{} completed successfully, result: {}'.format(
4504 actionName,
4505 task.info.result))
4506 else:
4507 self.logger.info('Task {} completed successfully.'.format(actionName))
4508 else:
4509 self.logger.error('{} did not complete successfully: {} '.format(
4510 actionName,
4511 task.info.error)
4512 )
4513
4514 return task.info.result
4515
4516 def add_pci_to_vm(self,host_object, vm_object, host_pci_dev):
4517 """
4518 Method to add pci device in given VM
4519
4520 Args:
4521 host_object - vSphere host object
4522 vm_object - vSphere VM object
4523 host_pci_dev - host_pci_dev must be one of the devices from the
4524 host_object.hardware.pciDevice list
4525 which is configured as a PCI passthrough device
4526
4527 Returns:
4528 task object
4529 """
4530 task = None
4531 if vm_object and host_object and host_pci_dev:
4532 try :
4533 #Add PCI device to VM
4534 pci_passthroughs = vm_object.environmentBrowser.QueryConfigTarget(host=None).pciPassthrough
4535 systemid_by_pciid = {item.pciDevice.id: item.systemId for item in pci_passthroughs}
4536
4537 if host_pci_dev.id not in systemid_by_pciid:
4538 self.logger.error("Device {} is not a passthrough device ".format(host_pci_dev))
4539 return None
4540
4541 deviceId = hex(host_pci_dev.deviceId % 2**16).lstrip('0x')
4542 backing = vim.VirtualPCIPassthroughDeviceBackingInfo(deviceId=deviceId,
4543 id=host_pci_dev.id,
4544 systemId=systemid_by_pciid[host_pci_dev.id],
4545 vendorId=host_pci_dev.vendorId,
4546 deviceName=host_pci_dev.deviceName)
4547
4548 hba_object = vim.VirtualPCIPassthrough(key=-100, backing=backing)
4549
4550 new_device_config = vim.VirtualDeviceConfigSpec(device=hba_object)
4551 new_device_config.operation = "add"
4552 vmConfigSpec = vim.vm.ConfigSpec()
4553 vmConfigSpec.deviceChange = [new_device_config]
4554
4555 task = vm_object.ReconfigVM_Task(spec=vmConfigSpec)
4556 self.logger.info("Adding PCI device {} into VM {} from host {} ".format(
4557 host_pci_dev, vm_object, host_object)
4558 )
4559 except Exception as exp:
4560 self.logger.error("Error occurred while adding pci devive {} to VM {}: {}".format(
4561 host_pci_dev,
4562 vm_object,
4563 exp))
4564 return task
4565
4566 def get_vm_vcenter_info(self):
4567 """
4568 Method to get details of vCenter and vm
4569
4570 Args:
4571 vapp_uuid - uuid of vApp or VM
4572
4573 Returns:
4574 Moref Id of VM and deails of vCenter
4575 """
4576 vm_vcenter_info = {}
4577
4578 if self.vcenter_ip is not None:
4579 vm_vcenter_info["vm_vcenter_ip"] = self.vcenter_ip
4580 else:
4581 raise vimconn.vimconnException(message="vCenter IP is not provided."\
4582 " Please provide vCenter IP while attaching datacenter to tenant in --config")
4583 if self.vcenter_port is not None:
4584 vm_vcenter_info["vm_vcenter_port"] = self.vcenter_port
4585 else:
4586 raise vimconn.vimconnException(message="vCenter port is not provided."\
4587 " Please provide vCenter port while attaching datacenter to tenant in --config")
4588 if self.vcenter_user is not None:
4589 vm_vcenter_info["vm_vcenter_user"] = self.vcenter_user
4590 else:
4591 raise vimconn.vimconnException(message="vCenter user is not provided."\
4592 " Please provide vCenter user while attaching datacenter to tenant in --config")
4593
4594 if self.vcenter_password is not None:
4595 vm_vcenter_info["vm_vcenter_password"] = self.vcenter_password
4596 else:
4597 raise vimconn.vimconnException(message="vCenter user password is not provided."\
4598 " Please provide vCenter user password while attaching datacenter to tenant in --config")
4599
4600 return vm_vcenter_info
4601
4602
4603 def get_vm_pci_details(self, vmuuid):
4604 """
4605 Method to get VM PCI device details from vCenter
4606
4607 Args:
4608 vm_obj - vSphere VM object
4609
4610 Returns:
4611 dict of PCI devives attached to VM
4612
4613 """
4614 vm_pci_devices_info = {}
4615 try:
4616 vcenter_conect, content = self.get_vcenter_content()
4617 vm_moref_id = self.get_vm_moref_id(vmuuid)
4618 if vm_moref_id:
4619 #Get VM and its host
4620 if content:
4621 host_obj, vm_obj = self.get_vm_obj(content, vm_moref_id)
4622 if host_obj and vm_obj:
4623 vm_pci_devices_info["host_name"]= host_obj.name
4624 vm_pci_devices_info["host_ip"]= host_obj.config.network.vnic[0].spec.ip.ipAddress
4625 for device in vm_obj.config.hardware.device:
4626 if type(device) == vim.vm.device.VirtualPCIPassthrough:
4627 device_details={'devide_id':device.backing.id,
4628 'pciSlotNumber':device.slotInfo.pciSlotNumber,
4629 }
4630 vm_pci_devices_info[device.deviceInfo.label] = device_details
4631 else:
4632 self.logger.error("Can not connect to vCenter while getting "\
4633 "PCI devices infromationn")
4634 return vm_pci_devices_info
4635 except Exception as exp:
4636 self.logger.error("Error occurred while getting VM infromationn"\
4637 " for VM : {}".format(exp))
4638 raise vimconn.vimconnException(message=exp)
4639
4640
4641 def reserve_memory_for_all_vms(self, vapp, memory_mb):
4642 """
4643 Method to reserve memory for all VMs
4644 Args :
4645 vapp - VApp
4646 memory_mb - Memory in MB
4647 Returns:
4648 None
4649 """
4650
4651 self.logger.info("Reserve memory for all VMs")
4652 for vms in vapp.get_all_vms():
4653 vm_id = vms.get('id').split(':')[-1]
4654
4655 url_rest_call = "{}/api/vApp/vm-{}/virtualHardwareSection/memory".format(self.url, vm_id)
4656
4657 headers = {'Accept':'application/*+xml;version=' + API_VERSION,
4658 'x-vcloud-authorization': self.client._session.headers['x-vcloud-authorization']}
4659 headers['Content-Type'] = 'application/vnd.vmware.vcloud.rasdItem+xml'
4660 response = self.perform_request(req_type='GET',
4661 url=url_rest_call,
4662 headers=headers)
4663
4664 if response.status_code == 403:
4665 response = self.retry_rest('GET', url_rest_call)
4666
4667 if response.status_code != 200:
4668 self.logger.error("REST call {} failed reason : {}"\
4669 "status code : {}".format(url_rest_call,
4670 response.content,
4671 response.status_code))
4672 raise vimconn.vimconnException("reserve_memory_for_all_vms : Failed to get "\
4673 "memory")
4674
4675 bytexml = bytes(bytearray(response.content, encoding='utf-8'))
4676 contentelem = lxmlElementTree.XML(bytexml)
4677 namespaces = {prefix:uri for prefix,uri in contentelem.nsmap.iteritems() if prefix}
4678 namespaces["xmlns"]= "http://www.vmware.com/vcloud/v1.5"
4679
4680 # Find the reservation element in the response
4681 memelem_list = contentelem.findall(".//rasd:Reservation", namespaces)
4682 for memelem in memelem_list:
4683 memelem.text = str(memory_mb)
4684
4685 newdata = lxmlElementTree.tostring(contentelem, pretty_print=True)
4686
4687 response = self.perform_request(req_type='PUT',
4688 url=url_rest_call,
4689 headers=headers,
4690 data=newdata)
4691
4692 if response.status_code == 403:
4693 add_headers = {'Content-Type': headers['Content-Type']}
4694 response = self.retry_rest('PUT', url_rest_call, add_headers, newdata)
4695
4696 if response.status_code != 202:
4697 self.logger.error("REST call {} failed reason : {}"\
4698 "status code : {} ".format(url_rest_call,
4699 response.content,
4700 response.status_code))
4701 raise vimconn.vimconnException("reserve_memory_for_all_vms : Failed to update "\
4702 "virtual hardware memory section")
4703 else:
4704 mem_task = self.get_task_from_response(response.content)
4705 result = self.client.get_task_monitor().wait_for_success(task=mem_task)
4706 if result.get('status') == 'success':
4707 self.logger.info("reserve_memory_for_all_vms(): VM {} succeeded "\
4708 .format(vm_id))
4709 else:
4710 self.logger.error("reserve_memory_for_all_vms(): VM {} failed "\
4711 .format(vm_id))
4712
4713 def connect_vapp_to_org_vdc_network(self, vapp_id, net_name):
4714 """
4715 Configure VApp network config with org vdc network
4716 Args :
4717 vapp - VApp
4718 Returns:
4719 None
4720 """
4721
4722 self.logger.info("Connecting vapp {} to org vdc network {}".
4723 format(vapp_id, net_name))
4724
4725 url_rest_call = "{}/api/vApp/vapp-{}/networkConfigSection/".format(self.url, vapp_id)
4726
4727 headers = {'Accept':'application/*+xml;version=' + API_VERSION,
4728 'x-vcloud-authorization': self.client._session.headers['x-vcloud-authorization']}
4729 response = self.perform_request(req_type='GET',
4730 url=url_rest_call,
4731 headers=headers)
4732
4733 if response.status_code == 403:
4734 response = self.retry_rest('GET', url_rest_call)
4735
4736 if response.status_code != 200:
4737 self.logger.error("REST call {} failed reason : {}"\
4738 "status code : {}".format(url_rest_call,
4739 response.content,
4740 response.status_code))
4741 raise vimconn.vimconnException("connect_vapp_to_org_vdc_network : Failed to get "\
4742 "network config section")
4743
4744 data = response.content
4745 headers['Content-Type'] = 'application/vnd.vmware.vcloud.networkConfigSection+xml'
4746 net_id = self.get_network_id_by_name(net_name)
4747 if not net_id:
4748 raise vimconn.vimconnException("connect_vapp_to_org_vdc_network : Failed to find "\
4749 "existing network")
4750
4751 bytexml = bytes(bytearray(data, encoding='utf-8'))
4752 newelem = lxmlElementTree.XML(bytexml)
4753 namespaces = {prefix: uri for prefix, uri in newelem.nsmap.iteritems() if prefix}
4754 namespaces["xmlns"] = "http://www.vmware.com/vcloud/v1.5"
4755 nwcfglist = newelem.findall(".//xmlns:NetworkConfig", namespaces)
4756
4757 newstr = """<NetworkConfig networkName="{}">
4758 <Configuration>
4759 <ParentNetwork href="{}/api/network/{}"/>
4760 <FenceMode>bridged</FenceMode>
4761 </Configuration>
4762 </NetworkConfig>
4763 """.format(net_name, self.url, net_id)
4764 newcfgelem = lxmlElementTree.fromstring(newstr)
4765 if nwcfglist:
4766 nwcfglist[0].addnext(newcfgelem)
4767
4768 newdata = lxmlElementTree.tostring(newelem, pretty_print=True)
4769
4770 response = self.perform_request(req_type='PUT',
4771 url=url_rest_call,
4772 headers=headers,
4773 data=newdata)
4774
4775 if response.status_code == 403:
4776 add_headers = {'Content-Type': headers['Content-Type']}
4777 response = self.retry_rest('PUT', url_rest_call, add_headers, newdata)
4778
4779 if response.status_code != 202:
4780 self.logger.error("REST call {} failed reason : {}"\
4781 "status code : {} ".format(url_rest_call,
4782 response.content,
4783 response.status_code))
4784 raise vimconn.vimconnException("connect_vapp_to_org_vdc_network : Failed to update "\
4785 "network config section")
4786 else:
4787 vapp_task = self.get_task_from_response(response.content)
4788 result = self.client.get_task_monitor().wait_for_success(task=vapp_task)
4789 if result.get('status') == 'success':
4790 self.logger.info("connect_vapp_to_org_vdc_network(): Vapp {} connected to "\
4791 "network {}".format(vapp_id, net_name))
4792 else:
4793 self.logger.error("connect_vapp_to_org_vdc_network(): Vapp {} failed to "\
4794 "connect to network {}".format(vapp_id, net_name))
4795
4796 def remove_primary_network_adapter_from_all_vms(self, vapp):
4797 """
4798 Method to remove network adapter type to vm
4799 Args :
4800 vapp - VApp
4801 Returns:
4802 None
4803 """
4804
4805 self.logger.info("Removing network adapter from all VMs")
4806 for vms in vapp.get_all_vms():
4807 vm_id = vms.get('id').split(':')[-1]
4808
4809 url_rest_call = "{}/api/vApp/vm-{}/networkConnectionSection/".format(self.url, vm_id)
4810
4811 headers = {'Accept':'application/*+xml;version=' + API_VERSION,
4812 'x-vcloud-authorization': self.client._session.headers['x-vcloud-authorization']}
4813 response = self.perform_request(req_type='GET',
4814 url=url_rest_call,
4815 headers=headers)
4816
4817 if response.status_code == 403:
4818 response = self.retry_rest('GET', url_rest_call)
4819
4820 if response.status_code != 200:
4821 self.logger.error("REST call {} failed reason : {}"\
4822 "status code : {}".format(url_rest_call,
4823 response.content,
4824 response.status_code))
4825 raise vimconn.vimconnException("remove_primary_network_adapter : Failed to get "\
4826 "network connection section")
4827
4828 data = response.content
4829 data = data.split('<Link rel="edit"')[0]
4830
4831 headers['Content-Type'] = 'application/vnd.vmware.vcloud.networkConnectionSection+xml'
4832
4833 newdata = """<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
4834 <NetworkConnectionSection xmlns="http://www.vmware.com/vcloud/v1.5"
4835 xmlns:ovf="http://schemas.dmtf.org/ovf/envelope/1"
4836 xmlns:vssd="http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/CIM_VirtualSystemSettingData"
4837 xmlns:common="http://schemas.dmtf.org/wbem/wscim/1/common"
4838 xmlns:rasd="http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/CIM_ResourceAllocationSettingData"
4839 xmlns:vmw="http://www.vmware.com/schema/ovf"
4840 xmlns:ovfenv="http://schemas.dmtf.org/ovf/environment/1"
4841 xmlns:vmext="http://www.vmware.com/vcloud/extension/v1.5"
4842 xmlns:ns9="http://www.vmware.com/vcloud/versions"
4843 href="{url}" type="application/vnd.vmware.vcloud.networkConnectionSection+xml" ovf:required="false">
4844 <ovf:Info>Specifies the available VM network connections</ovf:Info>
4845 <PrimaryNetworkConnectionIndex>0</PrimaryNetworkConnectionIndex>
4846 <Link rel="edit" href="{url}" type="application/vnd.vmware.vcloud.networkConnectionSection+xml"/>
4847 </NetworkConnectionSection>""".format(url=url_rest_call)
4848 response = self.perform_request(req_type='PUT',
4849 url=url_rest_call,
4850 headers=headers,
4851 data=newdata)
4852
4853 if response.status_code == 403:
4854 add_headers = {'Content-Type': headers['Content-Type']}
4855 response = self.retry_rest('PUT', url_rest_call, add_headers, newdata)
4856
4857 if response.status_code != 202:
4858 self.logger.error("REST call {} failed reason : {}"\
4859 "status code : {} ".format(url_rest_call,
4860 response.content,
4861 response.status_code))
4862 raise vimconn.vimconnException("remove_primary_network_adapter : Failed to update "\
4863 "network connection section")
4864 else:
4865 nic_task = self.get_task_from_response(response.content)
4866 result = self.client.get_task_monitor().wait_for_success(task=nic_task)
4867 if result.get('status') == 'success':
4868 self.logger.info("remove_primary_network_adapter(): VM {} conneced to "\
4869 "default NIC type".format(vm_id))
4870 else:
4871 self.logger.error("remove_primary_network_adapter(): VM {} failed to "\
4872 "connect NIC type".format(vm_id))
4873
4874 def add_network_adapter_to_vms(self, vapp, network_name, primary_nic_index, nicIndex, net, nic_type=None):
4875 """
4876 Method to add network adapter type to vm
4877 Args :
4878 network_name - name of network
4879 primary_nic_index - int value for primary nic index
4880 nicIndex - int value for nic index
4881 nic_type - specify model name to which add to vm
4882 Returns:
4883 None
4884 """
4885
4886 self.logger.info("Add network adapter to VM: network_name {} nicIndex {} nic_type {}".\
4887 format(network_name, nicIndex, nic_type))
4888 try:
4889 ip_address = None
4890 floating_ip = False
4891 mac_address = None
4892 if 'floating_ip' in net: floating_ip = net['floating_ip']
4893
4894 # Stub for ip_address feature
4895 if 'ip_address' in net: ip_address = net['ip_address']
4896
4897 if 'mac_address' in net: mac_address = net['mac_address']
4898
4899 if floating_ip:
4900 allocation_mode = "POOL"
4901 elif ip_address:
4902 allocation_mode = "MANUAL"
4903 else:
4904 allocation_mode = "DHCP"
4905
4906 if not nic_type:
4907 for vms in vapp.get_all_vms():
4908 vm_id = vms.get('id').split(':')[-1]
4909
4910 url_rest_call = "{}/api/vApp/vm-{}/networkConnectionSection/".format(self.url, vm_id)
4911
4912 headers = {'Accept':'application/*+xml;version=' + API_VERSION,
4913 'x-vcloud-authorization': self.client._session.headers['x-vcloud-authorization']}
4914 response = self.perform_request(req_type='GET',
4915 url=url_rest_call,
4916 headers=headers)
4917
4918 if response.status_code == 403:
4919 response = self.retry_rest('GET', url_rest_call)
4920
4921 if response.status_code != 200:
4922 self.logger.error("REST call {} failed reason : {}"\
4923 "status code : {}".format(url_rest_call,
4924 response.content,
4925 response.status_code))
4926 raise vimconn.vimconnException("add_network_adapter_to_vms : Failed to get "\
4927 "network connection section")
4928
4929 data = response.content
4930 data = data.split('<Link rel="edit"')[0]
4931 if '<PrimaryNetworkConnectionIndex>' not in data:
4932 self.logger.debug("add_network_adapter PrimaryNIC not in data")
4933 item = """<PrimaryNetworkConnectionIndex>{}</PrimaryNetworkConnectionIndex>
4934 <NetworkConnection network="{}">
4935 <NetworkConnectionIndex>{}</NetworkConnectionIndex>
4936 <IsConnected>true</IsConnected>
4937 <IpAddressAllocationMode>{}</IpAddressAllocationMode>
4938 </NetworkConnection>""".format(primary_nic_index, network_name, nicIndex,
4939 allocation_mode)
4940 # Stub for ip_address feature
4941 if ip_address:
4942 ip_tag = '<IpAddress>{}</IpAddress>'.format(ip_address)
4943 item = item.replace('</NetworkConnectionIndex>\n','</NetworkConnectionIndex>\n{}\n'.format(ip_tag))
4944
4945 if mac_address:
4946 mac_tag = '<MACAddress>{}</MACAddress>'.format(mac_address)
4947 item = item.replace('</IsConnected>\n','</IsConnected>\n{}\n'.format(mac_tag))
4948
4949 data = data.replace('</ovf:Info>\n','</ovf:Info>\n{}\n</NetworkConnectionSection>'.format(item))
4950 else:
4951 self.logger.debug("add_network_adapter PrimaryNIC in data")
4952 new_item = """<NetworkConnection network="{}">
4953 <NetworkConnectionIndex>{}</NetworkConnectionIndex>
4954 <IsConnected>true</IsConnected>
4955 <IpAddressAllocationMode>{}</IpAddressAllocationMode>
4956 </NetworkConnection>""".format(network_name, nicIndex,
4957 allocation_mode)
4958 # Stub for ip_address feature
4959 if ip_address:
4960 ip_tag = '<IpAddress>{}</IpAddress>'.format(ip_address)
4961 new_item = new_item.replace('</NetworkConnectionIndex>\n','</NetworkConnectionIndex>\n{}\n'.format(ip_tag))
4962
4963 if mac_address:
4964 mac_tag = '<MACAddress>{}</MACAddress>'.format(mac_address)
4965 new_item = new_item.replace('</IsConnected>\n','</IsConnected>\n{}\n'.format(mac_tag))
4966
4967 data = data + new_item + '</NetworkConnectionSection>'
4968
4969 headers['Content-Type'] = 'application/vnd.vmware.vcloud.networkConnectionSection+xml'
4970
4971 response = self.perform_request(req_type='PUT',
4972 url=url_rest_call,
4973 headers=headers,
4974 data=data)
4975
4976 if response.status_code == 403:
4977 add_headers = {'Content-Type': headers['Content-Type']}
4978 response = self.retry_rest('PUT', url_rest_call, add_headers, data)
4979
4980 if response.status_code != 202:
4981 self.logger.error("REST call {} failed reason : {}"\
4982 "status code : {} ".format(url_rest_call,
4983 response.content,
4984 response.status_code))
4985 raise vimconn.vimconnException("add_network_adapter_to_vms : Failed to update "\
4986 "network connection section")
4987 else:
4988 nic_task = self.get_task_from_response(response.content)
4989 result = self.client.get_task_monitor().wait_for_success(task=nic_task)
4990 if result.get('status') == 'success':
4991 self.logger.info("add_network_adapter_to_vms(): VM {} conneced to "\
4992 "default NIC type".format(vm_id))
4993 else:
4994 self.logger.error("add_network_adapter_to_vms(): VM {} failed to "\
4995 "connect NIC type".format(vm_id))
4996 else:
4997 for vms in vapp.get_all_vms():
4998 vm_id = vms.get('id').split(':')[-1]
4999
5000 url_rest_call = "{}/api/vApp/vm-{}/networkConnectionSection/".format(self.url, vm_id)
5001
5002 headers = {'Accept':'application/*+xml;version=' + API_VERSION,
5003 'x-vcloud-authorization': self.client._session.headers['x-vcloud-authorization']}
5004 response = self.perform_request(req_type='GET',
5005 url=url_rest_call,
5006 headers=headers)
5007
5008 if response.status_code == 403:
5009 response = self.retry_rest('GET', url_rest_call)
5010
5011 if response.status_code != 200:
5012 self.logger.error("REST call {} failed reason : {}"\
5013 "status code : {}".format(url_rest_call,
5014 response.content,
5015 response.status_code))
5016 raise vimconn.vimconnException("add_network_adapter_to_vms : Failed to get "\
5017 "network connection section")
5018 data = response.content
5019 data = data.split('<Link rel="edit"')[0]
5020 vcd_netadapter_type = nic_type
5021 if nic_type in ['SR-IOV', 'VF']:
5022 vcd_netadapter_type = "SRIOVETHERNETCARD"
5023
5024 if '<PrimaryNetworkConnectionIndex>' not in data:
5025 self.logger.debug("add_network_adapter PrimaryNIC not in data nic_type {}".format(nic_type))
5026 item = """<PrimaryNetworkConnectionIndex>{}</PrimaryNetworkConnectionIndex>
5027 <NetworkConnection network="{}">
5028 <NetworkConnectionIndex>{}</NetworkConnectionIndex>
5029 <IsConnected>true</IsConnected>
5030 <IpAddressAllocationMode>{}</IpAddressAllocationMode>
5031 <NetworkAdapterType>{}</NetworkAdapterType>
5032 </NetworkConnection>""".format(primary_nic_index, network_name, nicIndex,
5033 allocation_mode, vcd_netadapter_type)
5034 # Stub for ip_address feature
5035 if ip_address:
5036 ip_tag = '<IpAddress>{}</IpAddress>'.format(ip_address)
5037 item = item.replace('</NetworkConnectionIndex>\n','</NetworkConnectionIndex>\n{}\n'.format(ip_tag))
5038
5039 if mac_address:
5040 mac_tag = '<MACAddress>{}</MACAddress>'.format(mac_address)
5041 item = item.replace('</IsConnected>\n','</IsConnected>\n{}\n'.format(mac_tag))
5042
5043 data = data.replace('</ovf:Info>\n','</ovf:Info>\n{}\n</NetworkConnectionSection>'.format(item))
5044 else:
5045 self.logger.debug("add_network_adapter PrimaryNIC in data nic_type {}".format(nic_type))
5046 new_item = """<NetworkConnection network="{}">
5047 <NetworkConnectionIndex>{}</NetworkConnectionIndex>
5048 <IsConnected>true</IsConnected>
5049 <IpAddressAllocationMode>{}</IpAddressAllocationMode>
5050 <NetworkAdapterType>{}</NetworkAdapterType>
5051 </NetworkConnection>""".format(network_name, nicIndex,
5052 allocation_mode, vcd_netadapter_type)
5053 # Stub for ip_address feature
5054 if ip_address:
5055 ip_tag = '<IpAddress>{}</IpAddress>'.format(ip_address)
5056 new_item = new_item.replace('</NetworkConnectionIndex>\n','</NetworkConnectionIndex>\n{}\n'.format(ip_tag))
5057
5058 if mac_address:
5059 mac_tag = '<MACAddress>{}</MACAddress>'.format(mac_address)
5060 new_item = new_item.replace('</IsConnected>\n','</IsConnected>\n{}\n'.format(mac_tag))
5061
5062 data = data + new_item + '</NetworkConnectionSection>'
5063
5064 headers['Content-Type'] = 'application/vnd.vmware.vcloud.networkConnectionSection+xml'
5065
5066 response = self.perform_request(req_type='PUT',
5067 url=url_rest_call,
5068 headers=headers,
5069 data=data)
5070
5071 if response.status_code == 403:
5072 add_headers = {'Content-Type': headers['Content-Type']}
5073 response = self.retry_rest('PUT', url_rest_call, add_headers, data)
5074
5075 if response.status_code != 202:
5076 self.logger.error("REST call {} failed reason : {}"\
5077 "status code : {}".format(url_rest_call,
5078 response.content,
5079 response.status_code))
5080 raise vimconn.vimconnException("add_network_adapter_to_vms : Failed to update "\
5081 "network connection section")
5082 else:
5083 nic_task = self.get_task_from_response(response.content)
5084 result = self.client.get_task_monitor().wait_for_success(task=nic_task)
5085 if result.get('status') == 'success':
5086 self.logger.info("add_network_adapter_to_vms(): VM {} "\
5087 "conneced to NIC type {}".format(vm_id, nic_type))
5088 else:
5089 self.logger.error("add_network_adapter_to_vms(): VM {} "\
5090 "failed to connect NIC type {}".format(vm_id, nic_type))
5091 except Exception as exp:
5092 self.logger.error("add_network_adapter_to_vms() : exception occurred "\
5093 "while adding Network adapter")
5094 raise vimconn.vimconnException(message=exp)
5095
5096
5097 def set_numa_affinity(self, vmuuid, paired_threads_id):
5098 """
5099 Method to assign numa affinity in vm configuration parammeters
5100 Args :
5101 vmuuid - vm uuid
5102 paired_threads_id - one or more virtual processor
5103 numbers
5104 Returns:
5105 return if True
5106 """
5107 try:
5108 vcenter_conect, content = self.get_vcenter_content()
5109 vm_moref_id = self.get_vm_moref_id(vmuuid)
5110
5111 host_obj, vm_obj = self.get_vm_obj(content ,vm_moref_id)
5112 if vm_obj:
5113 config_spec = vim.vm.ConfigSpec()
5114 config_spec.extraConfig = []
5115 opt = vim.option.OptionValue()
5116 opt.key = 'numa.nodeAffinity'
5117 opt.value = str(paired_threads_id)
5118 config_spec.extraConfig.append(opt)
5119 task = vm_obj.ReconfigVM_Task(config_spec)
5120 if task:
5121 result = self.wait_for_vcenter_task(task, vcenter_conect)
5122 extra_config = vm_obj.config.extraConfig
5123 flag = False
5124 for opts in extra_config:
5125 if 'numa.nodeAffinity' in opts.key:
5126 flag = True
5127 self.logger.info("set_numa_affinity: Sucessfully assign numa affinity "\
5128 "value {} for vm {}".format(opt.value, vm_obj))
5129 if flag:
5130 return
5131 else:
5132 self.logger.error("set_numa_affinity: Failed to assign numa affinity")
5133 except Exception as exp:
5134 self.logger.error("set_numa_affinity : exception occurred while setting numa affinity "\
5135 "for VM {} : {}".format(vm_obj, vm_moref_id))
5136 raise vimconn.vimconnException("set_numa_affinity : Error {} failed to assign numa "\
5137 "affinity".format(exp))
5138
5139
5140 def cloud_init(self, vapp, cloud_config):
5141 """
5142 Method to inject ssh-key
5143 vapp - vapp object
5144 cloud_config a dictionary with:
5145 'key-pairs': (optional) list of strings with the public key to be inserted to the default user
5146 'users': (optional) list of users to be inserted, each item is a dict with:
5147 'name': (mandatory) user name,
5148 'key-pairs': (optional) list of strings with the public key to be inserted to the user
5149 'user-data': (optional) can be a string with the text script to be passed directly to cloud-init,
5150 or a list of strings, each one contains a script to be passed, usually with a MIMEmultipart file
5151 'config-files': (optional). List of files to be transferred. Each item is a dict with:
5152 'dest': (mandatory) string with the destination absolute path
5153 'encoding': (optional, by default text). Can be one of:
5154 'b64', 'base64', 'gz', 'gz+b64', 'gz+base64', 'gzip+b64', 'gzip+base64'
5155 'content' (mandatory): string with the content of the file
5156 'permissions': (optional) string with file permissions, typically octal notation '0644'
5157 'owner': (optional) file owner, string with the format 'owner:group'
5158 'boot-data-drive': boolean to indicate if user-data must be passed using a boot drive (hard disk
5159 """
5160 try:
5161 if not isinstance(cloud_config, dict):
5162 raise Exception("cloud_init : parameter cloud_config is not a dictionary")
5163 else:
5164 key_pairs = []
5165 userdata = []
5166 if "key-pairs" in cloud_config:
5167 key_pairs = cloud_config["key-pairs"]
5168
5169 if "users" in cloud_config:
5170 userdata = cloud_config["users"]
5171
5172 self.logger.debug("cloud_init : Guest os customization started..")
5173 customize_script = self.format_script(key_pairs=key_pairs, users_list=userdata)
5174 customize_script = customize_script.replace("&","&amp;")
5175 self.guest_customization(vapp, customize_script)
5176
5177 except Exception as exp:
5178 self.logger.error("cloud_init : exception occurred while injecting "\
5179 "ssh-key")
5180 raise vimconn.vimconnException("cloud_init : Error {} failed to inject "\
5181 "ssh-key".format(exp))
5182
5183 def format_script(self, key_pairs=[], users_list=[]):
5184 bash_script = """#!/bin/sh
5185 echo performing customization tasks with param $1 at `date "+DATE: %Y-%m-%d - TIME: %H:%M:%S"` >> /root/customization.log
5186 if [ "$1" = "precustomization" ];then
5187 echo performing precustomization tasks on `date "+DATE: %Y-%m-%d - TIME: %H:%M:%S"` >> /root/customization.log
5188 """
5189
5190 keys = "\n".join(key_pairs)
5191 if keys:
5192 keys_data = """
5193 if [ ! -d /root/.ssh ];then
5194 mkdir /root/.ssh
5195 chown root:root /root/.ssh
5196 chmod 700 /root/.ssh
5197 touch /root/.ssh/authorized_keys
5198 chown root:root /root/.ssh/authorized_keys
5199 chmod 600 /root/.ssh/authorized_keys
5200 # make centos with selinux happy
5201 which restorecon && restorecon -Rv /root/.ssh
5202 else
5203 touch /root/.ssh/authorized_keys
5204 chown root:root /root/.ssh/authorized_keys
5205 chmod 600 /root/.ssh/authorized_keys
5206 fi
5207 echo '{key}' >> /root/.ssh/authorized_keys
5208 """.format(key=keys)
5209
5210 bash_script+= keys_data
5211
5212 for user in users_list:
5213 if 'name' in user: user_name = user['name']
5214 if 'key-pairs' in user:
5215 user_keys = "\n".join(user['key-pairs'])
5216 else:
5217 user_keys = None
5218
5219 add_user_name = """
5220 useradd -d /home/{user_name} -m -g users -s /bin/bash {user_name}
5221 """.format(user_name=user_name)
5222
5223 bash_script+= add_user_name
5224
5225 if user_keys:
5226 user_keys_data = """
5227 mkdir /home/{user_name}/.ssh
5228 chown {user_name}:{user_name} /home/{user_name}/.ssh
5229 chmod 700 /home/{user_name}/.ssh
5230 touch /home/{user_name}/.ssh/authorized_keys
5231 chown {user_name}:{user_name} /home/{user_name}/.ssh/authorized_keys
5232 chmod 600 /home/{user_name}/.ssh/authorized_keys
5233 # make centos with selinux happy
5234 which restorecon && restorecon -Rv /home/{user_name}/.ssh
5235 echo '{user_key}' >> /home/{user_name}/.ssh/authorized_keys
5236 """.format(user_name=user_name,user_key=user_keys)
5237
5238 bash_script+= user_keys_data
5239
5240 return bash_script+"\n\tfi"
5241
5242 def guest_customization(self, vapp, customize_script):
5243 """
5244 Method to customize guest os
5245 vapp - Vapp object
5246 customize_script - Customize script to be run at first boot of VM.
5247 """
5248 for vm in vapp.get_all_vms():
5249 vm_id = vm.get('id').split(':')[-1]
5250 vm_name = vm.get('name')
5251 vm_name = vm_name.replace('_','-')
5252
5253 vm_customization_url = "{}/api/vApp/vm-{}/guestCustomizationSection/".format(self.url, vm_id)
5254 headers = {'Accept':'application/*+xml;version=' + API_VERSION,
5255 'x-vcloud-authorization': self.client._session.headers['x-vcloud-authorization']}
5256
5257 headers['Content-Type'] = "application/vnd.vmware.vcloud.guestCustomizationSection+xml"
5258
5259 data = """<GuestCustomizationSection
5260 xmlns="http://www.vmware.com/vcloud/v1.5"
5261 xmlns:ovf="http://schemas.dmtf.org/ovf/envelope/1"
5262 ovf:required="false" href="{}" type="application/vnd.vmware.vcloud.guestCustomizationSection+xml">
5263 <ovf:Info>Specifies Guest OS Customization Settings</ovf:Info>
5264 <Enabled>true</Enabled>
5265 <ChangeSid>false</ChangeSid>
5266 <VirtualMachineId>{}</VirtualMachineId>
5267 <JoinDomainEnabled>false</JoinDomainEnabled>
5268 <UseOrgSettings>false</UseOrgSettings>
5269 <AdminPasswordEnabled>false</AdminPasswordEnabled>
5270 <AdminPasswordAuto>true</AdminPasswordAuto>
5271 <AdminAutoLogonEnabled>false</AdminAutoLogonEnabled>
5272 <AdminAutoLogonCount>0</AdminAutoLogonCount>
5273 <ResetPasswordRequired>false</ResetPasswordRequired>
5274 <CustomizationScript>{}</CustomizationScript>
5275 <ComputerName>{}</ComputerName>
5276 <Link href="{}" type="application/vnd.vmware.vcloud.guestCustomizationSection+xml" rel="edit"/>
5277 </GuestCustomizationSection>
5278 """.format(vm_customization_url,
5279 vm_id,
5280 customize_script,
5281 vm_name,
5282 vm_customization_url)
5283
5284 response = self.perform_request(req_type='PUT',
5285 url=vm_customization_url,
5286 headers=headers,
5287 data=data)
5288 if response.status_code == 202:
5289 guest_task = self.get_task_from_response(response.content)
5290 self.client.get_task_monitor().wait_for_success(task=guest_task)
5291 self.logger.info("guest_customization : customized guest os task "\
5292 "completed for VM {}".format(vm_name))
5293 else:
5294 self.logger.error("guest_customization : task for customized guest os"\
5295 "failed for VM {}".format(vm_name))
5296 raise vimconn.vimconnException("guest_customization : failed to perform"\
5297 "guest os customization on VM {}".format(vm_name))
5298
5299 def add_new_disk(self, vapp_uuid, disk_size):
5300 """
5301 Method to create an empty vm disk
5302
5303 Args:
5304 vapp_uuid - is vapp identifier.
5305 disk_size - size of disk to be created in GB
5306
5307 Returns:
5308 None
5309 """
5310 status = False
5311 vm_details = None
5312 try:
5313 #Disk size in GB, convert it into MB
5314 if disk_size is not None:
5315 disk_size_mb = int(disk_size) * 1024
5316 vm_details = self.get_vapp_details_rest(vapp_uuid)
5317
5318 if vm_details and "vm_virtual_hardware" in vm_details:
5319 self.logger.info("Adding disk to VM: {} disk size:{}GB".format(vm_details["name"], disk_size))
5320 disk_href = vm_details["vm_virtual_hardware"]["disk_edit_href"]
5321 status = self.add_new_disk_rest(disk_href, disk_size_mb)
5322
5323 except Exception as exp:
5324 msg = "Error occurred while creating new disk {}.".format(exp)
5325 self.rollback_newvm(vapp_uuid, msg)
5326
5327 if status:
5328 self.logger.info("Added new disk to VM: {} disk size:{}GB".format(vm_details["name"], disk_size))
5329 else:
5330 #If failed to add disk, delete VM
5331 msg = "add_new_disk: Failed to add new disk to {}".format(vm_details["name"])
5332 self.rollback_newvm(vapp_uuid, msg)
5333
5334
5335 def add_new_disk_rest(self, disk_href, disk_size_mb):
5336 """
5337 Retrives vApp Disks section & add new empty disk
5338
5339 Args:
5340 disk_href: Disk section href to addd disk
5341 disk_size_mb: Disk size in MB
5342
5343 Returns: Status of add new disk task
5344 """
5345 status = False
5346 if self.client._session:
5347 headers = {'Accept':'application/*+xml;version=' + API_VERSION,
5348 'x-vcloud-authorization': self.client._session.headers['x-vcloud-authorization']}
5349 response = self.perform_request(req_type='GET',
5350 url=disk_href,
5351 headers=headers)
5352
5353 if response.status_code == 403:
5354 response = self.retry_rest('GET', disk_href)
5355
5356 if response.status_code != requests.codes.ok:
5357 self.logger.error("add_new_disk_rest: GET REST API call {} failed. Return status code {}"
5358 .format(disk_href, response.status_code))
5359 return status
5360 try:
5361 #Find but type & max of instance IDs assigned to disks
5362 lxmlroot_respond = lxmlElementTree.fromstring(response.content)
5363 namespaces = {prefix:uri for prefix,uri in lxmlroot_respond.nsmap.iteritems() if prefix}
5364 #For python3
5365 #namespaces = {prefix:uri for prefix,uri in lxmlroot_respond.nsmap.items() if prefix}
5366 namespaces["xmlns"]= "http://www.vmware.com/vcloud/v1.5"
5367 instance_id = 0
5368 for item in lxmlroot_respond.iterfind('xmlns:Item',namespaces):
5369 if item.find("rasd:Description",namespaces).text == "Hard disk":
5370 inst_id = int(item.find("rasd:InstanceID" ,namespaces).text)
5371 if inst_id > instance_id:
5372 instance_id = inst_id
5373 disk_item = item.find("rasd:HostResource" ,namespaces)
5374 bus_subtype = disk_item.attrib["{"+namespaces['xmlns']+"}busSubType"]
5375 bus_type = disk_item.attrib["{"+namespaces['xmlns']+"}busType"]
5376
5377 instance_id = instance_id + 1
5378 new_item = """<Item>
5379 <rasd:Description>Hard disk</rasd:Description>
5380 <rasd:ElementName>New disk</rasd:ElementName>
5381 <rasd:HostResource
5382 xmlns:vcloud="http://www.vmware.com/vcloud/v1.5"
5383 vcloud:capacity="{}"
5384 vcloud:busSubType="{}"
5385 vcloud:busType="{}"></rasd:HostResource>
5386 <rasd:InstanceID>{}</rasd:InstanceID>
5387 <rasd:ResourceType>17</rasd:ResourceType>
5388 </Item>""".format(disk_size_mb, bus_subtype, bus_type, instance_id)
5389
5390 new_data = response.content
5391 #Add new item at the bottom
5392 new_data = new_data.replace('</Item>\n</RasdItemsList>', '</Item>\n{}\n</RasdItemsList>'.format(new_item))
5393
5394 # Send PUT request to modify virtual hardware section with new disk
5395 headers['Content-Type'] = 'application/vnd.vmware.vcloud.rasdItemsList+xml; charset=ISO-8859-1'
5396
5397 response = self.perform_request(req_type='PUT',
5398 url=disk_href,
5399 data=new_data,
5400 headers=headers)
5401
5402 if response.status_code == 403:
5403 add_headers = {'Content-Type': headers['Content-Type']}
5404 response = self.retry_rest('PUT', disk_href, add_headers, new_data)
5405
5406 if response.status_code != 202:
5407 self.logger.error("PUT REST API call {} failed. Return status code {}. Response Content:{}"
5408 .format(disk_href, response.status_code, response.content))
5409 else:
5410 add_disk_task = self.get_task_from_response(response.content)
5411 result = self.client.get_task_monitor().wait_for_success(task=add_disk_task)
5412 if result.get('status') == 'success':
5413 status = True
5414 else:
5415 self.logger.error("Add new disk REST task failed to add {} MB disk".format(disk_size_mb))
5416
5417 except Exception as exp:
5418 self.logger.error("Error occurred calling rest api for creating new disk {}".format(exp))
5419
5420 return status
5421
5422
5423 def add_existing_disk(self, catalogs=None, image_id=None, size=None, template_name=None, vapp_uuid=None):
5424 """
5425 Method to add existing disk to vm
5426 Args :
5427 catalogs - List of VDC catalogs
5428 image_id - Catalog ID
5429 template_name - Name of template in catalog
5430 vapp_uuid - UUID of vApp
5431 Returns:
5432 None
5433 """
5434 disk_info = None
5435 vcenter_conect, content = self.get_vcenter_content()
5436 #find moref-id of vm in image
5437 catalog_vm_info = self.get_vapp_template_details(catalogs=catalogs,
5438 image_id=image_id,
5439 )
5440
5441 if catalog_vm_info and "vm_vcenter_info" in catalog_vm_info:
5442 if "vm_moref_id" in catalog_vm_info["vm_vcenter_info"]:
5443 catalog_vm_moref_id = catalog_vm_info["vm_vcenter_info"].get("vm_moref_id", None)
5444 if catalog_vm_moref_id:
5445 self.logger.info("Moref_id of VM in catalog : {}" .format(catalog_vm_moref_id))
5446 host, catalog_vm_obj = self.get_vm_obj(content, catalog_vm_moref_id)
5447 if catalog_vm_obj:
5448 #find existing disk
5449 disk_info = self.find_disk(catalog_vm_obj)
5450 else:
5451 exp_msg = "No VM with image id {} found".format(image_id)
5452 self.rollback_newvm(vapp_uuid, exp_msg, exp_type="NotFound")
5453 else:
5454 exp_msg = "No Image found with image ID {} ".format(image_id)
5455 self.rollback_newvm(vapp_uuid, exp_msg, exp_type="NotFound")
5456
5457 if disk_info:
5458 self.logger.info("Existing disk_info : {}".format(disk_info))
5459 #get VM
5460 vm_moref_id = self.get_vm_moref_id(vapp_uuid)
5461 host, vm_obj = self.get_vm_obj(content, vm_moref_id)
5462 if vm_obj:
5463 status = self.add_disk(vcenter_conect=vcenter_conect,
5464 vm=vm_obj,
5465 disk_info=disk_info,
5466 size=size,
5467 vapp_uuid=vapp_uuid
5468 )
5469 if status:
5470 self.logger.info("Disk from image id {} added to {}".format(image_id,
5471 vm_obj.config.name)
5472 )
5473 else:
5474 msg = "No disk found with image id {} to add in VM {}".format(
5475 image_id,
5476 vm_obj.config.name)
5477 self.rollback_newvm(vapp_uuid, msg, exp_type="NotFound")
5478
5479
5480 def find_disk(self, vm_obj):
5481 """
5482 Method to find details of existing disk in VM
5483 Args :
5484 vm_obj - vCenter object of VM
5485 image_id - Catalog ID
5486 Returns:
5487 disk_info : dict of disk details
5488 """
5489 disk_info = {}
5490 if vm_obj:
5491 try:
5492 devices = vm_obj.config.hardware.device
5493 for device in devices:
5494 if type(device) is vim.vm.device.VirtualDisk:
5495 if isinstance(device.backing,vim.vm.device.VirtualDisk.FlatVer2BackingInfo) and hasattr(device.backing, 'fileName'):
5496 disk_info["full_path"] = device.backing.fileName
5497 disk_info["datastore"] = device.backing.datastore
5498 disk_info["capacityKB"] = device.capacityInKB
5499 break
5500 except Exception as exp:
5501 self.logger.error("find_disk() : exception occurred while "\
5502 "getting existing disk details :{}".format(exp))
5503 return disk_info
5504
5505
5506 def add_disk(self, vcenter_conect=None, vm=None, size=None, vapp_uuid=None, disk_info={}):
5507 """
5508 Method to add existing disk in VM
5509 Args :
5510 vcenter_conect - vCenter content object
5511 vm - vCenter vm object
5512 disk_info : dict of disk details
5513 Returns:
5514 status : status of add disk task
5515 """
5516 datastore = disk_info["datastore"] if "datastore" in disk_info else None
5517 fullpath = disk_info["full_path"] if "full_path" in disk_info else None
5518 capacityKB = disk_info["capacityKB"] if "capacityKB" in disk_info else None
5519 if size is not None:
5520 #Convert size from GB to KB
5521 sizeKB = int(size) * 1024 * 1024
5522 #compare size of existing disk and user given size.Assign whicherver is greater
5523 self.logger.info("Add Existing disk : sizeKB {} , capacityKB {}".format(
5524 sizeKB, capacityKB))
5525 if sizeKB > capacityKB:
5526 capacityKB = sizeKB
5527
5528 if datastore and fullpath and capacityKB:
5529 try:
5530 spec = vim.vm.ConfigSpec()
5531 # get all disks on a VM, set unit_number to the next available
5532 unit_number = 0
5533 for dev in vm.config.hardware.device:
5534 if hasattr(dev.backing, 'fileName'):
5535 unit_number = int(dev.unitNumber) + 1
5536 # unit_number 7 reserved for scsi controller
5537 if unit_number == 7:
5538 unit_number += 1
5539 if isinstance(dev, vim.vm.device.VirtualDisk):
5540 #vim.vm.device.VirtualSCSIController
5541 controller_key = dev.controllerKey
5542
5543 self.logger.info("Add Existing disk : unit number {} , controller key {}".format(
5544 unit_number, controller_key))
5545 # add disk here
5546 dev_changes = []
5547 disk_spec = vim.vm.device.VirtualDeviceSpec()
5548 disk_spec.operation = vim.vm.device.VirtualDeviceSpec.Operation.add
5549 disk_spec.device = vim.vm.device.VirtualDisk()
5550 disk_spec.device.backing = \
5551 vim.vm.device.VirtualDisk.FlatVer2BackingInfo()
5552 disk_spec.device.backing.thinProvisioned = True
5553 disk_spec.device.backing.diskMode = 'persistent'
5554 disk_spec.device.backing.datastore = datastore
5555 disk_spec.device.backing.fileName = fullpath
5556
5557 disk_spec.device.unitNumber = unit_number
5558 disk_spec.device.capacityInKB = capacityKB
5559 disk_spec.device.controllerKey = controller_key
5560 dev_changes.append(disk_spec)
5561 spec.deviceChange = dev_changes
5562 task = vm.ReconfigVM_Task(spec=spec)
5563 status = self.wait_for_vcenter_task(task, vcenter_conect)
5564 return status
5565 except Exception as exp:
5566 exp_msg = "add_disk() : exception {} occurred while adding disk "\
5567 "{} to vm {}".format(exp,
5568 fullpath,
5569 vm.config.name)
5570 self.rollback_newvm(vapp_uuid, exp_msg)
5571 else:
5572 msg = "add_disk() : Can not add disk to VM with disk info {} ".format(disk_info)
5573 self.rollback_newvm(vapp_uuid, msg)
5574
5575
5576 def get_vcenter_content(self):
5577 """
5578 Get the vsphere content object
5579 """
5580 try:
5581 vm_vcenter_info = self.get_vm_vcenter_info()
5582 except Exception as exp:
5583 self.logger.error("Error occurred while getting vCenter infromationn"\
5584 " for VM : {}".format(exp))
5585 raise vimconn.vimconnException(message=exp)
5586
5587 context = None
5588 if hasattr(ssl, '_create_unverified_context'):
5589 context = ssl._create_unverified_context()
5590
5591 vcenter_conect = SmartConnect(
5592 host=vm_vcenter_info["vm_vcenter_ip"],
5593 user=vm_vcenter_info["vm_vcenter_user"],
5594 pwd=vm_vcenter_info["vm_vcenter_password"],
5595 port=int(vm_vcenter_info["vm_vcenter_port"]),
5596 sslContext=context
5597 )
5598 atexit.register(Disconnect, vcenter_conect)
5599 content = vcenter_conect.RetrieveContent()
5600 return vcenter_conect, content
5601
5602
5603 def get_vm_moref_id(self, vapp_uuid):
5604 """
5605 Get the moref_id of given VM
5606 """
5607 try:
5608 if vapp_uuid:
5609 vm_details = self.get_vapp_details_rest(vapp_uuid, need_admin_access=True)
5610 if vm_details and "vm_vcenter_info" in vm_details:
5611 vm_moref_id = vm_details["vm_vcenter_info"].get("vm_moref_id", None)
5612 return vm_moref_id
5613
5614 except Exception as exp:
5615 self.logger.error("Error occurred while getting VM moref ID "\
5616 " for VM : {}".format(exp))
5617 return None
5618
5619
5620 def get_vapp_template_details(self, catalogs=None, image_id=None , template_name=None):
5621 """
5622 Method to get vApp template details
5623 Args :
5624 catalogs - list of VDC catalogs
5625 image_id - Catalog ID to find
5626 template_name : template name in catalog
5627 Returns:
5628 parsed_respond : dict of vApp tempalte details
5629 """
5630 parsed_response = {}
5631
5632 vca = self.connect_as_admin()
5633 if not vca:
5634 raise vimconn.vimconnConnectionException("Failed to connect vCD")
5635
5636 try:
5637 org, vdc = self.get_vdc_details()
5638 catalog = self.get_catalog_obj(image_id, catalogs)
5639 if catalog:
5640 items = org.get_catalog_item(catalog.get('name'), catalog.get('name'))
5641 catalog_items = [items.attrib]
5642
5643 if len(catalog_items) == 1:
5644 headers = {'Accept':'application/*+xml;version=' + API_VERSION,
5645 'x-vcloud-authorization': vca._session.headers['x-vcloud-authorization']}
5646
5647 response = self.perform_request(req_type='GET',
5648 url=catalog_items[0].get('href'),
5649 headers=headers)
5650 catalogItem = XmlElementTree.fromstring(response.content)
5651 entity = [child for child in catalogItem if child.get("type") == "application/vnd.vmware.vcloud.vAppTemplate+xml"][0]
5652 vapp_tempalte_href = entity.get("href")
5653 #get vapp details and parse moref id
5654
5655 namespaces = {"vssd":"http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/CIM_VirtualSystemSettingData" ,
5656 'ovf': 'http://schemas.dmtf.org/ovf/envelope/1',
5657 'vmw': 'http://www.vmware.com/schema/ovf',
5658 'vm': 'http://www.vmware.com/vcloud/v1.5',
5659 'rasd':"http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/CIM_ResourceAllocationSettingData",
5660 'vmext':"http://www.vmware.com/vcloud/extension/v1.5",
5661 'xmlns':"http://www.vmware.com/vcloud/v1.5"
5662 }
5663
5664 if vca._session:
5665 response = self.perform_request(req_type='GET',
5666 url=vapp_tempalte_href,
5667 headers=headers)
5668
5669 if response.status_code != requests.codes.ok:
5670 self.logger.debug("REST API call {} failed. Return status code {}".format(
5671 vapp_tempalte_href, response.status_code))
5672
5673 else:
5674 xmlroot_respond = XmlElementTree.fromstring(response.content)
5675 children_section = xmlroot_respond.find('vm:Children/', namespaces)
5676 if children_section is not None:
5677 vCloud_extension_section = children_section.find('xmlns:VCloudExtension', namespaces)
5678 if vCloud_extension_section is not None:
5679 vm_vcenter_info = {}
5680 vim_info = vCloud_extension_section.find('vmext:VmVimInfo', namespaces)
5681 vmext = vim_info.find('vmext:VmVimObjectRef', namespaces)
5682 if vmext is not None:
5683 vm_vcenter_info["vm_moref_id"] = vmext.find('vmext:MoRef', namespaces).text
5684 parsed_response["vm_vcenter_info"]= vm_vcenter_info
5685
5686 except Exception as exp :
5687 self.logger.info("Error occurred calling rest api for getting vApp details {}".format(exp))
5688
5689 return parsed_response
5690
5691
5692 def rollback_newvm(self, vapp_uuid, msg , exp_type="Genric"):
5693 """
5694 Method to delete vApp
5695 Args :
5696 vapp_uuid - vApp UUID
5697 msg - Error message to be logged
5698 exp_type : Exception type
5699 Returns:
5700 None
5701 """
5702 if vapp_uuid:
5703 status = self.delete_vminstance(vapp_uuid)
5704 else:
5705 msg = "No vApp ID"
5706 self.logger.error(msg)
5707 if exp_type == "Genric":
5708 raise vimconn.vimconnException(msg)
5709 elif exp_type == "NotFound":
5710 raise vimconn.vimconnNotFoundException(message=msg)
5711
5712 def add_sriov(self, vapp_uuid, sriov_nets, vmname_andid):
5713 """
5714 Method to attach SRIOV adapters to VM
5715
5716 Args:
5717 vapp_uuid - uuid of vApp/VM
5718 sriov_nets - SRIOV devices infromation as specified in VNFD (flavor)
5719 vmname_andid - vmname
5720
5721 Returns:
5722 The status of add SRIOV adapter task , vm object and
5723 vcenter_conect object
5724 """
5725 vm_obj = None
5726 vcenter_conect, content = self.get_vcenter_content()
5727 vm_moref_id = self.get_vm_moref_id(vapp_uuid)
5728
5729 if vm_moref_id:
5730 try:
5731 no_of_sriov_devices = len(sriov_nets)
5732 if no_of_sriov_devices > 0:
5733 #Get VM and its host
5734 host_obj, vm_obj = self.get_vm_obj(content, vm_moref_id)
5735 self.logger.info("VM {} is currently on host {}".format(vm_obj, host_obj))
5736 if host_obj and vm_obj:
5737 #get SRIOV devies from host on which vapp is currently installed
5738 avilable_sriov_devices = self.get_sriov_devices(host_obj,
5739 no_of_sriov_devices,
5740 )
5741
5742 if len(avilable_sriov_devices) == 0:
5743 #find other hosts with active pci devices
5744 new_host_obj , avilable_sriov_devices = self.get_host_and_sriov_devices(
5745 content,
5746 no_of_sriov_devices,
5747 )
5748
5749 if new_host_obj is not None and len(avilable_sriov_devices)> 0:
5750 #Migrate vm to the host where SRIOV devices are available
5751 self.logger.info("Relocate VM {} on new host {}".format(vm_obj,
5752 new_host_obj))
5753 task = self.relocate_vm(new_host_obj, vm_obj)
5754 if task is not None:
5755 result = self.wait_for_vcenter_task(task, vcenter_conect)
5756 self.logger.info("Migrate VM status: {}".format(result))
5757 host_obj = new_host_obj
5758 else:
5759 self.logger.info("Fail to migrate VM : {}".format(result))
5760 raise vimconn.vimconnNotFoundException(
5761 "Fail to migrate VM : {} to host {}".format(
5762 vmname_andid,
5763 new_host_obj)
5764 )
5765
5766 if host_obj is not None and avilable_sriov_devices is not None and len(avilable_sriov_devices)> 0:
5767 #Add SRIOV devices one by one
5768 for sriov_net in sriov_nets:
5769 network_name = sriov_net.get('net_id')
5770 dvs_portgr_name = self.create_dvPort_group(network_name)
5771 if sriov_net.get('type') == "VF" or sriov_net.get('type') == "SR-IOV":
5772 #add vlan ID ,Modify portgroup for vlan ID
5773 self.configure_vlanID(content, vcenter_conect, network_name)
5774
5775 task = self.add_sriov_to_vm(content,
5776 vm_obj,
5777 host_obj,
5778 network_name,
5779 avilable_sriov_devices[0]
5780 )
5781 if task:
5782 status= self.wait_for_vcenter_task(task, vcenter_conect)
5783 if status:
5784 self.logger.info("Added SRIOV {} to VM {}".format(
5785 no_of_sriov_devices,
5786 str(vm_obj)))
5787 else:
5788 self.logger.error("Fail to add SRIOV {} to VM {}".format(
5789 no_of_sriov_devices,
5790 str(vm_obj)))
5791 raise vimconn.vimconnUnexpectedResponse(
5792 "Fail to add SRIOV adapter in VM ".format(str(vm_obj))
5793 )
5794 return True, vm_obj, vcenter_conect
5795 else:
5796 self.logger.error("Currently there is no host with"\
5797 " {} number of avaialble SRIOV "\
5798 "VFs required for VM {}".format(
5799 no_of_sriov_devices,
5800 vmname_andid)
5801 )
5802 raise vimconn.vimconnNotFoundException(
5803 "Currently there is no host with {} "\
5804 "number of avaialble SRIOV devices required for VM {}".format(
5805 no_of_sriov_devices,
5806 vmname_andid))
5807 else:
5808 self.logger.debug("No infromation about SRIOV devices {} ",sriov_nets)
5809
5810 except vmodl.MethodFault as error:
5811 self.logger.error("Error occurred while adding SRIOV {} ",error)
5812 return None, vm_obj, vcenter_conect
5813
5814
5815 def get_sriov_devices(self,host, no_of_vfs):
5816 """
5817 Method to get the details of SRIOV devices on given host
5818 Args:
5819 host - vSphere host object
5820 no_of_vfs - number of VFs needed on host
5821
5822 Returns:
5823 array of SRIOV devices
5824 """
5825 sriovInfo=[]
5826 if host:
5827 for device in host.config.pciPassthruInfo:
5828 if isinstance(device,vim.host.SriovInfo) and device.sriovActive:
5829 if device.numVirtualFunction >= no_of_vfs:
5830 sriovInfo.append(device)
5831 break
5832 return sriovInfo
5833
5834
5835 def get_host_and_sriov_devices(self, content, no_of_vfs):
5836 """
5837 Method to get the details of SRIOV devices infromation on all hosts
5838
5839 Args:
5840 content - vSphere host object
5841 no_of_vfs - number of pci VFs needed on host
5842
5843 Returns:
5844 array of SRIOV devices and host object
5845 """
5846 host_obj = None
5847 sriov_device_objs = None
5848 try:
5849 if content:
5850 container = content.viewManager.CreateContainerView(content.rootFolder,
5851 [vim.HostSystem], True)
5852 for host in container.view:
5853 devices = self.get_sriov_devices(host, no_of_vfs)
5854 if devices:
5855 host_obj = host
5856 sriov_device_objs = devices
5857 break
5858 except Exception as exp:
5859 self.logger.error("Error {} occurred while finding SRIOV devices on host: {}".format(exp, host_obj))
5860
5861 return host_obj,sriov_device_objs
5862
5863
5864 def add_sriov_to_vm(self,content, vm_obj, host_obj, network_name, sriov_device):
5865 """
5866 Method to add SRIOV adapter to vm
5867
5868 Args:
5869 host_obj - vSphere host object
5870 vm_obj - vSphere vm object
5871 content - vCenter content object
5872 network_name - name of distributed virtaul portgroup
5873 sriov_device - SRIOV device info
5874
5875 Returns:
5876 task object
5877 """
5878 devices = []
5879 vnic_label = "sriov nic"
5880 try:
5881 dvs_portgr = self.get_dvport_group(network_name)
5882 network_name = dvs_portgr.name
5883 nic = vim.vm.device.VirtualDeviceSpec()
5884 # VM device
5885 nic.operation = vim.vm.device.VirtualDeviceSpec.Operation.add
5886 nic.device = vim.vm.device.VirtualSriovEthernetCard()
5887 nic.device.addressType = 'assigned'
5888 #nic.device.key = 13016
5889 nic.device.deviceInfo = vim.Description()
5890 nic.device.deviceInfo.label = vnic_label
5891 nic.device.deviceInfo.summary = network_name
5892 nic.device.backing = vim.vm.device.VirtualEthernetCard.NetworkBackingInfo()
5893
5894 nic.device.backing.network = self.get_obj(content, [vim.Network], network_name)
5895 nic.device.backing.deviceName = network_name
5896 nic.device.backing.useAutoDetect = False
5897 nic.device.connectable = vim.vm.device.VirtualDevice.ConnectInfo()
5898 nic.device.connectable.startConnected = True
5899 nic.device.connectable.allowGuestControl = True
5900
5901 nic.device.sriovBacking = vim.vm.device.VirtualSriovEthernetCard.SriovBackingInfo()
5902 nic.device.sriovBacking.physicalFunctionBacking = vim.vm.device.VirtualPCIPassthrough.DeviceBackingInfo()
5903 nic.device.sriovBacking.physicalFunctionBacking.id = sriov_device.id
5904
5905 devices.append(nic)
5906 vmconf = vim.vm.ConfigSpec(deviceChange=devices)
5907 task = vm_obj.ReconfigVM_Task(vmconf)
5908 return task
5909 except Exception as exp:
5910 self.logger.error("Error {} occurred while adding SRIOV adapter in VM: {}".format(exp, vm_obj))
5911 return None
5912
5913
5914 def create_dvPort_group(self, network_name):
5915 """
5916 Method to create disributed virtual portgroup
5917
5918 Args:
5919 network_name - name of network/portgroup
5920
5921 Returns:
5922 portgroup key
5923 """
5924 try:
5925 new_network_name = [network_name, '-', str(uuid.uuid4())]
5926 network_name=''.join(new_network_name)
5927 vcenter_conect, content = self.get_vcenter_content()
5928
5929 dv_switch = self.get_obj(content, [vim.DistributedVirtualSwitch], self.dvs_name)
5930 if dv_switch:
5931 dv_pg_spec = vim.dvs.DistributedVirtualPortgroup.ConfigSpec()
5932 dv_pg_spec.name = network_name
5933
5934 dv_pg_spec.type = vim.dvs.DistributedVirtualPortgroup.PortgroupType.earlyBinding
5935 dv_pg_spec.defaultPortConfig = vim.dvs.VmwareDistributedVirtualSwitch.VmwarePortConfigPolicy()
5936 dv_pg_spec.defaultPortConfig.securityPolicy = vim.dvs.VmwareDistributedVirtualSwitch.SecurityPolicy()
5937 dv_pg_spec.defaultPortConfig.securityPolicy.allowPromiscuous = vim.BoolPolicy(value=False)
5938 dv_pg_spec.defaultPortConfig.securityPolicy.forgedTransmits = vim.BoolPolicy(value=False)
5939 dv_pg_spec.defaultPortConfig.securityPolicy.macChanges = vim.BoolPolicy(value=False)
5940
5941 task = dv_switch.AddDVPortgroup_Task([dv_pg_spec])
5942 self.wait_for_vcenter_task(task, vcenter_conect)
5943
5944 dvPort_group = self.get_obj(content, [vim.dvs.DistributedVirtualPortgroup], network_name)
5945 if dvPort_group:
5946 self.logger.info("Created disributed virtaul port group: {}".format(dvPort_group))
5947 return dvPort_group.key
5948 else:
5949 self.logger.debug("No disributed virtual switch found with name {}".format(network_name))
5950
5951 except Exception as exp:
5952 self.logger.error("Error occurred while creating disributed virtaul port group {}"\
5953 " : {}".format(network_name, exp))
5954 return None
5955
5956 def reconfig_portgroup(self, content, dvPort_group_name , config_info={}):
5957 """
5958 Method to reconfigure disributed virtual portgroup
5959
5960 Args:
5961 dvPort_group_name - name of disributed virtual portgroup
5962 content - vCenter content object
5963 config_info - disributed virtual portgroup configuration
5964
5965 Returns:
5966 task object
5967 """
5968 try:
5969 dvPort_group = self.get_dvport_group(dvPort_group_name)
5970 if dvPort_group:
5971 dv_pg_spec = vim.dvs.DistributedVirtualPortgroup.ConfigSpec()
5972 dv_pg_spec.configVersion = dvPort_group.config.configVersion
5973 dv_pg_spec.defaultPortConfig = vim.dvs.VmwareDistributedVirtualSwitch.VmwarePortConfigPolicy()
5974 if "vlanID" in config_info:
5975 dv_pg_spec.defaultPortConfig.vlan = vim.dvs.VmwareDistributedVirtualSwitch.VlanIdSpec()
5976 dv_pg_spec.defaultPortConfig.vlan.vlanId = config_info.get('vlanID')
5977
5978 task = dvPort_group.ReconfigureDVPortgroup_Task(spec=dv_pg_spec)
5979 return task
5980 else:
5981 return None
5982 except Exception as exp:
5983 self.logger.error("Error occurred while reconfiguraing disributed virtaul port group {}"\
5984 " : {}".format(dvPort_group_name, exp))
5985 return None
5986
5987
5988 def destroy_dvport_group(self , dvPort_group_name):
5989 """
5990 Method to destroy disributed virtual portgroup
5991
5992 Args:
5993 network_name - name of network/portgroup
5994
5995 Returns:
5996 True if portgroup successfully got deleted else false
5997 """
5998 vcenter_conect, content = self.get_vcenter_content()
5999 try:
6000 status = None
6001 dvPort_group = self.get_dvport_group(dvPort_group_name)
6002 if dvPort_group:
6003 task = dvPort_group.Destroy_Task()
6004 status = self.wait_for_vcenter_task(task, vcenter_conect)
6005 return status
6006 except vmodl.MethodFault as exp:
6007 self.logger.error("Caught vmodl fault {} while deleting disributed virtaul port group {}".format(
6008 exp, dvPort_group_name))
6009 return None
6010
6011
6012 def get_dvport_group(self, dvPort_group_name):
6013 """
6014 Method to get disributed virtual portgroup
6015
6016 Args:
6017 network_name - name of network/portgroup
6018
6019 Returns:
6020 portgroup object
6021 """
6022 vcenter_conect, content = self.get_vcenter_content()
6023 dvPort_group = None
6024 try:
6025 container = content.viewManager.CreateContainerView(content.rootFolder, [vim.dvs.DistributedVirtualPortgroup], True)
6026 for item in container.view:
6027 if item.key == dvPort_group_name:
6028 dvPort_group = item
6029 break
6030 return dvPort_group
6031 except vmodl.MethodFault as exp:
6032 self.logger.error("Caught vmodl fault {} for disributed virtaul port group {}".format(
6033 exp, dvPort_group_name))
6034 return None
6035
6036 def get_vlanID_from_dvs_portgr(self, dvPort_group_name):
6037 """
6038 Method to get disributed virtual portgroup vlanID
6039
6040 Args:
6041 network_name - name of network/portgroup
6042
6043 Returns:
6044 vlan ID
6045 """
6046 vlanId = None
6047 try:
6048 dvPort_group = self.get_dvport_group(dvPort_group_name)
6049 if dvPort_group:
6050 vlanId = dvPort_group.config.defaultPortConfig.vlan.vlanId
6051 except vmodl.MethodFault as exp:
6052 self.logger.error("Caught vmodl fault {} for disributed virtaul port group {}".format(
6053 exp, dvPort_group_name))
6054 return vlanId
6055
6056
6057 def configure_vlanID(self, content, vcenter_conect, dvPort_group_name):
6058 """
6059 Method to configure vlanID in disributed virtual portgroup vlanID
6060
6061 Args:
6062 network_name - name of network/portgroup
6063
6064 Returns:
6065 None
6066 """
6067 vlanID = self.get_vlanID_from_dvs_portgr(dvPort_group_name)
6068 if vlanID == 0:
6069 #configure vlanID
6070 vlanID = self.genrate_vlanID(dvPort_group_name)
6071 config = {"vlanID":vlanID}
6072 task = self.reconfig_portgroup(content, dvPort_group_name,
6073 config_info=config)
6074 if task:
6075 status= self.wait_for_vcenter_task(task, vcenter_conect)
6076 if status:
6077 self.logger.info("Reconfigured Port group {} for vlan ID {}".format(
6078 dvPort_group_name,vlanID))
6079 else:
6080 self.logger.error("Fail reconfigure portgroup {} for vlanID{}".format(
6081 dvPort_group_name, vlanID))
6082
6083
6084 def genrate_vlanID(self, network_name):
6085 """
6086 Method to get unused vlanID
6087 Args:
6088 network_name - name of network/portgroup
6089 Returns:
6090 vlanID
6091 """
6092 vlan_id = None
6093 used_ids = []
6094 if self.config.get('vlanID_range') == None:
6095 raise vimconn.vimconnConflictException("You must provide a 'vlanID_range' "\
6096 "at config value before creating sriov network with vlan tag")
6097 if "used_vlanIDs" not in self.persistent_info:
6098 self.persistent_info["used_vlanIDs"] = {}
6099 else:
6100 used_ids = self.persistent_info["used_vlanIDs"].values()
6101 #For python3
6102 #used_ids = list(self.persistent_info["used_vlanIDs"].values())
6103
6104 for vlanID_range in self.config.get('vlanID_range'):
6105 start_vlanid , end_vlanid = vlanID_range.split("-")
6106 if start_vlanid > end_vlanid:
6107 raise vimconn.vimconnConflictException("Invalid vlan ID range {}".format(
6108 vlanID_range))
6109
6110 for id in xrange(int(start_vlanid), int(end_vlanid) + 1):
6111 #For python3
6112 #for id in range(int(start_vlanid), int(end_vlanid) + 1):
6113 if id not in used_ids:
6114 vlan_id = id
6115 self.persistent_info["used_vlanIDs"][network_name] = vlan_id
6116 return vlan_id
6117 if vlan_id is None:
6118 raise vimconn.vimconnConflictException("All Vlan IDs are in use")
6119
6120
6121 def get_obj(self, content, vimtype, name):
6122 """
6123 Get the vsphere object associated with a given text name
6124 """
6125 obj = None
6126 container = content.viewManager.CreateContainerView(content.rootFolder, vimtype, True)
6127 for item in container.view:
6128 if item.name == name:
6129 obj = item
6130 break
6131 return obj
6132
6133
6134 def insert_media_to_vm(self, vapp, image_id):
6135 """
6136 Method to insert media CD-ROM (ISO image) from catalog to vm.
6137 vapp - vapp object to get vm id
6138 Image_id - image id for cdrom to be inerted to vm
6139 """
6140 # create connection object
6141 vca = self.connect()
6142 try:
6143 # fetching catalog details
6144 rest_url = "{}/api/catalog/{}".format(self.url, image_id)
6145 if vca._session:
6146 headers = {'Accept':'application/*+xml;version=' + API_VERSION,
6147 'x-vcloud-authorization': vca._session.headers['x-vcloud-authorization']}
6148 response = self.perform_request(req_type='GET',
6149 url=rest_url,
6150 headers=headers)
6151
6152 if response.status_code != 200:
6153 self.logger.error("REST call {} failed reason : {}"\
6154 "status code : {}".format(url_rest_call,
6155 response.content,
6156 response.status_code))
6157 raise vimconn.vimconnException("insert_media_to_vm(): Failed to get "\
6158 "catalog details")
6159 # searching iso name and id
6160 iso_name,media_id = self.get_media_details(vca, response.content)
6161
6162 if iso_name and media_id:
6163 data ="""<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
6164 <ns6:MediaInsertOrEjectParams
6165 xmlns="http://www.vmware.com/vcloud/versions" xmlns:ns2="http://schemas.dmtf.org/ovf/envelope/1" xmlns:ns3="http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/CIM_VirtualSystemSettingData" xmlns:ns4="http://schemas.dmtf.org/wbem/wscim/1/common" xmlns:ns5="http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/CIM_ResourceAllocationSettingData" xmlns:ns6="http://www.vmware.com/vcloud/v1.5" xmlns:ns7="http://www.vmware.com/schema/ovf" xmlns:ns8="http://schemas.dmtf.org/ovf/environment/1" xmlns:ns9="http://www.vmware.com/vcloud/extension/v1.5">
6166 <ns6:Media
6167 type="application/vnd.vmware.vcloud.media+xml"
6168 name="{}.iso"
6169 id="urn:vcloud:media:{}"
6170 href="https://{}/api/media/{}"/>
6171 </ns6:MediaInsertOrEjectParams>""".format(iso_name, media_id,
6172 self.url,media_id)
6173
6174 for vms in vapp.get_all_vms():
6175 vm_id = vms.get('id').split(':')[-1]
6176
6177 headers['Content-Type'] = 'application/vnd.vmware.vcloud.mediaInsertOrEjectParams+xml'
6178 rest_url = "{}/api/vApp/vm-{}/media/action/insertMedia".format(self.url,vm_id)
6179
6180 response = self.perform_request(req_type='POST',
6181 url=rest_url,
6182 data=data,
6183 headers=headers)
6184
6185 if response.status_code != 202:
6186 self.logger.error("Failed to insert CD-ROM to vm")
6187 raise vimconn.vimconnException("insert_media_to_vm() : Failed to insert"\
6188 "ISO image to vm")
6189 else:
6190 task = self.get_task_from_response(response.content)
6191 result = self.client.get_task_monitor().wait_for_success(task=task)
6192 if result.get('status') == 'success':
6193 self.logger.info("insert_media_to_vm(): Sucessfully inserted media ISO"\
6194 " image to vm {}".format(vm_id))
6195
6196 except Exception as exp:
6197 self.logger.error("insert_media_to_vm() : exception occurred "\
6198 "while inserting media CD-ROM")
6199 raise vimconn.vimconnException(message=exp)
6200
6201
6202 def get_media_details(self, vca, content):
6203 """
6204 Method to get catalog item details
6205 vca - connection object
6206 content - Catalog details
6207 Return - Media name, media id
6208 """
6209 cataloghref_list = []
6210 try:
6211 if content:
6212 vm_list_xmlroot = XmlElementTree.fromstring(content)
6213 for child in vm_list_xmlroot.iter():
6214 if 'CatalogItem' in child.tag:
6215 cataloghref_list.append(child.attrib.get('href'))
6216 if cataloghref_list is not None:
6217 for href in cataloghref_list:
6218 if href:
6219 headers = {'Accept':'application/*+xml;version=' + API_VERSION,
6220 'x-vcloud-authorization': vca._session.headers['x-vcloud-authorization']}
6221 response = self.perform_request(req_type='GET',
6222 url=href,
6223 headers=headers)
6224 if response.status_code != 200:
6225 self.logger.error("REST call {} failed reason : {}"\
6226 "status code : {}".format(href,
6227 response.content,
6228 response.status_code))
6229 raise vimconn.vimconnException("get_media_details : Failed to get "\
6230 "catalogitem details")
6231 list_xmlroot = XmlElementTree.fromstring(response.content)
6232 for child in list_xmlroot.iter():
6233 if 'Entity' in child.tag:
6234 if 'media' in child.attrib.get('href'):
6235 name = child.attrib.get('name')
6236 media_id = child.attrib.get('href').split('/').pop()
6237 return name,media_id
6238 else:
6239 self.logger.debug("Media name and id not found")
6240 return False,False
6241 except Exception as exp:
6242 self.logger.error("get_media_details : exception occurred "\
6243 "getting media details")
6244 raise vimconn.vimconnException(message=exp)
6245
6246
6247 def retry_rest(self, method, url, add_headers=None, data=None):
6248 """ Method to get Token & retry respective REST request
6249 Args:
6250 api - REST API - Can be one of 'GET' or 'PUT' or 'POST'
6251 url - request url to be used
6252 add_headers - Additional headers (optional)
6253 data - Request payload data to be passed in request
6254 Returns:
6255 response - Response of request
6256 """
6257 response = None
6258
6259 #Get token
6260 self.get_token()
6261
6262 if self.client._session:
6263 headers = {'Accept':'application/*+xml;version=' + API_VERSION,
6264 'x-vcloud-authorization': self.client._session.headers['x-vcloud-authorization']}
6265
6266 if add_headers:
6267 headers.update(add_headers)
6268
6269 if method == 'GET':
6270 response = self.perform_request(req_type='GET',
6271 url=url,
6272 headers=headers)
6273 elif method == 'PUT':
6274 response = self.perform_request(req_type='PUT',
6275 url=url,
6276 headers=headers,
6277 data=data)
6278 elif method == 'POST':
6279 response = self.perform_request(req_type='POST',
6280 url=url,
6281 headers=headers,
6282 data=data)
6283 elif method == 'DELETE':
6284 response = self.perform_request(req_type='DELETE',
6285 url=url,
6286 headers=headers)
6287 return response
6288
6289
6290 def get_token(self):
6291 """ Generate a new token if expired
6292
6293 Returns:
6294 The return client object that letter can be used to connect to vCloud director as admin for VDC
6295 """
6296 try:
6297 self.logger.debug("Generate token for vca {} as {} to datacenter {}.".format(self.org_name,
6298 self.user,
6299 self.org_name))
6300 host = self.url
6301 client = Client(host, verify_ssl_certs=False)
6302 client.set_credentials(BasicLoginCredentials(self.user, self.org_name, self.passwd))
6303 # connection object
6304 self.client = client
6305
6306 except:
6307 raise vimconn.vimconnConnectionException("Can't connect to a vCloud director org: "
6308 "{} as user: {}".format(self.org_name, self.user))
6309
6310 if not client:
6311 raise vimconn.vimconnConnectionException("Failed while reconnecting vCD")
6312
6313
6314 def get_vdc_details(self):
6315 """ Get VDC details using pyVcloud Lib
6316
6317 Returns org and vdc object
6318 """
6319 vdc = None
6320 try:
6321 org = Org(self.client, resource=self.client.get_org())
6322 vdc = org.get_vdc(self.tenant_name)
6323 except Exception as e:
6324 # pyvcloud not giving a specific exception, Refresh nevertheless
6325 self.logger.debug("Received exception {}, refreshing token ".format(str(e)))
6326
6327 #Retry once, if failed by refreshing token
6328 if vdc is None:
6329 self.get_token()
6330 org = Org(self.client, resource=self.client.get_org())
6331 vdc = org.get_vdc(self.tenant_name)
6332
6333 return org, vdc
6334
6335
6336 def perform_request(self, req_type, url, headers=None, data=None):
6337 """Perform the POST/PUT/GET/DELETE request."""
6338
6339 #Log REST request details
6340 self.log_request(req_type, url=url, headers=headers, data=data)
6341 # perform request and return its result
6342 if req_type == 'GET':
6343 response = requests.get(url=url,
6344 headers=headers,
6345 verify=False)
6346 elif req_type == 'PUT':
6347 response = requests.put(url=url,
6348 headers=headers,
6349 data=data,
6350 verify=False)
6351 elif req_type == 'POST':
6352 response = requests.post(url=url,
6353 headers=headers,
6354 data=data,
6355 verify=False)
6356 elif req_type == 'DELETE':
6357 response = requests.delete(url=url,
6358 headers=headers,
6359 verify=False)
6360 #Log the REST response
6361 self.log_response(response)
6362
6363 return response
6364
6365
6366 def log_request(self, req_type, url=None, headers=None, data=None):
6367 """Logs REST request details"""
6368
6369 if req_type is not None:
6370 self.logger.debug("Request type: {}".format(req_type))
6371
6372 if url is not None:
6373 self.logger.debug("Request url: {}".format(url))
6374
6375 if headers is not None:
6376 for header in headers:
6377 self.logger.debug("Request header: {}: {}".format(header, headers[header]))
6378
6379 if data is not None:
6380 self.logger.debug("Request data: {}".format(data))
6381
6382
6383 def log_response(self, response):
6384 """Logs REST response details"""
6385
6386 self.logger.debug("Response status code: {} ".format(response.status_code))
6387
6388
6389 def get_task_from_response(self, content):
6390 """
6391 content - API response content(response.content)
6392 return task object
6393 """
6394 xmlroot = XmlElementTree.fromstring(content)
6395 if xmlroot.tag.split('}')[1] == "Task":
6396 return xmlroot
6397 else:
6398 for ele in xmlroot:
6399 if ele.tag.split("}")[1] == "Tasks":
6400 task = ele[0]
6401 break
6402 return task
6403
6404
6405 def power_on_vapp(self,vapp_id, vapp_name):
6406 """
6407 vapp_id - vApp uuid
6408 vapp_name - vAapp name
6409 return - Task object
6410 """
6411 headers = {'Accept':'application/*+xml;version=' + API_VERSION,
6412 'x-vcloud-authorization': self.client._session.headers['x-vcloud-authorization']}
6413
6414 poweron_href = "{}/api/vApp/vapp-{}/power/action/powerOn".format(self.url,
6415 vapp_id)
6416 response = self.perform_request(req_type='POST',
6417 url=poweron_href,
6418 headers=headers)
6419
6420 if response.status_code != 202:
6421 self.logger.error("REST call {} failed reason : {}"\
6422 "status code : {} ".format(poweron_href,
6423 response.content,
6424 response.status_code))
6425 raise vimconn.vimconnException("power_on_vapp() : Failed to power on "\
6426 "vApp {}".format(vapp_name))
6427 else:
6428 poweron_task = self.get_task_from_response(response.content)
6429 return poweron_task
6430
6431