예제 #1
0
    def _select_datastore_summary(self, size_bytes, datastores):
        """Get best summary from datastore list that can accomodate volume.

        The implementation selects datastore based on maximum relative
        free space, which is (free_space/total_space) and has free space to
        store the volume backing.

        :param size_bytes: Size in bytes of the volume
        :param datastores: Datastores from which a choice is to be made
                           for the volume
        :return: Best datastore summary to be picked for the volume
        """
        best_summary = None
        best_ratio = 0
        for datastore in datastores:
            summary = self.volumeops.get_summary(datastore)
            if summary.freeSpace > size_bytes:
                ratio = float(summary.freeSpace) / summary.capacity
                if ratio > best_ratio:
                    best_ratio = ratio
                    best_summary = summary

        if not best_summary:
            msg = _("Unable to pick datastore to accomodate %(size)s bytes "
                    "from the datastores: %(dss)s.")
            LOG.error(msg % {'size': size_bytes, 'dss': datastores})
            raise error_util.VimException(msg % {
                'size': size_bytes,
                'dss': datastores
            })

        LOG.debug(_("Selected datastore: %s for the volume.") % best_summary)
        return best_summary
예제 #2
0
    def _create_backing_in_inventory(self, volume):
        """Creates backing under any suitable host.

        The method tries to pick datastore that can fit the volume under
        any host in the inventory.

        :param volume: Volume object
        :return: Reference to the created backing
        """

        retrv_result = self.volumeops.get_hosts()
        while retrv_result:
            hosts = retrv_result.objects
            if not hosts:
                break
            backing = None
            for host in hosts:
                try:
                    backing = self._create_backing(volume, host.obj)
                    if backing:
                        break
                except error_util.VimException as excep:
                    LOG.warn(_("Unable to find suitable datastore for "
                               "volume: %(vol)s under host: %(host)s. "
                               "More details: %(excep)s") %
                             {'vol': volume['name'],
                              'host': host.obj, 'excep': excep})
            if backing:
                self.volumeops.cancel_retrieval(retrv_result)
                return backing
            retrv_result = self.volumeops.continue_retrieval(retrv_result)

        msg = _("Unable to create volume: %s in the inventory.")
        LOG.error(msg % volume['name'])
        raise error_util.VimException(msg % volume['name'])
예제 #3
0
    def get_dss_rp(self, host):
        """Get datastores and resource pool of the host.

        :param host: Managed object reference of the host
        :return: Datastores mounted to the host and resource pool to which
                 the host belongs to
        """
        props = self._session.invoke_api(vim_util, 'get_object_properties',
                                         self._session.vim, host,
                                         ['datastore', 'parent'])
        # Get datastores and compute resource or cluster compute resource
        datastores = None
        compute_resource = None
        for elem in props:
            for prop in elem.propSet:
                if prop.name == 'datastore':
                    datastores = prop.val.ManagedObjectReference
                elif prop.name == 'parent':
                    compute_resource = prop.val
        # Get resource pool from compute resource or cluster compute resource
        resource_pool = self._session.invoke_api(vim_util,
                                                 'get_object_property',
                                                 self._session.vim,
                                                 compute_resource,
                                                 'resourcePool')
        if not datastores:
            msg = _("There are no datastores present under %s.")
            LOG.error(msg % host)
            raise error_util.VimException(msg % host)
        return (datastores, resource_pool)
예제 #4
0
    def __init__(self, session, host, rp_ref, vm_folder_ref, vm_create_spec,
                 vmdk_size):
        """Initialize a writer for vmdk file.

        :param session: a valid api session to ESX/VC server
        :param host: the ESX or VC host IP
        :param rp_ref: resource pool into which backing VM is imported
        :param vm_folder_ref: VM folder in ESX/VC inventory to use as parent
               of backing VM
        :param vm_create_spec: backing VM created using this create spec
        :param vmdk_size: VMDK size to be imported into backing VM
        """
        self._session = session
        self._vmdk_size = vmdk_size
        self._progress = 0
        lease = session.invoke_api(session.vim,
                                   'ImportVApp',
                                   rp_ref,
                                   spec=vm_create_spec,
                                   folder=vm_folder_ref)
        session.wait_for_lease_ready(lease)
        self._lease = lease
        lease_info = session.invoke_api(vim_util, 'get_object_property',
                                        session.vim, lease, 'info')
        self._vm_ref = lease_info.entity
        # Find the url for vmdk device
        url = self.find_vmdk_url(lease_info, host)
        if not url:
            msg = _("Could not retrieve URL from lease.")
            LOG.exception(msg)
            raise error_util.VimException(msg)
        LOG.info(_LI("Opening vmdk url: %s for write.") % url)

        # Prepare the http connection to the vmdk url
        cookies = session.vim.client.options.transport.cookiejar
        _urlparse = urlparse.urlparse(url)
        scheme, netloc, path, _params, query, _fragment = _urlparse
        if scheme == 'http':
            conn = httplib.HTTPConnection(netloc)
        elif scheme == 'https':
            conn = httplib.HTTPSConnection(netloc)
        if query:
            path = path + '?' + query
        conn.putrequest('PUT', path)
        conn.putheader('User-Agent', USER_AGENT)
        conn.putheader('Content-Length', str(vmdk_size))
        conn.putheader('Overwrite', 't')
        conn.putheader('Cookie', self._build_vim_cookie_headers(cookies))
        conn.putheader('Content-Type', 'binary/octet-stream')
        conn.endheaders()
        self.conn = conn
        VMwareHTTPFile.__init__(self, conn)
예제 #5
0
    def _create_backing_in_inventory(self, volume):
        """Creates backing under any suitable host.

        The method tries to pick datastore that can fit the volume under
        any host in the inventory.

        :param volume: Volume object
        :return: Reference to the created backing
        """
        # Get all hosts
        hosts = self.volumeops.get_hosts()
        if not hosts:
            msg = _("There are no hosts in the inventory.")
            LOG.error(msg)
            raise error_util.VimException(msg)

        backing = None
        for host in hosts:
            try:
                host = hosts[0].obj
                backing = self._create_backing(volume, host)
                break
            except error_util.VimException as excep:
                LOG.warn(
                    _("Unable to find suitable datastore for "
                      "volume: %(vol)s under host: %(host)s. "
                      "More details: %(excep)s") % {
                          'vol': volume['name'],
                          'host': host,
                          'excep': excep
                      })
        if backing:
            return backing
        msg = _("Unable to create volume: %(vol)s on the hosts: %(hosts)s.")
        LOG.error(msg % {'vol': volume['name'], 'hosts': hosts})
        raise error_util.VimException(msg % {
            'vol': volume['name'],
            'hosts': hosts
        })
예제 #6
0
    def _select_datastore_summary(self, size_bytes, datastores):
        """Get the best datastore summary from the given datastore list.

        The implementation selects a datastore which is connected to maximum
        number of hosts, provided there is enough space to accommodate the
        volume. Ties are broken based on space utilization; datastore with
        low space utilization is preferred.

        :param size_bytes: Size in bytes of the volume
        :param datastores: Datastores from which a choice is to be made
                           for the volume
        :return: Summary of the best datastore selected for volume
        """
        best_summary = None
        max_host_count = 0
        best_space_utilization = 1.0

        for datastore in datastores:
            summary = self.volumeops.get_summary(datastore)
            if summary.freeSpace > size_bytes:
                host_count = len(self.volumeops.get_connected_hosts(datastore))
                if host_count > max_host_count:
                    max_host_count = host_count
                    best_space_utilization = self._compute_space_utilization(
                        summary)
                    best_summary = summary
                elif host_count == max_host_count:
                    # break the tie based on space utilization
                    space_utilization = self._compute_space_utilization(
                        summary)
                    if space_utilization < best_space_utilization:
                        best_space_utilization = space_utilization
                        best_summary = summary

        if not best_summary:
            msg = _("Unable to pick datastore to accommodate %(size)s bytes "
                    "from the datastores: %(dss)s.") % {
                        'size': size_bytes,
                        'dss': datastores
                    }
            LOG.error(msg)
            raise error_util.VimException(msg)

        LOG.debug(
            _("Selected datastore: %(datastore)s with %(host_count)d "
              "connected host(s) for the volume.") % {
                  'datastore': best_summary,
                  'host_count': max_host_count
              })
        return best_summary
예제 #7
0
    def test_invoke_api_with_expected_exception(self):
        api_session = self._create_api_session(True)
        ret = mock.Mock()
        responses = [error_util.VimException(None), ret]

        def api(*args, **kwargs):
            response = responses.pop(0)
            if isinstance(response, Exception):
                raise response
            return response

        module = mock.Mock()
        module.api = api
        with mock.patch.object(greenthread, 'sleep'):
            self.assertEqual(ret, api_session.invoke_api(module, 'api'))
예제 #8
0
    def get_dss_rp(self, host):
        """Get accessible datastores and resource pool of the host.

        :param host: Managed object reference of the host
        :return: Datastores accessible to the host and resource pool to which
                 the host belongs to
        """

        props = self._session.invoke_api(vim_util, 'get_object_properties',
                                         self._session.vim, host,
                                         ['datastore', 'parent'])
        # Get datastores and compute resource or cluster compute resource
        datastores = []
        compute_resource = None
        for elem in props:
            for prop in elem.propSet:
                if prop.name == 'datastore' and prop.val:
                    # Consider only if datastores are present under host
                    datastores = prop.val.ManagedObjectReference
                elif prop.name == 'parent':
                    compute_resource = prop.val
        LOG.debug(_("Datastores attached to host %(host)s are: %(ds)s."), {
            'host': host,
            'ds': datastores
        })
        # Filter datastores based on if it is accessible, mounted and writable
        valid_dss = []
        for datastore in datastores:
            if self._is_valid(datastore, host):
                valid_dss.append(datastore)
        # Get resource pool from compute resource or cluster compute resource
        resource_pool = self._session.invoke_api(vim_util,
                                                 'get_object_property',
                                                 self._session.vim,
                                                 compute_resource,
                                                 'resourcePool')
        if not valid_dss:
            msg = _("There are no valid datastores attached to %s.") % host
            LOG.error(msg)
            raise error_util.VimException(msg)
        else:
            LOG.debug(_("Valid datastores are: %s"), valid_dss)
        return (valid_dss, resource_pool)
예제 #9
0
    def _select_ds_for_volume(self, size_gb):
        """Select datastore that can accommodate a volume of given size.

        Returns the selected datastore summary along with a compute host and
        its resource pool and folder where the volume can be created
        :return: (host, rp, folder, summary)
        """
        retrv_result = self.volumeops.get_hosts()
        while retrv_result:
            hosts = retrv_result.objects
            if not hosts:
                break
            (selected_host, rp, folder, summary) = (None, None, None, None)
            for host in hosts:
                host = host.obj
                try:
                    (dss, rp) = self.volumeops.get_dss_rp(host)
                    (folder,
                     summary) = self._get_folder_ds_summary(size_gb, rp, dss)
                    selected_host = host
                    break
                except error_util.VimException as excep:
                    LOG.warn(
                        _("Unable to find suitable datastore for volume "
                          "of size: %(vol)s GB under host: %(host)s. "
                          "More details: %(excep)s") % {
                              'vol': size_gb,
                              'host': host.obj,
                              'excep': excep
                          })
            if selected_host:
                self.volumeops.cancel_retrieval(retrv_result)
                return (selected_host, rp, folder, summary)
            retrv_result = self.volumeops.continue_retrieval(retrv_result)

        msg = _("Unable to find host to accommodate a disk of size: %s "
                "in the inventory.") % size_gb
        LOG.error(msg)
        raise error_util.VimException(msg)
예제 #10
0
    def __init__(self, session, host, vm_ref, vmdk_path, vmdk_size):
        """Initialize a writer for vmdk file.

        During an export operation the vmdk disk is converted to a
        stream-optimized sparse disk format. So the size of the VMDK
        after export may be smaller than the current vmdk disk size.

        :param session: a valid api session to ESX/VC server
        :param host: the ESX or VC host IP
        :param vm_ref: backing VM whose vmdk is to be exported
        :param vmdk_path: datastore relative path to vmdk file to be exported
        :param vmdk_size: current disk size of vmdk file to be exported
        """
        self._session = session
        self._vmdk_size = vmdk_size
        self._progress = 0
        lease = session.invoke_api(session.vim, 'ExportVm', vm_ref)
        session.wait_for_lease_ready(lease)
        self._lease = lease
        lease_info = session.invoke_api(vim_util, 'get_object_property',
                                        session.vim, lease, 'info')

        # find the right disk url corresponding to given vmdk_path
        url = self.find_vmdk_url(lease_info, host)
        if not url:
            msg = _("Could not retrieve URL from lease.")
            LOG.exception(msg)
            raise error_util.VimException(msg)
        LOG.info(_("Opening vmdk url: %s for read.") % url)

        cookies = session.vim.client.options.transport.cookiejar
        headers = {
            'User-Agent': USER_AGENT,
            'Cookie': self._build_vim_cookie_headers(cookies)
        }
        request = urllib2.Request(url, None, headers)
        conn = urllib2.urlopen(request)
        VMwareHTTPFile.__init__(self, conn)
예제 #11
0
 def func(*args, **kwargs):
     raise error_util.VimException(None)
예제 #12
0
        def vim_request_handler(managed_object, **kwargs):
            """Handler for VI SDK calls.

            Builds the SOAP message and parses the response for fault
            checking and other errors.

            :param managed_object:Managed object reference
            :param kwargs: Keyword arguments of the call
            :return: Response of the API call
            """

            try:
                if isinstance(managed_object, str):
                    # For strings use string value for value and type
                    # of the managed object.
                    managed_object = get_moref(managed_object, managed_object)
                request = getattr(self.client.service, attr_name)
                response = request(managed_object, **kwargs)
                if (attr_name.lower() == 'retrievepropertiesex'):
                    retrieve_properties_ex_fault_checker(response)
                return response

            except error_util.VimFaultException as excep:
                raise

            except suds.WebFault as excep:
                doc = excep.document
                detail = doc.childAtPath('/Envelope/Body/Fault/detail')
                fault_list = []
                for child in detail.getChildren():
                    fault_list.append(child.get('type'))
                raise error_util.VimFaultException(fault_list, excep)

            except AttributeError as excep:
                raise error_util.VimAttributeException(
                    _("No such SOAP method "
                      "%(attr)s. Detailed "
                      "error: %(excep)s.") % {
                          'attr': attr_name,
                          'excep': excep
                      })

            except (httplib.CannotSendRequest, httplib.ResponseNotReady,
                    httplib.CannotSendHeader) as excep:
                raise error_util.SessionOverLoadException(
                    _("httplib error in "
                      "%(attr)s: "
                      "%(excep)s.") % {
                          'attr': attr_name,
                          'excep': excep
                      })

            except (urllib2.URLError, urllib2.HTTPError) as excep:
                raise error_util.VimConnectionException(
                    _("urllib2 error in %(attr)s: %(excep)s.") % {
                        'attr': attr_name,
                        'excep': excep
                    })

            except Exception as excep:
                # Socket errors which need special handling for they
                # might be caused by server API call overload
                if (str(excep).find(ADDRESS_IN_USE_ERROR) != -1
                        or str(excep).find(CONN_ABORT_ERROR)) != -1:
                    raise error_util.SessionOverLoadException(
                        _("Socket error "
                          "in %(attr)s: "
                          "%(excep)s.") % {
                              'attr': attr_name,
                              'excep': excep
                          })
                # Type error that needs special handling for it might be
                # caused by server API call overload
                elif str(excep).find(RESP_NOT_XML_ERROR) != -1:
                    raise error_util.SessionOverLoadException(
                        _("Type error "
                          "in %(attr)s: "
                          "%(excep)s.") % {
                              'attr': attr_name,
                              'excep': excep
                          })
                else:
                    raise error_util.VimException(
                        _("Error in %(attr)s. "
                          "Detailed error: "
                          "%(excep)s.") % {
                              'attr': attr_name,
                              'excep': excep
                          })
예제 #13
0
    def test_select_datastore(self, filter_datastores, get_profile_id):
        # Test with no hosts.
        size_bytes = units.Ki
        req = {self._ds_sel.SIZE_BYTES: size_bytes}
        self._vops.get_hosts.return_value = mock.Mock(objects=[])

        self.assertEqual((), self._ds_sel.select_datastore(req))
        self._vops.get_hosts.assert_called_once_with()

        # Test with single host with no valid datastores.
        host_1 = mock.sentinel.host_1
        self._vops.get_hosts.return_value = mock.Mock(
            objects=[mock.Mock(obj=host_1)])
        self._vops.continue_retrieval.return_value = None
        self._vops.get_dss_rp.side_effect = error_util.VimException('error')

        self.assertEqual((), self._ds_sel.select_datastore(req))
        self._vops.get_dss_rp.assert_called_once_with(host_1)

        # Test with three hosts and vCenter connection problem while fetching
        # datastores for the second host.
        self._vops.get_dss_rp.reset_mock()
        host_2 = mock.sentinel.host_2
        host_3 = mock.sentinel.host_3
        self._vops.get_hosts.return_value = mock.Mock(objects=[
            mock.Mock(obj=host_1),
            mock.Mock(obj=host_2),
            mock.Mock(obj=host_3)
        ])
        self._vops.get_dss_rp.side_effect = [
            error_util.VimException('no valid datastores'),
            error_util.VimConnectionException('connection error')
        ]

        self.assertRaises(error_util.VimConnectionException,
                          self._ds_sel.select_datastore, req)
        get_dss_rp_exp_calls = [mock.call(host_1), mock.call(host_2)]
        self.assertEqual(get_dss_rp_exp_calls,
                         self._vops.get_dss_rp.call_args_list)

        # Modify previous case to return datastores for second and third host,
        # where none of them meet the requirements which include a storage
        # profile and affinity requirements.
        aff_ds_types = [ds_sel.DatastoreType.VMFS]
        req[ds_sel.DatastoreSelector.HARD_AFFINITY_DS_TYPE] = aff_ds_types

        ds_1a = mock.sentinel.ds_1a
        anti_affinity_ds = [ds_1a]
        req[ds_sel.DatastoreSelector.HARD_ANTI_AFFINITY_DS] = anti_affinity_ds

        profile_name = mock.sentinel.profile_name
        req[ds_sel.DatastoreSelector.PROFILE_NAME] = profile_name

        profile_id = mock.sentinel.profile_id
        get_profile_id.return_value = profile_id

        ds_2a = mock.sentinel.ds_2a
        ds_2b = mock.sentinel.ds_2b
        ds_3a = mock.sentinel.ds_3a

        self._vops.get_dss_rp.reset_mock()
        rp_2 = mock.sentinel.rp_2
        rp_3 = mock.sentinel.rp_3
        self._vops.get_dss_rp.side_effect = [
            error_util.VimException('no valid datastores'),
            ([ds_2a, ds_2b], rp_2), ([ds_3a], rp_3)
        ]

        filter_datastores.return_value = []

        self.assertEqual((), self._ds_sel.select_datastore(req))
        get_profile_id.assert_called_once_with(profile_name)
        get_dss_rp_exp_calls.append(mock.call(host_3))
        self.assertEqual(get_dss_rp_exp_calls,
                         self._vops.get_dss_rp.call_args_list)
        filter_datastores_exp_calls = [
            mock.call([ds_2a, ds_2b], size_bytes, profile_id, anti_affinity_ds,
                      aff_ds_types),
            mock.call([ds_3a], size_bytes, profile_id, anti_affinity_ds,
                      aff_ds_types)
        ]
        self.assertEqual(filter_datastores_exp_calls,
                         filter_datastores.call_args_list)

        # Modify previous case to have a non-empty summary list after filtering
        # with preferred utilization threshold unset.
        self._vops.get_dss_rp.side_effect = [
            error_util.VimException('no valid datastores'),
            ([ds_2a, ds_2b], rp_2), ([ds_3a], rp_3)
        ]

        summary_2b = self._create_summary(ds_2b,
                                          free_space=0.5 * units.Mi,
                                          capacity=units.Mi)
        filter_datastores.side_effect = [[summary_2b]]
        self._vops.get_connected_hosts.return_value = [host_1]

        self.assertEqual((host_2, rp_2, summary_2b),
                         self._ds_sel.select_datastore(req))

        # Modify previous case to have a preferred utilization threshold
        # satsified by one datastore.
        self._vops.get_dss_rp.side_effect = [
            error_util.VimException('no valid datastores'),
            ([ds_2a, ds_2b], rp_2), ([ds_3a], rp_3)
        ]

        req[ds_sel.DatastoreSelector.PREF_UTIL_THRESH] = 0.4
        summary_3a = self._create_summary(ds_3a,
                                          free_space=0.7 * units.Mi,
                                          capacity=units.Mi)
        filter_datastores.side_effect = [[summary_2b], [summary_3a]]

        self.assertEqual((host_3, rp_3, summary_3a),
                         self._ds_sel.select_datastore(req))

        # Modify previous case to have a preferred utilization threshold
        # which cannot be satisfied.
        self._vops.get_dss_rp.side_effect = [
            error_util.VimException('no valid datastores'),
            ([ds_2a, ds_2b], rp_2), ([ds_3a], rp_3)
        ]
        filter_datastores.side_effect = [[summary_2b], [summary_3a]]

        req[ds_sel.DatastoreSelector.PREF_UTIL_THRESH] = 0.2
        summary_2b.freeSpace = 0.75 * units.Mi

        self.assertEqual((host_2, rp_2, summary_2b),
                         self._ds_sel.select_datastore(req))

        # Clear side effects.
        self._vops.get_dss_rp.side_effect = None