¡@

Home 

OpenStack Study: api.py

OpenStack Index

**** CubicPower OpenStack Study ****

# Copyright (c) 2013 VMware, Inc.

# All Rights Reserved.

#

# Licensed under the Apache License, Version 2.0 (the "License"); you may

# not use this file except in compliance with the License. You may obtain

# a copy of the License at

#

# http://www.apache.org/licenses/LICENSE-2.0

#

# Unless required by applicable law or agreed to in writing, software

# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT

# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the

# License for the specific language governing permissions and limitations

# under the License.

"""

Session and API call management for VMware ESX/VC server.

Provides abstraction over cinder.volume.drivers.vmware.vim.Vim SOAP calls.

"""

from cinder.openstack.common import log as logging

from cinder.openstack.common import loopingcall

from cinder.volume.drivers.vmware import error_util

from cinder.volume.drivers.vmware import pbm

from cinder.volume.drivers.vmware import vim

from cinder.volume.drivers.vmware import vim_util

LOG = logging.getLogger(__name__)

**** CubicPower OpenStack Study ****

class Retry(object):

"""Decorator for retrying a function upon suggested exceptions.

The method retries for given number of times and the sleep

time increments till the max sleep time is reached.

If max retries is set to -1, then the decorated function is

invoked in

**** CubicPower OpenStack Study ****

    def __init__(self, max_retry_count=-1, inc_sleep_time=10,

                 max_sleep_time=60, exceptions=()):

        """Initialize retry object based on input params.

        :param max_retry_count: Max number of times, a function must be

                                retried when one of input 'exceptions'

                                is caught. The default -1 will always

                                retry the function till a non-exception

                                case, or an un-wanted error case arises.

        :param inc_sleep_time: Incremental time in seconds for sleep time

                               between retrial

        :param max_sleep_time: Max sleep time beyond which the sleep time will

                               not be incremented using param inc_sleep_time

                               and max_sleep_time will be used as sleep time

        :param exceptions: Suggested exceptions for which the function must be

                           retried

        """

        self._max_retry_count = max_retry_count

        self._inc_sleep_time = inc_sleep_time

        self._max_sleep_time = max_sleep_time

        self._exceptions = exceptions

        self._retry_count = 0

        self._sleep_time = 0

**** CubicPower OpenStack Study ****

    def __call__(self, f):

        def _func(*args, **kwargs):

            try:

                result = f(*args, **kwargs)

            except self._exceptions as excep:

                LOG.exception(_("Failure while invoking function: "

                                "%(func)s. Error: %(excep)s.") %

                              {'func': f.__name__, 'excep': excep})

                if (self._max_retry_count != -1 and

                        self._retry_count >= self._max_retry_count):

                    raise excep

                else:

                    self._retry_count += 1

                    self._sleep_time += self._inc_sleep_time

                    return self._sleep_time

            except Exception as excep:

                raise excep

            # got result. Stop the loop.

            raise loopingcall.LoopingCallDone(result)

        def func(*args, **kwargs):

            loop = loopingcall.DynamicLoopingCall(_func, *args, **kwargs)

            timer = loop.start(periodic_interval_max=self._max_sleep_time)

            return timer.wait()

        return func

**** CubicPower OpenStack Study ****

        def _func(*args, **kwargs):

            try:

                result = f(*args, **kwargs)

            except self._exceptions as excep:

                LOG.exception(_("Failure while invoking function: "

                                "%(func)s. Error: %(excep)s.") %

                              {'func': f.__name__, 'excep': excep})

                if (self._max_retry_count != -1 and

                        self._retry_count >= self._max_retry_count):

                    raise excep

                else:

                    self._retry_count += 1

                    self._sleep_time += self._inc_sleep_time

                    return self._sleep_time

            except Exception as excep:

                raise excep

            # got result. Stop the loop.

            raise loopingcall.LoopingCallDone(result)

**** CubicPower OpenStack Study ****

        def func(*args, **kwargs):

            loop = loopingcall.DynamicLoopingCall(_func, *args, **kwargs)

            timer = loop.start(periodic_interval_max=self._max_sleep_time)

            return timer.wait()

        return func

**** CubicPower OpenStack Study ****

class VMwareAPISession(object):

"""Sets up a session with the server and handles all calls made to it."""

@Retry(exceptions=(Exception))

**** CubicPower OpenStack Study ****

    def __init__(self, server_ip, server_username, server_password,

                 api_retry_count, task_poll_interval, scheme='https',

                 create_session=True, wsdl_loc=None, pbm_wsdl=None):

        """Constructs session object.

        :param server_ip: IP address of ESX/VC server

        :param server_username: Username of ESX/VC server admin user

        :param server_password: Password for param server_username

        :param api_retry_count: Number of times an API must be retried upon

                                session/connection related errors

        :param task_poll_interval: Sleep time in seconds for polling an

                                   on-going async task as part of the API call

        :param scheme: http or https protocol

        :param create_session: Boolean whether to set up connection at the

                               time of instance creation

        :param wsdl_loc: VIM WSDL file location for invoking SOAP calls on

                         server using suds

        :param pbm_wsdl: PBM WSDL file location. If set to None the storage

                         policy related functionality will be disabled.

        """

        self._server_ip = server_ip

        self._server_username = server_username

        self._server_password = server_password

        self._wsdl_loc = wsdl_loc

        self._api_retry_count = api_retry_count

        self._task_poll_interval = task_poll_interval

        self._scheme = scheme

        self._session_id = None

        self._session_username = None

        self._vim = None

        self._pbm_wsdl = pbm_wsdl

        self._pbm = None

        if create_session:

            self.create_session()

    @property

**** CubicPower OpenStack Study ****

    def vim(self):

        if not self._vim:

            self._vim = vim.Vim(protocol=self._scheme, host=self._server_ip,

                                wsdl_loc=self._wsdl_loc)

        return self._vim

    @property

**** CubicPower OpenStack Study ****

    def pbm(self):

        if not self._pbm and self._pbm_wsdl:

            self._pbm = pbm.PBMClient(self.vim, self._pbm_wsdl,

                                      protocol=self._scheme,

                                      host=self._server_ip)

        return self._pbm

**** CubicPower OpenStack Study ****

    def create_session(self):

        """Establish session with the server."""

        # Login and setup the session with the server for making

        # API calls

        session_manager = self.vim.service_content.sessionManager

        session = self.vim.Login(session_manager,

                                 userName=self._server_username,

                                 password=self._server_password)

        # Terminate the earlier session, if possible (For the sake of

        # preserving sessions as there is a limit to the number of

        # sessions we can have)

        if self._session_id:

            try:

                self.vim.TerminateSession(session_manager,

                                          sessionId=[self._session_id])

            except Exception as excep:

                # This exception is something we can live with. It is

                # just an extra caution on our side. The session may

                # have been cleared. We could have made a call to

                # SessionIsActive, but that is an overhead because we

                # anyway would have to call TerminateSession.

                LOG.exception(_("Error while terminating session: %s.") %

                              excep)

        self._session_id = session.key

        # We need to save the username in the session since we may need it

        # later to check active session. The SessionIsActive method requires

        # the username parameter to be exactly same as that in the session

        # object. We can't use the username used for login since the Login

        # method ignores the case.

        self._session_username = session.userName

        if self.pbm:

            self.pbm.set_cookie()

        LOG.info(_("Successfully established connection to the server."))

**** CubicPower OpenStack Study ****

    def __del__(self):

        """Logs-out the sessions."""

        try:

            self.vim.Logout(self.vim.service_content.sessionManager)

        except Exception as excep:

            LOG.exception(_("Error while logging out from vim session: %s."),

                          excep)

        if self._pbm:

            try:

                self.pbm.Logout(self.pbm.service_content.sessionManager)

            except Exception as excep:

                LOG.exception(_("Error while logging out from pbm session: "

                                "%s."), excep)

**** CubicPower OpenStack Study ****

    def invoke_api(self, module, method, *args, **kwargs):

        """Wrapper method for invoking APIs.

        Here we retry the API calls for exceptions which may come because

        of session overload.

        Make sure if a Vim instance is being passed here, this session's

        Vim (self.vim) instance is used, as we retry establishing session

        in case of session timedout.

        :param module: Module invoking the VI SDK calls

        :param method: Method in the module that invokes the VI SDK call

        :param args: Arguments to the method

        :param kwargs: Keyword arguments to the method

        :return: Response of the API call

        """

        @Retry(max_retry_count=self._api_retry_count,

               exceptions=(error_util.VimException))

        def _invoke_api(module, method, *args, **kwargs):

            while True:

                try:

                    api_method = getattr(module, method)

                    return api_method(*args, **kwargs)

                except error_util.VimFaultException as excep:

                    if error_util.NOT_AUTHENTICATED not in excep.fault_list:

                        raise excep

                    # If it is a not-authenticated fault, we re-authenticate

                    # the user and retry the API invocation.

                    # The not-authenticated fault is set by the fault checker

                    # due to an empty response. An empty response could be a

                    # valid response; for e.g., response for the query to

                    # return the VMs in an ESX server which has no VMs in it.

                    # Also, the server responds with an empty response in the

                    # case of an inactive session. Therefore, we need a way to

                    # differentiate between these two cases.

                    if self._is_current_session_active():

                        LOG.debug(_("Returning empty response for "

                                    "%(module)s.%(method)s invocation."),

                                  {'module': module,

                                   'method': method})

                        return []

                    # empty response is due to an inactive session

                    LOG.warn(_("Current session: %(session)s is inactive; "

                               "re-creating the session while invoking "

                               "method %(module)s.%(method)s."),

                             {'session': self._session_id,

                              'module': module,

                              'method': method},

                             exc_info=True)

                    self.create_session()

        return _invoke_api(module, method, *args, **kwargs)

**** CubicPower OpenStack Study ****

        def _invoke_api(module, method, *args, **kwargs):

            while True:

                try:

                    api_method = getattr(module, method)

                    return api_method(*args, **kwargs)

                except error_util.VimFaultException as excep:

                    if error_util.NOT_AUTHENTICATED not in excep.fault_list:

                        raise excep

                    # If it is a not-authenticated fault, we re-authenticate

                    # the user and retry the API invocation.

                    # The not-authenticated fault is set by the fault checker

                    # due to an empty response. An empty response could be a

                    # valid response; for e.g., response for the query to

                    # return the VMs in an ESX server which has no VMs in it.

                    # Also, the server responds with an empty response in the

                    # case of an inactive session. Therefore, we need a way to

                    # differentiate between these two cases.

                    if self._is_current_session_active():

                        LOG.debug(_("Returning empty response for "

                                    "%(module)s.%(method)s invocation."),

                                  {'module': module,

                                   'method': method})

                        return []

                    # empty response is due to an inactive session

                    LOG.warn(_("Current session: %(session)s is inactive; "

                               "re-creating the session while invoking "

                               "method %(module)s.%(method)s."),

                             {'session': self._session_id,

                              'module': module,

                              'method': method},

                             exc_info=True)

                    self.create_session()

        return _invoke_api(module, method, *args, **kwargs)

**** CubicPower OpenStack Study ****

    def _is_current_session_active(self):

        """Check if current session is active.

        :returns: True if the session is active; False otherwise

        """

        LOG.debug(_("Checking if the current session: %s is active."),

                  self._session_id)

        is_active = False

        try:

            is_active = self.vim.SessionIsActive(

                self.vim.service_content.sessionManager,

                sessionID=self._session_id,

                userName=self._session_username)

        except error_util.VimException:

            LOG.warn(_("Error occurred while checking whether the "

                       "current session: %s is active."),

                     self._session_id,

                     exc_info=True)

        return is_active

**** CubicPower OpenStack Study ****

    def wait_for_task(self, task):

        """Return a deferred that will give the result of the given task.

        The task is polled until it completes. The method returns the task

        information upon successful completion.

        :param task: Managed object reference of the task

        :return: Task info upon successful completion of the task

        """

        loop = loopingcall.FixedIntervalLoopingCall(self._poll_task, task)

        return loop.start(self._task_poll_interval).wait()

**** CubicPower OpenStack Study ****

    def _poll_task(self, task):

        """Poll the given task.

        If the task completes successfully then returns task info.

        In case of error sends back appropriate error.

        :param task: Managed object reference of the task

        :param event: Event that captures task status

        """

        try:

            task_info = self.invoke_api(vim_util, 'get_object_property',

                                        self.vim, task, 'info')

            if task_info.state in ['queued', 'running']:

                # If task already completed on server, it will not return

                # the progress.

                if hasattr(task_info, 'progress'):

                    LOG.debug(_("Task: %(task)s progress: %(prog)s.") %

                              {'task': task, 'prog': task_info.progress})

                return

            elif task_info.state == 'success':

                LOG.debug(_("Task %s status: success.") % task)

            else:

                error_msg = str(task_info.error.localizedMessage)

                LOG.exception(_("Task: %(task)s failed with error: %(err)s.") %

                              {'task': task, 'err': error_msg})

                raise error_util.VimFaultException([], error_msg)

        except Exception as excep:

            LOG.exception(_("Task: %(task)s failed with error: %(err)s.") %

                          {'task': task, 'err': excep})

            raise excep

        # got the result. So stop the loop.

        raise loopingcall.LoopingCallDone(task_info)

**** CubicPower OpenStack Study ****

    def wait_for_lease_ready(self, lease):

        loop = loopingcall.FixedIntervalLoopingCall(self._poll_lease, lease)

        return loop.start(self._task_poll_interval).wait()

**** CubicPower OpenStack Study ****

    def _poll_lease(self, lease):

        try:

            state = self.invoke_api(vim_util, 'get_object_property',

                                    self.vim, lease, 'state')

            if state == 'ready':

                # done

                LOG.debug(_("Lease is ready."))

            elif state == 'initializing':

                LOG.debug(_("Lease initializing..."))

                return

            elif state == 'error':

                error_msg = self.invoke_api(vim_util, 'get_object_property',

                                            self.vim, lease, 'error')

                LOG.exception(error_msg)

                excep = error_util.VimFaultException([], error_msg)

                raise excep

            else:

                # unknown state - complain

                error_msg = _("Error: unknown lease state %s.") % state

                raise error_util.VimFaultException([], error_msg)

        except Exception as excep:

            LOG.exception(excep)

            raise excep

        # stop the loop since state is ready

        raise loopingcall.LoopingCallDone()