Merge "VMware storage backend should use oslo.vmware"
This commit is contained in:
commit
9001bf283e
@ -1,273 +0,0 @@
|
||||
# Copyright (c) 2014 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 glance.vmware.vim.Vim SOAP calls.
|
||||
"""
|
||||
|
||||
from eventlet import event
|
||||
|
||||
import glance.openstack.common.log as logging
|
||||
from glance.openstack.common import loopingcall
|
||||
from glance.store.vmware import error_util
|
||||
from glance.store.vmware import vim
|
||||
from glance.store.vmware import vim_util
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
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 indefinitely till no exception is thrown or if
|
||||
the caught exception is not in the list of suggested exceptions.
|
||||
"""
|
||||
|
||||
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
|
||||
|
||||
def __call__(self, f):
|
||||
|
||||
def _func(done, *args, **kwargs):
|
||||
try:
|
||||
result = f(*args, **kwargs)
|
||||
done.send(result)
|
||||
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):
|
||||
done.send_exception(excep)
|
||||
else:
|
||||
self._retry_count += 1
|
||||
self._sleep_time += self._inc_sleep_time
|
||||
return self._sleep_time
|
||||
except Exception as excep:
|
||||
done.send_exception(excep)
|
||||
return 0
|
||||
|
||||
def func(*args, **kwargs):
|
||||
done = event.Event()
|
||||
loop = loopingcall.DynamicLoopingCall(_func, done, *args, **kwargs)
|
||||
loop.start(periodic_interval_max=self._max_sleep_time)
|
||||
result = done.wait()
|
||||
loop.stop()
|
||||
return result
|
||||
|
||||
return func
|
||||
|
||||
|
||||
class VMwareAPISession(object):
|
||||
"""Sets up a session with the server and handles all calls made to it."""
|
||||
|
||||
def __init__(self, server_ip, server_username, server_password,
|
||||
api_retry_count, task_poll_interval=5.0,
|
||||
scheme='https', create_session=True,
|
||||
wsdl_loc=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 scheme: http or https protocol
|
||||
:param create_session: Boolean whether to set up connection at the
|
||||
time of instance creation
|
||||
:param wsdl_loc: WSDL file location for invoking SOAP calls on server
|
||||
using suds
|
||||
"""
|
||||
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._vim = None
|
||||
if create_session:
|
||||
self.create_session()
|
||||
|
||||
@property
|
||||
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
|
||||
|
||||
@Retry(exceptions=(Exception))
|
||||
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
|
||||
LOG.info(_("Successfully established connection to the server."))
|
||||
|
||||
def __del__(self):
|
||||
"""Logs-out the session."""
|
||||
try:
|
||||
self.vim.Logout(self.vim.service_content.sessionManager)
|
||||
except Exception as excep:
|
||||
LOG.exception(_("Error while logging out the user: %s.") %
|
||||
excep)
|
||||
|
||||
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):
|
||||
last_fault_list = []
|
||||
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.
|
||||
|
||||
# Because of the idle session returning an empty
|
||||
# RetrieveProperties response and also the same is
|
||||
# returned when there is an empty answer to a query
|
||||
# (e.g. no VMs on the host), we have no way to
|
||||
# differentiate.
|
||||
# So if the previous response was also an empty
|
||||
# response and after creating a new session, we get
|
||||
# the same empty response, then we are sure of the
|
||||
# response being an empty response.
|
||||
if error_util.NOT_AUTHENTICATED in last_fault_list:
|
||||
return []
|
||||
last_fault_list = excep.fault_list
|
||||
LOG.warn(_("Not authenticated error occurred. "
|
||||
"Will create session and try "
|
||||
"API call again: %s.") % excep)
|
||||
self.create_session()
|
||||
|
||||
return _invoke_api(module, method, *args, **kwargs)
|
||||
|
||||
def _stop_loop(self, loop):
|
||||
loop.stop()
|
||||
|
||||
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
|
||||
"""
|
||||
done = event.Event()
|
||||
loop = loopingcall.FixedIntervalLoopingCall(self._poll_task,
|
||||
task, done)
|
||||
loop.start(self._task_poll_interval)
|
||||
task_info = done.wait()
|
||||
loop.stop()
|
||||
return task_info
|
||||
|
||||
def _poll_task(self, task, done):
|
||||
"""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 done: 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)
|
||||
done.send(task_info)
|
||||
else:
|
||||
error_msg = str(task_info.error.localizedMessage)
|
||||
LOG.exception(_("Task: %(task)s failed with error: %(err)s.") %
|
||||
{'task': task, 'err': error_msg})
|
||||
done.send_exception(error_util.VimFaultException([],
|
||||
error_msg))
|
||||
except Exception as excep:
|
||||
LOG.exception(_("Task: %(task)s failed with error: %(err)s.") %
|
||||
{'task': task, 'err': excep})
|
||||
done.send_exception(excep)
|
@ -1,48 +0,0 @@
|
||||
# Copyright (c) 2014 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.
|
||||
|
||||
"""
|
||||
Exception classes and SOAP response error checking module.
|
||||
"""
|
||||
|
||||
from glance.common import exception
|
||||
|
||||
|
||||
NOT_AUTHENTICATED = 'NotAuthenticated'
|
||||
|
||||
|
||||
class VimException(exception.GlanceException):
|
||||
"""The VIM Exception class."""
|
||||
|
||||
def __init__(self, msg):
|
||||
exception.GlanceException.__init__(self, msg)
|
||||
|
||||
|
||||
class SessionOverLoadException(VimException):
|
||||
"""Session Overload Exception."""
|
||||
pass
|
||||
|
||||
|
||||
class VimAttributeException(VimException):
|
||||
"""VI Attribute Error."""
|
||||
pass
|
||||
|
||||
|
||||
class VimFaultException(VimException):
|
||||
"""The VIM Fault exception class."""
|
||||
|
||||
def __init__(self, fault_list, msg):
|
||||
super(VimFaultException, self).__init__(msg)
|
||||
self.fault_list = fault_list
|
@ -1,241 +0,0 @@
|
||||
# Copyright (c) 2014 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.
|
||||
|
||||
"""
|
||||
Classes for making VMware VI SOAP calls.
|
||||
"""
|
||||
|
||||
import httplib
|
||||
import logging
|
||||
|
||||
import suds
|
||||
|
||||
from glance.store.vmware import error_util
|
||||
|
||||
logging.getLogger('suds').setLevel(logging.INFO)
|
||||
|
||||
RESP_NOT_XML_ERROR = "Response is 'text/html', not 'text/xml'"
|
||||
CONN_ABORT_ERROR = 'Software caused connection abort'
|
||||
ADDRESS_IN_USE_ERROR = 'Address already in use'
|
||||
|
||||
|
||||
def get_moref(value, type):
|
||||
"""Get managed object reference.
|
||||
|
||||
:param value: value for the managed object
|
||||
:param type: type of the managed object
|
||||
:return: Managed object reference with with input value and type
|
||||
"""
|
||||
moref = suds.sudsobject.Property(value)
|
||||
moref._type = type
|
||||
return moref
|
||||
|
||||
|
||||
class VIMMessagePlugin(suds.plugin.MessagePlugin):
|
||||
|
||||
def addAttributeForValue(self, node):
|
||||
"""Helper to handle AnyType.
|
||||
|
||||
suds does not handle AnyType properly.
|
||||
VI SDK requires type attribute to be set when AnyType is used
|
||||
|
||||
:param node: XML value node
|
||||
"""
|
||||
if node.name == 'value':
|
||||
node.set('xsi:type', 'xsd:string')
|
||||
|
||||
def marshalled(self, context):
|
||||
"""Marshal soap context.
|
||||
|
||||
Provides the plugin with the opportunity to prune empty
|
||||
nodes and fixup nodes before sending it to the server.
|
||||
|
||||
:param context: SOAP context
|
||||
"""
|
||||
# suds builds the entire request object based on the wsdl schema.
|
||||
# VI SDK throws server errors if optional SOAP nodes are sent
|
||||
# without values, e.g. <test/> as opposed to <test>test</test>
|
||||
context.envelope.prune()
|
||||
context.envelope.walk(self.addAttributeForValue)
|
||||
|
||||
|
||||
class Vim(object):
|
||||
"""The VIM Object."""
|
||||
|
||||
def __init__(self, protocol='https', host='localhost', wsdl_loc=None):
|
||||
"""Create communication interfaces for initiating SOAP transactions.
|
||||
|
||||
:param protocol: http or https
|
||||
:param host: Server IPAddress[:port] or Hostname[:port]
|
||||
:param wsdl_loc: Optional location of the VIM WSDL
|
||||
"""
|
||||
self._protocol = protocol
|
||||
self._host_name = host
|
||||
if not wsdl_loc:
|
||||
wsdl_loc = Vim._get_wsdl_loc(protocol, host)
|
||||
soap_url = Vim._get_soap_url(protocol, host)
|
||||
self._client = suds.client.Client(wsdl_loc, location=soap_url,
|
||||
plugins=[VIMMessagePlugin()])
|
||||
self._service_content = self.RetrieveServiceContent('ServiceInstance')
|
||||
|
||||
@staticmethod
|
||||
def _get_wsdl_loc(protocol, host_name):
|
||||
"""Return default WSDL file location hosted at the server.
|
||||
|
||||
:param protocol: http or https
|
||||
:param host_name: ESX/VC server host name
|
||||
:return: Default WSDL file location hosted at the server
|
||||
"""
|
||||
return '%s://%s/sdk/vimService.wsdl' % (protocol, host_name)
|
||||
|
||||
@staticmethod
|
||||
def _get_soap_url(protocol, host_name):
|
||||
"""Return URL to SOAP services for ESX/VC server.
|
||||
|
||||
:param protocol: https or http
|
||||
:param host_name: ESX/VC server host name
|
||||
:return: URL to SOAP services for ESX/VC server
|
||||
"""
|
||||
return '%s://%s/sdk' % (protocol, host_name)
|
||||
|
||||
@property
|
||||
def service_content(self):
|
||||
return self._service_content
|
||||
|
||||
@property
|
||||
def client(self):
|
||||
return self._client
|
||||
|
||||
def __getattr__(self, attr_name):
|
||||
"""Makes the API call and gets the result."""
|
||||
|
||||
def retrieve_properties_ex_fault_checker(response):
|
||||
"""Checks the RetrievePropertiesEx response for errors.
|
||||
|
||||
Certain faults are sent as part of the SOAP body as property of
|
||||
missingSet. For example NotAuthenticated fault. The method raises
|
||||
appropriate VimFaultException when an error is found.
|
||||
|
||||
:param response: Response from RetrievePropertiesEx API call
|
||||
"""
|
||||
|
||||
fault_list = []
|
||||
if not response:
|
||||
# This is the case when the session has timed out. ESX SOAP
|
||||
# server sends an empty RetrievePropertiesExResponse. Normally
|
||||
# missingSet in the returnval field has the specifics about
|
||||
# the error, but that's not the case with a timed out idle
|
||||
# session. It is as bad as a terminated session for we cannot
|
||||
# use the session. So setting fault to NotAuthenticated fault.
|
||||
fault_list = [error_util.NOT_AUTHENTICATED]
|
||||
else:
|
||||
for obj_cont in response:
|
||||
if hasattr(obj_cont, 'missingSet'):
|
||||
for missing_elem in obj_cont.missingSet:
|
||||
fault_type = missing_elem.fault.fault.__class__
|
||||
# Fault needs to be added to the type of fault
|
||||
# for uniformity in error checking as SOAP faults
|
||||
# define
|
||||
fault_list.append(fault_type.__name__)
|
||||
if fault_list:
|
||||
exc_msg_list = ', '.join(fault_list)
|
||||
raise error_util.VimFaultException(fault_list,
|
||||
_("Error(s): %s occurred "
|
||||
"in the call to "
|
||||
"RetrievePropertiesEx.") %
|
||||
exc_msg_list)
|
||||
|
||||
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, str(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 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})
|
||||
return vim_request_handler
|
||||
|
||||
def __repr__(self):
|
||||
return "VIM Object."
|
||||
|
||||
def __str__(self):
|
||||
return "VIM Object."
|
@ -1,301 +0,0 @@
|
||||
# Copyright (c) 2014 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.
|
||||
|
||||
"""
|
||||
The VMware API utility module.
|
||||
"""
|
||||
|
||||
|
||||
def build_selection_spec(client_factory, name):
|
||||
"""Builds the selection spec.
|
||||
|
||||
:param client_factory: Factory to get API input specs
|
||||
:param name: Name for the selection spec
|
||||
:return: Selection spec
|
||||
"""
|
||||
sel_spec = client_factory.create('ns0:SelectionSpec')
|
||||
sel_spec.name = name
|
||||
return sel_spec
|
||||
|
||||
|
||||
def build_traversal_spec(client_factory, name, type, path, skip,
|
||||
select_set):
|
||||
"""Builds the traversal spec object.
|
||||
|
||||
:param client_factory: Factory to get API input specs
|
||||
:param name: Name for the traversal spec
|
||||
:param type: Type of the managed object reference
|
||||
:param path: Property path of the managed object reference
|
||||
:param skip: Whether or not to filter the object identified by param path
|
||||
:param select_set: Set of selection specs specifying additional objects
|
||||
to filter
|
||||
:return: Traversal spec
|
||||
"""
|
||||
traversal_spec = client_factory.create('ns0:TraversalSpec')
|
||||
traversal_spec.name = name
|
||||
traversal_spec.type = type
|
||||
traversal_spec.path = path
|
||||
traversal_spec.skip = skip
|
||||
traversal_spec.selectSet = select_set
|
||||
return traversal_spec
|
||||
|
||||
|
||||
def build_recursive_traversal_spec(client_factory):
|
||||
"""Builds Recursive Traversal Spec to traverse managed object hierarchy.
|
||||
|
||||
:param client_factory: Factory to get API input specs
|
||||
:return: Recursive traversal spec
|
||||
"""
|
||||
visit_folders_select_spec = build_selection_spec(client_factory,
|
||||
'visitFolders')
|
||||
# Next hop from Datacenter
|
||||
dc_to_hf = build_traversal_spec(client_factory, 'dc_to_hf', 'Datacenter',
|
||||
'hostFolder', False,
|
||||
[visit_folders_select_spec])
|
||||
dc_to_vmf = build_traversal_spec(client_factory, 'dc_to_vmf', 'Datacenter',
|
||||
'vmFolder', False,
|
||||
[visit_folders_select_spec])
|
||||
|
||||
# Next hop from HostSystem
|
||||
h_to_vm = build_traversal_spec(client_factory, 'h_to_vm', 'HostSystem',
|
||||
'vm', False,
|
||||
[visit_folders_select_spec])
|
||||
|
||||
# Next hop from ComputeResource
|
||||
cr_to_h = build_traversal_spec(client_factory, 'cr_to_h',
|
||||
'ComputeResource', 'host', False, [])
|
||||
cr_to_ds = build_traversal_spec(client_factory, 'cr_to_ds',
|
||||
'ComputeResource', 'datastore', False, [])
|
||||
|
||||
rp_to_rp_select_spec = build_selection_spec(client_factory, 'rp_to_rp')
|
||||
rp_to_vm_select_spec = build_selection_spec(client_factory, 'rp_to_vm')
|
||||
|
||||
cr_to_rp = build_traversal_spec(client_factory, 'cr_to_rp',
|
||||
'ComputeResource', 'resourcePool', False,
|
||||
[rp_to_rp_select_spec,
|
||||
rp_to_vm_select_spec])
|
||||
|
||||
# Next hop from ClusterComputeResource
|
||||
ccr_to_h = build_traversal_spec(client_factory, 'ccr_to_h',
|
||||
'ClusterComputeResource', 'host',
|
||||
False, [])
|
||||
ccr_to_ds = build_traversal_spec(client_factory, 'ccr_to_ds',
|
||||
'ClusterComputeResource', 'datastore',
|
||||
False, [])
|
||||
ccr_to_rp = build_traversal_spec(client_factory, 'ccr_to_rp',
|
||||
'ClusterComputeResource', 'resourcePool',
|
||||
False,
|
||||
[rp_to_rp_select_spec,
|
||||
rp_to_vm_select_spec])
|
||||
# Next hop from ResourcePool
|
||||
rp_to_rp = build_traversal_spec(client_factory, 'rp_to_rp', 'ResourcePool',
|
||||
'resourcePool', False,
|
||||
[rp_to_rp_select_spec,
|
||||
rp_to_vm_select_spec])
|
||||
rp_to_vm = build_traversal_spec(client_factory, 'rp_to_vm', 'ResourcePool',
|
||||
'vm', False,
|
||||
[rp_to_rp_select_spec,
|
||||
rp_to_vm_select_spec])
|
||||
|
||||
# Get the assorted traversal spec which takes care of the objects to
|
||||
# be searched for from the rootFolder
|
||||
traversal_spec = build_traversal_spec(client_factory, 'visitFolders',
|
||||
'Folder', 'childEntity', False,
|
||||
[visit_folders_select_spec,
|
||||
h_to_vm, dc_to_hf, dc_to_vmf,
|
||||
cr_to_ds, cr_to_h, cr_to_rp,
|
||||
ccr_to_h, ccr_to_ds, ccr_to_rp,
|
||||
rp_to_rp, rp_to_vm])
|
||||
return traversal_spec
|
||||
|
||||
|
||||
def build_property_spec(client_factory, type='VirtualMachine',
|
||||
properties_to_collect=None,
|
||||
all_properties=False):
|
||||
"""Builds the Property Spec.
|
||||
|
||||
:param client_factory: Factory to get API input specs
|
||||
:param type: Type of the managed object reference property
|
||||
:param properties_to_collect: Properties of the managed object reference
|
||||
to be collected while traversal filtering
|
||||
:param all_properties: Whether all the properties of managed object
|
||||
reference needs to be collected
|
||||
:return: Property spec
|
||||
"""
|
||||
if not properties_to_collect:
|
||||
properties_to_collect = ['name']
|
||||
|
||||
property_spec = client_factory.create('ns0:PropertySpec')
|
||||
property_spec.all = all_properties
|
||||
property_spec.pathSet = properties_to_collect
|
||||
property_spec.type = type
|
||||
return property_spec
|
||||
|
||||
|
||||
def build_object_spec(client_factory, root_folder, traversal_specs):
|
||||
"""Builds the object Spec.
|
||||
|
||||
:param client_factory: Factory to get API input specs
|
||||
:param root_folder: Root folder reference as the starting point for
|
||||
traversal
|
||||
:param traversal_specs: filter specs required for traversal
|
||||
:return: Object spec
|
||||
"""
|
||||
object_spec = client_factory.create('ns0:ObjectSpec')
|
||||
object_spec.obj = root_folder
|
||||
object_spec.skip = False
|
||||
object_spec.selectSet = traversal_specs
|
||||
return object_spec
|
||||
|
||||
|
||||
def build_property_filter_spec(client_factory, property_specs, object_specs):
|
||||
"""Builds the Property Filter Spec.
|
||||
|
||||
:param client_factory: Factory to get API input specs
|
||||
:param property_specs: Property specs to be collected for filtered objects
|
||||
:param object_specs: Object specs to identify objects to be filtered
|
||||
:return: Property filter spec
|
||||
"""
|
||||
property_filter_spec = client_factory.create('ns0:PropertyFilterSpec')
|
||||
property_filter_spec.propSet = property_specs
|
||||
property_filter_spec.objectSet = object_specs
|
||||
return property_filter_spec
|
||||
|
||||
|
||||
def get_objects(vim, type, max_objects, props_to_collect=None,
|
||||
all_properties=False):
|
||||
"""Gets all managed object references of a specified type.
|
||||
|
||||
It is caller's responsibility to continue or cancel retrieval.
|
||||
|
||||
:param vim: Vim object
|
||||
:param type: Type of the managed object reference
|
||||
:param max_objects: Maximum number of objects that should be returned in
|
||||
a single call
|
||||
:param props_to_collect: Properties of the managed object reference
|
||||
to be collected
|
||||
:param all_properties: Whether all properties of the managed object
|
||||
reference are to be collected
|
||||
:return: All managed object references of a specified type
|
||||
"""
|
||||
|
||||
if not props_to_collect:
|
||||
props_to_collect = ['name']
|
||||
|
||||
client_factory = vim.client.factory
|
||||
recur_trav_spec = build_recursive_traversal_spec(client_factory)
|
||||
object_spec = build_object_spec(client_factory,
|
||||
vim.service_content.rootFolder,
|
||||
[recur_trav_spec])
|
||||
property_spec = build_property_spec(client_factory, type=type,
|
||||
properties_to_collect=props_to_collect,
|
||||
all_properties=all_properties)
|
||||
property_filter_spec = build_property_filter_spec(client_factory,
|
||||
[property_spec],
|
||||
[object_spec])
|
||||
options = client_factory.create('ns0:RetrieveOptions')
|
||||
options.maxObjects = max_objects
|
||||
return vim.RetrievePropertiesEx(vim.service_content.propertyCollector,
|
||||
specSet=[property_filter_spec],
|
||||
options=options)
|
||||
|
||||
|
||||
def get_object_properties(vim, mobj, properties):
|
||||
"""Gets properties of the managed object specified.
|
||||
|
||||
:param vim: Vim object
|
||||
:param mobj: Reference to the managed object
|
||||
:param properties: Properties of the managed object reference
|
||||
to be retrieved
|
||||
:return: Properties of the managed object specified
|
||||
"""
|
||||
|
||||
client_factory = vim.client.factory
|
||||
if mobj is None:
|
||||
return None
|
||||
collector = vim.service_content.propertyCollector
|
||||
property_filter_spec = client_factory.create('ns0:PropertyFilterSpec')
|
||||
property_spec = client_factory.create('ns0:PropertySpec')
|
||||
property_spec.all = (properties is None or len(properties) == 0)
|
||||
property_spec.pathSet = properties
|
||||
property_spec.type = mobj._type
|
||||
object_spec = client_factory.create('ns0:ObjectSpec')
|
||||
object_spec.obj = mobj
|
||||
object_spec.skip = False
|
||||
property_filter_spec.propSet = [property_spec]
|
||||
property_filter_spec.objectSet = [object_spec]
|
||||
options = client_factory.create('ns0:RetrieveOptions')
|
||||
options.maxObjects = 1
|
||||
retrieve_result = vim.RetrievePropertiesEx(collector,
|
||||
specSet=[property_filter_spec],
|
||||
options=options)
|
||||
cancel_retrieval(vim, retrieve_result)
|
||||
return retrieve_result.objects
|
||||
|
||||
|
||||
def _get_token(retrieve_result):
|
||||
"""Get token from results to obtain next set of results.
|
||||
|
||||
:retrieve_result: Result from the RetrievePropertiesEx API
|
||||
:return: Token to obtain next set of results. None if no more results.
|
||||
"""
|
||||
return getattr(retrieve_result, 'token', None)
|
||||
|
||||
|
||||
def cancel_retrieval(vim, retrieve_result):
|
||||
"""Cancels the retrieve operation if necessary.
|
||||
|
||||
:param vim: Vim object
|
||||
:param retrieve_result: Result from the RetrievePropertiesEx API
|
||||
"""
|
||||
|
||||
token = _get_token(retrieve_result)
|
||||
if token:
|
||||
collector = vim.service_content.propertyCollector
|
||||
vim.CancelRetrievePropertiesEx(collector, token=token)
|
||||
|
||||
|
||||
def continue_retrieval(vim, retrieve_result):
|
||||
"""Continue retrieving results, if present.
|
||||
|
||||
:param vim: Vim object
|
||||
:param retrieve_result: Result from the RetrievePropertiesEx API
|
||||
"""
|
||||
|
||||
token = _get_token(retrieve_result)
|
||||
if token:
|
||||
collector = vim.service_content.propertyCollector
|
||||
return vim.ContinueRetrievePropertiesEx(collector, token=token)
|
||||
|
||||
|
||||
def get_object_property(vim, mobj, property_name):
|
||||
"""Gets property of the managed object specified.
|
||||
|
||||
:param vim: Vim object
|
||||
:param mobj: Reference to the managed object
|
||||
:param property_name: Name of the property to be retrieved
|
||||
:return: Property of the managed object specified
|
||||
"""
|
||||
props = get_object_properties(vim, mobj, [property_name])
|
||||
prop_val = None
|
||||
if props:
|
||||
prop = None
|
||||
if hasattr(props[0], 'propSet'):
|
||||
# propSet will be set only if the server provides value
|
||||
# for the field
|
||||
prop = props[0].propSet
|
||||
if prop:
|
||||
prop_val = prop[0].val
|
||||
return prop_val
|
@ -20,6 +20,7 @@ import httplib
|
||||
|
||||
import netaddr
|
||||
from oslo.config import cfg
|
||||
from oslo.vmware import api
|
||||
import six.moves.urllib.parse as urlparse
|
||||
|
||||
from glance.common import exception
|
||||
@ -27,7 +28,7 @@ import glance.openstack.common.log as logging
|
||||
import glance.store
|
||||
import glance.store.base
|
||||
import glance.store.location
|
||||
from glance.store.vmware import api
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
@ -23,17 +23,21 @@ VMware Datastore backend
|
||||
|
||||
import ConfigParser
|
||||
import httplib
|
||||
import logging
|
||||
import os
|
||||
|
||||
import oslo.config.cfg
|
||||
from oslo.vmware import api
|
||||
import six.moves.urllib.parse as urlparse
|
||||
import testtools
|
||||
|
||||
from glance.store.vmware import api
|
||||
import glance.store.vmware_datastore
|
||||
import glance.tests.functional.store as store_tests
|
||||
|
||||
|
||||
logging.getLogger('suds').setLevel(logging.INFO)
|
||||
|
||||
|
||||
def read_config(path):
|
||||
cp = ConfigParser.RawConfigParser()
|
||||
cp.read(path)
|
||||
@ -47,6 +51,7 @@ def parse_config(config):
|
||||
'vmware_server_username',
|
||||
'vmware_server_password',
|
||||
'vmware_api_retry_count',
|
||||
'vmware_task_poll_interval',
|
||||
'vmware_store_image_dir',
|
||||
'vmware_datacenter_path',
|
||||
'vmware_datastore_name',
|
||||
@ -63,13 +68,14 @@ class VMwareDatastoreStoreError(RuntimeError):
|
||||
|
||||
|
||||
def vsphere_connect(server_ip, server_username, server_password,
|
||||
api_retry_count, scheme='https',
|
||||
create_session=True, wsdl_loc=None):
|
||||
api_retry_count, task_poll_interval,
|
||||
scheme='https', create_session=True, wsdl_loc=None):
|
||||
try:
|
||||
return api.VMwareAPISession(server_ip,
|
||||
server_username,
|
||||
server_password,
|
||||
api_retry_count,
|
||||
task_poll_interval,
|
||||
scheme=scheme,
|
||||
create_session=create_session,
|
||||
wsdl_loc=wsdl_loc)
|
||||
@ -105,6 +111,7 @@ class TestVMwareDatastoreStore(store_tests.BaseTestCase, testtools.TestCase):
|
||||
config['vmware_server_username'],
|
||||
config['vmware_server_password'],
|
||||
config['vmware_api_retry_count'],
|
||||
config['vmware_task_poll_interval'],
|
||||
scheme=scheme)
|
||||
|
||||
self.vmware_config = config
|
||||
|
@ -78,7 +78,7 @@ class FakeHTTPConnection(object):
|
||||
|
||||
class TestStore(base.StoreClearingUnitTest):
|
||||
|
||||
@mock.patch('glance.store.vmware.api.VMwareAPISession', autospec=True)
|
||||
@mock.patch('oslo.vmware.api.VMwareAPISession', autospec=True)
|
||||
def setUp(self, mock_session):
|
||||
"""Establish a clean test environment"""
|
||||
super(TestStore, self).setUp()
|
||||
|
@ -22,11 +22,13 @@ iso8601>=0.1.8
|
||||
ordereddict
|
||||
oslo.config>=1.2.0
|
||||
stevedore>=0.14
|
||||
suds>=0.4
|
||||
|
||||
# For Swift storage backend.
|
||||
python-swiftclient>=1.6
|
||||
|
||||
# For VMware storage backed.
|
||||
oslo.vmware
|
||||
|
||||
# For paste.util.template used in keystone.common.template
|
||||
Paste
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user