
With over 12k lines of code in openstackcloud class it is hardly manageable. Split methods on a service base into corresponding Mixings. It still make sense to think how individual mixins are structured (it is not very nice, when function in an object use property of self, which is not defined in the class, but exists only in a resulting mixed object). Initialization of individual Mixins in the connection should be also reviewed Change-Id: I81050d66e3a2dc0997e3e7620ff2c4e1891bfeb7
422 lines
16 KiB
Python
422 lines
16 KiB
Python
# 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 :class:`~openstack.connection.Connection` class is the primary interface
|
|
to the Python SDK. It maintains a context for a connection to a region of
|
|
a cloud provider. The :class:`~openstack.connection.Connection` has an
|
|
attribute to access each OpenStack service.
|
|
|
|
At a minimum, the :class:`~openstack.connection.Connection` class needs to be
|
|
created with a config or the parameters to build one.
|
|
|
|
While the overall system is very flexible, there are four main use cases
|
|
for different ways to create a :class:`~openstack.connection.Connection`.
|
|
|
|
* Using config settings and keyword arguments as described in
|
|
:ref:`openstack-config`
|
|
* Using only keyword arguments passed to the constructor ignoring config files
|
|
and environment variables.
|
|
* Using an existing authenticated `keystoneauth1.session.Session`, such as
|
|
might exist inside of an OpenStack service operational context.
|
|
* Using an existing :class:`~openstack.config.cloud_region.CloudRegion`.
|
|
|
|
Using config settings
|
|
---------------------
|
|
|
|
For users who want to create a :class:`~openstack.connection.Connection` making
|
|
use of named clouds in ``clouds.yaml`` files, ``OS_`` environment variables
|
|
and python keyword arguments, the :func:`openstack.connect` factory function
|
|
is the recommended way to go:
|
|
|
|
.. code-block:: python
|
|
|
|
import openstack
|
|
|
|
conn = openstack.connect(cloud='example', region_name='earth1')
|
|
|
|
If the application in question is a command line application that should also
|
|
accept command line arguments, an `argparse.Namespace` can be passed to
|
|
:func:`openstack.connect` that will have relevant arguments added to it and
|
|
then subsequently consumed by the constructor:
|
|
|
|
.. code-block:: python
|
|
|
|
import argparse
|
|
import openstack
|
|
|
|
options = argparse.ArgumentParser(description='Awesome OpenStack App')
|
|
conn = openstack.connect(options=options)
|
|
|
|
Using Only Keyword Arguments
|
|
----------------------------
|
|
|
|
If the application wants to avoid loading any settings from ``clouds.yaml`` or
|
|
environment variables, use the :class:`~openstack.connection.Connection`
|
|
constructor directly. As long as the ``cloud`` argument is omitted or ``None``,
|
|
the :class:`~openstack.connection.Connection` constructor will not load
|
|
settings from files or the environment.
|
|
|
|
.. note::
|
|
|
|
This is a different default behavior than the :func:`~openstack.connect`
|
|
factory function. In :func:`~openstack.connect` if ``cloud`` is omitted
|
|
or ``None``, a default cloud will be loaded, defaulting to the ``envvars``
|
|
cloud if it exists.
|
|
|
|
.. code-block:: python
|
|
|
|
from openstack import connection
|
|
|
|
conn = connection.Connection(
|
|
region_name='example-region',
|
|
auth=dict(
|
|
auth_url='https://auth.example.com',
|
|
username='amazing-user',
|
|
password='super-secret-password',
|
|
project_id='33aa1afc-03fe-43b8-8201-4e0d3b4b8ab5',
|
|
user_domain_id='054abd68-9ad9-418b-96d3-3437bb376703'),
|
|
compute_api_version='2',
|
|
identity_interface='internal')
|
|
|
|
Per-service settings as needed by `keystoneauth1.adapter.Adapter` such as
|
|
``api_version``, ``service_name``, and ``interface`` can be set, as seen
|
|
above, by prefixing them with the official ``service-type`` name of the
|
|
service. ``region_name`` is a setting for the entire
|
|
:class:`~openstack.config.cloud_region.CloudRegion` and cannot be set per
|
|
service.
|
|
|
|
From existing authenticated Session
|
|
-----------------------------------
|
|
|
|
For applications that already have an authenticated Session, simply passing
|
|
it to the :class:`~openstack.connection.Connection` constructor is all that
|
|
is needed:
|
|
|
|
.. code-block:: python
|
|
|
|
from openstack import connection
|
|
|
|
conn = connection.Connection(
|
|
session=session,
|
|
region_name='example-region',
|
|
compute_api_version='2',
|
|
identity_interface='internal')
|
|
|
|
From existing CloudRegion
|
|
-------------------------
|
|
|
|
If you already have an :class:`~openstack.config.cloud_region.CloudRegion`
|
|
you can pass it in instead:
|
|
|
|
.. code-block:: python
|
|
|
|
from openstack import connection
|
|
import openstack.config
|
|
|
|
config = openstack.config.get_cloud_region(
|
|
cloud='example', region_name='earth')
|
|
conn = connection.Connection(config=config)
|
|
|
|
Using the Connection
|
|
--------------------
|
|
|
|
Services are accessed through an attribute named after the service's official
|
|
service-type.
|
|
|
|
List
|
|
~~~~
|
|
|
|
An iterator containing a list of all the projects is retrieved in this manner:
|
|
|
|
.. code-block:: python
|
|
|
|
projects = conn.identity.projects()
|
|
|
|
Find or create
|
|
~~~~~~~~~~~~~~
|
|
|
|
If you wanted to make sure you had a network named 'zuul', you would first
|
|
try to find it and if that fails, you would create it::
|
|
|
|
network = conn.network.find_network("zuul")
|
|
if network is None:
|
|
network = conn.network.create_network(name="zuul")
|
|
|
|
Additional information about the services can be found in the
|
|
:ref:`service-proxies` documentation.
|
|
"""
|
|
import warnings
|
|
|
|
import keystoneauth1.exceptions
|
|
import requestsexceptions
|
|
import six
|
|
|
|
from openstack import _log
|
|
from openstack._meta import connection as _meta
|
|
from openstack.cloud import openstackcloud as _cloud
|
|
from openstack.cloud import _baremetal
|
|
from openstack.cloud import _block_storage
|
|
from openstack.cloud import _compute
|
|
from openstack.cloud import _clustering
|
|
from openstack.cloud import _coe
|
|
from openstack.cloud import _dns
|
|
from openstack.cloud import _floating_ip
|
|
from openstack.cloud import _identity
|
|
from openstack.cloud import _image
|
|
from openstack.cloud import _network
|
|
from openstack.cloud import _network_common
|
|
from openstack.cloud import _object_store
|
|
from openstack.cloud import _orchestration
|
|
from openstack.cloud import _security_group
|
|
from openstack import config as _config
|
|
from openstack.config import cloud_region
|
|
from openstack import exceptions
|
|
from openstack import service_description
|
|
|
|
__all__ = [
|
|
'from_config',
|
|
'Connection',
|
|
]
|
|
|
|
if requestsexceptions.SubjectAltNameWarning:
|
|
warnings.filterwarnings(
|
|
'ignore', category=requestsexceptions.SubjectAltNameWarning)
|
|
|
|
_logger = _log.setup_logging('openstack')
|
|
|
|
|
|
def from_config(cloud=None, config=None, options=None, **kwargs):
|
|
"""Create a Connection using openstack.config
|
|
|
|
:param str cloud:
|
|
Use the `cloud` configuration details when creating the Connection.
|
|
:param openstack.config.cloud_region.CloudRegion config:
|
|
An existing CloudRegion configuration. If no `config` is provided,
|
|
`openstack.config.OpenStackConfig` will be called, and the provided
|
|
`name` will be used in determining which cloud's configuration
|
|
details will be used in creation of the `Connection` instance.
|
|
:param argparse.Namespace options:
|
|
Allows direct passing in of options to be added to the cloud config.
|
|
This does not have to be an actual instance of argparse.Namespace,
|
|
despite the naming of the
|
|
`openstack.config.loader.OpenStackConfig.get_one` argument to which
|
|
it is passed.
|
|
|
|
:rtype: :class:`~openstack.connection.Connection`
|
|
"""
|
|
# TODO(mordred) Backwards compat while we transition
|
|
cloud = kwargs.pop('cloud_name', cloud)
|
|
config = kwargs.pop('cloud_config', config)
|
|
if config is None:
|
|
config = _config.OpenStackConfig().get_one(
|
|
cloud=cloud, argparse=options, **kwargs)
|
|
|
|
return Connection(config=config)
|
|
|
|
|
|
class Connection(six.with_metaclass(_meta.ConnectionMeta,
|
|
_cloud._OpenStackCloudMixin,
|
|
_baremetal.BaremetalCloudMixin,
|
|
_block_storage.BlockStorageCloudMixin,
|
|
_compute.ComputeCloudMixin,
|
|
_clustering.ClusteringCloudMixin,
|
|
_coe.CoeCloudMixin,
|
|
_dns.DnsCloudMixin,
|
|
_floating_ip.FloatingIPCloudMixin,
|
|
_identity.IdentityCloudMixin,
|
|
_image.ImageCloudMixin,
|
|
_network.NetworkCloudMixin,
|
|
_network_common.NetworkCommonCloudMixin,
|
|
_object_store.ObjectStoreCloudMixin,
|
|
_orchestration.OrchestrationCloudMixin,
|
|
_security_group.SecurityGroupCloudMixin
|
|
)):
|
|
|
|
def __init__(self, cloud=None, config=None, session=None,
|
|
app_name=None, app_version=None,
|
|
extra_services=None,
|
|
strict=False,
|
|
use_direct_get=False,
|
|
task_manager=None,
|
|
rate_limit=None,
|
|
**kwargs):
|
|
"""Create a connection to a cloud.
|
|
|
|
A connection needs information about how to connect, how to
|
|
authenticate and how to select the appropriate services to use.
|
|
|
|
The recommended way to provide this information is by referencing
|
|
a named cloud config from an existing `clouds.yaml` file. The cloud
|
|
name ``envvars`` may be used to consume a cloud configured via ``OS_``
|
|
environment variables.
|
|
|
|
A pre-existing :class:`~openstack.config.cloud_region.CloudRegion`
|
|
object can be passed in lieu of a cloud name, for cases where the user
|
|
already has a fully formed CloudRegion and just wants to use it.
|
|
|
|
Similarly, if for some reason the user already has a
|
|
:class:`~keystoneauth1.session.Session` and wants to use it, it may be
|
|
passed in.
|
|
|
|
:param str cloud: Name of the cloud from config to use.
|
|
:param config: CloudRegion object representing the config for the
|
|
region of the cloud in question.
|
|
:type config: :class:`~openstack.config.cloud_region.CloudRegion`
|
|
:param session: A session object compatible with
|
|
:class:`~keystoneauth1.session.Session`.
|
|
:type session: :class:`~keystoneauth1.session.Session`
|
|
:param str app_name: Name of the application to be added to User Agent.
|
|
:param str app_version: Version of the application to be added to
|
|
User Agent.
|
|
:param extra_services: List of
|
|
:class:`~openstack.service_description.ServiceDescription`
|
|
objects describing services that openstacksdk otherwise does not
|
|
know about.
|
|
:param bool use_direct_get:
|
|
For get methods, make specific REST calls for server-side
|
|
filtering instead of making list calls and filtering client-side.
|
|
Default false.
|
|
:param task_manager:
|
|
Ignored. Exists for backwards compat during transition. Rate limit
|
|
parameters should be passed directly to the `rate_limit` parameter.
|
|
:param rate_limit:
|
|
Client-side rate limit, expressed in calls per second. The
|
|
parameter can either be a single float, or it can be a dict with
|
|
keys as service-type and values as floats expressing the calls
|
|
per second for that service. Defaults to None, which means no
|
|
rate-limiting is performed.
|
|
:param kwargs: If a config is not provided, the rest of the parameters
|
|
provided are assumed to be arguments to be passed to the
|
|
CloudRegion constructor.
|
|
"""
|
|
self.config = config
|
|
self._extra_services = {}
|
|
if extra_services:
|
|
for service in extra_services:
|
|
self._extra_services[service.service_type] = service
|
|
|
|
if not self.config:
|
|
if session:
|
|
self.config = cloud_region.from_session(
|
|
session=session,
|
|
app_name=app_name, app_version=app_version,
|
|
load_yaml_config=False,
|
|
load_envvars=False,
|
|
rate_limit=rate_limit,
|
|
**kwargs)
|
|
else:
|
|
self.config = _config.get_cloud_region(
|
|
cloud=cloud,
|
|
app_name=app_name, app_version=app_version,
|
|
load_yaml_config=cloud is not None,
|
|
load_envvars=cloud is not None,
|
|
rate_limit=rate_limit,
|
|
**kwargs)
|
|
|
|
self._session = None
|
|
self._proxies = {}
|
|
self.use_direct_get = use_direct_get
|
|
self.strict_mode = strict
|
|
# Call the _*CloudMixin constructors while we work on
|
|
# integrating things better.
|
|
_cloud._OpenStackCloudMixin.__init__(self)
|
|
_baremetal.BaremetalCloudMixin.__init__(self)
|
|
_block_storage.BlockStorageCloudMixin.__init__(self)
|
|
_clustering.ClusteringCloudMixin.__init__(self)
|
|
_coe.CoeCloudMixin.__init__(self)
|
|
_compute.ComputeCloudMixin.__init__(self)
|
|
_dns.DnsCloudMixin.__init__(self)
|
|
_floating_ip.FloatingIPCloudMixin.__init__(self)
|
|
_identity.IdentityCloudMixin.__init__(self)
|
|
_image.ImageCloudMixin.__init__(self)
|
|
_network_common.NetworkCommonCloudMixin.__init__(self)
|
|
_network.NetworkCloudMixin.__init__(self)
|
|
_object_store.ObjectStoreCloudMixin.__init__(self)
|
|
_orchestration.OrchestrationCloudMixin.__init__(self)
|
|
_security_group.SecurityGroupCloudMixin.__init__(self)
|
|
|
|
@property
|
|
def session(self):
|
|
if not self._session:
|
|
self._session = self.config.get_session()
|
|
# Hide a reference to the connection on the session to help with
|
|
# backwards compatibility for folks trying to just pass
|
|
# conn.session to a Resource method's session argument.
|
|
self.session._sdk_connection = self
|
|
return self._session
|
|
|
|
def add_service(self, service):
|
|
"""Add a service to the Connection.
|
|
|
|
Attaches an instance of the :class:`~openstack.proxy.Proxy`
|
|
class contained in
|
|
:class:`~openstack.service_description.ServiceDescription`.
|
|
The :class:`~openstack.proxy.Proxy` will be attached to the
|
|
`Connection` by its ``service_type`` and by any ``aliases`` that
|
|
may be specified.
|
|
|
|
:param openstack.service_description.ServiceDescription service:
|
|
Object describing the service to be attached. As a convenience,
|
|
if ``service`` is a string it will be treated as a ``service_type``
|
|
and a basic
|
|
:class:`~openstack.service_description.ServiceDescription`
|
|
will be created.
|
|
"""
|
|
# If we don't have a proxy, just instantiate Proxy so that
|
|
# we get an adapter.
|
|
if isinstance(service, six.string_types):
|
|
service = service_description.ServiceDescription(service)
|
|
|
|
# Directly invoke descriptor of the ServiceDescription
|
|
def getter(self):
|
|
return service.__get__(self, service)
|
|
|
|
# Register the ServiceDescription class (as property)
|
|
# with every known alias for a "runtime descriptor"
|
|
for attr_name in service.all_types:
|
|
setattr(
|
|
self.__class__,
|
|
attr_name.replace('-', '_'),
|
|
property(fget=getter)
|
|
)
|
|
|
|
def authorize(self):
|
|
"""Authorize this Connection
|
|
|
|
.. note:: This method is optional. When an application makes a call
|
|
to any OpenStack service, this method allows you to request
|
|
a token manually before attempting to do anything else.
|
|
|
|
:returns: A string token.
|
|
|
|
:raises: :class:`~openstack.exceptions.HttpException` if the
|
|
authorization fails due to reasons like the credentials
|
|
provided are unable to be authorized or the `auth_type`
|
|
argument is missing, etc.
|
|
"""
|
|
try:
|
|
return self.session.get_token()
|
|
except keystoneauth1.exceptions.ClientException as e:
|
|
raise exceptions.raise_from_response(e.response)
|
|
|
|
def close(self):
|
|
"""Release any resources held open."""
|
|
if self.__pool_executor:
|
|
self.__pool_executor.shutdown()
|
|
|
|
def __enter__(self):
|
|
return self
|
|
|
|
def __exit__(self, exc_type, exc_value, traceback):
|
|
self.close()
|