Esempio n. 1
0
    def __init__(self,
                 xaddr,
                 user,
                 passwd,
                 url,
                 cache_location='/tmp/suds',
                 cache_duration=None,
                 encrypt=True,
                 daemon=False,
                 ws_client=None):

        if not os.path.isfile(url):
            raise ONVIFError('%s doesn`t exist!' % url)

        # Create cache object
        # NOTE: if cache_location is specified,
        # onvif must has the permission to access it.
        cache = ObjectCache(location=cache_location)
        # cache_duration: cache will expire in `cache_duration` days
        if cache_duration is not None:
            cache.setduration(days=cache_duration)

        # Convert pathname to url
        self.url = urlparse.urljoin('file:', urllib.pathname2url(url))
        self.xaddr = xaddr
        # Create soap client
        if not ws_client:
            self.ws_client = Client(url=self.url,
                                    location=self.xaddr,
                                    cache=cache)
        else:
            self.ws_client = ws_client
            self.ws_client.set_options(location=self.xaddr)

        # Set soap header for authentication
        self.user = user
        self.passwd = passwd
        # Indicate wether password digest is needed
        self.encrypt = encrypt

        self.daemon = daemon

        self.set_wsse()

        # Method to create type instance of service method defined in WSDL
        self.create_type = self.ws_client.factory.create
Esempio n. 2
0
    def __init__(self, username, password, debug=False):

        # suds has schema cache by default, here you can set manually
        oc = ObjectCache()
        oc.setduration(days=1)
        oc.setlocation("./cache")

        self.debug = debug
        if debug:
            logging.getLogger("suds.server").setLevel(logging.DEBUG)
            logging.getLogger("suds.client").setLevel(logging.DEBUG)
            logging.getLogger("suds.transport").setLevel(logging.DEBUG)
        else:
            logging.getLogger("suds.client").setLevel(logging.CRITICAL)  # hide soap faults

        try:
            self.client = suds.client.Client(url=self.url, username=username, password=password, cache=None)
        except suds.transport.TransportError as (err):
            raise Exception(
                "Authentication error: Invalid username or password."
                if err.httpcode == 401
                else "Unknown initialization error: %s" % str(err)
            )
Esempio n. 3
0
class Sdk(object):
    """
    This class contains all SDK related methods
    """

    def __init__(self, host, login, passwd):
        """
        Initializes the SDK
        """
        self._host = host
        self._username = login
        self._password = passwd
        self._sessionID = None
        self._check_session = True

        self._cache = ObjectCache()
        self._cache.setduration(weeks=1)

        self._client = Client('https://%s/sdk/vimService?wsdl' % host,
                              cache=self._cache,
                              cachingpolicy=1)
        self._client.set_options(location='https://%s/sdk' % host,
                                 plugins=[ValueExtender()])

        service_reference = self._build_property('ServiceInstance')
        self._serviceContent = self._client.service.RetrieveServiceContent(
            service_reference)

        # In case of an ESXi host, this would be 'HostAgent'
        self._is_vcenter = self._serviceContent.about.apiType == 'VirtualCenter'
        if not self._is_vcenter:
            # pylint: disable=line-too-long
            self._login()
            self._esxHost = self._get_object(
                self._serviceContent.rootFolder,
                prop_type='HostSystem',
                traversal={'name': 'FolderTraversalSpec',
                           'type': 'Folder',
                           'path': 'childEntity',
                           'traversal': {'name': 'DatacenterTraversalSpec',
                                         'type': 'Datacenter',
                                         'path': 'hostFolder',
                                         'traversal': {'name': 'DFolderTraversalSpec',
                                                       'type': 'Folder',
                                                       'path': 'childEntity',
                                                       'traversal': {'name': 'ComputeResourceTravelSpec',  # noqa
                                                                     'type': 'ComputeResource',
                                                                     'path': 'host'}}}},
                properties=['name']
            ).obj_identifier
            # pylint: enable=line-too-long
        else:
            self._esxHost = None

    @authenticated(force=True)
    def _get_vcenter_hosts(self):
        """
        reload vCenter info (host name and summary)
        """
        if not self._is_vcenter:
            raise RuntimeError('Must be connected to a vCenter Server API.')
        datacenter_info = self._get_object(
            self._serviceContent.rootFolder,
            prop_type='HostSystem',
            traversal={'name': 'FolderTraversalSpec',
                       'type': 'Folder',
                       'path': 'childEntity',
                       'traversal': {'name': 'DatacenterTraversalSpec',
                                     'type': 'Datacenter',
                                     'path': 'hostFolder',
                                     'traversal': {'name': 'DFolderTraversalSpec',
                                                   'type': 'Folder',
                                                   'path': 'childEntity',
                                                   'traversal': {'name': 'ComputeResourceTravelSpec',  # noqa
                                                                 'type': 'ComputeResource',
                                                                 'path': 'host'}}}},
            properties=['name', 'summary.runtime', 'config.virtualNicManagerInfo.netConfig']
        )
        return datacenter_info

    def get_host_status_by_ip(self, host_ip):
        """
        Return host status by ip, from vcenter info
        Must be connected to a vcenter server api
        """
        host = self._get_host_info_by_ip(host_ip)
        return host.summary.runtime.powerState

    def get_host_status_by_pk(self, pk):
        """
        Return host status by pk, from vcenter info
        Must be connected to a vcenter server api
        """
        host = self._get_host_info_by_pk(pk)
        return host.summary.runtime.powerState

    def get_host_primary_key(self, host_ip):
        """
        Return host primary key, based on current ip
        Must be connected to a vcenter server api
        """
        host = self._get_host_info_by_ip(host_ip)
        return host.obj_identifier.value

    def _get_host_info_by_ip(self, host_ip):
        """
        Return HostSystem object by ip, from vcenter info
        Must be connected to a vcenter server api
        """
        datacenter_info = self._get_vcenter_hosts()
        for host in datacenter_info:
            for nic in host.config.virtualNicManagerInfo.netConfig.VirtualNicManagerNetConfig:
                if nic.nicType == 'management':
                    for vnic in nic.candidateVnic:
                        if vnic.spec.ip.ipAddress == host_ip:
                            return host
        raise RuntimeError('Host with ip {0} not found in datacenter info'.format(host_ip))

    def _get_host_info_by_pk(self, pk):
        """
        Return HostSystem object by pk, from vcenter info
        Must be connected to a vcenter server api
        """
        datacenter_info = self._get_vcenter_hosts()
        for host in datacenter_info:
            if host.obj_identifier.value == pk:
                return host

    def get_hosts(self):
        """
        Gets a neutral list of all hosts available
        """
        host_data = self._get_vcenter_hosts()
        host_data = [] if host_data is None else host_data
        hosts = {}
        for host in host_data:
            ips = []
            for nic in host.config.virtualNicManagerInfo.netConfig.VirtualNicManagerNetConfig:
                if nic.nicType == 'management':
                    for vnic in nic.candidateVnic:
                        ips.append(vnic.spec.ip.ipAddress)
            hosts[host.obj_identifier.value] = {'name': host.name,
                                                'ips': ips}
        return hosts

    def test_connection(self):
        """
        Tests the connection
        """
        self._login()
        return True

    def list_hosts_in_datacenter(self):
        """
        return a list of registered host names in vCenter
        must be connected to a vcenter server api
        """
        datacenter_info = self._get_vcenter_hosts()
        return [host.name for host in datacenter_info]

    def validate_result(self, result, message=None):
        """
        Validates a given result. Returning True if the task succeeded, raising an error if not
        """
        if hasattr(result, '_type') and result._type == 'Task':
            return self.validate_result(self.get_task_info(result), message)
        elif hasattr(result, 'info'):
            if result.info.state == 'success':
                return True
            elif result.info.state == 'error':
                error = result.info.error.localizedMessage
                raise Exception(('%s: %s' % (message, error)) if message else error)
        raise Exception(('%s: %s' % (message, 'Unexpected result'))
                        if message else 'Unexpected result')

    @authenticated()
    def get_task_info(self, task):
        """
        Loads the task details
        """
        return self._get_object(task)

    @authenticated()
    def get_vm_ip_information(self):
        """
        Get the IP information for all vms on a given esxi host
        """
        esxhost = self._validate_host(None)
        configuration = []
        for vm in self._get_object(esxhost,
                                   prop_type='VirtualMachine',
                                   traversal={
                                       'name': 'HostSystemTraversalSpec',
                                       'type': 'HostSystem',
                                       'path': 'vm'},
                                   properties=['name', 'guest.net', 'config.files']):
            vmi = {'id': str(vm.obj_identifier.value),
                   'vmxpath': str(vm.config.files.vmPathName),
                   'name': str(vm.name),
                   'net': []}
            if vm.guest.net:
                for net in vm.guest.net[0]:
                    vmi['net'].append({'mac': str(net.macAddress),
                                       'ipaddresses': [str(i.ipAddress)
                                                       for i in net.ipConfig.ipAddress]})
            configuration.append(vmi)
        return configuration

    @authenticated()
    def exists(self, name=None, key=None):
        """
        Checks whether a vm with a given name or key exists on a given esxi host
        """
        esxhost = self._validate_host(None)
        if name is not None or key is not None:
            try:
                if name is not None:
                    vms = [vm for vm in
                           self._get_object(esxhost,
                                            prop_type='VirtualMachine',
                                            traversal={'name': 'HostSystemTraversalSpec',
                                                       'type': 'HostSystem',
                                                       'path': 'vm'},
                                            properties=['name']) if vm.name == name]
                    if len(vms) == 0:
                        return None
                    else:
                        return vms[0].obj_identifier
                if key is not None:
                    return self._get_object(
                        self._build_property('VirtualMachine', key),
                        properties=['name']).obj_identifier
            except:
                return None
        else:
            raise Exception('A name or key should be passed.')

    @authenticated()
    def get_vm(self, key):
        """
        Retreives a vm object, based on its key
        """
        vmid = self.exists(key=key)
        if vmid is None:
            raise RuntimeError('Virtual Machine with key {} could not be found.'.format(key))
        vm = self._get_object(vmid)
        return vm

    @authenticated()
    def get_vms(self, ip, mountpoint):
        """
        Get all vMachines using a given nfs share
        """
        esxhost = self._validate_host(None)
        datastore = self.get_datastore(ip, mountpoint)
        filtered_vms = []
        vms = self._get_object(esxhost,
                               prop_type='VirtualMachine',
                               traversal={'name': 'HostSystemTraversalSpec',
                                          'type': 'HostSystem',
                                          'path': 'vm'},
                               properties=['name', 'config'])
        for vm in vms:
            mapping = self._get_vm_datastore_mapping(vm)
            if datastore.name in mapping:
                filtered_vms.append(vm)
        return filtered_vms

    @authenticated()
    def set_disk_mode(self, vmid, disks, mode, wait=True):
        """
        Sets the disk mode for a set of disks
        """
        config = self._client.factory.create('ns0:VirtualMachineConfigSpec')
        config.deviceChange = []

        disk_type = type(self._client.factory.create('ns0:VirtualDisk'))

        vmid = self.exists(key=vmid)
        vm = self._get_object(vmid)
        for device in vm.config.hardware.devices:
            if type(device) == disk_type and hasattr(device, 'backing') \
                    and device.backing.fileName in disks:
                backing = self._client.factory.create(
                    'ns0:VirtualDiskFlatVer2BackingInfo')
                backing.diskMode = mode
                device = self._client.factory.create('ns0:VirtualDisk')
                device.backing = backing
                diskSpec = self._client.factory.create(
                    'ns0:VirtualDeviceConfigSpec')
                diskSpec.operation = 'edit'
                diskSpec.fileOperation = None
                diskSpec.device = device
                config.deviceChange.append(diskSpec)

        task = self._client.service.ReconfigVM_Task(vm.obj_identifier, config)

        if wait:
            self.wait_for_task(task)
        return task

    def _create_disk(self, factory, key, disk, unit, datastore):
        """
        Creates a disk spec for a given backing device
        Example for parameter disk: {'name': diskname, 'backingdevice': 'disk-flat.vmdk'}
        """
        deviceInfo = factory.create('ns0:Description')
        deviceInfo.label = disk['name']
        deviceInfo.summary = 'Disk %s' % disk['name']
        backing = factory.create('ns0:VirtualDiskFlatVer2BackingInfo')
        backing.diskMode = 'persistent'
        backing.fileName = '[%s] %s' % (datastore.name, disk['backingdevice'])
        backing.thinProvisioned = True
        device = factory.create('ns0:VirtualDisk')
        device.controllerKey = key
        device.key = -200 - unit
        device.unitNumber = unit
        device.deviceInfo = deviceInfo
        device.backing = backing
        diskSpec = factory.create('ns0:VirtualDeviceConfigSpec')
        diskSpec.operation = 'add'
        diskSpec.fileOperation = None
        diskSpec.device = device
        return diskSpec

    def _create_file_info(self, factory, datastore):
        """
        Creates a file info object
        """
        fileInfo = factory.create('ns0:VirtualMachineFileInfo')
        fileInfo.vmPathName = '[%s]' % datastore
        return fileInfo

    def _create_nic(self, factory, device_type, device_label, device_summary, network, unit):
        """
        Creates a NIC spec
        """
        deviceInfo = factory.create('ns0:Description')
        deviceInfo.label = device_label
        deviceInfo.summary = device_summary
        backing = factory.create('ns0:VirtualEthernetCardNetworkBackingInfo')
        backing.deviceName = network
        device = factory.create('ns0:%s' % device_type)
        device.addressType = 'Generated'
        device.wakeOnLanEnabled = True
        device.controllerKey = 100  # PCI Controller
        device.key = -300 - unit
        device.unitNumber = unit
        device.backing = backing
        device.deviceInfo = deviceInfo
        nicSpec = factory.create('ns0:VirtualDeviceConfigSpec')
        nicSpec.operation = 'add'
        nicSpec.fileOperation = None
        nicSpec.device = device
        return nicSpec

    def _create_disk_controller(self, factory, key):
        """
        Create a disk controller
        """
        deviceInfo = self._client.factory.create('ns0:Description')
        deviceInfo.label = 'SCSI controller 0'
        deviceInfo.summary = 'LSI Logic SAS'
        controller = factory.create('ns0:VirtualLsiLogicSASController')
        controller.busNumber = 0
        controller.key = key
        controller.sharedBus = 'noSharing'
        controller.deviceInfo = deviceInfo
        controllerSpec = factory.create('ns0:VirtualDeviceConfigSpec')
        controllerSpec.operation = 'add'
        controllerSpec.fileOperation = None
        controllerSpec.device = controller
        return controllerSpec

    def _create_option_value(self, factory, key, value):
        """
        Create option values
        """
        option = factory.create('ns0:OptionValue')
        option.key = key
        option.value = value
        return option

    @authenticated()
    def copy_file(self, source, destination, wait=True):
        """
        Copies a file on the datastore
        """
        task = self._client.service.CopyDatastoreFile_Task(
            _this=self._serviceContent.fileManager,
            sourceName=source,
            destinationName=destination)

        if wait:
            self.wait_for_task(task)
        return task

    @authenticated()
    def create_vm_from_template(self, name, source_vm, disks, ip, mountpoint, wait=True):
        """
        Create a vm based on an existing vtemplate on specified tgt hypervisor
        Raises RuntimeError if datastore is not available at (ip, mountpoint)
        """

        esxhost = self._validate_host(None)
        host_data = self._get_host_data(esxhost)

        datastore = self.get_datastore(ip, mountpoint)
        # Build basic config information
        config = self._client.factory.create('ns0:VirtualMachineConfigSpec')
        config.name = name
        config.numCPUs = source_vm.config.hardware.numCPU
        config.memoryMB = source_vm.config.hardware.memoryMB
        config.guestId = source_vm.config.guestId
        config.deviceChange = []
        config.extraConfig = []
        config.files = self._create_file_info(self._client.factory, datastore.name)

        disk_controller_key = -101
        config.deviceChange.append(
            self._create_disk_controller(self._client.factory,
                                         disk_controller_key))

        # Add disk devices
        for disk in disks:
            config.deviceChange.append(
                self._create_disk(self._client.factory, disk_controller_key,
                                  disk, disks.index(disk), datastore))

        # Add network
        nw_type = type(self._client.factory.create('ns0:VirtualEthernetCardNetworkBackingInfo'))
        for device in source_vm.config.hardware.device:
            if hasattr(device, 'backing') and type(device.backing) == nw_type:
                config.deviceChange.append(
                    self._create_nic(self._client.factory,
                                     device.__class__.__name__,
                                     device.deviceInfo.label,
                                     device.deviceInfo.summary,
                                     device.backing.deviceName,
                                     device.unitNumber))

        # Copy additional properties
        extraconfigstoskip = ['nvram']
        for item in source_vm.config.extraConfig:
            if not item.key in extraconfigstoskip:
                config.extraConfig.append(
                    self._create_option_value(self._client.factory,
                                              item.key,
                                              item.value))

        task = self._client.service.CreateVM_Task(host_data['folder'],
                                                  config=config,
                                                  pool=host_data['resourcePool'],
                                                  host=host_data['host'])

        if wait:
            self.wait_for_task(task)
        return task

    @authenticated()
    def clone_vm(self, vmid, name, disks, wait=True):
        """
        Clone a existing VM configuration

        @param vmid: unique id of the vm
        @param name: name of the clone vm
        @param disks: list of disks to use in vm configuration
        @param kvmport: kvm port for the clone vm
        @param esxhost: esx host identifier on which to clone the vm
        @param wait: wait for task to complete or not (True/False)
        """

        esxhost = self._validate_host(None)
        host_data = self._get_host_data(esxhost)

        source_vm_object = self.exists(key=vmid)
        if not source_vm_object:
            raise Exception('VM with key reference %s not found' % vmid)
        source_vm = self._get_object(source_vm_object)
        datastore = self._get_object(source_vm.datastore[0][0])

        # Build basic config information
        config = self._client.factory.create('ns0:VirtualMachineConfigSpec')
        config.name = name
        config.numCPUs = source_vm.config.hardware.numCPU
        config.memoryMB = source_vm.config.hardware.memoryMB
        config.guestId = source_vm.config.guestId
        config.deviceChange = []
        config.extraConfig = []
        config.files = self._create_file_info(
            self._client.factory, datastore.name)

        disk_controller_key = -101
        config.deviceChange.append(
            self._create_disk_controller(self._client.factory,
                                         disk_controller_key))

        # Add disk devices
        for disk in disks:
            config.deviceChange.append(
                self._create_disk(self._client.factory, disk_controller_key,
                                  disk, disks.index(disk), datastore))
            self.copy_file(
                '[{0}] {1}'.format(datastore.name, '%s.vmdk'
                                   % disk['name'].split('_')[-1].replace('-clone', '')),
                '[{0}] {1}'.format(datastore.name, disk['backingdevice']))

        # Add network
        nw_type = type(self._client.factory.create(
            'ns0:VirtualEthernetCardNetworkBackingInfo'))
        for device in source_vm.config.hardware.device:
            if hasattr(device, 'backing') and type(device.backing) == nw_type:
                config.deviceChange.append(
                    self._create_nic(self._client.factory,
                                     device.__class__.__name__,
                                     device.deviceInfo.label,
                                     device.deviceInfo.summary,
                                     device.backing.deviceName,
                                     device.unitNumber))

        # Copy additional properties
        extraconfigstoskip = ['nvram']
        for item in source_vm.config.extraConfig:
            if not item.key in extraconfigstoskip:
                config.extraConfig.append(
                    self._create_option_value(self._client.factory,
                                              item.key,
                                              item.value))

        task = self._client.service.CreateVM_Task(host_data['folder'],
                                                  config=config,
                                                  pool=host_data['resourcePool'],
                                                  host=host_data['host'])
        if wait:
            self.wait_for_task(task)
        return task

    @authenticated()
    def get_vm(self, key):
        vmid = self.exists(key=key)
        if vmid is None:
            raise RuntimeError('Virtual Machine with key {} could not be found.'.format(key))
        vm = self._get_object(vmid)
        return vm

    @authenticated()
    def get_datastore(self, ip, mountpoint):
        """
        @param ip : hypervisor ip to query for datastore presence
        @param mountpoint: nfs mountpoint on hypervisor
        @rtype: sdk datastore object
        @return: object when found else None
        """

        datastore = None
        esxhost = self._validate_host(None)
        host_system = self._get_object(esxhost, properties=['datastore'])
        for store in host_system.datastore[0]:
            store = self._get_object(store)
            if not store.summary.accessible:
                raise RuntimeError('Datastore {0} is not accessible at mountpoint {1}'.format(store.name, mountpoint))
            if hasattr(store.info, 'nas'):
                if store.info.nas.remoteHost == ip and store.info.nas.remotePath == mountpoint:
                    datastore = store

        return datastore

    @authenticated()
    def is_datastore_available(self, ip, mountpoint):
        """
        @param ip : hypervisor ip to query for datastore presence
        @param mountpoint: nfs mountpoint on hypervisor
        @rtype: boolean
        @return: True | False
        """

        if self.get_datastore(ip, mountpoint):
            return True
        else:
            return False

    def make_agnostic_config(self, vm_object):
        regex = '\[([^\]]+)\]\s(.+)'
        match = re.search(regex, vm_object.config.files.vmPathName)
        esxhost = self._validate_host(None)

        config = {'name': vm_object.config.name,
                  'id': vm_object.obj_identifier.value,
                  'backing': {'filename': match.group(2),
                              'datastore': match.group(1)},
                  'disks': [],
                  'datastores': {}}

        for device in vm_object.config.hardware.device:
            if device.__class__.__name__ == 'VirtualDisk':
                if device.backing is not None and \
                        device.backing.fileName is not None:
                    backingfile = device.backing.fileName
                    match = re.search(regex, backingfile)
                    if match:
                        filename = match.group(2)
                        backingfile = filename.replace('.vmdk', '-flat.vmdk')
                        config['disks'].append({'filename': filename,
                                                'backingfilename': backingfile,
                                                'datastore': match.group(1),
                                                'name': device.deviceInfo.label,
                                                'order': device.unitNumber})

        host_system = self._get_object(esxhost, properties=['datastore'])
        for store in host_system.datastore[0]:
            store = self._get_object(store)
            if hasattr(store.info, 'nas'):
                config['datastores'][store.info.name] = '{}:{}'.format(store.info.nas.remoteHost,
                                                                       store.info.nas.remotePath)

        return config

    @authenticated()
    def delete_vm(self, vmid, storagedriver_mountpoint, storagedriver_storage_ip, devicename, wait=False):
        """
        Delete a given vm
        """
        machine = None
        if vmid:
            try:
                machine = self._build_property('VirtualMachine', vmid)
            except Exception as ex:
                logger.error('SDK domain retrieve failed by vmid: {}'.format(ex))
        elif storagedriver_mountpoint and storagedriver_storage_ip and devicename:
            try:
                machine_info = self.get_nfs_datastore_object(storagedriver_storage_ip, storagedriver_mountpoint, devicename)[0]
                machine = self._build_property('VirtualMachine', machine_info.obj_identifier.value)
            except Exception as ex:
                logger.error('SDK domain retrieve failed by nfs datastore info: {}'.format(ex))
        if machine:
            task = self._client.service.Destroy_Task(machine)
            if wait:
                self.wait_for_task(task)

        if storagedriver_mountpoint and devicename:
            vmx_path = os.path.join(storagedriver_mountpoint, devicename)
            if os.path.exists(vmx_path):
                dir_name = os.path.dirname(vmx_path)
                logger.debug('Removing leftover files in {0}'.format(dir_name))
                try:
                    shutil.rmtree(dir_name)
                    logger.debug('Removed dir tree {}'.format(dir_name))
                except Exception as exception:
                    logger.error('Failed to remove dir tree {0}. Reason: {1}'.format(dir_name, str(exception)))
        return task

    @authenticated()
    def get_power_state(self, vmid):
        """
        Get the power state of a given vm
        """
        return self._get_object(self._build_property('VirtualMachine', vmid),
                                properties=['runtime.powerState']).runtime.powerState

    @authenticated()
    def mount_nfs_datastore(self, name, remote_host, remote_path):
        """
        Mounts a given NFS export as a datastore
        """
        esxhost = self._validate_host(None)
        host = self._get_object(esxhost, properties=['datastore',
                                                     'name',
                                                     'configManager',
                                                     'configManager.datastoreSystem'])
        for store in host.datastore[0]:
            store = self._get_object(store)
            if hasattr(store.info, 'nas'):
                if store.info.name == name:
                    if store.info.nas.remoteHost == remote_host and \
                            store.info.nas.remotePath == remote_path:
                        # We'll remove this store, as it's identical to the once we'll add,
                        # forcing a refresh
                        self._client.service.RemoveDatastore(host.configManager.datastoreSystem,
                                                             store.obj_identifier)
                        break
                    else:
                        raise RuntimeError('A datastore {0} already exists, pointing to {1}:{2}'.format(
                            name, store.info.nas.remoteHost, store.info.nas.remotePath
                        ))
        spec = self._client.factory.create('ns0:HostNasVolumeSpec')
        spec.accessMode = 'readWrite'
        spec.localPath = name
        spec.remoteHost = remote_host
        spec.remotePath = remote_path
        spec.type = 'nfs'
        return self._client.service.CreateNasDatastore(host.configManager.datastoreSystem, spec)

    @authenticated()
    def wait_for_task(self, task):
        """
        Wait for a task to be completed
        """
        state = self.get_task_info(task).info.state
        while state in ['running', 'queued']:
            sleep(1)
            state = self.get_task_info(task).info.state

    @authenticated()
    def get_nfs_datastore_object(self, ip, mountpoint, filename):
        """
        ip : "10.130.12.200", string
        mountpoint: "/srv/volumefs", string
        filename: "cfovs001/vhd0(-flat).vmdk"
        identify nfs datastore on this esx host based on ip and mount
        check if filename is present on datastore
        if file is .vmdk return VirtualDisk object for corresponding virtual disk
        if file is .vmx return VirtualMachineConfigInfo for corresponding vm

        @rtype: tuple
        @return: A tuple. First item: vm config, second item: Device if a vmdk was given
        """

        filename = filename.replace('-flat.vmdk', '.vmdk')  # Support both -flat.vmdk and .vmdk
        if not filename.endswith('.vmdk') and not filename.endswith('.vmx'):
            raise ValueError('Unexpected filetype')
        esxhost = self._validate_host(None)

        datastore = self.get_datastore(ip, mountpoint)
        if not datastore:
            raise RuntimeError('Could not find datastore')

        vms = self._get_object(esxhost,
                               prop_type='VirtualMachine',
                               traversal={'name': 'HostSystemTraversalSpec',
                                          'type': 'HostSystem',
                                          'path': 'vm'},
                               properties=['name', 'config'])
        if not vms:
            raise RuntimeError('No vMachines found')
        for vm in vms:
            mapping = self._get_vm_datastore_mapping(vm)
            if datastore.name in mapping:
                if filename in mapping[datastore.name]:
                    return vm, mapping[datastore.name][filename]
        raise RuntimeError('Could not locate given file on the given datastore')

    def _get_vm_datastore_mapping(self, vm):
        """
        Creates a datastore mapping for a vm's devices
        Structure
            {<datastore name>: {<backing filename>: <device>,
                                <backing filename>: <device>},
             <datastore name>: {<backing filename>: <device>}}
        Example
            {'datastore A': {'/machine1/machine1.vmx': <device>,
                             '/machine1/disk1.vmdk': <device>},
             'datastore B': {'/machine1/disk2.vmdk': <device>}}
        """
        def extract_names(backingfile, given_mapping, metadata=None):
            match = re.search('\[([^\]]+)\]\s(.+)', backingfile)
            if match:
                datastore_name = match.group(1)
                filename = match.group(2)
                if datastore_name not in mapping:
                    given_mapping[datastore_name] = {}
                given_mapping[datastore_name][filename] = metadata
            return given_mapping

        virtual_disk_type = self._client.factory.create('ns0:VirtualDisk')
        flat_type = self._client.factory.create('ns0:VirtualDiskFlatVer2BackingInfo')

        mapping = {}
        for device in vm.config.hardware.device:
            if isinstance(device, type(virtual_disk_type)):
                if device.backing is not None and isinstance(device.backing, type(flat_type)):
                    mapping = extract_names(device.backing.fileName, mapping, device)
        mapping = extract_names(vm.config.files.vmPathName, mapping)
        return mapping

    def _get_host_data(self, esxhost):
        """
        Get host data for a given esxhost
        """
        hostobject = self._get_object(
            esxhost, properties=['parent', 'datastore', 'network'])
        datastore = self._get_object(
            hostobject.datastore[0][0], properties=['info']).info
        computeresource = self._get_object(
            hostobject.parent, properties=['resourcePool', 'parent'])
        datacenter = self._get_object(
            computeresource.parent, properties=['parent']).parent
        vm_folder = self._get_object(
            datacenter, properties=['vmFolder']).vmFolder

        return {'host': esxhost,
                'computeResource': computeresource,
                'resourcePool': computeresource.resourcePool,
                'datacenter': datacenter,
                'folder': vm_folder,
                'datastore': datastore,
                'network': hostobject.network[0]}

    def _get_host_iqn_mapping(self, esxhost, rescan=False):
        """
        Get the IQN mapping for a given esx host, optionally rescanning the host
        """
        # pylint: disable=line-too-long
        regex = re.compile('^key-vim.host.PlugStoreTopology.Path-iqn.+?,(?P<iqn>iqn.*?),t,1-(?P<eui>eui.+)$')  # noqa
        # pylint: enable=line-too-long

        hostobject = self._get_object(
            esxhost, properties=['configManager.storageSystem'])
        stg_ssystem = self._get_object(hostobject.configManager.storageSystem,
                                       properties=['storageDeviceInfo',
                                                   'storageDeviceInfo.plugStoreTopology.device'])
        if rescan:
            # Force a rescan of the vmfs
            self._client.service.RescanVmfs(stg_ssystem.obj_identifier)
            stg_ssystem = self._get_object(
                hostobject.configManager.storageSystem,
                properties=['storageDeviceInfo',
                            'storageDeviceInfo.plugStoreTopology.device'])

        device_info_mapping = {}
        for disk in stg_ssystem.storageDeviceInfo.scsiLun:
            device_info_mapping[disk.key] = disk.uuid

        iqn_mapping = {}
        for device in stg_ssystem.storageDeviceInfo.plugStoreTopology\
                                 .device.HostPlugStoreTopologyDevice:
            for path in device.path:
                match = regex.search(path)
                if match:
                    groups = match.groupdict()
                    iqn_mapping[groups['iqn']] = {'eui': groups['eui'],
                                                  'lun': device.lun,
                                                  'uuid': device_info_mapping[device.lun]}

        return iqn_mapping

    def _get_object(self, key_object, prop_type=None, traversal=None, properties=None):
        """
        Gets an object based on a given set of query parameters. Only the requested properties
        will be loaded. If no properties are specified, all will be loaded
        """
        object_spec = self._client.factory.create('ns0:ObjectSpec')
        object_spec.obj = key_object

        property_spec = self._client.factory.create('ns0:PropertySpec')
        property_spec.type = key_object._type if prop_type is None else prop_type
        if properties is None:
            property_spec.all = True
        else:
            property_spec.all = False
            property_spec.pathSet = properties

        if traversal is not None:
            select_set_ptr = object_spec
            while True:
                select_set_ptr.selectSet = self._client.factory.create(
                    'ns0:TraversalSpec')
                select_set_ptr.selectSet.name = traversal['name']
                select_set_ptr.selectSet.type = traversal['type']
                select_set_ptr.selectSet.path = traversal['path']
                if 'traversal' in traversal:
                    traversal = traversal['traversal']
                    select_set_ptr = select_set_ptr.selectSet
                else:
                    break

        property_filter_spec = self._client.factory.create(
            'ns0:PropertyFilterSpec')
        property_filter_spec.objectSet = [object_spec]
        property_filter_spec.propSet = [property_spec]

        found_objects = self._client.service.RetrieveProperties(
            self._serviceContent.propertyCollector,
            [property_filter_spec]
        )

        if len(found_objects) > 0:
            for item in found_objects:
                item.obj_identifier = item.obj
                del item.obj

                if hasattr(item, 'missingSet'):
                    for missing_item in item.missingSet:
                        if missing_item.fault.fault.__class__.__name__ == 'NotAuthenticated':
                            raise NotAuthenticatedException()

                for propSet in item.propSet:
                    if '.' in propSet.name:
                        working_item = item
                        path = str(propSet.name).split('.')
                        part_counter = 0
                        for part in path:
                            part_counter += 1
                            if part_counter < len(path):
                                if not part in working_item.__dict__:
                                    setattr(working_item, part, type(part, (), {})())
                                working_item = working_item.__dict__[part]
                            else:
                                setattr(working_item, part, propSet.val)
                    else:
                        setattr(item, propSet.name, propSet.val)
                del item.propSet
            if len(found_objects) == 1:
                return found_objects[0]
            else:
                return found_objects

        return None

    def _build_property(self, property_name, value=None):
        """
        Create a property object with given name and value
        """
        new_property = Property(property_name)
        new_property._type = property_name
        if value is not None:
            new_property.value = value
        return new_property

    def _validate_host(self, host):
        """
        Validates wheteher a given host is valid
        """
        if host is None:
            if self._is_vcenter:
                raise Exception(
                    'A HostSystem reference is mandatory for a vCenter Server')
            else:
                return self._esxHost
        else:
            if hasattr(host, '_type') and host._type == 'HostSystem':
                return self._get_object(host, properties=['name']).obj_identifier
            else:
                return self._get_object(
                    self._build_property('HostSystem', host),
                    properties=['name']).obj_identifier

    def _login(self):
        """
        Executes a logout (to make sure we're logged out), and logs in again
        """
        self._logout()
        self._sessionID = self._client.service.Login(
            self._serviceContent.sessionManager,
            self._username,
            self._password,
            None
        ).key

    def _logout(self):
        """
        Logs out the current session
        """
        try:
            self._client.service.Logout(self._serviceContent.sessionManager)
        except:
            pass
Esempio n. 4
0
class Sdk(object):
    """
    This class contains all SDK related methods
    """
    def __init__(self, host, login, passwd):
        """
        Initializes the SDK
        """
        self._host = host
        self._username = login
        self._password = passwd
        self._sessionID = None
        self._check_session = True

        self._cache = ObjectCache()
        self._cache.setduration(weeks=1)

        self._client = Client('https://{0}/sdk/vimService?wsdl'.format(host),
                              cache=self._cache,
                              cachingpolicy=1)
        self._client.set_options(location='https://{0}/sdk'.format(host),
                                 plugins=[ValueExtender()])

        service_reference = self._build_property('ServiceInstance')
        self._serviceContent = self._client.service.RetrieveServiceContent(
            service_reference)

        # In case of an ESXi host, this would be 'HostAgent'
        self.is_vcenter = self._serviceContent.about.apiType == 'VirtualCenter'
        if not self.is_vcenter:
            self._login()
            self._esxHost = self._get_object(
                self._serviceContent.rootFolder,
                prop_type='HostSystem',
                traversal={
                    'name': 'FolderTraversalSpec',
                    'type': 'Folder',
                    'path': 'childEntity',
                    'traversal': {
                        'name': 'DatacenterTraversalSpec',
                        'type': 'Datacenter',
                        'path': 'hostFolder',
                        'traversal': {
                            'name': 'DFolderTraversalSpec',
                            'type': 'Folder',
                            'path': 'childEntity',
                            'traversal': {
                                'name': 'ComputeResourceTravelSpec',
                                'type': 'ComputeResource',
                                'path': 'host'
                            }
                        }
                    }
                },
                properties=['name']).obj_identifier
        else:
            # @TODO: We need to extend all calls to specify the ESXi host where the action needs to be executed.
            # We cannot just assume an ESXi host here, as this is important for certain calls like creating a VM.
            self._esxHost = None

    @authenticated(force=True)
    def _get_vcenter_hosts(self):
        """
        reload vCenter info (host name and summary)
        """
        if not self.is_vcenter:
            raise RuntimeError('Must be connected to a vCenter Server API.')
        return self._get_object(self._serviceContent.rootFolder,
                                prop_type='HostSystem',
                                traversal={
                                    'name': 'FolderTraversalSpec',
                                    'type': 'Folder',
                                    'path': 'childEntity',
                                    'traversal': {
                                        'name': 'DatacenterTraversalSpec',
                                        'type': 'Datacenter',
                                        'path': 'hostFolder',
                                        'traversal': {
                                            'name': 'DFolderTraversalSpec',
                                            'type': 'Folder',
                                            'path': 'childEntity',
                                            'traversal': {
                                                'name':
                                                'ComputeResourceTravelSpec',
                                                'type': 'ComputeResource',
                                                'path': 'host'
                                            }
                                        }
                                    }
                                },
                                properties=[
                                    'name', 'summary.runtime',
                                    'config.virtualNicManagerInfo.netConfig'
                                ],
                                as_list=True)

    def get_host_status_by_ip(self, host_ip):
        """
        Return host status by ip, from vcenter info
        Must be connected to a vcenter server api
        """
        host = self._get_host_info_by_ip(host_ip)
        return host.summary.runtime.powerState

    def get_host_status_by_pk(self, pk):
        """
        Return host status by pk, from vcenter info
        Must be connected to a vcenter server api
        """
        host = self._get_host_info_by_pk(pk)
        return host.summary.runtime.powerState

    def get_host_primary_key(self, host_ip):
        """
        Return host primary key, based on current ip
        Must be connected to a vcenter server api
        """
        host = self._get_host_info_by_ip(host_ip)
        return host.obj_identifier.value

    def _get_host_info_by_ip(self, host_ip):
        """
        Return HostSystem object by ip, from vcenter info
        Must be connected to a vcenter server api
        """
        datacenter_info = self._get_vcenter_hosts()
        for host in datacenter_info:
            for nic in host.config.virtualNicManagerInfo.netConfig.VirtualNicManagerNetConfig:
                if nic.nicType == 'management':
                    for vnic in nic.candidateVnic:
                        if vnic.spec.ip.ipAddress == host_ip:
                            return host
        raise RuntimeError(
            'Host with ip {0} not found in datacenter info'.format(host_ip))

    def _get_host_info_by_pk(self, pk):
        """
        Return HostSystem object by pk, from vcenter info
        Must be connected to a vcenter server api
        """
        datacenter_info = self._get_vcenter_hosts()
        for host in datacenter_info:
            if host.obj_identifier.value == pk:
                return host

    def get_hosts(self):
        """
        Gets a neutral list of all hosts available
        """
        host_data = self._get_vcenter_hosts()
        host_data = [] if host_data is None else host_data
        hosts = {}
        for host in host_data:
            ips = []
            for nic in host.config.virtualNicManagerInfo.netConfig.VirtualNicManagerNetConfig:
                if nic.nicType == 'management':
                    for vnic in nic.candidateVnic:
                        ips.append(vnic.spec.ip.ipAddress)
            hosts[host.obj_identifier.value] = {'name': host.name, 'ips': ips}
        return hosts

    def test_connection(self):
        """
        Tests the connection
        """
        self._login()
        return True

    def list_hosts_in_datacenter(self):
        """
        return a list of registered host names in vCenter
        must be connected to a vcenter server api
        """
        datacenter_info = self._get_vcenter_hosts()
        return [host.name for host in datacenter_info]

    def validate_result(self, result, message=None):
        """
        Validates a given result. Returning True if the task succeeded, raising an error if not
        """
        if hasattr(result, '_type') and result._type == 'Task':
            return self.validate_result(self.get_task_info(result), message)
        elif hasattr(result, 'info'):
            if result.info.state == 'success':
                return True
            elif result.info.state == 'error':
                error = result.info.error.localizedMessage
                raise Exception((
                    '{0}: {1}'.format(message, error)) if message else error)
        raise Exception(('{0}: {1}'.format(message, 'Unexpected result')
                         ) if message else 'Unexpected result')

    @authenticated()
    def get_task_info(self, task):
        """
        Loads the task details
        """
        return self._get_object(task)

    @authenticated()
    def get_vm_ip_information(self):
        """
        Get the IP information for all vms on a given esxi host
        """
        esxhost = self._validate_host(None)
        configuration = []
        for vm in self._get_object(
                esxhost,
                prop_type='VirtualMachine',
                traversal={
                    'name': 'HostSystemTraversalSpec',
                    'type': 'HostSystem',
                    'path': 'vm'
                },
                properties=['name', 'guest.net', 'config.files'],
                as_list=True):
            vmi = {
                'id': str(vm.obj_identifier.value),
                'vmxpath': str(vm.config.files.vmPathName),
                'name': str(vm.name),
                'net': []
            }
            if vm.guest.net:
                for net in vm.guest.net[0]:
                    vmi['net'].append({
                        'mac':
                        str(net.macAddress),
                        'ipaddresses':
                        [str(i.ipAddress) for i in net.ipConfig.ipAddress]
                    })
            configuration.append(vmi)
        return configuration

    @authenticated()
    def exists(self, name=None, key=None):
        """
        Checks whether a vm with a given name or key exists on a given esxi host
        """
        esxhost = self._validate_host(self._esxHost)
        if name is not None or key is not None:
            try:
                if name is not None:
                    vms = [
                        vm for vm in self._get_object(
                            esxhost,
                            prop_type='VirtualMachine',
                            traversal={
                                'name': 'HostSystemTraversalSpec',
                                'type': 'HostSystem',
                                'path': 'vm'
                            },
                            properties=['name'],
                            as_list=True) if vm.name == name
                    ]
                    if len(vms) == 0:
                        return None
                    else:
                        return vms[0].obj_identifier
                if key is not None:
                    return self._get_object(Sdk._build_property(
                        'VirtualMachine', key),
                                            properties=['name']).obj_identifier
            except:
                return None
        else:
            raise Exception('A name or key should be passed.')

    @authenticated()
    def get_vm(self, key):
        """
        Retreives a vm object, based on its key
        """
        vmid = self.exists(key=key)
        if vmid is None:
            raise RuntimeError(
                'Virtual Machine with key {} could not be found.'.format(key))
        vm = self._get_object(vmid)
        return vm

    @authenticated()
    def get_vm_device_info(self, key):
        """
        Return a vm config, based on its key
        """
        vm = self.get_vm(key)
        filename = vm.config.files.vmPathName
        regex = '\[([^\]]+)\]\s(.+)'
        match = re.search(regex, filename)
        disks = self._get_vmachine_vdisks(vm)
        return {
            'file_name': match.group(2),
            'host_name': vm.name,
            'vpool_name': match.group(1),
            'disks': disks
        }

    @authenticated()
    def get_all_vms(self):
        """
        Get all vMachines on all esxhosts registered to this vCenter
        :return: list
        """
        if not self.is_vcenter:
            raise RuntimeError('Must be connected to a vCenter Server API.')
        hosts = self._get_vcenter_hosts()
        guests = []
        for host in hosts:
            esxhost = self._validate_host(host.obj_identifier)
            vms = self._get_object(esxhost,
                                   prop_type='VirtualMachine',
                                   traversal={
                                       'name': 'HostSystemTraversalSpec',
                                       'type': 'HostSystem',
                                       'path': 'vm'
                                   },
                                   properties=['name', 'config'],
                                   as_list=True)
            for vm in vms:
                guests.append({
                    'id': vm.obj_identifier.value,
                    'name': vm.name,
                    'instance_name': vm.name
                })
        return guests

    def _get_vmachine_vdisks(self, vm_object):
        disks = []
        regex = '\[([^\]]+)\]\s(.+)'
        disk_type = type(self._client.factory.create('ns0:VirtualDisk'))
        for device in vm_object.config.hardware.device:
            if isinstance(device, disk_type):
                backingfile = device.backing.fileName
                match = re.search(regex, backingfile)
                if match:
                    disks.append({
                        'filename': match.group(2),
                        'datastore': match.group(1),
                        'name': device.deviceInfo.label
                    })
        return disks

    @authenticated()
    def get_all_vdisks(self):
        """
        Get all vDisks on all esxhosts registered to this vCenter
        # similar to cinder.volumes.list()
        :return: list
        """
        if not self.is_vcenter:
            raise RuntimeError('Must be connected to a vCenter Server API.')
        hosts = self._get_vcenter_hosts()

        disks = []
        for host in hosts:
            esxhost = self._validate_host(host.obj_identifier)
            vms = self._get_object(esxhost,
                                   prop_type='VirtualMachine',
                                   traversal={
                                       'name': 'HostSystemTraversalSpec',
                                       'type': 'HostSystem',
                                       'path': 'vm'
                                   },
                                   properties=['name', 'config'],
                                   as_list=True)
            for vm in vms:
                disks.extend(self._get_vmachine_vdisks(vm))
        return disks

    @authenticated()
    def get_vms(self, ip, mountpoint):
        """
        Get all vMachines using a given nfs share
        """
        esxhost = self._validate_host(None)
        datastore = self.get_datastore(ip, mountpoint)
        filtered_vms = []
        vms = self._get_object(esxhost,
                               prop_type='VirtualMachine',
                               traversal={
                                   'name': 'HostSystemTraversalSpec',
                                   'type': 'HostSystem',
                                   'path': 'vm'
                               },
                               properties=['name', 'config'],
                               as_list=True)
        for vm in vms:
            mapping = self._get_vm_datastore_mapping(vm)
            if datastore.name in mapping:
                filtered_vms.append(vm)
        return filtered_vms

    @authenticated()
    def set_disk_mode(self, vmid, disks, mode, wait=True):
        """
        Sets the disk mode for a set of disks
        """
        config = self._client.factory.create('ns0:VirtualMachineConfigSpec')
        config.deviceChange = []

        disk_type = type(self._client.factory.create('ns0:VirtualDisk'))

        vmid = self.exists(key=vmid)
        vm = self._get_object(vmid)
        for device in vm.config.hardware.devices:
            if type(device) == disk_type and hasattr(
                    device, 'backing') and device.backing.fileName in disks:
                backing = self._client.factory.create(
                    'ns0:VirtualDiskFlatVer2BackingInfo')
                backing.diskMode = mode
                device = self._client.factory.create('ns0:VirtualDisk')
                device.backing = backing
                disk_spec = self._client.factory.create(
                    'ns0:VirtualDeviceConfigSpec')
                disk_spec.operation = 'edit'
                disk_spec.fileOperation = None
                disk_spec.device = device
                config.deviceChange.append(disk_spec)

        task = self._client.service.ReconfigVM_Task(vm.obj_identifier, config)

        if wait:
            self.wait_for_task(task)
        return task

    @staticmethod
    def _create_disk(factory, key, disk, unit, datastore):
        """
        Creates a disk spec for a given backing device
        Example for parameter disk: {'name': diskname, 'backingdevice': 'disk-flat.vmdk'}
        """
        device_info = factory.create('ns0:Description')
        device_info.label = disk['name']
        device_info.summary = 'Disk {0}'.format(disk['name'])
        backing = factory.create('ns0:VirtualDiskFlatVer2BackingInfo')
        backing.diskMode = 'persistent'
        backing.fileName = '[{0}] {1}'.format(datastore.name,
                                              disk['backingdevice'])
        backing.thinProvisioned = True
        device = factory.create('ns0:VirtualDisk')
        device.controllerKey = key
        device.key = -200 - unit
        device.unitNumber = unit
        device.deviceInfo = device_info
        device.backing = backing
        disk_spec = factory.create('ns0:VirtualDeviceConfigSpec')
        disk_spec.operation = 'add'
        disk_spec.fileOperation = None
        disk_spec.device = device
        return disk_spec

    @staticmethod
    def _create_file_info(factory, datastore):
        """
        Creates a file info object
        """
        file_info = factory.create('ns0:VirtualMachineFileInfo')
        file_info.vmPathName = '[{0}]'.format(datastore)
        return file_info

    @staticmethod
    def _create_nic(factory, device_type, device_label, device_summary,
                    network, unit):
        """
        Creates a NIC spec
        """
        device_info = factory.create('ns0:Description')
        device_info.label = device_label
        device_info.summary = device_summary
        backing = factory.create('ns0:VirtualEthernetCardNetworkBackingInfo')
        backing.deviceName = network
        device = factory.create('ns0:{0}'.format(device_type))
        device.addressType = 'Generated'
        device.wakeOnLanEnabled = True
        device.controllerKey = 100  # PCI Controller
        device.key = -300 - unit
        device.unitNumber = unit
        device.backing = backing
        device.deviceInfo = device_info
        nic_spec = factory.create('ns0:VirtualDeviceConfigSpec')
        nic_spec.operation = 'add'
        nic_spec.fileOperation = None
        nic_spec.device = device
        return nic_spec

    def _create_disk_controller(self, factory, key):
        """
        Create a disk controller
        """
        device_info = self._client.factory.create('ns0:Description')
        device_info.label = 'SCSI controller 0'
        device_info.summary = 'LSI Logic SAS'
        controller = factory.create('ns0:VirtualLsiLogicSASController')
        controller.busNumber = 0
        controller.key = key
        controller.sharedBus = 'noSharing'
        controller.deviceInfo = device_info
        controller_spec = factory.create('ns0:VirtualDeviceConfigSpec')
        controller_spec.operation = 'add'
        controller_spec.fileOperation = None
        controller_spec.device = controller
        return controller_spec

    @staticmethod
    def _create_option_value(factory, key, value):
        """
        Create option values
        """
        option = factory.create('ns0:OptionValue')
        option.key = key
        option.value = value
        return option

    @authenticated()
    def copy_file(self, source, destination, wait=True):
        """
        Copies a file on the datastore
        """
        task = self._client.service.CopyDatastoreFile_Task(
            _this=self._serviceContent.fileManager,
            sourceName=source,
            destinationName=destination)

        if wait:
            self.wait_for_task(task)
        return task

    @authenticated()
    def create_vm_from_template(self,
                                name,
                                source_vm,
                                disks,
                                ip,
                                mountpoint,
                                wait=True):
        """
        Create a vm based on an existing vtemplate on specified tgt hypervisor
        Raises RuntimeError if datastore is not available at (ip, mountpoint)
        """

        esxhost = self._validate_host(None)
        host_data = self._get_host_data(esxhost)

        datastore = self.get_datastore(ip, mountpoint)
        # Build basic config information
        config = self._client.factory.create('ns0:VirtualMachineConfigSpec')
        config.name = name
        config.numCPUs = source_vm.config.hardware.numCPU
        config.memoryMB = source_vm.config.hardware.memoryMB
        config.guestId = source_vm.config.guestId
        config.deviceChange = []
        config.extraConfig = []
        config.files = Sdk._create_file_info(self._client.factory,
                                             datastore.name)

        disk_controller_key = -101
        config.deviceChange.append(
            self._create_disk_controller(self._client.factory,
                                         disk_controller_key))

        # Add disk devices
        for disk in disks:
            config.deviceChange.append(
                Sdk._create_disk(self._client.factory, disk_controller_key,
                                 disk, disks.index(disk), datastore))

        # Add network
        nw_type = type(
            self._client.factory.create(
                'ns0:VirtualEthernetCardNetworkBackingInfo'))
        for device in source_vm.config.hardware.device:
            if hasattr(device, 'backing') and type(device.backing) == nw_type:
                config.deviceChange.append(
                    Sdk._create_nic(self._client.factory,
                                    device.__class__.__name__,
                                    device.deviceInfo.label,
                                    device.deviceInfo.summary,
                                    device.backing.deviceName,
                                    device.unitNumber))

        # Copy additional properties
        extraconfigstoskip = ['nvram']
        for item in source_vm.config.extraConfig:
            if item.key not in extraconfigstoskip:
                config.extraConfig.append(
                    Sdk._create_option_value(self._client.factory, item.key,
                                             item.value))

        task = self._client.service.CreateVM_Task(
            host_data['folder'],
            config=config,
            pool=host_data['resourcePool'],
            host=host_data['host'])

        if wait:
            self.wait_for_task(task)
        return task

    @authenticated()
    def clone_vm(self, vmid, name, disks, wait=True):
        """
        Clone a existing VM configuration

        @param vmid: unique id of the vm
        @param name: name of the clone vm
        @param disks: list of disks to use in vm configuration
        @param wait: wait for task to complete or not (True/False)
        """

        esxhost = self._validate_host(None)
        host_data = self._get_host_data(esxhost)

        source_vm_object = self.exists(key=vmid)
        if not source_vm_object:
            raise Exception('VM with key reference {0} not found'.format(vmid))
        source_vm = self._get_object(source_vm_object)
        datastore = self._get_object(source_vm.datastore[0][0])

        # Build basic config information
        config = self._client.factory.create('ns0:VirtualMachineConfigSpec')
        config.name = name
        config.numCPUs = source_vm.config.hardware.numCPU
        config.memoryMB = source_vm.config.hardware.memoryMB
        config.guestId = source_vm.config.guestId
        config.deviceChange = []
        config.extraConfig = []
        config.files = Sdk._create_file_info(self._client.factory,
                                             datastore.name)

        disk_controller_key = -101
        config.deviceChange.append(
            self._create_disk_controller(self._client.factory,
                                         disk_controller_key))

        # Add disk devices
        for disk in disks:
            config.deviceChange.append(
                Sdk._create_disk(self._client.factory, disk_controller_key,
                                 disk, disks.index(disk), datastore))
            self.copy_file(
                '[{0}] {1}.vmdk'.format(
                    datastore.name,
                    disk['name'].split('_')[-1].replace('-clone', '')),
                '[{0}] {1}'.format(datastore.name, disk['backingdevice']))

        # Add network
        nw_type = type(
            self._client.factory.create(
                'ns0:VirtualEthernetCardNetworkBackingInfo'))
        for device in source_vm.config.hardware.device:
            if hasattr(device, 'backing') and type(device.backing) == nw_type:
                config.deviceChange.append(
                    Sdk._create_nic(self._client.factory,
                                    device.__class__.__name__,
                                    device.deviceInfo.label,
                                    device.deviceInfo.summary,
                                    device.backing.deviceName,
                                    device.unitNumber))

        # Copy additional properties
        extraconfigstoskip = ['nvram']
        for item in source_vm.config.extraConfig:
            if item.key not in extraconfigstoskip:
                config.extraConfig.append(
                    Sdk._create_option_value(self._client.factory, item.key,
                                             item.value))

        task = self._client.service.CreateVM_Task(
            host_data['folder'],
            config=config,
            pool=host_data['resourcePool'],
            host=host_data['host'])
        if wait:
            self.wait_for_task(task)
        return task

    @authenticated()
    def get_datastore(self, ip, mountpoint, host=None):
        """
        @param ip : hypervisor ip to query for datastore presence
        @param mountpoint: nfs mountpoint on hypervisor
        @rtype: sdk datastore object
        @return: object when found else None
        """

        datastore = None
        if host is None:
            host = self._esxHost
        esxhost = self._validate_host(host)
        host_system = self._get_object(esxhost, properties=['datastore'])
        for store in host_system.datastore[0]:
            store = self._get_object(store)
            if not store.summary.accessible:
                logger.warning(
                    'Datastore {0} is not accessible, skipping'.format(
                        store.name))
            if hasattr(store.info, 'nas'):
                if store.info.nas.remoteHost == ip and store.info.nas.remotePath == mountpoint:
                    datastore = store

        return datastore

    @authenticated()
    def is_datastore_available(self, ip, mountpoint):
        """
        @param ip : hypervisor ip to query for datastore presence
        @param mountpoint: nfs mountpoint on hypervisor
        @rtype: boolean
        @return: True | False
        """

        if self.get_datastore(ip, mountpoint):
            return True
        else:
            return False

    def make_agnostic_config(self, vm_object, host=None):
        regex = '\[([^\]]+)\]\s(.+)'
        match = re.search(regex, vm_object.config.files.vmPathName)
        if host is None:
            host = self._esxHost
        esxhost = self._validate_host(host)

        config = {
            'name': vm_object.config.name,
            'id': vm_object.obj_identifier.value,
            'backing': {
                'filename': match.group(2),
                'datastore': match.group(1)
            },
            'disks': [],
            'datastores': {}
        }

        for device in vm_object.config.hardware.device:
            if device.__class__.__name__ == 'VirtualDisk':
                if device.backing is not None and device.backing.fileName is not None:
                    backingfile = device.backing.fileName
                    match = re.search(regex, backingfile)
                    if match:
                        filename = match.group(2)
                        backingfile = filename.replace('.vmdk', '-flat.vmdk')
                        backingfile = '/{0}'.format(backingfile.strip('/'))
                        config['disks'].append({
                            'filename': filename,
                            'backingfilename': backingfile,
                            'datastore': match.group(1),
                            'name': device.deviceInfo.label,
                            'order': device.unitNumber
                        })

        host_system = self._get_object(esxhost, properties=['datastore'])
        for store in host_system.datastore[0]:
            store = self._get_object(store)
            if hasattr(store.info, 'nas'):
                config['datastores'][store.info.name] = '{}:{}'.format(
                    store.info.nas.remoteHost, store.info.nas.remotePath)

        return config

    @authenticated()
    def delete_vm(self,
                  vmid,
                  storagedriver_mountpoint,
                  storagedriver_storage_ip,
                  devicename,
                  wait=False):
        """
        Delete a given vm
        """
        machine = None
        task = None
        if vmid:
            try:
                machine = Sdk._build_property('VirtualMachine', vmid)
            except Exception as ex:
                logger.error(
                    'SDK domain retrieve failed by vmid: {}'.format(ex))
        elif storagedriver_mountpoint and storagedriver_storage_ip and devicename:
            try:
                machine_info = self.get_nfs_datastore_object(
                    storagedriver_storage_ip, storagedriver_mountpoint,
                    devicename)[0]
                machine = Sdk._build_property(
                    'VirtualMachine', machine_info.obj_identifier.value)
            except Exception as ex:
                logger.error(
                    'SDK domain retrieve failed by nfs datastore info: {}'.
                    format(ex))
        if machine:
            task = self._client.service.Destroy_Task(machine)
            if wait:
                self.wait_for_task(task)

        if storagedriver_mountpoint and devicename:
            vmx_path = os.path.join(storagedriver_mountpoint, devicename)
            if os.path.exists(vmx_path):
                dir_name = os.path.dirname(vmx_path)
                logger.debug('Removing leftover files in {0}'.format(dir_name))
                try:
                    shutil.rmtree(dir_name)
                    logger.debug('Removed dir tree {}'.format(dir_name))
                except Exception as exception:
                    logger.error(
                        'Failed to remove dir tree {0}. Reason: {1}'.format(
                            dir_name, str(exception)))
        return task

    @authenticated()
    def get_power_state(self, vmid):
        """
        Get the power state of a given vm
        """
        return self._get_object(Sdk._build_property('VirtualMachine', vmid),
                                properties=['runtime.powerState'
                                            ]).runtime.powerState

    @authenticated()
    def mount_nfs_datastore(self, name, remote_host, remote_path):
        """
        Mounts a given NFS export as a datastore
        """
        esxhost = self._validate_host(None)
        host = self._get_object(esxhost,
                                properties=[
                                    'datastore', 'name', 'configManager',
                                    'configManager.datastoreSystem'
                                ])
        for store in host.datastore[0]:
            store = self._get_object(store)
            if hasattr(store.info, 'nas'):
                if store.info.name == name:
                    if store.info.nas.remoteHost == remote_host and store.info.nas.remotePath == remote_path:
                        # We'll remove this store, as it's identical to the once we'll add,
                        # forcing a refresh
                        self._client.service.RemoveDatastore(
                            host.configManager.datastoreSystem,
                            store.obj_identifier)
                        break
                    else:
                        raise RuntimeError(
                            'A datastore {0} already exists, pointing to {1}:{2}'
                            .format(name, store.info.nas.remoteHost,
                                    store.info.nas.remotePath))
        spec = self._client.factory.create('ns0:HostNasVolumeSpec')
        spec.accessMode = 'readWrite'
        spec.localPath = name
        spec.remoteHost = remote_host
        spec.remotePath = remote_path
        spec.type = 'nfs'
        return self._client.service.CreateNasDatastore(
            host.configManager.datastoreSystem, spec)

    @authenticated()
    def wait_for_task(self, task):
        """
        Wait for a task to be completed
        """
        state = self.get_task_info(task).info.state
        while state in ['running', 'queued']:
            sleep(1)
            state = self.get_task_info(task).info.state

    @authenticated()
    def get_nfs_datastore_object(self, ip, mountpoint, filename, host=None):
        """
        ip : "10.130.12.200", string
        mountpoint: "/srv/volumefs", string
        filename: "cfovs001/vhd0(-flat).vmdk"
        identify nfs datastore on this esx host based on ip and mount
        check if filename is present on datastore
        if file is .vmdk return VirtualDisk object for corresponding virtual disk
        if file is .vmx return VirtualMachineConfigInfo for corresponding vm

        @rtype: tuple
        @return: A tuple. First item: vm config, second item: Device if a vmdk was given
        """

        filename = filename.replace(
            '-flat.vmdk', '.vmdk')  # Support both -flat.vmdk and .vmdk
        if not filename.endswith('.vmdk') and not filename.endswith('.vmx'):
            raise ValueError('Unexpected filetype')
        if host is None:
            host = self._esxHost
        esxhost = self._validate_host(host)

        datastore = self.get_datastore(ip, mountpoint, host=esxhost)
        if not datastore:
            raise RuntimeError('Could not find datastore')

        vms = self._get_object(esxhost,
                               prop_type='VirtualMachine',
                               traversal={
                                   'name': 'HostSystemTraversalSpec',
                                   'type': 'HostSystem',
                                   'path': 'vm'
                               },
                               properties=['name', 'config'],
                               as_list=True)
        if not vms:
            raise RuntimeError('No vMachines found')
        for vm in vms:
            mapping = self._get_vm_datastore_mapping(vm)
            if datastore.name in mapping:
                if filename in mapping[datastore.name]:
                    return vm, mapping[datastore.name][filename]
        raise RuntimeError(
            'Could not locate given file on the given datastore')

    def file_exists(self, ip, mountpoint, filename):
        try:
            self.get_nfs_datastore_object(ip, mountpoint, filename)
            return True
        except Exception, ex:
            logger.debug('File does not exist: {0}'.format(ex))
            return False
Esempio n. 5
0
class Sdk(object):
    """
    This class contains all SDK related methods
    """
    def __init__(self, host, login, passwd):
        """
        Initializes the SDK
        """
        self._host = host
        self._username = login
        self._password = passwd
        self._sessionID = None
        self._check_session = True

        self._cache = ObjectCache()
        self._cache.setduration(weeks=1)

        self._client = Client('https://%s/sdk/vimService?wsdl' % host,
                              cache=self._cache,
                              cachingpolicy=1)
        self._client.set_options(location='https://%s/sdk' % host,
                                 plugins=[ValueExtender()])

        service_reference = self._build_property('ServiceInstance')
        self._serviceContent = self._client.service.RetrieveServiceContent(
            service_reference)

        # In case of an ESXi host, this would be 'HostAgent'
        self._is_vcenter = self._serviceContent.about.apiType == 'VirtualCenter'
        if not self._is_vcenter:
            # pylint: disable=line-too-long
            self._login()
            self._esxHost = self._get_object(
                self._serviceContent.rootFolder,
                prop_type='HostSystem',
                traversal={
                    'name': 'FolderTraversalSpec',
                    'type': 'Folder',
                    'path': 'childEntity',
                    'traversal': {
                        'name': 'DatacenterTraversalSpec',
                        'type': 'Datacenter',
                        'path': 'hostFolder',
                        'traversal': {
                            'name': 'DFolderTraversalSpec',
                            'type': 'Folder',
                            'path': 'childEntity',
                            'traversal': {
                                'name': 'ComputeResourceTravelSpec',  # noqa
                                'type': 'ComputeResource',
                                'path': 'host'
                            }
                        }
                    }
                },
                properties=['name']).obj_identifier
            # pylint: enable=line-too-long
        else:
            self._esxHost = None

    @authenticated(force=True)
    def _get_vcenter_hosts(self):
        """
        reload vCenter info (host name and summary)
        """
        if not self._is_vcenter:
            raise RuntimeError('Must be connected to a vCenter Server API.')
        datacenter_info = self._get_object(
            self._serviceContent.rootFolder,
            prop_type='HostSystem',
            traversal={
                'name': 'FolderTraversalSpec',
                'type': 'Folder',
                'path': 'childEntity',
                'traversal': {
                    'name': 'DatacenterTraversalSpec',
                    'type': 'Datacenter',
                    'path': 'hostFolder',
                    'traversal': {
                        'name': 'DFolderTraversalSpec',
                        'type': 'Folder',
                        'path': 'childEntity',
                        'traversal': {
                            'name': 'ComputeResourceTravelSpec',  # noqa
                            'type': 'ComputeResource',
                            'path': 'host'
                        }
                    }
                }
            },
            properties=[
                'name', 'summary.runtime',
                'config.virtualNicManagerInfo.netConfig'
            ])
        return datacenter_info

    def get_host_status_by_ip(self, host_ip):
        """
        Return host status by ip, from vcenter info
        Must be connected to a vcenter server api
        """
        host = self._get_host_info_by_ip(host_ip)
        return host.summary.runtime.powerState

    def get_host_status_by_pk(self, pk):
        """
        Return host status by pk, from vcenter info
        Must be connected to a vcenter server api
        """
        host = self._get_host_info_by_pk(pk)
        return host.summary.runtime.powerState

    def get_host_primary_key(self, host_ip):
        """
        Return host primary key, based on current ip
        Must be connected to a vcenter server api
        """
        host = self._get_host_info_by_ip(host_ip)
        return host.obj_identifier.value

    def _get_host_info_by_ip(self, host_ip):
        """
        Return HostSystem object by ip, from vcenter info
        Must be connected to a vcenter server api
        """
        datacenter_info = self._get_vcenter_hosts()
        for host in datacenter_info:
            for nic in host.config.virtualNicManagerInfo.netConfig.VirtualNicManagerNetConfig:
                if nic.nicType == 'management':
                    for vnic in nic.candidateVnic:
                        if vnic.spec.ip.ipAddress == host_ip:
                            return host
        raise RuntimeError(
            'Host with ip {0} not found in datacenter info'.format(host_ip))

    def _get_host_info_by_pk(self, pk):
        """
        Return HostSystem object by pk, from vcenter info
        Must be connected to a vcenter server api
        """
        datacenter_info = self._get_vcenter_hosts()
        for host in datacenter_info:
            if host.obj_identifier.value == pk:
                return host

    def get_hosts(self):
        """
        Gets a neutral list of all hosts available
        """
        host_data = self._get_vcenter_hosts()
        host_data = [] if host_data is None else host_data
        hosts = {}
        for host in host_data:
            ips = []
            for nic in host.config.virtualNicManagerInfo.netConfig.VirtualNicManagerNetConfig:
                if nic.nicType == 'management':
                    for vnic in nic.candidateVnic:
                        ips.append(vnic.spec.ip.ipAddress)
            hosts[host.obj_identifier.value] = {'name': host.name, 'ips': ips}
        return hosts

    def test_connection(self):
        """
        Tests the connection
        """
        self._login()
        return True

    def list_hosts_in_datacenter(self):
        """
        return a list of registered host names in vCenter
        must be connected to a vcenter server api
        """
        datacenter_info = self._get_vcenter_hosts()
        return [host.name for host in datacenter_info]

    def validate_result(self, result, message=None):
        """
        Validates a given result. Returning True if the task succeeded, raising an error if not
        """
        if hasattr(result, '_type') and result._type == 'Task':
            return self.validate_result(self.get_task_info(result), message)
        elif hasattr(result, 'info'):
            if result.info.state == 'success':
                return True
            elif result.info.state == 'error':
                error = result.info.error.localizedMessage
                raise Exception(('%s: %s' %
                                 (message, error)) if message else error)
        raise Exception(('%s: %s' % (message, 'Unexpected result')
                         ) if message else 'Unexpected result')

    @authenticated()
    def get_task_info(self, task):
        """
        Loads the task details
        """
        return self._get_object(task)

    @authenticated()
    def get_vm_ip_information(self):
        """
        Get the IP information for all vms on a given esxi host
        """
        esxhost = self._validate_host(None)
        configuration = []
        for vm in self._get_object(
                esxhost,
                prop_type='VirtualMachine',
                traversal={
                    'name': 'HostSystemTraversalSpec',
                    'type': 'HostSystem',
                    'path': 'vm'
                },
                properties=['name', 'guest.net', 'config.files']):
            vmi = {
                'id': str(vm.obj_identifier.value),
                'vmxpath': str(vm.config.files.vmPathName),
                'name': str(vm.name),
                'net': []
            }
            if vm.guest.net:
                for net in vm.guest.net[0]:
                    vmi['net'].append({
                        'mac':
                        str(net.macAddress),
                        'ipaddresses':
                        [str(i.ipAddress) for i in net.ipConfig.ipAddress]
                    })
            configuration.append(vmi)
        return configuration

    @authenticated()
    def exists(self, name=None, key=None):
        """
        Checks whether a vm with a given name or key exists on a given esxi host
        """
        esxhost = self._validate_host(None)
        if name is not None or key is not None:
            try:
                if name is not None:
                    vms = [
                        vm for vm in self._get_object(
                            esxhost,
                            prop_type='VirtualMachine',
                            traversal={
                                'name': 'HostSystemTraversalSpec',
                                'type': 'HostSystem',
                                'path': 'vm'
                            },
                            properties=['name']) if vm.name == name
                    ]
                    if len(vms) == 0:
                        return None
                    else:
                        return vms[0].obj_identifier
                if key is not None:
                    return self._get_object(self._build_property(
                        'VirtualMachine', key),
                                            properties=['name']).obj_identifier
            except:
                return None
        else:
            raise Exception('A name or key should be passed.')

    @authenticated()
    def get_vm(self, key):
        """
        Retreives a vm object, based on its key
        """
        vmid = self.exists(key=key)
        if vmid is None:
            raise RuntimeError(
                'Virtual Machine with key {} could not be found.'.format(key))
        vm = self._get_object(vmid)
        return vm

    @authenticated()
    def get_vms(self, ip, mountpoint):
        """
        Get all vMachines using a given nfs share
        """
        esxhost = self._validate_host(None)
        datastore = self.get_datastore(ip, mountpoint)
        filtered_vms = []
        vms = self._get_object(esxhost,
                               prop_type='VirtualMachine',
                               traversal={
                                   'name': 'HostSystemTraversalSpec',
                                   'type': 'HostSystem',
                                   'path': 'vm'
                               },
                               properties=['name', 'config'])
        for vm in vms:
            mapping = self._get_vm_datastore_mapping(vm)
            if datastore.name in mapping:
                filtered_vms.append(vm)
        return filtered_vms

    @authenticated()
    def set_disk_mode(self, vmid, disks, mode, wait=True):
        """
        Sets the disk mode for a set of disks
        """
        config = self._client.factory.create('ns0:VirtualMachineConfigSpec')
        config.deviceChange = []

        disk_type = type(self._client.factory.create('ns0:VirtualDisk'))

        vmid = self.exists(key=vmid)
        vm = self._get_object(vmid)
        for device in vm.config.hardware.devices:
            if type(device) == disk_type and hasattr(device, 'backing') \
                    and device.backing.fileName in disks:
                backing = self._client.factory.create(
                    'ns0:VirtualDiskFlatVer2BackingInfo')
                backing.diskMode = mode
                device = self._client.factory.create('ns0:VirtualDisk')
                device.backing = backing
                diskSpec = self._client.factory.create(
                    'ns0:VirtualDeviceConfigSpec')
                diskSpec.operation = 'edit'
                diskSpec.fileOperation = None
                diskSpec.device = device
                config.deviceChange.append(diskSpec)

        task = self._client.service.ReconfigVM_Task(vm.obj_identifier, config)

        if wait:
            self.wait_for_task(task)
        return task

    def _create_disk(self, factory, key, disk, unit, datastore):
        """
        Creates a disk spec for a given backing device
        Example for parameter disk: {'name': diskname, 'backingdevice': 'disk-flat.vmdk'}
        """
        deviceInfo = factory.create('ns0:Description')
        deviceInfo.label = disk['name']
        deviceInfo.summary = 'Disk %s' % disk['name']
        backing = factory.create('ns0:VirtualDiskFlatVer2BackingInfo')
        backing.diskMode = 'persistent'
        backing.fileName = '[%s] %s' % (datastore.name, disk['backingdevice'])
        backing.thinProvisioned = True
        device = factory.create('ns0:VirtualDisk')
        device.controllerKey = key
        device.key = -200 - unit
        device.unitNumber = unit
        device.deviceInfo = deviceInfo
        device.backing = backing
        diskSpec = factory.create('ns0:VirtualDeviceConfigSpec')
        diskSpec.operation = 'add'
        diskSpec.fileOperation = None
        diskSpec.device = device
        return diskSpec

    def _create_file_info(self, factory, datastore):
        """
        Creates a file info object
        """
        fileInfo = factory.create('ns0:VirtualMachineFileInfo')
        fileInfo.vmPathName = '[%s]' % datastore
        return fileInfo

    def _create_nic(self, factory, device_type, device_label, device_summary,
                    network, unit):
        """
        Creates a NIC spec
        """
        deviceInfo = factory.create('ns0:Description')
        deviceInfo.label = device_label
        deviceInfo.summary = device_summary
        backing = factory.create('ns0:VirtualEthernetCardNetworkBackingInfo')
        backing.deviceName = network
        device = factory.create('ns0:%s' % device_type)
        device.addressType = 'Generated'
        device.wakeOnLanEnabled = True
        device.controllerKey = 100  # PCI Controller
        device.key = -300 - unit
        device.unitNumber = unit
        device.backing = backing
        device.deviceInfo = deviceInfo
        nicSpec = factory.create('ns0:VirtualDeviceConfigSpec')
        nicSpec.operation = 'add'
        nicSpec.fileOperation = None
        nicSpec.device = device
        return nicSpec

    def _create_disk_controller(self, factory, key):
        """
        Create a disk controller
        """
        deviceInfo = self._client.factory.create('ns0:Description')
        deviceInfo.label = 'SCSI controller 0'
        deviceInfo.summary = 'LSI Logic SAS'
        controller = factory.create('ns0:VirtualLsiLogicSASController')
        controller.busNumber = 0
        controller.key = key
        controller.sharedBus = 'noSharing'
        controller.deviceInfo = deviceInfo
        controllerSpec = factory.create('ns0:VirtualDeviceConfigSpec')
        controllerSpec.operation = 'add'
        controllerSpec.fileOperation = None
        controllerSpec.device = controller
        return controllerSpec

    def _create_option_value(self, factory, key, value):
        """
        Create option values
        """
        option = factory.create('ns0:OptionValue')
        option.key = key
        option.value = value
        return option

    @authenticated()
    def copy_file(self, source, destination, wait=True):
        """
        Copies a file on the datastore
        """
        task = self._client.service.CopyDatastoreFile_Task(
            _this=self._serviceContent.fileManager,
            sourceName=source,
            destinationName=destination)

        if wait:
            self.wait_for_task(task)
        return task

    @authenticated()
    def create_vm_from_template(self,
                                name,
                                source_vm,
                                disks,
                                ip,
                                mountpoint,
                                wait=True):
        """
        Create a vm based on an existing vtemplate on specified tgt hypervisor
        Raises RuntimeError if datastore is not available at (ip, mountpoint)
        """

        esxhost = self._validate_host(None)
        host_data = self._get_host_data(esxhost)

        datastore = self.get_datastore(ip, mountpoint)
        # Build basic config information
        config = self._client.factory.create('ns0:VirtualMachineConfigSpec')
        config.name = name
        config.numCPUs = source_vm.config.hardware.numCPU
        config.memoryMB = source_vm.config.hardware.memoryMB
        config.guestId = source_vm.config.guestId
        config.deviceChange = []
        config.extraConfig = []
        config.files = self._create_file_info(self._client.factory,
                                              datastore.name)

        disk_controller_key = -101
        config.deviceChange.append(
            self._create_disk_controller(self._client.factory,
                                         disk_controller_key))

        # Add disk devices
        for disk in disks:
            config.deviceChange.append(
                self._create_disk(self._client.factory, disk_controller_key,
                                  disk, disks.index(disk), datastore))

        # Add network
        nw_type = type(
            self._client.factory.create(
                'ns0:VirtualEthernetCardNetworkBackingInfo'))
        for device in source_vm.config.hardware.device:
            if hasattr(device, 'backing') and type(device.backing) == nw_type:
                config.deviceChange.append(
                    self._create_nic(self._client.factory,
                                     device.__class__.__name__,
                                     device.deviceInfo.label,
                                     device.deviceInfo.summary,
                                     device.backing.deviceName,
                                     device.unitNumber))

        # Copy additional properties
        extraconfigstoskip = ['nvram']
        for item in source_vm.config.extraConfig:
            if not item.key in extraconfigstoskip:
                config.extraConfig.append(
                    self._create_option_value(self._client.factory, item.key,
                                              item.value))

        task = self._client.service.CreateVM_Task(
            host_data['folder'],
            config=config,
            pool=host_data['resourcePool'],
            host=host_data['host'])

        if wait:
            self.wait_for_task(task)
        return task

    @authenticated()
    def clone_vm(self, vmid, name, disks, wait=True):
        """
        Clone a existing VM configuration

        @param vmid: unique id of the vm
        @param name: name of the clone vm
        @param disks: list of disks to use in vm configuration
        @param kvmport: kvm port for the clone vm
        @param esxhost: esx host identifier on which to clone the vm
        @param wait: wait for task to complete or not (True/False)
        """

        esxhost = self._validate_host(None)
        host_data = self._get_host_data(esxhost)

        source_vm_object = self.exists(key=vmid)
        if not source_vm_object:
            raise Exception('VM with key reference %s not found' % vmid)
        source_vm = self._get_object(source_vm_object)
        datastore = self._get_object(source_vm.datastore[0][0])

        # Build basic config information
        config = self._client.factory.create('ns0:VirtualMachineConfigSpec')
        config.name = name
        config.numCPUs = source_vm.config.hardware.numCPU
        config.memoryMB = source_vm.config.hardware.memoryMB
        config.guestId = source_vm.config.guestId
        config.deviceChange = []
        config.extraConfig = []
        config.files = self._create_file_info(self._client.factory,
                                              datastore.name)

        disk_controller_key = -101
        config.deviceChange.append(
            self._create_disk_controller(self._client.factory,
                                         disk_controller_key))

        # Add disk devices
        for disk in disks:
            config.deviceChange.append(
                self._create_disk(self._client.factory, disk_controller_key,
                                  disk, disks.index(disk), datastore))
            self.copy_file(
                '[{0}] {1}'.format(
                    datastore.name, '%s.vmdk' %
                    disk['name'].split('_')[-1].replace('-clone', '')),
                '[{0}] {1}'.format(datastore.name, disk['backingdevice']))

        # Add network
        nw_type = type(
            self._client.factory.create(
                'ns0:VirtualEthernetCardNetworkBackingInfo'))
        for device in source_vm.config.hardware.device:
            if hasattr(device, 'backing') and type(device.backing) == nw_type:
                config.deviceChange.append(
                    self._create_nic(self._client.factory,
                                     device.__class__.__name__,
                                     device.deviceInfo.label,
                                     device.deviceInfo.summary,
                                     device.backing.deviceName,
                                     device.unitNumber))

        # Copy additional properties
        extraconfigstoskip = ['nvram']
        for item in source_vm.config.extraConfig:
            if not item.key in extraconfigstoskip:
                config.extraConfig.append(
                    self._create_option_value(self._client.factory, item.key,
                                              item.value))

        task = self._client.service.CreateVM_Task(
            host_data['folder'],
            config=config,
            pool=host_data['resourcePool'],
            host=host_data['host'])
        if wait:
            self.wait_for_task(task)
        return task

    @authenticated()
    def get_vm(self, key):
        vmid = self.exists(key=key)
        if vmid is None:
            raise RuntimeError(
                'Virtual Machine with key {} could not be found.'.format(key))
        vm = self._get_object(vmid)
        return vm

    @authenticated()
    def get_datastore(self, ip, mountpoint):
        """
        @param ip : hypervisor ip to query for datastore presence
        @param mountpoint: nfs mountpoint on hypervisor
        @rtype: sdk datastore object
        @return: object when found else None
        """

        datastore = None
        esxhost = self._validate_host(None)
        host_system = self._get_object(esxhost, properties=['datastore'])
        for store in host_system.datastore[0]:
            store = self._get_object(store)
            if not store.summary.accessible:
                raise RuntimeError(
                    'Datastore {0} is not accessible at mountpoint {1}'.format(
                        store.name, mountpoint))
            if hasattr(store.info, 'nas'):
                if store.info.nas.remoteHost == ip and store.info.nas.remotePath == mountpoint:
                    datastore = store

        return datastore

    @authenticated()
    def is_datastore_available(self, ip, mountpoint):
        """
        @param ip : hypervisor ip to query for datastore presence
        @param mountpoint: nfs mountpoint on hypervisor
        @rtype: boolean
        @return: True | False
        """

        if self.get_datastore(ip, mountpoint):
            return True
        else:
            return False

    def make_agnostic_config(self, vm_object):
        regex = '\[([^\]]+)\]\s(.+)'
        match = re.search(regex, vm_object.config.files.vmPathName)
        esxhost = self._validate_host(None)

        config = {
            'name': vm_object.config.name,
            'id': vm_object.obj_identifier.value,
            'backing': {
                'filename': match.group(2),
                'datastore': match.group(1)
            },
            'disks': [],
            'datastores': {}
        }

        for device in vm_object.config.hardware.device:
            if device.__class__.__name__ == 'VirtualDisk':
                if device.backing is not None and \
                        device.backing.fileName is not None:
                    backingfile = device.backing.fileName
                    match = re.search(regex, backingfile)
                    if match:
                        filename = match.group(2)
                        backingfile = filename.replace('.vmdk', '-flat.vmdk')
                        config['disks'].append({
                            'filename': filename,
                            'backingfilename': backingfile,
                            'datastore': match.group(1),
                            'name': device.deviceInfo.label,
                            'order': device.unitNumber
                        })

        host_system = self._get_object(esxhost, properties=['datastore'])
        for store in host_system.datastore[0]:
            store = self._get_object(store)
            if hasattr(store.info, 'nas'):
                config['datastores'][store.info.name] = '{}:{}'.format(
                    store.info.nas.remoteHost, store.info.nas.remotePath)

        return config

    @authenticated()
    def delete_vm(self,
                  vmid,
                  storagedriver_mountpoint,
                  storagedriver_storage_ip,
                  devicename,
                  wait=False):
        """
        Delete a given vm
        """
        machine = None
        if vmid:
            try:
                machine = self._build_property('VirtualMachine', vmid)
            except Exception as ex:
                logger.error(
                    'SDK domain retrieve failed by vmid: {}'.format(ex))
        elif storagedriver_mountpoint and storagedriver_storage_ip and devicename:
            try:
                machine_info = self.get_nfs_datastore_object(
                    storagedriver_storage_ip, storagedriver_mountpoint,
                    devicename)[0]
                machine = self._build_property(
                    'VirtualMachine', machine_info.obj_identifier.value)
            except Exception as ex:
                logger.error(
                    'SDK domain retrieve failed by nfs datastore info: {}'.
                    format(ex))
        if machine:
            task = self._client.service.Destroy_Task(machine)
            if wait:
                self.wait_for_task(task)

        if storagedriver_mountpoint and devicename:
            vmx_path = os.path.join(storagedriver_mountpoint, devicename)
            if os.path.exists(vmx_path):
                dir_name = os.path.dirname(vmx_path)
                logger.debug('Removing leftover files in {0}'.format(dir_name))
                try:
                    shutil.rmtree(dir_name)
                    logger.debug('Removed dir tree {}'.format(dir_name))
                except Exception as exception:
                    logger.error(
                        'Failed to remove dir tree {0}. Reason: {1}'.format(
                            dir_name, str(exception)))
        return task

    @authenticated()
    def get_power_state(self, vmid):
        """
        Get the power state of a given vm
        """
        return self._get_object(self._build_property('VirtualMachine', vmid),
                                properties=['runtime.powerState'
                                            ]).runtime.powerState

    @authenticated()
    def mount_nfs_datastore(self, name, remote_host, remote_path):
        """
        Mounts a given NFS export as a datastore
        """
        esxhost = self._validate_host(None)
        host = self._get_object(esxhost,
                                properties=[
                                    'datastore', 'name', 'configManager',
                                    'configManager.datastoreSystem'
                                ])
        for store in host.datastore[0]:
            store = self._get_object(store)
            if hasattr(store.info, 'nas'):
                if store.info.name == name:
                    if store.info.nas.remoteHost == remote_host and \
                            store.info.nas.remotePath == remote_path:
                        # We'll remove this store, as it's identical to the once we'll add,
                        # forcing a refresh
                        self._client.service.RemoveDatastore(
                            host.configManager.datastoreSystem,
                            store.obj_identifier)
                        break
                    else:
                        raise RuntimeError(
                            'A datastore {0} already exists, pointing to {1}:{2}'
                            .format(name, store.info.nas.remoteHost,
                                    store.info.nas.remotePath))
        spec = self._client.factory.create('ns0:HostNasVolumeSpec')
        spec.accessMode = 'readWrite'
        spec.localPath = name
        spec.remoteHost = remote_host
        spec.remotePath = remote_path
        spec.type = 'nfs'
        return self._client.service.CreateNasDatastore(
            host.configManager.datastoreSystem, spec)

    @authenticated()
    def wait_for_task(self, task):
        """
        Wait for a task to be completed
        """
        state = self.get_task_info(task).info.state
        while state in ['running', 'queued']:
            sleep(1)
            state = self.get_task_info(task).info.state

    @authenticated()
    def get_nfs_datastore_object(self, ip, mountpoint, filename):
        """
        ip : "10.130.12.200", string
        mountpoint: "/srv/volumefs", string
        filename: "cfovs001/vhd0(-flat).vmdk"
        identify nfs datastore on this esx host based on ip and mount
        check if filename is present on datastore
        if file is .vmdk return VirtualDisk object for corresponding virtual disk
        if file is .vmx return VirtualMachineConfigInfo for corresponding vm

        @rtype: tuple
        @return: A tuple. First item: vm config, second item: Device if a vmdk was given
        """

        filename = filename.replace(
            '-flat.vmdk', '.vmdk')  # Support both -flat.vmdk and .vmdk
        if not filename.endswith('.vmdk') and not filename.endswith('.vmx'):
            raise ValueError('Unexpected filetype')
        esxhost = self._validate_host(None)

        datastore = self.get_datastore(ip, mountpoint)
        if not datastore:
            raise RuntimeError('Could not find datastore')

        vms = self._get_object(esxhost,
                               prop_type='VirtualMachine',
                               traversal={
                                   'name': 'HostSystemTraversalSpec',
                                   'type': 'HostSystem',
                                   'path': 'vm'
                               },
                               properties=['name', 'config'])
        if not vms:
            raise RuntimeError('No vMachines found')
        for vm in vms:
            mapping = self._get_vm_datastore_mapping(vm)
            if datastore.name in mapping:
                if filename in mapping[datastore.name]:
                    return vm, mapping[datastore.name][filename]
        raise RuntimeError(
            'Could not locate given file on the given datastore')

    def _get_vm_datastore_mapping(self, vm):
        """
        Creates a datastore mapping for a vm's devices
        Structure
            {<datastore name>: {<backing filename>: <device>,
                                <backing filename>: <device>},
             <datastore name>: {<backing filename>: <device>}}
        Example
            {'datastore A': {'/machine1/machine1.vmx': <device>,
                             '/machine1/disk1.vmdk': <device>},
             'datastore B': {'/machine1/disk2.vmdk': <device>}}
        """
        def extract_names(backingfile, given_mapping, metadata=None):
            match = re.search('\[([^\]]+)\]\s(.+)', backingfile)
            if match:
                datastore_name = match.group(1)
                filename = match.group(2)
                if datastore_name not in mapping:
                    given_mapping[datastore_name] = {}
                given_mapping[datastore_name][filename] = metadata
            return given_mapping

        virtual_disk_type = self._client.factory.create('ns0:VirtualDisk')
        flat_type = self._client.factory.create(
            'ns0:VirtualDiskFlatVer2BackingInfo')

        mapping = {}
        for device in vm.config.hardware.device:
            if isinstance(device, type(virtual_disk_type)):
                if device.backing is not None and isinstance(
                        device.backing, type(flat_type)):
                    mapping = extract_names(device.backing.fileName, mapping,
                                            device)
        mapping = extract_names(vm.config.files.vmPathName, mapping)
        return mapping

    def _get_host_data(self, esxhost):
        """
        Get host data for a given esxhost
        """
        hostobject = self._get_object(
            esxhost, properties=['parent', 'datastore', 'network'])
        datastore = self._get_object(hostobject.datastore[0][0],
                                     properties=['info']).info
        computeresource = self._get_object(
            hostobject.parent, properties=['resourcePool', 'parent'])
        datacenter = self._get_object(computeresource.parent,
                                      properties=['parent']).parent
        vm_folder = self._get_object(datacenter,
                                     properties=['vmFolder']).vmFolder

        return {
            'host': esxhost,
            'computeResource': computeresource,
            'resourcePool': computeresource.resourcePool,
            'datacenter': datacenter,
            'folder': vm_folder,
            'datastore': datastore,
            'network': hostobject.network[0]
        }

    def _get_host_iqn_mapping(self, esxhost, rescan=False):
        """
        Get the IQN mapping for a given esx host, optionally rescanning the host
        """
        # pylint: disable=line-too-long
        regex = re.compile(
            '^key-vim.host.PlugStoreTopology.Path-iqn.+?,(?P<iqn>iqn.*?),t,1-(?P<eui>eui.+)$'
        )  # noqa
        # pylint: enable=line-too-long

        hostobject = self._get_object(
            esxhost, properties=['configManager.storageSystem'])
        stg_ssystem = self._get_object(
            hostobject.configManager.storageSystem,
            properties=[
                'storageDeviceInfo',
                'storageDeviceInfo.plugStoreTopology.device'
            ])
        if rescan:
            # Force a rescan of the vmfs
            self._client.service.RescanVmfs(stg_ssystem.obj_identifier)
            stg_ssystem = self._get_object(
                hostobject.configManager.storageSystem,
                properties=[
                    'storageDeviceInfo',
                    'storageDeviceInfo.plugStoreTopology.device'
                ])

        device_info_mapping = {}
        for disk in stg_ssystem.storageDeviceInfo.scsiLun:
            device_info_mapping[disk.key] = disk.uuid

        iqn_mapping = {}
        for device in stg_ssystem.storageDeviceInfo.plugStoreTopology\
                                 .device.HostPlugStoreTopologyDevice:
            for path in device.path:
                match = regex.search(path)
                if match:
                    groups = match.groupdict()
                    iqn_mapping[groups['iqn']] = {
                        'eui': groups['eui'],
                        'lun': device.lun,
                        'uuid': device_info_mapping[device.lun]
                    }

        return iqn_mapping

    def _get_object(self,
                    key_object,
                    prop_type=None,
                    traversal=None,
                    properties=None):
        """
        Gets an object based on a given set of query parameters. Only the requested properties
        will be loaded. If no properties are specified, all will be loaded
        """
        object_spec = self._client.factory.create('ns0:ObjectSpec')
        object_spec.obj = key_object

        property_spec = self._client.factory.create('ns0:PropertySpec')
        property_spec.type = key_object._type if prop_type is None else prop_type
        if properties is None:
            property_spec.all = True
        else:
            property_spec.all = False
            property_spec.pathSet = properties

        if traversal is not None:
            select_set_ptr = object_spec
            while True:
                select_set_ptr.selectSet = self._client.factory.create(
                    'ns0:TraversalSpec')
                select_set_ptr.selectSet.name = traversal['name']
                select_set_ptr.selectSet.type = traversal['type']
                select_set_ptr.selectSet.path = traversal['path']
                if 'traversal' in traversal:
                    traversal = traversal['traversal']
                    select_set_ptr = select_set_ptr.selectSet
                else:
                    break

        property_filter_spec = self._client.factory.create(
            'ns0:PropertyFilterSpec')
        property_filter_spec.objectSet = [object_spec]
        property_filter_spec.propSet = [property_spec]

        found_objects = self._client.service.RetrieveProperties(
            self._serviceContent.propertyCollector, [property_filter_spec])

        if len(found_objects) > 0:
            for item in found_objects:
                item.obj_identifier = item.obj
                del item.obj

                if hasattr(item, 'missingSet'):
                    for missing_item in item.missingSet:
                        if missing_item.fault.fault.__class__.__name__ == 'NotAuthenticated':
                            raise NotAuthenticatedException()

                for propSet in item.propSet:
                    if '.' in propSet.name:
                        working_item = item
                        path = str(propSet.name).split('.')
                        part_counter = 0
                        for part in path:
                            part_counter += 1
                            if part_counter < len(path):
                                if not part in working_item.__dict__:
                                    setattr(working_item, part,
                                            type(part, (), {})())
                                working_item = working_item.__dict__[part]
                            else:
                                setattr(working_item, part, propSet.val)
                    else:
                        setattr(item, propSet.name, propSet.val)
                del item.propSet
            if len(found_objects) == 1:
                return found_objects[0]
            else:
                return found_objects

        return None

    def _build_property(self, property_name, value=None):
        """
        Create a property object with given name and value
        """
        new_property = Property(property_name)
        new_property._type = property_name
        if value is not None:
            new_property.value = value
        return new_property

    def _validate_host(self, host):
        """
        Validates wheteher a given host is valid
        """
        if host is None:
            if self._is_vcenter:
                raise Exception(
                    'A HostSystem reference is mandatory for a vCenter Server')
            else:
                return self._esxHost
        else:
            if hasattr(host, '_type') and host._type == 'HostSystem':
                return self._get_object(host,
                                        properties=['name']).obj_identifier
            else:
                return self._get_object(self._build_property(
                    'HostSystem', host),
                                        properties=['name']).obj_identifier

    def _login(self):
        """
        Executes a logout (to make sure we're logged out), and logs in again
        """
        self._logout()
        self._sessionID = self._client.service.Login(
            self._serviceContent.sessionManager, self._username,
            self._password, None).key

    def _logout(self):
        """
        Logs out the current session
        """
        try:
            self._client.service.Logout(self._serviceContent.sessionManager)
        except:
            pass
Esempio n. 6
0
 def build_object_cache(self):
     cache = ObjectCache()
     cache.setduration(seconds=config.get('soap.client_timeout'))
     cache.setlocation(config.get('soap.cache_location'))
     return cache
Esempio n. 7
0
class Sdk(object):
    """
    This class contains all SDK related methods
    """

    def __init__(self, host, login, passwd):
        """
        Initializes the SDK
        """
        self._host = host
        self._username = login
        self._password = passwd
        self._sessionID = None
        self._check_session = True

        self._cache = ObjectCache()
        self._cache.setduration(weeks=1)

        self._client = Client('https://{0}/sdk/vimService?wsdl'.format(host),
                              cache=self._cache,
                              cachingpolicy=1)
        self._client.set_options(location='https://{0}/sdk'.format(host),
                                 plugins=[ValueExtender()])

        service_reference = self._build_property('ServiceInstance')
        self._serviceContent = self._client.service.RetrieveServiceContent(
            service_reference
        )

        # In case of an ESXi host, this would be 'HostAgent'
        self.is_vcenter = self._serviceContent.about.apiType == 'VirtualCenter'
        if not self.is_vcenter:
            self._login()
            self._esxHost = self._get_object(
                self._serviceContent.rootFolder,
                prop_type='HostSystem',
                traversal={'name': 'FolderTraversalSpec',
                           'type': 'Folder',
                           'path': 'childEntity',
                           'traversal': {'name': 'DatacenterTraversalSpec',
                                         'type': 'Datacenter',
                                         'path': 'hostFolder',
                                         'traversal': {'name': 'DFolderTraversalSpec',
                                                       'type': 'Folder',
                                                       'path': 'childEntity',
                                                       'traversal': {'name': 'ComputeResourceTravelSpec',
                                                                     'type': 'ComputeResource',
                                                                     'path': 'host'}}}},
                properties=['name']
            ).obj_identifier
        else:
            # @TODO: We need to extend all calls to specify the ESXi host where the action needs to be executed.
            # We cannot just assume an ESXi host here, as this is important for certain calls like creating a VM.
            self._esxHost = None

    @authenticated(force=True)
    def _get_vcenter_hosts(self):
        """
        reload vCenter info (host name and summary)
        """
        if not self.is_vcenter:
            raise RuntimeError('Must be connected to a vCenter Server API.')
        return self._get_object(
            self._serviceContent.rootFolder,
            prop_type='HostSystem',
            traversal={'name': 'FolderTraversalSpec',
                       'type': 'Folder',
                       'path': 'childEntity',
                       'traversal': {'name': 'DatacenterTraversalSpec',
                                     'type': 'Datacenter',
                                     'path': 'hostFolder',
                                     'traversal': {'name': 'DFolderTraversalSpec',
                                                   'type': 'Folder',
                                                   'path': 'childEntity',
                                                   'traversal': {'name': 'ComputeResourceTravelSpec',
                                                                 'type': 'ComputeResource',
                                                                 'path': 'host'}}}},
            properties=['name', 'summary.runtime', 'config.virtualNicManagerInfo.netConfig'],
            as_list=True
        )

    def get_host_status_by_ip(self, host_ip):
        """
        Return host status by ip, from vcenter info
        Must be connected to a vcenter server api
        """
        host = self._get_host_info_by_ip(host_ip)
        return host.summary.runtime.powerState

    def get_host_status_by_pk(self, pk):
        """
        Return host status by pk, from vcenter info
        Must be connected to a vcenter server api
        """
        host = self._get_host_info_by_pk(pk)
        return host.summary.runtime.powerState

    def get_host_primary_key(self, host_ip):
        """
        Return host primary key, based on current ip
        Must be connected to a vcenter server api
        """
        host = self._get_host_info_by_ip(host_ip)
        return host.obj_identifier.value

    def _get_host_info_by_ip(self, host_ip):
        """
        Return HostSystem object by ip, from vcenter info
        Must be connected to a vcenter server api
        """
        datacenter_info = self._get_vcenter_hosts()
        for host in datacenter_info:
            for nic in host.config.virtualNicManagerInfo.netConfig.VirtualNicManagerNetConfig:
                if nic.nicType == 'management':
                    for vnic in nic.candidateVnic:
                        if vnic.spec.ip.ipAddress == host_ip:
                            return host
        raise RuntimeError('Host with ip {0} not found in datacenter info'.format(host_ip))

    def _get_host_info_by_pk(self, pk):
        """
        Return HostSystem object by pk, from vcenter info
        Must be connected to a vcenter server api
        """
        datacenter_info = self._get_vcenter_hosts()
        for host in datacenter_info:
            if host.obj_identifier.value == pk:
                return host

    def get_hosts(self):
        """
        Gets a neutral list of all hosts available
        """
        host_data = self._get_vcenter_hosts()
        host_data = [] if host_data is None else host_data
        hosts = {}
        for host in host_data:
            ips = []
            for nic in host.config.virtualNicManagerInfo.netConfig.VirtualNicManagerNetConfig:
                if nic.nicType == 'management':
                    for vnic in nic.candidateVnic:
                        ips.append(vnic.spec.ip.ipAddress)
            hosts[host.obj_identifier.value] = {'name': host.name,
                                                'ips': ips}
        return hosts

    def test_connection(self):
        """
        Tests the connection
        """
        self._login()
        return True

    def list_hosts_in_datacenter(self):
        """
        return a list of registered host names in vCenter
        must be connected to a vcenter server api
        """
        datacenter_info = self._get_vcenter_hosts()
        return [host.name for host in datacenter_info]

    def validate_result(self, result, message=None):
        """
        Validates a given result. Returning True if the task succeeded, raising an error if not
        """
        if hasattr(result, '_type') and result._type == 'Task':
            return self.validate_result(self.get_task_info(result), message)
        elif hasattr(result, 'info'):
            if result.info.state == 'success':
                return True
            elif result.info.state == 'error':
                error = result.info.error.localizedMessage
                raise Exception(('{0}: {1}'.format(message, error)) if message else error)
        raise Exception(('{0}: {1}'.format(message, 'Unexpected result'))
                        if message else 'Unexpected result')

    @authenticated()
    def get_task_info(self, task):
        """
        Loads the task details
        """
        return self._get_object(task)

    @authenticated()
    def get_vm_ip_information(self):
        """
        Get the IP information for all vms on a given esxi host
        """
        esxhost = self._validate_host(None)
        configuration = []
        for vm in self._get_object(esxhost,
                                   prop_type='VirtualMachine',
                                   traversal={
                                       'name': 'HostSystemTraversalSpec',
                                       'type': 'HostSystem',
                                       'path': 'vm'},
                                   properties=['name', 'guest.net', 'config.files'],
                                   as_list=True):
            vmi = {'id': str(vm.obj_identifier.value),
                   'vmxpath': str(vm.config.files.vmPathName),
                   'name': str(vm.name),
                   'net': []}
            if vm.guest.net:
                for net in vm.guest.net[0]:
                    vmi['net'].append({'mac': str(net.macAddress),
                                       'ipaddresses': [str(i.ipAddress)
                                                       for i in net.ipConfig.ipAddress]})
            configuration.append(vmi)
        return configuration

    @authenticated()
    def exists(self, name=None, key=None):
        """
        Checks whether a vm with a given name or key exists on a given esxi host
        """
        esxhost = self._validate_host(self._esxHost)
        if name is not None or key is not None:
            try:
                if name is not None:
                    vms = [vm for vm in
                           self._get_object(esxhost,
                                            prop_type='VirtualMachine',
                                            traversal={'name': 'HostSystemTraversalSpec',
                                                       'type': 'HostSystem',
                                                       'path': 'vm'},
                                            properties=['name'],
                                            as_list=True)
                           if vm.name == name]
                    if len(vms) == 0:
                        return None
                    else:
                        return vms[0].obj_identifier
                if key is not None:
                    return self._get_object(
                        Sdk._build_property('VirtualMachine', key),
                        properties=['name']
                    ).obj_identifier
            except:
                return None
        else:
            raise Exception('A name or key should be passed.')

    @authenticated()
    def get_vm(self, key):
        """
        Retreives a vm object, based on its key
        """
        vmid = self.exists(key=key)
        if vmid is None:
            raise RuntimeError('Virtual Machine with key {} could not be found.'.format(key))
        vm = self._get_object(vmid)
        return vm

    @authenticated()
    def get_vm_device_info(self, key):
        """
        Return a vm config, based on its key
        """
        vm = self.get_vm(key)
        filename = vm.config.files.vmPathName
        regex = '\[([^\]]+)\]\s(.+)'
        match = re.search(regex, filename)
        disks = self._get_vmachine_vdisks(vm)
        return {'file_name': match.group(2),
                'host_name': vm.name,
                'vpool_name': match.group(1),
                'disks': disks}

    @authenticated()
    def get_all_vms(self):
        """
        Get all vMachines on all esxhosts registered to this vCenter
        :return: list
        """
        if not self.is_vcenter:
            raise RuntimeError('Must be connected to a vCenter Server API.')
        hosts = self._get_vcenter_hosts()
        guests = []
        for host in hosts:
            esxhost = self._validate_host(host.obj_identifier)
            vms = self._get_object(esxhost,
                                   prop_type='VirtualMachine',
                                   traversal={'name': 'HostSystemTraversalSpec',
                                              'type': 'HostSystem',
                                              'path': 'vm'},
                                   properties=['name', 'config'],
                                   as_list=True)
            for vm in vms:
                guests.append({'id': vm.obj_identifier.value,
                               'name': vm.name,
                               'instance_name': vm.name})
        return guests

    def _get_vmachine_vdisks(self, vm_object):
        disks = []
        regex = '\[([^\]]+)\]\s(.+)'
        disk_type = type(self._client.factory.create('ns0:VirtualDisk'))
        for device in vm_object.config.hardware.device:
            if isinstance(device, disk_type):
                backingfile = device.backing.fileName
                match = re.search(regex, backingfile)
                if match:
                    disks.append({'filename': match.group(2),
                                  'datastore': match.group(1),
                                  'name': device.deviceInfo.label})
        return disks

    @authenticated()
    def get_all_vdisks(self):
        """
        Get all vDisks on all esxhosts registered to this vCenter
        # similar to cinder.volumes.list()
        :return: list
        """
        if not self.is_vcenter:
            raise RuntimeError('Must be connected to a vCenter Server API.')
        hosts = self._get_vcenter_hosts()

        disks = []
        for host in hosts:
            esxhost = self._validate_host(host.obj_identifier)
            vms = self._get_object(esxhost,
                                   prop_type='VirtualMachine',
                                   traversal={'name': 'HostSystemTraversalSpec',
                                              'type': 'HostSystem',
                                              'path': 'vm'},
                                   properties=['name', 'config'],
                                   as_list=True)
            for vm in vms:
                disks.extend(self._get_vmachine_vdisks(vm))
        return disks

    @authenticated()
    def get_vms(self, ip, mountpoint):
        """
        Get all vMachines using a given nfs share
        """
        esxhost = self._validate_host(None)
        datastore = self.get_datastore(ip, mountpoint)
        filtered_vms = []
        vms = self._get_object(esxhost,
                               prop_type='VirtualMachine',
                               traversal={'name': 'HostSystemTraversalSpec',
                                          'type': 'HostSystem',
                                          'path': 'vm'},
                               properties=['name', 'config'],
                               as_list=True)
        for vm in vms:
            mapping = self._get_vm_datastore_mapping(vm)
            if datastore.name in mapping:
                filtered_vms.append(vm)
        return filtered_vms

    @authenticated()
    def set_disk_mode(self, vmid, disks, mode, wait=True):
        """
        Sets the disk mode for a set of disks
        """
        config = self._client.factory.create('ns0:VirtualMachineConfigSpec')
        config.deviceChange = []

        disk_type = type(self._client.factory.create('ns0:VirtualDisk'))

        vmid = self.exists(key=vmid)
        vm = self._get_object(vmid)
        for device in vm.config.hardware.devices:
            if type(device) == disk_type and hasattr(device, 'backing') and device.backing.fileName in disks:
                backing = self._client.factory.create('ns0:VirtualDiskFlatVer2BackingInfo')
                backing.diskMode = mode
                device = self._client.factory.create('ns0:VirtualDisk')
                device.backing = backing
                disk_spec = self._client.factory.create('ns0:VirtualDeviceConfigSpec')
                disk_spec.operation = 'edit'
                disk_spec.fileOperation = None
                disk_spec.device = device
                config.deviceChange.append(disk_spec)

        task = self._client.service.ReconfigVM_Task(vm.obj_identifier, config)

        if wait:
            self.wait_for_task(task)
        return task

    @staticmethod
    def _create_disk(factory, key, disk, unit, datastore):
        """
        Creates a disk spec for a given backing device
        Example for parameter disk: {'name': diskname, 'backingdevice': 'disk-flat.vmdk'}
        """
        device_info = factory.create('ns0:Description')
        device_info.label = disk['name']
        device_info.summary = 'Disk {0}'.format(disk['name'])
        backing = factory.create('ns0:VirtualDiskFlatVer2BackingInfo')
        backing.diskMode = 'persistent'
        backing.fileName = '[{0}] {1}'.format(datastore.name, disk['backingdevice'])
        backing.thinProvisioned = True
        device = factory.create('ns0:VirtualDisk')
        device.controllerKey = key
        device.key = -200 - unit
        device.unitNumber = unit
        device.deviceInfo = device_info
        device.backing = backing
        disk_spec = factory.create('ns0:VirtualDeviceConfigSpec')
        disk_spec.operation = 'add'
        disk_spec.fileOperation = None
        disk_spec.device = device
        return disk_spec

    @staticmethod
    def _create_file_info(factory, datastore):
        """
        Creates a file info object
        """
        file_info = factory.create('ns0:VirtualMachineFileInfo')
        file_info.vmPathName = '[{0}]'.format(datastore)
        return file_info

    @staticmethod
    def _create_nic(factory, device_type, device_label, device_summary, network, unit):
        """
        Creates a NIC spec
        """
        device_info = factory.create('ns0:Description')
        device_info.label = device_label
        device_info.summary = device_summary
        backing = factory.create('ns0:VirtualEthernetCardNetworkBackingInfo')
        backing.deviceName = network
        device = factory.create('ns0:{0}'.format(device_type))
        device.addressType = 'Generated'
        device.wakeOnLanEnabled = True
        device.controllerKey = 100  # PCI Controller
        device.key = -300 - unit
        device.unitNumber = unit
        device.backing = backing
        device.deviceInfo = device_info
        nic_spec = factory.create('ns0:VirtualDeviceConfigSpec')
        nic_spec.operation = 'add'
        nic_spec.fileOperation = None
        nic_spec.device = device
        return nic_spec

    def _create_disk_controller(self, factory, key):
        """
        Create a disk controller
        """
        device_info = self._client.factory.create('ns0:Description')
        device_info.label = 'SCSI controller 0'
        device_info.summary = 'LSI Logic SAS'
        controller = factory.create('ns0:VirtualLsiLogicSASController')
        controller.busNumber = 0
        controller.key = key
        controller.sharedBus = 'noSharing'
        controller.deviceInfo = device_info
        controller_spec = factory.create('ns0:VirtualDeviceConfigSpec')
        controller_spec.operation = 'add'
        controller_spec.fileOperation = None
        controller_spec.device = controller
        return controller_spec

    @staticmethod
    def _create_option_value(factory, key, value):
        """
        Create option values
        """
        option = factory.create('ns0:OptionValue')
        option.key = key
        option.value = value
        return option

    @authenticated()
    def copy_file(self, source, destination, wait=True):
        """
        Copies a file on the datastore
        """
        task = self._client.service.CopyDatastoreFile_Task(
            _this=self._serviceContent.fileManager,
            sourceName=source,
            destinationName=destination
        )

        if wait:
            self.wait_for_task(task)
        return task

    @authenticated()
    def create_vm_from_template(self, name, source_vm, disks, ip, mountpoint, wait=True):
        """
        Create a vm based on an existing vtemplate on specified tgt hypervisor
        Raises RuntimeError if datastore is not available at (ip, mountpoint)
        """

        esxhost = self._validate_host(None)
        host_data = self._get_host_data(esxhost)

        datastore = self.get_datastore(ip, mountpoint)
        # Build basic config information
        config = self._client.factory.create('ns0:VirtualMachineConfigSpec')
        config.name = name
        config.numCPUs = source_vm.config.hardware.numCPU
        config.memoryMB = source_vm.config.hardware.memoryMB
        config.guestId = source_vm.config.guestId
        config.deviceChange = []
        config.extraConfig = []
        config.files = Sdk._create_file_info(self._client.factory, datastore.name)

        disk_controller_key = -101
        config.deviceChange.append(
            self._create_disk_controller(self._client.factory,
                                         disk_controller_key))

        # Add disk devices
        for disk in disks:
            config.deviceChange.append(
                Sdk._create_disk(self._client.factory, disk_controller_key,
                                 disk, disks.index(disk), datastore))

        # Add network
        nw_type = type(self._client.factory.create('ns0:VirtualEthernetCardNetworkBackingInfo'))
        for device in source_vm.config.hardware.device:
            if hasattr(device, 'backing') and type(device.backing) == nw_type:
                config.deviceChange.append(
                    Sdk._create_nic(self._client.factory,
                                    device.__class__.__name__,
                                    device.deviceInfo.label,
                                    device.deviceInfo.summary,
                                    device.backing.deviceName,
                                    device.unitNumber))

        # Copy additional properties
        extraconfigstoskip = ['nvram']
        for item in source_vm.config.extraConfig:
            if item.key not in extraconfigstoskip:
                config.extraConfig.append(
                    Sdk._create_option_value(self._client.factory,
                                             item.key,
                                             item.value))

        task = self._client.service.CreateVM_Task(host_data['folder'],
                                                  config=config,
                                                  pool=host_data['resourcePool'],
                                                  host=host_data['host'])

        if wait:
            self.wait_for_task(task)
        return task

    @authenticated()
    def clone_vm(self, vmid, name, disks, wait=True):
        """
        Clone a existing VM configuration

        @param vmid: unique id of the vm
        @param name: name of the clone vm
        @param disks: list of disks to use in vm configuration
        @param wait: wait for task to complete or not (True/False)
        """

        esxhost = self._validate_host(None)
        host_data = self._get_host_data(esxhost)

        source_vm_object = self.exists(key=vmid)
        if not source_vm_object:
            raise Exception('VM with key reference {0} not found'.format(vmid))
        source_vm = self._get_object(source_vm_object)
        datastore = self._get_object(source_vm.datastore[0][0])

        # Build basic config information
        config = self._client.factory.create('ns0:VirtualMachineConfigSpec')
        config.name = name
        config.numCPUs = source_vm.config.hardware.numCPU
        config.memoryMB = source_vm.config.hardware.memoryMB
        config.guestId = source_vm.config.guestId
        config.deviceChange = []
        config.extraConfig = []
        config.files = Sdk._create_file_info(self._client.factory, datastore.name)

        disk_controller_key = -101
        config.deviceChange.append(
            self._create_disk_controller(self._client.factory,
                                         disk_controller_key))

        # Add disk devices
        for disk in disks:
            config.deviceChange.append(
                Sdk._create_disk(self._client.factory, disk_controller_key,
                                 disk, disks.index(disk), datastore))
            self.copy_file(
                '[{0}] {1}.vmdk'.format(datastore.name, disk['name'].split('_')[-1].replace('-clone', '')),
                '[{0}] {1}'.format(datastore.name, disk['backingdevice'])
            )

        # Add network
        nw_type = type(self._client.factory.create('ns0:VirtualEthernetCardNetworkBackingInfo'))
        for device in source_vm.config.hardware.device:
            if hasattr(device, 'backing') and type(device.backing) == nw_type:
                config.deviceChange.append(
                    Sdk._create_nic(self._client.factory,
                                    device.__class__.__name__,
                                    device.deviceInfo.label,
                                    device.deviceInfo.summary,
                                    device.backing.deviceName,
                                    device.unitNumber))

        # Copy additional properties
        extraconfigstoskip = ['nvram']
        for item in source_vm.config.extraConfig:
            if item.key not in extraconfigstoskip:
                config.extraConfig.append(
                    Sdk._create_option_value(self._client.factory,
                                             item.key,
                                             item.value)
                )

        task = self._client.service.CreateVM_Task(host_data['folder'],
                                                  config=config,
                                                  pool=host_data['resourcePool'],
                                                  host=host_data['host'])
        if wait:
            self.wait_for_task(task)
        return task

    @authenticated()
    def get_datastore(self, ip, mountpoint, host=None):
        """
        @param ip : hypervisor ip to query for datastore presence
        @param mountpoint: nfs mountpoint on hypervisor
        @rtype: sdk datastore object
        @return: object when found else None
        """

        datastore = None
        if host is None:
            host = self._esxHost
        esxhost = self._validate_host(host)
        host_system = self._get_object(esxhost, properties=['datastore'])
        for store in host_system.datastore[0]:
            store = self._get_object(store)
            if not store.summary.accessible:
                logger.warning('Datastore {0} is not accessible, skipping'.format(store.name))
            if hasattr(store.info, 'nas'):
                if store.info.nas.remoteHost == ip and store.info.nas.remotePath == mountpoint:
                    datastore = store

        return datastore

    @authenticated()
    def is_datastore_available(self, ip, mountpoint):
        """
        @param ip : hypervisor ip to query for datastore presence
        @param mountpoint: nfs mountpoint on hypervisor
        @rtype: boolean
        @return: True | False
        """

        if self.get_datastore(ip, mountpoint):
            return True
        else:
            return False

    def make_agnostic_config(self, vm_object, host=None):
        regex = '\[([^\]]+)\]\s(.+)'
        match = re.search(regex, vm_object.config.files.vmPathName)
        if host is None:
            host = self._esxHost
        esxhost = self._validate_host(host)

        config = {'name': vm_object.config.name,
                  'id': vm_object.obj_identifier.value,
                  'backing': {'filename': match.group(2),
                              'datastore': match.group(1)},
                  'disks': [],
                  'datastores': {}}

        for device in vm_object.config.hardware.device:
            if device.__class__.__name__ == 'VirtualDisk':
                if device.backing is not None and device.backing.fileName is not None:
                    backingfile = device.backing.fileName
                    match = re.search(regex, backingfile)
                    if match:
                        filename = match.group(2)
                        backingfile = filename.replace('.vmdk', '-flat.vmdk')
                        backingfile = '/{0}'.format(backingfile.strip('/'))
                        config['disks'].append({'filename': filename,
                                                'backingfilename': backingfile,
                                                'datastore': match.group(1),
                                                'name': device.deviceInfo.label,
                                                'order': device.unitNumber})

        host_system = self._get_object(esxhost, properties=['datastore'])
        for store in host_system.datastore[0]:
            store = self._get_object(store)
            if hasattr(store.info, 'nas'):
                config['datastores'][store.info.name] = '{}:{}'.format(store.info.nas.remoteHost,
                                                                       store.info.nas.remotePath)

        return config

    @authenticated()
    def delete_vm(self, vmid, storagedriver_mountpoint, storagedriver_storage_ip, devicename, wait=False):
        """
        Delete a given vm
        """
        machine = None
        task = None
        if vmid:
            try:
                machine = Sdk._build_property('VirtualMachine', vmid)
            except Exception as ex:
                logger.error('SDK domain retrieve failed by vmid: {}'.format(ex))
        elif storagedriver_mountpoint and storagedriver_storage_ip and devicename:
            try:
                machine_info = self.get_nfs_datastore_object(storagedriver_storage_ip, storagedriver_mountpoint, devicename)[0]
                machine = Sdk._build_property('VirtualMachine', machine_info.obj_identifier.value)
            except Exception as ex:
                logger.error('SDK domain retrieve failed by nfs datastore info: {}'.format(ex))
        if machine:
            task = self._client.service.Destroy_Task(machine)
            if wait:
                self.wait_for_task(task)

        if storagedriver_mountpoint and devicename:
            vmx_path = os.path.join(storagedriver_mountpoint, devicename)
            if os.path.exists(vmx_path):
                dir_name = os.path.dirname(vmx_path)
                logger.debug('Removing leftover files in {0}'.format(dir_name))
                try:
                    shutil.rmtree(dir_name)
                    logger.debug('Removed dir tree {}'.format(dir_name))
                except Exception as exception:
                    logger.error('Failed to remove dir tree {0}. Reason: {1}'.format(dir_name, str(exception)))
        return task

    @authenticated()
    def get_power_state(self, vmid):
        """
        Get the power state of a given vm
        """
        return self._get_object(Sdk._build_property('VirtualMachine', vmid),
                                properties=['runtime.powerState']).runtime.powerState

    @authenticated()
    def mount_nfs_datastore(self, name, remote_host, remote_path):
        """
        Mounts a given NFS export as a datastore
        """
        esxhost = self._validate_host(None)
        host = self._get_object(esxhost, properties=['datastore',
                                                     'name',
                                                     'configManager',
                                                     'configManager.datastoreSystem'])
        for store in host.datastore[0]:
            store = self._get_object(store)
            if hasattr(store.info, 'nas'):
                if store.info.name == name:
                    if store.info.nas.remoteHost == remote_host and store.info.nas.remotePath == remote_path:
                        # We'll remove this store, as it's identical to the once we'll add,
                        # forcing a refresh
                        self._client.service.RemoveDatastore(host.configManager.datastoreSystem,
                                                             store.obj_identifier)
                        break
                    else:
                        raise RuntimeError('A datastore {0} already exists, pointing to {1}:{2}'.format(
                            name, store.info.nas.remoteHost, store.info.nas.remotePath
                        ))
        spec = self._client.factory.create('ns0:HostNasVolumeSpec')
        spec.accessMode = 'readWrite'
        spec.localPath = name
        spec.remoteHost = remote_host
        spec.remotePath = remote_path
        spec.type = 'nfs'
        return self._client.service.CreateNasDatastore(host.configManager.datastoreSystem, spec)

    @authenticated()
    def wait_for_task(self, task):
        """
        Wait for a task to be completed
        """
        state = self.get_task_info(task).info.state
        while state in ['running', 'queued']:
            sleep(1)
            state = self.get_task_info(task).info.state

    @authenticated()
    def get_nfs_datastore_object(self, ip, mountpoint, filename, host=None):
        """
        ip : "10.130.12.200", string
        mountpoint: "/srv/volumefs", string
        filename: "cfovs001/vhd0(-flat).vmdk"
        identify nfs datastore on this esx host based on ip and mount
        check if filename is present on datastore
        if file is .vmdk return VirtualDisk object for corresponding virtual disk
        if file is .vmx return VirtualMachineConfigInfo for corresponding vm

        @rtype: tuple
        @return: A tuple. First item: vm config, second item: Device if a vmdk was given
        """

        filename = filename.replace('-flat.vmdk', '.vmdk')  # Support both -flat.vmdk and .vmdk
        if not filename.endswith('.vmdk') and not filename.endswith('.vmx'):
            raise ValueError('Unexpected filetype')
        if host is None:
            host = self._esxHost
        esxhost = self._validate_host(host)

        datastore = self.get_datastore(ip, mountpoint, host=esxhost)
        if not datastore:
            raise RuntimeError('Could not find datastore')

        vms = self._get_object(esxhost,
                               prop_type='VirtualMachine',
                               traversal={'name': 'HostSystemTraversalSpec',
                                          'type': 'HostSystem',
                                          'path': 'vm'},
                               properties=['name', 'config'],
                               as_list=True)
        if not vms:
            raise RuntimeError('No vMachines found')
        for vm in vms:
            mapping = self._get_vm_datastore_mapping(vm)
            if datastore.name in mapping:
                if filename in mapping[datastore.name]:
                    return vm, mapping[datastore.name][filename]
        raise RuntimeError('Could not locate given file on the given datastore')

    def file_exists(self, ip, mountpoint, filename):
        try:
            self.get_nfs_datastore_object(ip, mountpoint, filename)
            return True
        except Exception, ex:
            logger.debug('File does not exist: {0}'.format(ex))
            return False
Esempio n. 8
0
 def activate_cache(self):
     oc = ObjectCache()
     oc.setduration(days=365)
Esempio n. 9
0
 def build_object_cache(self):
     cache = ObjectCache()
     cache.setduration(seconds=config.get('soap.client_timeout'))
     cache.setlocation(config.get('soap.cache_location'))
     return cache