Merge "Create OpenStackClients convenience class"
This commit is contained in:
commit
25d27f0288
@ -125,14 +125,8 @@ function create_watcher_conf {
|
|||||||
iniset $WATCHER_CONF oslo_messaging_rabbit rabbit_password $RABBIT_PASSWORD
|
iniset $WATCHER_CONF oslo_messaging_rabbit rabbit_password $RABBIT_PASSWORD
|
||||||
iniset $WATCHER_CONF oslo_messaging_rabbit rabbit_host $RABBIT_HOST
|
iniset $WATCHER_CONF oslo_messaging_rabbit rabbit_host $RABBIT_HOST
|
||||||
|
|
||||||
iniset $WATCHER_CONF keystone_authtoken admin_user watcher
|
|
||||||
iniset $WATCHER_CONF keystone_authtoken admin_password $SERVICE_PASSWORD
|
|
||||||
iniset $WATCHER_CONF keystone_authtoken admin_tenant_name $SERVICE_TENANT_NAME
|
|
||||||
|
|
||||||
configure_auth_token_middleware $WATCHER_CONF watcher $WATCHER_AUTH_CACHE_DIR
|
configure_auth_token_middleware $WATCHER_CONF watcher $WATCHER_AUTH_CACHE_DIR
|
||||||
|
configure_auth_token_middleware $WATCHER_CONF watcher $WATCHER_AUTH_CACHE_DIR "watcher_clients_auth"
|
||||||
iniset $WATCHER_CONF keystone_authtoken auth_uri $KEYSTONE_SERVICE_URI/v3
|
|
||||||
iniset $WATCHER_CONF keystone_authtoken auth_version v3
|
|
||||||
|
|
||||||
if is_fedora || is_suse; then
|
if is_fedora || is_suse; then
|
||||||
# watcher defaults to /usr/local/bin, but fedora and suse pip like to
|
# watcher defaults to /usr/local/bin, but fedora and suse pip like to
|
||||||
|
@ -166,11 +166,17 @@ The configuration file is organized into the following sections:
|
|||||||
* ``[api]`` - API server configuration
|
* ``[api]`` - API server configuration
|
||||||
* ``[database]`` - SQL driver configuration
|
* ``[database]`` - SQL driver configuration
|
||||||
* ``[keystone_authtoken]`` - Keystone Authentication plugin configuration
|
* ``[keystone_authtoken]`` - Keystone Authentication plugin configuration
|
||||||
|
* ``[watcher_clients_auth]`` - Keystone auth configuration for clients
|
||||||
* ``[watcher_applier]`` - Watcher Applier module configuration
|
* ``[watcher_applier]`` - Watcher Applier module configuration
|
||||||
* ``[watcher_decision_engine]`` - Watcher Decision Engine module configuration
|
* ``[watcher_decision_engine]`` - Watcher Decision Engine module configuration
|
||||||
* ``[watcher_goals]`` - Goals mapping configuration
|
* ``[watcher_goals]`` - Goals mapping configuration
|
||||||
* ``[watcher_strategies]`` - Strategy configuration
|
* ``[watcher_strategies]`` - Strategy configuration
|
||||||
* ``[oslo_messaging_rabbit]`` - Oslo Messaging RabbitMQ driver configuration
|
* ``[oslo_messaging_rabbit]`` - Oslo Messaging RabbitMQ driver configuration
|
||||||
|
* ``[ceilometer_client]`` - Ceilometer client configuration
|
||||||
|
* ``[cinder_client]`` - Cinder client configuration
|
||||||
|
* ``[glance_client]`` - Glance client configuration
|
||||||
|
* ``[nova_client]`` - Nova client configuration
|
||||||
|
* ``[neutron_client]`` - Neutron client configuration
|
||||||
|
|
||||||
The Watcher configuration file is expected to be named
|
The Watcher configuration file is expected to be named
|
||||||
``watcher.conf``. When starting Watcher, you can specify a different
|
``watcher.conf``. When starting Watcher, you can specify a different
|
||||||
@ -246,7 +252,11 @@ so that the watcher service is configured for your needs.
|
|||||||
|
|
||||||
# Complete public Identity API endpoint (string value)
|
# Complete public Identity API endpoint (string value)
|
||||||
#auth_uri=<None>
|
#auth_uri=<None>
|
||||||
auth_uri=http://IDENTITY_IP:5000/v3
|
auth_uri=http://IDENTITY_IP:5000/
|
||||||
|
|
||||||
|
# API version of the admin Identity API endpoint. (string value)
|
||||||
|
#auth_version=<None>
|
||||||
|
auth_version=v3
|
||||||
|
|
||||||
# Complete admin Identity API endpoint. This should specify the
|
# Complete admin Identity API endpoint. This should specify the
|
||||||
# unversioned root endpoint e.g. https://localhost:35357/ (string
|
# unversioned root endpoint e.g. https://localhost:35357/ (string
|
||||||
@ -271,6 +281,46 @@ so that the watcher service is configured for your needs.
|
|||||||
# value)
|
# value)
|
||||||
#signing_dir=<None>
|
#signing_dir=<None>
|
||||||
|
|
||||||
|
#. Configure the credentials to use to authenticate with the Identity Service
|
||||||
|
for the different project clients::
|
||||||
|
|
||||||
|
[watcher_clients_auth]
|
||||||
|
|
||||||
|
# Authentication type to load (unknown value)
|
||||||
|
# Deprecated group/name - [DEFAULT]/auth_plugin
|
||||||
|
#auth_type = <None>
|
||||||
|
auth_type = password
|
||||||
|
|
||||||
|
# Authentication URL (unknown value)
|
||||||
|
#auth_url = <None>
|
||||||
|
auth_url = http://IDENTITY_IP:35357
|
||||||
|
|
||||||
|
# Username (unknown value)
|
||||||
|
# Deprecated group/name - [DEFAULT]/username
|
||||||
|
#username = <None>
|
||||||
|
username=watcher
|
||||||
|
|
||||||
|
# User's password (unknown value)
|
||||||
|
#password = <None>
|
||||||
|
password = WATCHER_PASSWORD
|
||||||
|
|
||||||
|
# Domain ID containing project (unknown value)
|
||||||
|
#project_domain_id = <None>
|
||||||
|
project_domain_id = default
|
||||||
|
|
||||||
|
# User's domain id (unknown value)
|
||||||
|
#user_domain_id = <None>
|
||||||
|
user_domain_id = default
|
||||||
|
|
||||||
|
#. Configure the clients to use a specific version if desired. For example, to
|
||||||
|
configure Watcher to use a Nova client with version 2.1, use::
|
||||||
|
|
||||||
|
[nova_client]
|
||||||
|
|
||||||
|
# Version of Nova API to use in novaclient. (string value)
|
||||||
|
#api_version = 2
|
||||||
|
api_version = 2.1
|
||||||
|
|
||||||
#. Create the Watcher Service database tables::
|
#. Create the Watcher Service database tables::
|
||||||
|
|
||||||
$ watcher-db-manage --config-file /etc/watcher/watcher.conf create_schema
|
$ watcher-db-manage --config-file /etc/watcher/watcher.conf create_schema
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
enum34;python_version=='2.7' or python_version=='2.6'
|
enum34;python_version=='2.7' or python_version=='2.6'
|
||||||
jsonpatch>=1.1
|
jsonpatch>=1.1
|
||||||
|
keystoneauth1>=2.1.0
|
||||||
keystonemiddleware>=2.0.0,!=2.4.0
|
keystonemiddleware>=2.0.0,!=2.4.0
|
||||||
oslo.config>=2.3.0 # Apache-2.0
|
oslo.config>=2.3.0 # Apache-2.0
|
||||||
oslo.db>=2.4.1 # Apache-2.0
|
oslo.db>=2.4.1 # Apache-2.0
|
||||||
|
@ -52,7 +52,7 @@ class DefaultActionPlanHandler(base.BaseActionPlanHandler):
|
|||||||
self.notify(self.action_plan_uuid,
|
self.notify(self.action_plan_uuid,
|
||||||
event_types.EventTypes.LAUNCH_ACTION_PLAN,
|
event_types.EventTypes.LAUNCH_ACTION_PLAN,
|
||||||
ap_objects.State.ONGOING)
|
ap_objects.State.ONGOING)
|
||||||
applier = default.DefaultApplier(self.applier_manager, self.ctx)
|
applier = default.DefaultApplier(self.ctx, self.applier_manager)
|
||||||
result = applier.execute(self.action_plan_uuid)
|
result = applier.execute(self.action_plan_uuid)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
LOG.exception(e)
|
LOG.exception(e)
|
||||||
|
@ -22,12 +22,22 @@ import abc
|
|||||||
|
|
||||||
import six
|
import six
|
||||||
|
|
||||||
|
from watcher.common import clients
|
||||||
|
|
||||||
|
|
||||||
@six.add_metaclass(abc.ABCMeta)
|
@six.add_metaclass(abc.ABCMeta)
|
||||||
class BaseAction(object):
|
class BaseAction(object):
|
||||||
def __init__(self):
|
def __init__(self, osc=None):
|
||||||
|
""":param osc: an OpenStackClients instance"""
|
||||||
self._input_parameters = {}
|
self._input_parameters = {}
|
||||||
self._applies_to = ""
|
self._applies_to = ""
|
||||||
|
self._osc = osc
|
||||||
|
|
||||||
|
@property
|
||||||
|
def osc(self):
|
||||||
|
if not self._osc:
|
||||||
|
self._osc = clients.OpenStackClients()
|
||||||
|
return self._osc
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def input_parameters(self):
|
def input_parameters(self):
|
||||||
|
@ -21,8 +21,7 @@
|
|||||||
from watcher._i18n import _
|
from watcher._i18n import _
|
||||||
from watcher.applier.actions import base
|
from watcher.applier.actions import base
|
||||||
from watcher.common import exception
|
from watcher.common import exception
|
||||||
from watcher.common import keystone as kclient
|
from watcher.common import nova_helper
|
||||||
from watcher.common import nova as nclient
|
|
||||||
from watcher.decision_engine.model import hypervisor_state as hstate
|
from watcher.decision_engine.model import hypervisor_state as hstate
|
||||||
|
|
||||||
|
|
||||||
@ -57,13 +56,11 @@ class ChangeNovaServiceState(base.BaseAction):
|
|||||||
raise exception.IllegalArgumentException(
|
raise exception.IllegalArgumentException(
|
||||||
message=_("The target state is not defined"))
|
message=_("The target state is not defined"))
|
||||||
|
|
||||||
keystone = kclient.KeystoneClient()
|
nova = nova_helper.NovaHelper(osc=self.osc)
|
||||||
wrapper = nclient.NovaClient(keystone.get_credentials(),
|
|
||||||
session=keystone.get_session())
|
|
||||||
if state is True:
|
if state is True:
|
||||||
return wrapper.enable_service_nova_compute(self.host)
|
return nova.enable_service_nova_compute(self.host)
|
||||||
else:
|
else:
|
||||||
return wrapper.disable_service_nova_compute(self.host)
|
return nova.disable_service_nova_compute(self.host)
|
||||||
|
|
||||||
def precondition(self):
|
def precondition(self):
|
||||||
pass
|
pass
|
||||||
|
@ -28,9 +28,10 @@ class ActionFactory(object):
|
|||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.action_loader = default.DefaultActionLoader()
|
self.action_loader = default.DefaultActionLoader()
|
||||||
|
|
||||||
def make_action(self, object_action):
|
def make_action(self, object_action, osc=None):
|
||||||
LOG.debug("Creating instance of %s", object_action.action_type)
|
LOG.debug("Creating instance of %s", object_action.action_type)
|
||||||
loaded_action = self.action_loader.load(name=object_action.action_type)
|
loaded_action = self.action_loader.load(name=object_action.action_type,
|
||||||
|
osc=osc)
|
||||||
loaded_action.input_parameters = object_action.input_parameters
|
loaded_action.input_parameters = object_action.input_parameters
|
||||||
loaded_action.applies_to = object_action.applies_to
|
loaded_action.applies_to = object_action.applies_to
|
||||||
return loaded_action
|
return loaded_action
|
||||||
|
@ -21,8 +21,7 @@ from oslo_log import log
|
|||||||
|
|
||||||
from watcher.applier.actions import base
|
from watcher.applier.actions import base
|
||||||
from watcher.common import exception
|
from watcher.common import exception
|
||||||
from watcher.common import keystone as kclient
|
from watcher.common import nova_helper
|
||||||
from watcher.common import nova as nclient
|
|
||||||
|
|
||||||
LOG = log.getLogger(__name__)
|
LOG = log.getLogger(__name__)
|
||||||
|
|
||||||
@ -45,15 +44,13 @@ class Migrate(base.BaseAction):
|
|||||||
return self.input_parameters.get('src_hypervisor')
|
return self.input_parameters.get('src_hypervisor')
|
||||||
|
|
||||||
def migrate(self, destination):
|
def migrate(self, destination):
|
||||||
keystone = kclient.KeystoneClient()
|
nova = nova_helper.NovaHelper(osc=self.osc)
|
||||||
wrapper = nclient.NovaClient(keystone.get_credentials(),
|
LOG.debug("Migrate instance %s to %s", self.instance_uuid,
|
||||||
session=keystone.get_session())
|
|
||||||
LOG.debug("Migrate instance %s to %s ", self.instance_uuid,
|
|
||||||
destination)
|
destination)
|
||||||
instance = wrapper.find_instance(self.instance_uuid)
|
instance = nova.find_instance(self.instance_uuid)
|
||||||
if instance:
|
if instance:
|
||||||
if self.migration_type == 'live':
|
if self.migration_type == 'live':
|
||||||
return wrapper.live_migrate_instance(
|
return nova.live_migrate_instance(
|
||||||
instance_id=self.instance_uuid, dest_hostname=destination)
|
instance_id=self.instance_uuid, dest_hostname=destination)
|
||||||
else:
|
else:
|
||||||
raise exception.Invalid(
|
raise exception.Invalid(
|
||||||
|
@ -28,7 +28,7 @@ CONF = cfg.CONF
|
|||||||
|
|
||||||
|
|
||||||
class DefaultApplier(base.BaseApplier):
|
class DefaultApplier(base.BaseApplier):
|
||||||
def __init__(self, applier_manager, context):
|
def __init__(self, context, applier_manager):
|
||||||
super(DefaultApplier, self).__init__()
|
super(DefaultApplier, self).__init__()
|
||||||
self._applier_manager = applier_manager
|
self._applier_manager = applier_manager
|
||||||
self._loader = default.DefaultWorkFlowEngineLoader()
|
self._loader = default.DefaultWorkFlowEngineLoader()
|
||||||
@ -48,9 +48,10 @@ class DefaultApplier(base.BaseApplier):
|
|||||||
if self._engine is None:
|
if self._engine is None:
|
||||||
selected_workflow_engine = CONF.watcher_applier.workflow_engine
|
selected_workflow_engine = CONF.watcher_applier.workflow_engine
|
||||||
LOG.debug("Loading workflow engine %s ", selected_workflow_engine)
|
LOG.debug("Loading workflow engine %s ", selected_workflow_engine)
|
||||||
self._engine = self._loader.load(name=selected_workflow_engine)
|
self._engine = self._loader.load(
|
||||||
self._engine.context = self.context
|
name=selected_workflow_engine,
|
||||||
self._engine.applier_manager = self.applier_manager
|
context=self.context,
|
||||||
|
applier_manager=self.applier_manager)
|
||||||
return self._engine
|
return self._engine
|
||||||
|
|
||||||
def execute(self, action_plan_uuid):
|
def execute(self, action_plan_uuid):
|
||||||
|
@ -22,33 +22,33 @@ import six
|
|||||||
|
|
||||||
from watcher.applier.actions import factory
|
from watcher.applier.actions import factory
|
||||||
from watcher.applier.messaging import event_types
|
from watcher.applier.messaging import event_types
|
||||||
|
from watcher.common import clients
|
||||||
from watcher.common.messaging.events import event
|
from watcher.common.messaging.events import event
|
||||||
from watcher import objects
|
from watcher import objects
|
||||||
|
|
||||||
|
|
||||||
@six.add_metaclass(abc.ABCMeta)
|
@six.add_metaclass(abc.ABCMeta)
|
||||||
class BaseWorkFlowEngine(object):
|
class BaseWorkFlowEngine(object):
|
||||||
def __init__(self):
|
def __init__(self, context=None, applier_manager=None):
|
||||||
self._applier_manager = None
|
self._context = context
|
||||||
self._context = None
|
self._applier_manager = applier_manager
|
||||||
self._action_factory = factory.ActionFactory()
|
self._action_factory = factory.ActionFactory()
|
||||||
|
self._osc = None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def context(self):
|
def context(self):
|
||||||
return self._context
|
return self._context
|
||||||
|
|
||||||
@context.setter
|
@property
|
||||||
def context(self, c):
|
def osc(self):
|
||||||
self._context = c
|
if not self._osc:
|
||||||
|
self._osc = clients.OpenStackClients()
|
||||||
|
return self._osc
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def applier_manager(self):
|
def applier_manager(self):
|
||||||
return self._applier_manager
|
return self._applier_manager
|
||||||
|
|
||||||
@applier_manager.setter
|
|
||||||
def applier_manager(self, a):
|
|
||||||
self._applier_manager = a
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def action_factory(self):
|
def action_factory(self):
|
||||||
return self._action_factory
|
return self._action_factory
|
||||||
|
@ -89,7 +89,9 @@ class TaskFlowActionContainer(task.Task):
|
|||||||
@property
|
@property
|
||||||
def action(self):
|
def action(self):
|
||||||
if self.loaded_action is None:
|
if self.loaded_action is None:
|
||||||
action = self.engine.action_factory.make_action(self._db_action)
|
action = self.engine.action_factory.make_action(
|
||||||
|
self._db_action,
|
||||||
|
osc=self._engine.osc)
|
||||||
self.loaded_action = action
|
self.loaded_action = action
|
||||||
return self.loaded_action
|
return self.loaded_action
|
||||||
|
|
||||||
|
@ -17,30 +17,16 @@
|
|||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
#
|
#
|
||||||
|
|
||||||
from ceilometerclient import client
|
|
||||||
from ceilometerclient.exc import HTTPUnauthorized
|
from ceilometerclient.exc import HTTPUnauthorized
|
||||||
|
|
||||||
from watcher.common import keystone
|
from watcher.common import clients
|
||||||
|
|
||||||
|
|
||||||
class CeilometerClient(object):
|
class CeilometerHelper(object):
|
||||||
def __init__(self, api_version='2'):
|
def __init__(self, osc=None):
|
||||||
self._cmclient = None
|
""":param osc: an OpenStackClients instance"""
|
||||||
self._api_version = api_version
|
self.osc = osc if osc else clients.OpenStackClients()
|
||||||
|
self.ceilometer = self.osc.ceilometer()
|
||||||
@property
|
|
||||||
def cmclient(self):
|
|
||||||
"""Initialization of Ceilometer client."""
|
|
||||||
if not self._cmclient:
|
|
||||||
ksclient = keystone.KeystoneClient()
|
|
||||||
creds = ksclient.get_credentials()
|
|
||||||
endpoint = ksclient.get_endpoint(
|
|
||||||
service_type='metering',
|
|
||||||
endpoint_type='publicURL')
|
|
||||||
self._cmclient = client.get_client(self._api_version,
|
|
||||||
ceilometer_url=endpoint,
|
|
||||||
**creds)
|
|
||||||
return self._cmclient
|
|
||||||
|
|
||||||
def build_query(self, user_id=None, tenant_id=None, resource_id=None,
|
def build_query(self, user_id=None, tenant_id=None, resource_id=None,
|
||||||
user_ids=None, tenant_ids=None, resource_ids=None):
|
user_ids=None, tenant_ids=None, resource_ids=None):
|
||||||
@ -83,20 +69,21 @@ class CeilometerClient(object):
|
|||||||
try:
|
try:
|
||||||
return f(*args, **kargs)
|
return f(*args, **kargs)
|
||||||
except HTTPUnauthorized:
|
except HTTPUnauthorized:
|
||||||
self.reset_client()
|
self.osc.reset_clients()
|
||||||
|
self.ceilometer = self.osc.ceilometer()
|
||||||
return f(*args, **kargs)
|
return f(*args, **kargs)
|
||||||
except Exception:
|
except Exception:
|
||||||
raise
|
raise
|
||||||
|
|
||||||
def query_sample(self, meter_name, query, limit=1):
|
def query_sample(self, meter_name, query, limit=1):
|
||||||
return self.query_retry(f=self.cmclient.samples.list,
|
return self.query_retry(f=self.ceilometer.samples.list,
|
||||||
meter_name=meter_name,
|
meter_name=meter_name,
|
||||||
limit=limit,
|
limit=limit,
|
||||||
q=query)
|
q=query)
|
||||||
|
|
||||||
def statistic_list(self, meter_name, query=None, period=None):
|
def statistic_list(self, meter_name, query=None, period=None):
|
||||||
"""List of statistics."""
|
"""List of statistics."""
|
||||||
statistics = self.cmclient.statistics.list(
|
statistics = self.ceilometer.statistics.list(
|
||||||
meter_name=meter_name,
|
meter_name=meter_name,
|
||||||
q=query,
|
q=query,
|
||||||
period=period)
|
period=period)
|
||||||
@ -104,7 +91,8 @@ class CeilometerClient(object):
|
|||||||
|
|
||||||
def meter_list(self, query=None):
|
def meter_list(self, query=None):
|
||||||
"""List the user's meters."""
|
"""List the user's meters."""
|
||||||
meters = self.query_retry(f=self.cmclient.meters.list, query=query)
|
meters = self.query_retry(f=self.ceilometer.meters.list,
|
||||||
|
query=query)
|
||||||
return meters
|
return meters
|
||||||
|
|
||||||
def statistic_aggregation(self,
|
def statistic_aggregation(self,
|
||||||
@ -125,7 +113,7 @@ class CeilometerClient(object):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
query = self.build_query(resource_id=resource_id)
|
query = self.build_query(resource_id=resource_id)
|
||||||
statistic = self.query_retry(f=self.cmclient.statistics.list,
|
statistic = self.query_retry(f=self.ceilometer.statistics.list,
|
||||||
meter_name=meter_name,
|
meter_name=meter_name,
|
||||||
q=query,
|
q=query,
|
||||||
period=period,
|
period=period,
|
||||||
@ -156,6 +144,3 @@ class CeilometerClient(object):
|
|||||||
return samples[-1]._info['counter_volume']
|
return samples[-1]._info['counter_volume']
|
||||||
else:
|
else:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def reset_client(self):
|
|
||||||
self._cmclient = None
|
|
158
watcher/common/clients.py
Normal file
158
watcher/common/clients.py
Normal file
@ -0,0 +1,158 @@
|
|||||||
|
# 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.
|
||||||
|
|
||||||
|
from ceilometerclient import client as ceclient
|
||||||
|
from cinderclient import client as ciclient
|
||||||
|
from glanceclient import client as glclient
|
||||||
|
from keystoneauth1 import loading as ka_loading
|
||||||
|
from keystoneclient import client as keyclient
|
||||||
|
from neutronclient.neutron import client as netclient
|
||||||
|
from novaclient import client as nvclient
|
||||||
|
from oslo_config import cfg
|
||||||
|
|
||||||
|
from watcher._i18n import _
|
||||||
|
from watcher.common import exception
|
||||||
|
|
||||||
|
|
||||||
|
NOVA_CLIENT_OPTS = [
|
||||||
|
cfg.StrOpt('api_version',
|
||||||
|
default='2',
|
||||||
|
help=_('Version of Nova API to use in novaclient.'))]
|
||||||
|
|
||||||
|
GLANCE_CLIENT_OPTS = [
|
||||||
|
cfg.StrOpt('api_version',
|
||||||
|
default='2',
|
||||||
|
help=_('Version of Glance API to use in glanceclient.'))]
|
||||||
|
|
||||||
|
CINDER_CLIENT_OPTS = [
|
||||||
|
cfg.StrOpt('api_version',
|
||||||
|
default='2',
|
||||||
|
help=_('Version of Cinder API to use in cinderclient.'))]
|
||||||
|
|
||||||
|
CEILOMETER_CLIENT_OPTS = [
|
||||||
|
cfg.StrOpt('api_version',
|
||||||
|
default='2',
|
||||||
|
help=_('Version of Ceilometer API to use in '
|
||||||
|
'ceilometerclient.'))]
|
||||||
|
|
||||||
|
NEUTRON_CLIENT_OPTS = [
|
||||||
|
cfg.StrOpt('api_version',
|
||||||
|
default='2',
|
||||||
|
help=_('Version of Neutron API to use in neutronclient.'))]
|
||||||
|
|
||||||
|
cfg.CONF.register_opts(NOVA_CLIENT_OPTS, group='nova_client')
|
||||||
|
cfg.CONF.register_opts(GLANCE_CLIENT_OPTS, group='glance_client')
|
||||||
|
cfg.CONF.register_opts(CINDER_CLIENT_OPTS, group='cinder_client')
|
||||||
|
cfg.CONF.register_opts(CEILOMETER_CLIENT_OPTS, group='ceilometer_client')
|
||||||
|
cfg.CONF.register_opts(NEUTRON_CLIENT_OPTS, group='neutron_client')
|
||||||
|
|
||||||
|
_CLIENTS_AUTH_GROUP = 'watcher_clients_auth'
|
||||||
|
|
||||||
|
ka_loading.register_auth_conf_options(cfg.CONF, _CLIENTS_AUTH_GROUP)
|
||||||
|
ka_loading.register_session_conf_options(cfg.CONF, _CLIENTS_AUTH_GROUP)
|
||||||
|
|
||||||
|
|
||||||
|
class OpenStackClients(object):
|
||||||
|
"""Convenience class to create and cache client instances."""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.reset_clients()
|
||||||
|
|
||||||
|
def reset_clients(self):
|
||||||
|
self._session = None
|
||||||
|
self._keystone = None
|
||||||
|
self._nova = None
|
||||||
|
self._glance = None
|
||||||
|
self._cinder = None
|
||||||
|
self._ceilometer = None
|
||||||
|
self._neutron = None
|
||||||
|
|
||||||
|
def _get_keystone_session(self):
|
||||||
|
auth = ka_loading.load_auth_from_conf_options(cfg.CONF,
|
||||||
|
_CLIENTS_AUTH_GROUP)
|
||||||
|
sess = ka_loading.load_session_from_conf_options(cfg.CONF,
|
||||||
|
_CLIENTS_AUTH_GROUP,
|
||||||
|
auth=auth)
|
||||||
|
return sess
|
||||||
|
|
||||||
|
@property
|
||||||
|
def auth_url(self):
|
||||||
|
return self.keystone().auth_url
|
||||||
|
|
||||||
|
@property
|
||||||
|
def session(self):
|
||||||
|
if not self._session:
|
||||||
|
self._session = self._get_keystone_session()
|
||||||
|
return self._session
|
||||||
|
|
||||||
|
def _get_client_option(self, client, option):
|
||||||
|
return getattr(getattr(cfg.CONF, '%s_client' % client), option)
|
||||||
|
|
||||||
|
@exception.wrap_keystone_exception
|
||||||
|
def keystone(self):
|
||||||
|
if not self._keystone:
|
||||||
|
self._keystone = keyclient.Client(session=self.session)
|
||||||
|
|
||||||
|
return self._keystone
|
||||||
|
|
||||||
|
@exception.wrap_keystone_exception
|
||||||
|
def nova(self):
|
||||||
|
if self._nova:
|
||||||
|
return self._nova
|
||||||
|
|
||||||
|
novaclient_version = self._get_client_option('nova', 'api_version')
|
||||||
|
self._nova = nvclient.Client(novaclient_version,
|
||||||
|
session=self.session)
|
||||||
|
return self._nova
|
||||||
|
|
||||||
|
@exception.wrap_keystone_exception
|
||||||
|
def glance(self):
|
||||||
|
if self._glance:
|
||||||
|
return self._glance
|
||||||
|
|
||||||
|
glanceclient_version = self._get_client_option('glance', 'api_version')
|
||||||
|
self._glance = glclient.Client(glanceclient_version,
|
||||||
|
session=self.session)
|
||||||
|
return self._glance
|
||||||
|
|
||||||
|
@exception.wrap_keystone_exception
|
||||||
|
def cinder(self):
|
||||||
|
if self._cinder:
|
||||||
|
return self._cinder
|
||||||
|
|
||||||
|
cinderclient_version = self._get_client_option('cinder', 'api_version')
|
||||||
|
self._cinder = ciclient.Client(cinderclient_version,
|
||||||
|
session=self.session)
|
||||||
|
return self._cinder
|
||||||
|
|
||||||
|
@exception.wrap_keystone_exception
|
||||||
|
def ceilometer(self):
|
||||||
|
if self._ceilometer:
|
||||||
|
return self._ceilometer
|
||||||
|
|
||||||
|
ceilometerclient_version = self._get_client_option('ceilometer',
|
||||||
|
'api_version')
|
||||||
|
self._ceilometer = ceclient.Client(ceilometerclient_version,
|
||||||
|
session=self.session)
|
||||||
|
return self._ceilometer
|
||||||
|
|
||||||
|
@exception.wrap_keystone_exception
|
||||||
|
def neutron(self):
|
||||||
|
if self._neutron:
|
||||||
|
return self._neutron
|
||||||
|
|
||||||
|
neutronclient_version = self._get_client_option('neutron',
|
||||||
|
'api_version')
|
||||||
|
self._neutron = netclient.Client(neutronclient_version,
|
||||||
|
session=self.session)
|
||||||
|
self._neutron.format = 'json'
|
||||||
|
return self._neutron
|
@ -22,6 +22,10 @@ SHOULD include dedicated exception logging.
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
import functools
|
||||||
|
import sys
|
||||||
|
|
||||||
|
from keystoneclient import exceptions as keystone_exceptions
|
||||||
from oslo_config import cfg
|
from oslo_config import cfg
|
||||||
from oslo_log import log as logging
|
from oslo_log import log as logging
|
||||||
import six
|
import six
|
||||||
@ -40,6 +44,23 @@ CONF = cfg.CONF
|
|||||||
CONF.register_opts(exc_log_opts)
|
CONF.register_opts(exc_log_opts)
|
||||||
|
|
||||||
|
|
||||||
|
def wrap_keystone_exception(func):
|
||||||
|
"""Wrap keystone exceptions and throw Watcher specific exceptions."""
|
||||||
|
@functools.wraps(func)
|
||||||
|
def wrapped(*args, **kw):
|
||||||
|
try:
|
||||||
|
return func(*args, **kw)
|
||||||
|
except keystone_exceptions.AuthorizationFailure:
|
||||||
|
raise AuthorizationFailure(
|
||||||
|
client=func.__name__, reason=sys.exc_info()[1])
|
||||||
|
except keystone_exceptions.ClientException:
|
||||||
|
raise AuthorizationFailure(
|
||||||
|
client=func.__name__,
|
||||||
|
reason=(_('Unexpected keystone client error occurred: %s')
|
||||||
|
% sys.exc_info()[1]))
|
||||||
|
return wrapped
|
||||||
|
|
||||||
|
|
||||||
class WatcherException(Exception):
|
class WatcherException(Exception):
|
||||||
"""Base Watcher Exception
|
"""Base Watcher Exception
|
||||||
|
|
||||||
@ -226,6 +247,10 @@ class NoDataFound(WatcherException):
|
|||||||
msg_fmt = _('No rows were returned')
|
msg_fmt = _('No rows were returned')
|
||||||
|
|
||||||
|
|
||||||
|
class AuthorizationFailure(WatcherException):
|
||||||
|
msg_fmt = _('%(client)s connection failed. Reason: %(reason)s')
|
||||||
|
|
||||||
|
|
||||||
class KeystoneFailure(WatcherException):
|
class KeystoneFailure(WatcherException):
|
||||||
msg_fmt = _("'Keystone API endpoint is missing''")
|
msg_fmt = _("'Keystone API endpoint is missing''")
|
||||||
|
|
||||||
|
@ -1,131 +0,0 @@
|
|||||||
# -*- encoding: utf-8 -*-
|
|
||||||
# Copyright (c) 2015 b<>com
|
|
||||||
#
|
|
||||||
# Authors: Jean-Emile DARTOIS <jean-emile.dartois@b-com.com>
|
|
||||||
#
|
|
||||||
# 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.
|
|
||||||
#
|
|
||||||
|
|
||||||
from keystoneclient.auth.identity import generic
|
|
||||||
from keystoneclient import session as keystone_session
|
|
||||||
from oslo_config import cfg
|
|
||||||
from oslo_log import log
|
|
||||||
from six.moves.urllib.parse import urljoin
|
|
||||||
from six.moves.urllib.parse import urlparse
|
|
||||||
|
|
||||||
from watcher._i18n import _
|
|
||||||
from watcher.common import exception
|
|
||||||
|
|
||||||
|
|
||||||
LOG = log.getLogger(__name__)
|
|
||||||
CONF = cfg.CONF
|
|
||||||
|
|
||||||
CONF.import_opt('admin_user', 'keystonemiddleware.auth_token',
|
|
||||||
group='keystone_authtoken')
|
|
||||||
CONF.import_opt('admin_tenant_name', 'keystonemiddleware.auth_token',
|
|
||||||
group='keystone_authtoken')
|
|
||||||
CONF.import_opt('admin_password', 'keystonemiddleware.auth_token',
|
|
||||||
group='keystone_authtoken')
|
|
||||||
CONF.import_opt('auth_uri', 'keystonemiddleware.auth_token',
|
|
||||||
group='keystone_authtoken')
|
|
||||||
CONF.import_opt('auth_version', 'keystonemiddleware.auth_token',
|
|
||||||
group='keystone_authtoken')
|
|
||||||
CONF.import_opt('insecure', 'keystonemiddleware.auth_token',
|
|
||||||
group='keystone_authtoken')
|
|
||||||
|
|
||||||
|
|
||||||
class KeystoneClient(object):
|
|
||||||
def __init__(self):
|
|
||||||
self._ks_client = None
|
|
||||||
self._session = None
|
|
||||||
self._auth = None
|
|
||||||
self._token = None
|
|
||||||
|
|
||||||
def get_endpoint(self, **kwargs):
|
|
||||||
kc = self.get_ksclient()
|
|
||||||
if not kc.has_service_catalog():
|
|
||||||
raise exception.KeystoneFailure(
|
|
||||||
_('No Keystone service catalog loaded')
|
|
||||||
)
|
|
||||||
attr = None
|
|
||||||
filter_value = None
|
|
||||||
if kwargs.get('region_name'):
|
|
||||||
attr = 'region'
|
|
||||||
filter_value = kwargs.get('region_name')
|
|
||||||
return kc.service_catalog.url_for(
|
|
||||||
service_type=kwargs.get('service_type') or 'metering',
|
|
||||||
attr=attr,
|
|
||||||
filter_value=filter_value,
|
|
||||||
endpoint_type=kwargs.get('endpoint_type') or 'publicURL')
|
|
||||||
|
|
||||||
def _is_apiv3(self, auth_url, auth_version):
|
|
||||||
return auth_version == 'v3.0' or '/v3' in urlparse(auth_url).path
|
|
||||||
|
|
||||||
def get_keystone_url(self, auth_url, auth_version):
|
|
||||||
"""Gives an http/https url to contact keystone."""
|
|
||||||
|
|
||||||
api_v3 = self._is_apiv3(auth_url, auth_version)
|
|
||||||
api_version = 'v3' if api_v3 else 'v2.0'
|
|
||||||
# NOTE(lucasagomes): Get rid of the trailing '/' otherwise urljoin()
|
|
||||||
# fails to override the version in the URL
|
|
||||||
return urljoin(auth_url.rstrip('/'), api_version)
|
|
||||||
|
|
||||||
def get_ksclient(self, creds=None):
|
|
||||||
"""Get an endpoint and auth token from Keystone."""
|
|
||||||
auth_version = CONF.keystone_authtoken.auth_version
|
|
||||||
auth_url = CONF.keystone_authtoken.auth_uri
|
|
||||||
api_v3 = self._is_apiv3(auth_url, auth_version)
|
|
||||||
if creds is None:
|
|
||||||
ks_args = self._get_credentials(api_v3)
|
|
||||||
else:
|
|
||||||
ks_args = creds
|
|
||||||
|
|
||||||
if api_v3:
|
|
||||||
from keystoneclient.v3 import client
|
|
||||||
else:
|
|
||||||
from keystoneclient.v2_0 import client
|
|
||||||
# generic
|
|
||||||
# ksclient = client.Client(version=api_version,
|
|
||||||
# session=session,**ks_args)
|
|
||||||
|
|
||||||
return client.Client(**ks_args)
|
|
||||||
|
|
||||||
def _get_credentials(self, api_v3):
|
|
||||||
if api_v3:
|
|
||||||
creds = \
|
|
||||||
{'auth_url': CONF.keystone_authtoken.auth_uri,
|
|
||||||
'username': CONF.keystone_authtoken.admin_user,
|
|
||||||
'password': CONF.keystone_authtoken.admin_password,
|
|
||||||
'project_name': CONF.keystone_authtoken.admin_tenant_name,
|
|
||||||
'user_domain_name': "default",
|
|
||||||
'project_domain_name': "default"}
|
|
||||||
else:
|
|
||||||
creds = \
|
|
||||||
{'auth_url': CONF.keystone_authtoken.auth_uri,
|
|
||||||
'username': CONF.keystone_authtoken.admin_user,
|
|
||||||
'password': CONF.keystone_authtoken.admin_password,
|
|
||||||
'tenant_name': CONF.keystone_authtoken.admin_tenant_name}
|
|
||||||
LOG.debug(creds)
|
|
||||||
return creds
|
|
||||||
|
|
||||||
def get_credentials(self):
|
|
||||||
api_v3 = self._is_apiv3(CONF.keystone_authtoken.auth_uri,
|
|
||||||
CONF.keystone_authtoken.auth_version)
|
|
||||||
return self._get_credentials(api_v3)
|
|
||||||
|
|
||||||
def get_session(self):
|
|
||||||
creds = self.get_credentials()
|
|
||||||
self._auth = generic.Password(**creds)
|
|
||||||
session = keystone_session.Session(auth=self._auth)
|
|
||||||
return session
|
|
@ -31,7 +31,7 @@ class DefaultLoader(BaseLoader):
|
|||||||
super(DefaultLoader, self).__init__()
|
super(DefaultLoader, self).__init__()
|
||||||
self.namespace = namespace
|
self.namespace = namespace
|
||||||
|
|
||||||
def load(self, name):
|
def load(self, name, **kwargs):
|
||||||
try:
|
try:
|
||||||
LOG.debug("Loading in namespace %s => %s ", self.namespace, name)
|
LOG.debug("Loading in namespace %s => %s ", self.namespace, name)
|
||||||
driver_manager = DriverManager(namespace=self.namespace,
|
driver_manager = DriverManager(namespace=self.namespace,
|
||||||
@ -41,7 +41,7 @@ class DefaultLoader(BaseLoader):
|
|||||||
LOG.exception(exc)
|
LOG.exception(exc)
|
||||||
raise exception.LoadingError(name=name)
|
raise exception.LoadingError(name=name)
|
||||||
|
|
||||||
return loaded()
|
return loaded(**kwargs)
|
||||||
|
|
||||||
def list_available(self):
|
def list_available(self):
|
||||||
extension_manager = ExtensionManager(namespace=self.namespace)
|
extension_manager = ExtensionManager(namespace=self.namespace)
|
||||||
|
@ -23,29 +23,22 @@ import time
|
|||||||
from oslo_log import log
|
from oslo_log import log
|
||||||
|
|
||||||
import cinderclient.exceptions as ciexceptions
|
import cinderclient.exceptions as ciexceptions
|
||||||
import cinderclient.v2.client as ciclient
|
|
||||||
import glanceclient.v2.client as glclient
|
|
||||||
import neutronclient.neutron.client as netclient
|
|
||||||
import novaclient.client as nvclient
|
|
||||||
import novaclient.exceptions as nvexceptions
|
import novaclient.exceptions as nvexceptions
|
||||||
|
|
||||||
from watcher.common import keystone
|
from watcher.common import clients
|
||||||
|
|
||||||
LOG = log.getLogger(__name__)
|
LOG = log.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class NovaClient(object):
|
class NovaHelper(object):
|
||||||
NOVA_CLIENT_API_VERSION = "2"
|
|
||||||
|
|
||||||
def __init__(self, creds, session):
|
def __init__(self, osc=None):
|
||||||
self.user = creds['username']
|
""":param osc: an OpenStackClients instance"""
|
||||||
self.session = session
|
self.osc = osc if osc else clients.OpenStackClients()
|
||||||
self.neutron = None
|
self.neutron = self.osc.neutron()
|
||||||
self.cinder = None
|
self.cinder = self.osc.cinder()
|
||||||
self.nova = nvclient.Client(self.NOVA_CLIENT_API_VERSION,
|
self.nova = self.osc.nova()
|
||||||
session=session)
|
self.glance = self.osc.glance()
|
||||||
self.keystone = keystone.KeystoneClient().get_ksclient(creds)
|
|
||||||
self.glance = None
|
|
||||||
|
|
||||||
def get_hypervisors_list(self):
|
def get_hypervisors_list(self):
|
||||||
return self.nova.hypervisors.list()
|
return self.nova.hypervisors.list()
|
||||||
@ -180,9 +173,6 @@ class NovaClient(object):
|
|||||||
volume_id = attached_volume['id']
|
volume_id = attached_volume['id']
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if self.cinder is None:
|
|
||||||
self.cinder = ciclient.Client('2',
|
|
||||||
session=self.session)
|
|
||||||
volume = self.cinder.volumes.get(volume_id)
|
volume = self.cinder.volumes.get(volume_id)
|
||||||
|
|
||||||
attachments_list = getattr(volume, "attachments")
|
attachments_list = getattr(volume, "attachments")
|
||||||
@ -446,13 +436,6 @@ class NovaClient(object):
|
|||||||
:param metadata: a dictionary containing the list of
|
:param metadata: a dictionary containing the list of
|
||||||
key-value pairs to associate to the image as metadata.
|
key-value pairs to associate to the image as metadata.
|
||||||
"""
|
"""
|
||||||
if self.glance is None:
|
|
||||||
glance_endpoint = self.keystone. \
|
|
||||||
service_catalog.url_for(service_type='image',
|
|
||||||
endpoint_type='publicURL')
|
|
||||||
self.glance = glclient.Client(glance_endpoint,
|
|
||||||
token=self.keystone.auth_token)
|
|
||||||
|
|
||||||
LOG.debug(
|
LOG.debug(
|
||||||
"Trying to create an image from instance %s ..." % instance_id)
|
"Trying to create an image from instance %s ..." % instance_id)
|
||||||
|
|
||||||
@ -676,10 +659,6 @@ class NovaClient(object):
|
|||||||
|
|
||||||
def get_network_id_from_name(self, net_name="private"):
|
def get_network_id_from_name(self, net_name="private"):
|
||||||
"""This method returns the unique id of the provided network name"""
|
"""This method returns the unique id of the provided network name"""
|
||||||
if self.neutron is None:
|
|
||||||
self.neutron = netclient.Client('2.0', session=self.session)
|
|
||||||
self.neutron.format = 'json'
|
|
||||||
|
|
||||||
networks = self.neutron.list_networks(name=net_name)
|
networks = self.neutron.list_networks(name=net_name)
|
||||||
|
|
||||||
# LOG.debug(networks)
|
# LOG.debug(networks)
|
@ -15,6 +15,7 @@
|
|||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
from oslo_log import log
|
from oslo_log import log
|
||||||
|
|
||||||
|
from watcher.common import clients
|
||||||
from watcher.decision_engine.strategy.context.base import BaseStrategyContext
|
from watcher.decision_engine.strategy.context.base import BaseStrategyContext
|
||||||
from watcher.decision_engine.strategy.selection.default import \
|
from watcher.decision_engine.strategy.selection.default import \
|
||||||
DefaultStrategySelector
|
DefaultStrategySelector
|
||||||
@ -47,15 +48,17 @@ class DefaultStrategyContext(BaseStrategyContext):
|
|||||||
audit_template = objects.\
|
audit_template = objects.\
|
||||||
AuditTemplate.get_by_id(request_context, audit.audit_template_id)
|
AuditTemplate.get_by_id(request_context, audit.audit_template_id)
|
||||||
|
|
||||||
|
osc = clients.OpenStackClients()
|
||||||
|
|
||||||
# todo(jed) retrieve in audit_template parameters (threshold,...)
|
# todo(jed) retrieve in audit_template parameters (threshold,...)
|
||||||
# todo(jed) create ActionPlan
|
# todo(jed) create ActionPlan
|
||||||
collector_manager = self.collector.get_cluster_model_collector()
|
collector_manager = self.collector.get_cluster_model_collector(osc=osc)
|
||||||
|
|
||||||
# todo(jed) remove call to get_latest_cluster_data_model
|
# todo(jed) remove call to get_latest_cluster_data_model
|
||||||
cluster_data_model = collector_manager.get_latest_cluster_data_model()
|
cluster_data_model = collector_manager.get_latest_cluster_data_model()
|
||||||
|
|
||||||
selected_strategy = self.strategy_selector. \
|
selected_strategy = self.strategy_selector.define_from_goal(
|
||||||
define_from_goal(audit_template.goal)
|
audit_template.goal, osc=osc)
|
||||||
|
|
||||||
# todo(jed) add parameters and remove cluster_data_model
|
# todo(jed) add parameters and remove cluster_data_model
|
||||||
return selected_strategy.execute(cluster_data_model)
|
return selected_strategy.execute(cluster_data_model)
|
||||||
|
@ -48,11 +48,12 @@ class DefaultStrategySelector(base.BaseSelector):
|
|||||||
super(DefaultStrategySelector, self).__init__()
|
super(DefaultStrategySelector, self).__init__()
|
||||||
self.strategy_loader = default.DefaultStrategyLoader()
|
self.strategy_loader = default.DefaultStrategyLoader()
|
||||||
|
|
||||||
def define_from_goal(self, goal_name):
|
def define_from_goal(self, goal_name, osc=None):
|
||||||
|
""":param osc: an OpenStackClients instance"""
|
||||||
strategy_to_load = None
|
strategy_to_load = None
|
||||||
try:
|
try:
|
||||||
strategy_to_load = CONF.watcher_goals.goals[goal_name]
|
strategy_to_load = CONF.watcher_goals.goals[goal_name]
|
||||||
return self.strategy_loader.load(strategy_to_load)
|
return self.strategy_loader.load(strategy_to_load, osc=osc)
|
||||||
except KeyError as exc:
|
except KeyError as exc:
|
||||||
LOG.exception(exc)
|
LOG.exception(exc)
|
||||||
raise exception.WatcherException(
|
raise exception.WatcherException(
|
||||||
|
@ -33,10 +33,11 @@ provided as well.
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
import abc
|
import abc
|
||||||
|
|
||||||
from oslo_log import log
|
from oslo_log import log
|
||||||
import six
|
import six
|
||||||
|
|
||||||
|
from watcher.common import clients
|
||||||
from watcher.decision_engine.solution.default import DefaultSolution
|
from watcher.decision_engine.solution.default import DefaultSolution
|
||||||
from watcher.decision_engine.strategy.common.level import StrategyLevel
|
from watcher.decision_engine.strategy.common.level import StrategyLevel
|
||||||
|
|
||||||
@ -51,7 +52,8 @@ class BaseStrategy(object):
|
|||||||
Solution for a given Goal.
|
Solution for a given Goal.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, name=None, description=None):
|
def __init__(self, name=None, description=None, osc=None):
|
||||||
|
""":param osc: an OpenStackClients instance"""
|
||||||
self._name = name
|
self._name = name
|
||||||
self.description = description
|
self.description = description
|
||||||
# default strategy level
|
# default strategy level
|
||||||
@ -59,6 +61,7 @@ class BaseStrategy(object):
|
|||||||
self._cluster_state_collector = None
|
self._cluster_state_collector = None
|
||||||
# the solution given by the strategy
|
# the solution given by the strategy
|
||||||
self._solution = DefaultSolution()
|
self._solution = DefaultSolution()
|
||||||
|
self._osc = osc
|
||||||
|
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
def execute(self, model):
|
def execute(self, model):
|
||||||
@ -70,6 +73,12 @@ class BaseStrategy(object):
|
|||||||
:rtype: :class:`watcher.decision_engine.solution.base.BaseSolution`
|
:rtype: :class:`watcher.decision_engine.solution.base.BaseSolution`
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
@property
|
||||||
|
def osc(self):
|
||||||
|
if not self._osc:
|
||||||
|
self._osc = clients.OpenStackClients()
|
||||||
|
return self._osc
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def solution(self):
|
def solution(self):
|
||||||
return self._solution
|
return self._solution
|
||||||
|
@ -41,7 +41,8 @@ class BasicConsolidation(BaseStrategy):
|
|||||||
MIGRATION = "migrate"
|
MIGRATION = "migrate"
|
||||||
CHANGE_NOVA_SERVICE_STATE = "change_nova_service_state"
|
CHANGE_NOVA_SERVICE_STATE = "change_nova_service_state"
|
||||||
|
|
||||||
def __init__(self, name=DEFAULT_NAME, description=DEFAULT_DESCRIPTION):
|
def __init__(self, name=DEFAULT_NAME, description=DEFAULT_DESCRIPTION,
|
||||||
|
osc=None):
|
||||||
"""Basic offline Consolidation using live migration
|
"""Basic offline Consolidation using live migration
|
||||||
|
|
||||||
The basic consolidation algorithm has several limitations.
|
The basic consolidation algorithm has several limitations.
|
||||||
@ -68,8 +69,9 @@ class BasicConsolidation(BaseStrategy):
|
|||||||
|
|
||||||
:param name: the name of the strategy
|
:param name: the name of the strategy
|
||||||
:param description: a description of the strategy
|
:param description: a description of the strategy
|
||||||
|
:param osc: an OpenStackClients object
|
||||||
"""
|
"""
|
||||||
super(BasicConsolidation, self).__init__(name, description)
|
super(BasicConsolidation, self).__init__(name, description, osc)
|
||||||
|
|
||||||
# set default value for the number of released nodes
|
# set default value for the number of released nodes
|
||||||
self.number_of_released_nodes = 0
|
self.number_of_released_nodes = 0
|
||||||
@ -102,7 +104,7 @@ class BasicConsolidation(BaseStrategy):
|
|||||||
@property
|
@property
|
||||||
def ceilometer(self):
|
def ceilometer(self):
|
||||||
if self._ceilometer is None:
|
if self._ceilometer is None:
|
||||||
self._ceilometer = CeilometerClusterHistory()
|
self._ceilometer = CeilometerClusterHistory(osc=self.osc)
|
||||||
return self._ceilometer
|
return self._ceilometer
|
||||||
|
|
||||||
@ceilometer.setter
|
@ceilometer.setter
|
||||||
|
@ -30,8 +30,9 @@ class DummyStrategy(BaseStrategy):
|
|||||||
NOP = "nop"
|
NOP = "nop"
|
||||||
SLEEP = "sleep"
|
SLEEP = "sleep"
|
||||||
|
|
||||||
def __init__(self, name=DEFAULT_NAME, description=DEFAULT_DESCRIPTION):
|
def __init__(self, name=DEFAULT_NAME, description=DEFAULT_DESCRIPTION,
|
||||||
super(DummyStrategy, self).__init__(name, description)
|
osc=None):
|
||||||
|
super(DummyStrategy, self).__init__(name, description, osc)
|
||||||
|
|
||||||
def execute(self, model):
|
def execute(self, model):
|
||||||
parameters = {'message': 'hello World'}
|
parameters = {'message': 'hello World'}
|
||||||
|
@ -40,7 +40,8 @@ class OutletTempControl(BaseStrategy):
|
|||||||
|
|
||||||
MIGRATION = "migrate"
|
MIGRATION = "migrate"
|
||||||
|
|
||||||
def __init__(self, name=DEFAULT_NAME, description=DEFAULT_DESCRIPTION):
|
def __init__(self, name=DEFAULT_NAME, description=DEFAULT_DESCRIPTION,
|
||||||
|
osc=None):
|
||||||
"""[PoC]Outlet temperature control using live migration
|
"""[PoC]Outlet temperature control using live migration
|
||||||
|
|
||||||
It is a migration strategy based on the Outlet Temperature of physical
|
It is a migration strategy based on the Outlet Temperature of physical
|
||||||
@ -67,8 +68,9 @@ class OutletTempControl(BaseStrategy):
|
|||||||
|
|
||||||
:param name: the name of the strategy
|
:param name: the name of the strategy
|
||||||
:param description: a description of the strategy
|
:param description: a description of the strategy
|
||||||
|
:param osc: an OpenStackClients object
|
||||||
"""
|
"""
|
||||||
super(OutletTempControl, self).__init__(name, description)
|
super(OutletTempControl, self).__init__(name, description, osc)
|
||||||
# the migration plan will be triggered when the outlet temperature
|
# the migration plan will be triggered when the outlet temperature
|
||||||
# reaches threshold
|
# reaches threshold
|
||||||
# TODO(zhenzanz): Threshold should be configurable for each audit
|
# TODO(zhenzanz): Threshold should be configurable for each audit
|
||||||
@ -79,7 +81,7 @@ class OutletTempControl(BaseStrategy):
|
|||||||
@property
|
@property
|
||||||
def ceilometer(self):
|
def ceilometer(self):
|
||||||
if self._ceilometer is None:
|
if self._ceilometer is None:
|
||||||
self._ceilometer = CeilometerClusterHistory()
|
self._ceilometer = CeilometerClusterHistory(osc=self.osc)
|
||||||
return self._ceilometer
|
return self._ceilometer
|
||||||
|
|
||||||
@ceilometer.setter
|
@ceilometer.setter
|
||||||
|
@ -20,7 +20,7 @@
|
|||||||
|
|
||||||
from oslo_config import cfg
|
from oslo_config import cfg
|
||||||
from oslo_log import log
|
from oslo_log import log
|
||||||
from watcher.common.ceilometer import CeilometerClient
|
from watcher.common import ceilometer_helper
|
||||||
|
|
||||||
from watcher.metrics_engine.cluster_history.api import BaseClusterHistory
|
from watcher.metrics_engine.cluster_history.api import BaseClusterHistory
|
||||||
|
|
||||||
@ -29,8 +29,10 @@ LOG = log.getLogger(__name__)
|
|||||||
|
|
||||||
|
|
||||||
class CeilometerClusterHistory(BaseClusterHistory):
|
class CeilometerClusterHistory(BaseClusterHistory):
|
||||||
def __init__(self):
|
def __init__(self, osc=None):
|
||||||
self.ceilometer = CeilometerClient()
|
""":param osc: an OpenStackClients instance"""
|
||||||
|
super(CeilometerClusterHistory, self).__init__()
|
||||||
|
self.ceilometer = ceilometer_helper.CeilometerHelper(osc=osc)
|
||||||
|
|
||||||
def statistic_list(self, meter_name, query=None, period=None):
|
def statistic_list(self, meter_name, query=None, period=None):
|
||||||
return self.ceilometer.statistic_list(meter_name, query, period)
|
return self.ceilometer.statistic_list(meter_name, query, period)
|
||||||
|
@ -20,8 +20,7 @@
|
|||||||
from oslo_config import cfg
|
from oslo_config import cfg
|
||||||
from oslo_log import log
|
from oslo_log import log
|
||||||
|
|
||||||
from watcher.common.keystone import KeystoneClient
|
from watcher.common import nova_helper
|
||||||
from watcher.common.nova import NovaClient
|
|
||||||
from watcher.metrics_engine.cluster_model_collector.nova import \
|
from watcher.metrics_engine.cluster_model_collector.nova import \
|
||||||
NovaClusterModelCollector
|
NovaClusterModelCollector
|
||||||
|
|
||||||
@ -30,8 +29,7 @@ CONF = cfg.CONF
|
|||||||
|
|
||||||
|
|
||||||
class CollectorManager(object):
|
class CollectorManager(object):
|
||||||
def get_cluster_model_collector(self):
|
def get_cluster_model_collector(self, osc=None):
|
||||||
keystone = KeystoneClient()
|
""":param osc: an OpenStackClients instance"""
|
||||||
wrapper = NovaClient(keystone.get_credentials(),
|
nova = nova_helper.NovaHelper(osc=osc)
|
||||||
session=keystone.get_session())
|
return NovaClusterModelCollector(nova)
|
||||||
return NovaClusterModelCollector(wrapper=wrapper)
|
|
||||||
|
@ -15,8 +15,11 @@
|
|||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
|
from keystoneauth1 import loading as ka_loading
|
||||||
|
|
||||||
import watcher.api.app
|
import watcher.api.app
|
||||||
from watcher.applier import manager as applier_manager
|
from watcher.applier import manager as applier_manager
|
||||||
|
from watcher.common import clients
|
||||||
from watcher.decision_engine import manager as decision_engine_manger
|
from watcher.decision_engine import manager as decision_engine_manger
|
||||||
from watcher.decision_engine.planner import manager as planner_manager
|
from watcher.decision_engine.planner import manager as planner_manager
|
||||||
from watcher.decision_engine.strategy.selection import default \
|
from watcher.decision_engine.strategy.selection import default \
|
||||||
@ -29,7 +32,15 @@ def list_opts():
|
|||||||
('watcher_goals', strategy_selector.WATCHER_GOALS_OPTS),
|
('watcher_goals', strategy_selector.WATCHER_GOALS_OPTS),
|
||||||
('watcher_decision_engine',
|
('watcher_decision_engine',
|
||||||
decision_engine_manger.WATCHER_DECISION_ENGINE_OPTS),
|
decision_engine_manger.WATCHER_DECISION_ENGINE_OPTS),
|
||||||
('watcher_applier',
|
('watcher_applier', applier_manager.APPLIER_MANAGER_OPTS),
|
||||||
applier_manager.APPLIER_MANAGER_OPTS),
|
('watcher_planner', planner_manager.WATCHER_PLANNER_OPTS),
|
||||||
('watcher_planner', planner_manager.WATCHER_PLANNER_OPTS)
|
('nova_client', clients.NOVA_CLIENT_OPTS),
|
||||||
|
('glance_client', clients.GLANCE_CLIENT_OPTS),
|
||||||
|
('cinder_client', clients.CINDER_CLIENT_OPTS),
|
||||||
|
('ceilometer_client', clients.CEILOMETER_CLIENT_OPTS),
|
||||||
|
('neutron_client', clients.NEUTRON_CLIENT_OPTS),
|
||||||
|
('watcher_clients_auth',
|
||||||
|
(ka_loading.get_auth_common_conf_options() +
|
||||||
|
ka_loading.get_auth_plugin_conf_options('password') +
|
||||||
|
ka_loading.get_session_conf_options()))
|
||||||
]
|
]
|
||||||
|
@ -53,9 +53,9 @@ class FakeAction(abase.BaseAction):
|
|||||||
class TestDefaultWorkFlowEngine(base.DbTestCase):
|
class TestDefaultWorkFlowEngine(base.DbTestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super(TestDefaultWorkFlowEngine, self).setUp()
|
super(TestDefaultWorkFlowEngine, self).setUp()
|
||||||
self.engine = tflow.DefaultWorkFlowEngine()
|
self.engine = tflow.DefaultWorkFlowEngine(
|
||||||
self.engine.context = self.context
|
context=self.context,
|
||||||
self.engine.applier_manager = mock.MagicMock()
|
applier_manager=mock.MagicMock())
|
||||||
|
|
||||||
def test_execute(self):
|
def test_execute(self):
|
||||||
actions = mock.MagicMock()
|
actions = mock.MagicMock()
|
||||||
|
@ -1,99 +0,0 @@
|
|||||||
# -*- encoding: utf-8 -*-
|
|
||||||
# Copyright (c) 2015 b<>com
|
|
||||||
#
|
|
||||||
# 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.
|
|
||||||
|
|
||||||
from __future__ import absolute_import
|
|
||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
from mock import MagicMock
|
|
||||||
from mock import mock
|
|
||||||
from oslo_config import cfg
|
|
||||||
from watcher.common.ceilometer import CeilometerClient
|
|
||||||
|
|
||||||
from watcher.tests.base import BaseTestCase
|
|
||||||
CONF = cfg.CONF
|
|
||||||
|
|
||||||
|
|
||||||
class TestCeilometer(BaseTestCase):
|
|
||||||
def setUp(self):
|
|
||||||
super(TestCeilometer, self).setUp()
|
|
||||||
self.cm = CeilometerClient()
|
|
||||||
|
|
||||||
def test_build_query(self):
|
|
||||||
expected = [{'field': 'user_id', 'op': 'eq', 'value': u'user_id'},
|
|
||||||
{'field': 'project_id', 'op': 'eq', 'value': u'tenant_id'},
|
|
||||||
{'field': 'resource_id', 'op': 'eq',
|
|
||||||
'value': u'resource_id'}]
|
|
||||||
|
|
||||||
query = self.cm.build_query(user_id="user_id",
|
|
||||||
tenant_id="tenant_id",
|
|
||||||
resource_id="resource_id",
|
|
||||||
user_ids=["user_ids"],
|
|
||||||
tenant_ids=["tenant_ids"],
|
|
||||||
resource_ids=["resource_ids"])
|
|
||||||
self.assertEqual(query, expected)
|
|
||||||
|
|
||||||
@mock.patch('keystoneclient.v2_0.client.Client', autospec=True)
|
|
||||||
@mock.patch('ceilometerclient.v2.client.Client', autospec=True)
|
|
||||||
def test_get_ceilometer_v2(self, mock_keystone, mock_ceilometer):
|
|
||||||
cfg.CONF.set_override(
|
|
||||||
'auth_uri', "http://127.0.0.1:9898/v2", group="keystone_authtoken",
|
|
||||||
enforce_type=True
|
|
||||||
)
|
|
||||||
c = CeilometerClient(api_version='2')
|
|
||||||
from ceilometerclient.v2 import Client
|
|
||||||
self.assertIsInstance(c.cmclient, Client)
|
|
||||||
|
|
||||||
@mock.patch.object(CeilometerClient, "cmclient")
|
|
||||||
def test_statistic_aggregation(self, mock_keystone):
|
|
||||||
statistic = MagicMock()
|
|
||||||
expected_result = 100
|
|
||||||
statistic[-1]._info = {'aggregate': {'avg': expected_result}}
|
|
||||||
mock_keystone.statistics.list.return_value = statistic
|
|
||||||
val = self.cm.statistic_aggregation(
|
|
||||||
resource_id="VM_ID",
|
|
||||||
meter_name="cpu_util",
|
|
||||||
period="7300"
|
|
||||||
)
|
|
||||||
self.assertEqual(val, expected_result)
|
|
||||||
|
|
||||||
@mock.patch.object(CeilometerClient, "cmclient")
|
|
||||||
def test_get_last_sample(self, mock_keystone):
|
|
||||||
statistic = MagicMock()
|
|
||||||
expected_result = 100
|
|
||||||
statistic[-1]._info = {'counter_volume': expected_result}
|
|
||||||
mock_keystone.samples.list.return_value = statistic
|
|
||||||
val = self.cm.get_last_sample_value(
|
|
||||||
resource_id="id",
|
|
||||||
meter_name="compute.node.percent"
|
|
||||||
)
|
|
||||||
self.assertEqual(val, expected_result)
|
|
||||||
|
|
||||||
@mock.patch.object(CeilometerClient, "cmclient")
|
|
||||||
def test_get_last_sample_none(self, mock_keystone):
|
|
||||||
expected = []
|
|
||||||
mock_keystone.samples.list.return_value = expected
|
|
||||||
val = self.cm.get_last_sample_values(
|
|
||||||
resource_id="id",
|
|
||||||
meter_name="compute.node.percent"
|
|
||||||
)
|
|
||||||
self.assertEqual(val, expected)
|
|
||||||
|
|
||||||
@mock.patch.object(CeilometerClient, "cmclient")
|
|
||||||
def test_statistic_list(self, mock_keystone):
|
|
||||||
expected_value = []
|
|
||||||
mock_keystone.statistics.list.return_value = expected_value
|
|
||||||
val = self.cm.statistic_list(meter_name="cpu_util")
|
|
||||||
self.assertEqual(val, expected_value)
|
|
98
watcher/tests/common/test_ceilometer_helper.py
Normal file
98
watcher/tests/common/test_ceilometer_helper.py
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
# -*- encoding: utf-8 -*-
|
||||||
|
# Copyright (c) 2015 b<>com
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
from __future__ import absolute_import
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import mock
|
||||||
|
from oslo_config import cfg
|
||||||
|
|
||||||
|
from watcher.common import ceilometer_helper
|
||||||
|
from watcher.common import clients
|
||||||
|
from watcher.tests import base
|
||||||
|
|
||||||
|
CONF = cfg.CONF
|
||||||
|
|
||||||
|
|
||||||
|
@mock.patch.object(clients.OpenStackClients, 'ceilometer')
|
||||||
|
class TestCeilometerHelper(base.BaseTestCase):
|
||||||
|
|
||||||
|
def test_build_query(self, mock_ceilometer):
|
||||||
|
mock_ceilometer.return_value = mock.MagicMock()
|
||||||
|
cm = ceilometer_helper.CeilometerHelper()
|
||||||
|
expected = [{'field': 'user_id', 'op': 'eq', 'value': u'user_id'},
|
||||||
|
{'field': 'project_id', 'op': 'eq', 'value': u'tenant_id'},
|
||||||
|
{'field': 'resource_id', 'op': 'eq',
|
||||||
|
'value': u'resource_id'}]
|
||||||
|
|
||||||
|
query = cm.build_query(user_id="user_id",
|
||||||
|
tenant_id="tenant_id",
|
||||||
|
resource_id="resource_id",
|
||||||
|
user_ids=["user_ids"],
|
||||||
|
tenant_ids=["tenant_ids"],
|
||||||
|
resource_ids=["resource_ids"])
|
||||||
|
self.assertEqual(query, expected)
|
||||||
|
|
||||||
|
def test_statistic_aggregation(self, mock_ceilometer):
|
||||||
|
cm = ceilometer_helper.CeilometerHelper()
|
||||||
|
ceilometer = mock.MagicMock()
|
||||||
|
statistic = mock.MagicMock()
|
||||||
|
expected_result = 100
|
||||||
|
statistic[-1]._info = {'aggregate': {'avg': expected_result}}
|
||||||
|
ceilometer.statistics.list.return_value = statistic
|
||||||
|
mock_ceilometer.return_value = ceilometer
|
||||||
|
cm = ceilometer_helper.CeilometerHelper()
|
||||||
|
val = cm.statistic_aggregation(
|
||||||
|
resource_id="VM_ID",
|
||||||
|
meter_name="cpu_util",
|
||||||
|
period="7300"
|
||||||
|
)
|
||||||
|
self.assertEqual(val, expected_result)
|
||||||
|
|
||||||
|
def test_get_last_sample(self, mock_ceilometer):
|
||||||
|
ceilometer = mock.MagicMock()
|
||||||
|
statistic = mock.MagicMock()
|
||||||
|
expected_result = 100
|
||||||
|
statistic[-1]._info = {'counter_volume': expected_result}
|
||||||
|
ceilometer.samples.list.return_value = statistic
|
||||||
|
mock_ceilometer.return_value = ceilometer
|
||||||
|
cm = ceilometer_helper.CeilometerHelper()
|
||||||
|
val = cm.get_last_sample_value(
|
||||||
|
resource_id="id",
|
||||||
|
meter_name="compute.node.percent"
|
||||||
|
)
|
||||||
|
self.assertEqual(val, expected_result)
|
||||||
|
|
||||||
|
def test_get_last_sample_none(self, mock_ceilometer):
|
||||||
|
ceilometer = mock.MagicMock()
|
||||||
|
expected = []
|
||||||
|
ceilometer.samples.list.return_value = expected
|
||||||
|
mock_ceilometer.return_value = ceilometer
|
||||||
|
cm = ceilometer_helper.CeilometerHelper()
|
||||||
|
val = cm.get_last_sample_values(
|
||||||
|
resource_id="id",
|
||||||
|
meter_name="compute.node.percent"
|
||||||
|
)
|
||||||
|
self.assertEqual(val, expected)
|
||||||
|
|
||||||
|
def test_statistic_list(self, mock_ceilometer):
|
||||||
|
ceilometer = mock.MagicMock()
|
||||||
|
expected_value = []
|
||||||
|
ceilometer.statistics.list.return_value = expected_value
|
||||||
|
mock_ceilometer.return_value = ceilometer
|
||||||
|
cm = ceilometer_helper.CeilometerHelper()
|
||||||
|
val = cm.statistic_list(meter_name="cpu_util")
|
||||||
|
self.assertEqual(val, expected_value)
|
241
watcher/tests/common/test_clients.py
Normal file
241
watcher/tests/common/test_clients.py
Normal file
@ -0,0 +1,241 @@
|
|||||||
|
# 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.
|
||||||
|
|
||||||
|
from ceilometerclient import client as ceclient
|
||||||
|
import ceilometerclient.v2.client as ceclient_v2
|
||||||
|
from cinderclient import client as ciclient
|
||||||
|
from cinderclient.v1 import client as ciclient_v1
|
||||||
|
from glanceclient import client as glclient
|
||||||
|
from keystoneauth1 import loading as ka_loading
|
||||||
|
import mock
|
||||||
|
from neutronclient.neutron import client as netclient
|
||||||
|
from neutronclient.v2_0 import client as netclient_v2
|
||||||
|
from novaclient import client as nvclient
|
||||||
|
from oslo_config import cfg
|
||||||
|
|
||||||
|
from watcher.common import clients
|
||||||
|
from watcher.tests import base
|
||||||
|
|
||||||
|
|
||||||
|
class TestClients(base.BaseTestCase):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super(TestClients, self).setUp()
|
||||||
|
|
||||||
|
cfg.CONF.import_opt('api_version', 'watcher.common.clients',
|
||||||
|
group='nova_client')
|
||||||
|
cfg.CONF.import_opt('api_version', 'watcher.common.clients',
|
||||||
|
group='glance_client')
|
||||||
|
cfg.CONF.import_opt('api_version', 'watcher.common.clients',
|
||||||
|
group='cinder_client')
|
||||||
|
cfg.CONF.import_opt('api_version', 'watcher.common.clients',
|
||||||
|
group='ceilometer_client')
|
||||||
|
cfg.CONF.import_opt('api_version', 'watcher.common.clients',
|
||||||
|
group='neutron_client')
|
||||||
|
|
||||||
|
def test_get_keystone_session(self):
|
||||||
|
_AUTH_CONF_GROUP = 'watcher_clients_auth'
|
||||||
|
ka_loading.register_auth_conf_options(cfg.CONF, _AUTH_CONF_GROUP)
|
||||||
|
ka_loading.register_session_conf_options(cfg.CONF, _AUTH_CONF_GROUP)
|
||||||
|
|
||||||
|
cfg.CONF.set_override('auth_type', 'password',
|
||||||
|
group=_AUTH_CONF_GROUP)
|
||||||
|
|
||||||
|
# If we don't clean up the _AUTH_CONF_GROUP conf options, then other
|
||||||
|
# tests that run after this one will fail, complaining about required
|
||||||
|
# options that _AUTH_CONF_GROUP wants.
|
||||||
|
def cleanup_conf_from_loading():
|
||||||
|
# oslo_config doesn't seem to allow unregistering groups through a
|
||||||
|
# single method, so we do this instead
|
||||||
|
cfg.CONF.reset()
|
||||||
|
del cfg.CONF._groups[_AUTH_CONF_GROUP]
|
||||||
|
|
||||||
|
self.addCleanup(cleanup_conf_from_loading)
|
||||||
|
|
||||||
|
osc = clients.OpenStackClients()
|
||||||
|
|
||||||
|
expected = {'username': 'foousername',
|
||||||
|
'password': 'foopassword',
|
||||||
|
'auth_url': 'http://server.ip:35357',
|
||||||
|
'user_domain_id': 'foouserdomainid',
|
||||||
|
'project_domain_id': 'fooprojdomainid'}
|
||||||
|
|
||||||
|
def reset_register_opts_mock(conf_obj, original_method):
|
||||||
|
conf_obj.register_opts = original_method
|
||||||
|
|
||||||
|
original_register_opts = cfg.CONF.register_opts
|
||||||
|
self.addCleanup(reset_register_opts_mock,
|
||||||
|
cfg.CONF,
|
||||||
|
original_register_opts)
|
||||||
|
|
||||||
|
# Because some of the conf options for auth plugins are not registered
|
||||||
|
# until right before they are loaded, and because the method that does
|
||||||
|
# the actual loading of the conf option values is an anonymous method
|
||||||
|
# (see _getter method of load_from_conf_options in
|
||||||
|
# keystoneauth1.loading.conf.py), we need to manually monkey patch
|
||||||
|
# the register opts method so that we can override the conf values to
|
||||||
|
# our custom values.
|
||||||
|
def mock_register_opts(*args, **kwargs):
|
||||||
|
ret = original_register_opts(*args, **kwargs)
|
||||||
|
if 'group' in kwargs and kwargs['group'] == _AUTH_CONF_GROUP:
|
||||||
|
for key, value in expected.items():
|
||||||
|
cfg.CONF.set_override(key, value, group=_AUTH_CONF_GROUP)
|
||||||
|
return ret
|
||||||
|
|
||||||
|
cfg.CONF.register_opts = mock_register_opts
|
||||||
|
|
||||||
|
sess = osc.session
|
||||||
|
self.assertEqual(expected['auth_url'], sess.auth.auth_url)
|
||||||
|
self.assertEqual(expected['username'], sess.auth._username)
|
||||||
|
self.assertEqual(expected['password'], sess.auth._password)
|
||||||
|
self.assertEqual(expected['user_domain_id'], sess.auth._user_domain_id)
|
||||||
|
self.assertEqual(expected['project_domain_id'],
|
||||||
|
sess.auth._project_domain_id)
|
||||||
|
|
||||||
|
@mock.patch.object(nvclient, 'Client')
|
||||||
|
@mock.patch.object(clients.OpenStackClients, 'session')
|
||||||
|
def test_clients_nova(self, mock_session, mock_call):
|
||||||
|
osc = clients.OpenStackClients()
|
||||||
|
osc._nova = None
|
||||||
|
osc.nova()
|
||||||
|
mock_call.assert_called_once_with(cfg.CONF.nova_client.api_version,
|
||||||
|
session=mock_session)
|
||||||
|
|
||||||
|
@mock.patch.object(clients.OpenStackClients, 'session')
|
||||||
|
def test_clients_nova_diff_vers(self, mock_session):
|
||||||
|
cfg.CONF.set_override('api_version', '2.3',
|
||||||
|
group='nova_client')
|
||||||
|
osc = clients.OpenStackClients()
|
||||||
|
osc._nova = None
|
||||||
|
osc.nova()
|
||||||
|
self.assertEqual('2.3', osc.nova().api_version.get_string())
|
||||||
|
|
||||||
|
@mock.patch.object(clients.OpenStackClients, 'session')
|
||||||
|
def test_clients_nova_cached(self, mock_session):
|
||||||
|
osc = clients.OpenStackClients()
|
||||||
|
osc._nova = None
|
||||||
|
nova = osc.nova()
|
||||||
|
nova_cached = osc.nova()
|
||||||
|
self.assertEqual(nova, nova_cached)
|
||||||
|
|
||||||
|
@mock.patch.object(glclient, 'Client')
|
||||||
|
@mock.patch.object(clients.OpenStackClients, 'session')
|
||||||
|
def test_clients_glance(self, mock_session, mock_call):
|
||||||
|
osc = clients.OpenStackClients()
|
||||||
|
osc._glance = None
|
||||||
|
osc.glance()
|
||||||
|
mock_call.assert_called_once_with(cfg.CONF.glance_client.api_version,
|
||||||
|
session=mock_session)
|
||||||
|
|
||||||
|
@mock.patch.object(clients.OpenStackClients, 'session')
|
||||||
|
def test_clients_glance_diff_vers(self, mock_session):
|
||||||
|
cfg.CONF.set_override('api_version', '1',
|
||||||
|
group='glance_client')
|
||||||
|
osc = clients.OpenStackClients()
|
||||||
|
osc._glance = None
|
||||||
|
osc.glance()
|
||||||
|
self.assertEqual(1.0, osc.glance().version)
|
||||||
|
|
||||||
|
@mock.patch.object(clients.OpenStackClients, 'session')
|
||||||
|
def test_clients_glance_cached(self, mock_session):
|
||||||
|
osc = clients.OpenStackClients()
|
||||||
|
osc._glance = None
|
||||||
|
glance = osc.glance()
|
||||||
|
glance_cached = osc.glance()
|
||||||
|
self.assertEqual(glance, glance_cached)
|
||||||
|
|
||||||
|
@mock.patch.object(ciclient, 'Client')
|
||||||
|
@mock.patch.object(clients.OpenStackClients, 'session')
|
||||||
|
def test_clients_cinder(self, mock_session, mock_call):
|
||||||
|
osc = clients.OpenStackClients()
|
||||||
|
osc._cinder = None
|
||||||
|
osc.cinder()
|
||||||
|
mock_call.assert_called_once_with(cfg.CONF.cinder_client.api_version,
|
||||||
|
session=mock_session)
|
||||||
|
|
||||||
|
@mock.patch.object(clients.OpenStackClients, 'session')
|
||||||
|
def test_clients_cinder_diff_vers(self, mock_session):
|
||||||
|
cfg.CONF.set_override('api_version', '1',
|
||||||
|
group='cinder_client')
|
||||||
|
osc = clients.OpenStackClients()
|
||||||
|
osc._cinder = None
|
||||||
|
osc.cinder()
|
||||||
|
self.assertEqual(ciclient_v1.Client, type(osc.cinder()))
|
||||||
|
|
||||||
|
@mock.patch.object(clients.OpenStackClients, 'session')
|
||||||
|
def test_clients_cinder_cached(self, mock_session):
|
||||||
|
osc = clients.OpenStackClients()
|
||||||
|
osc._cinder = None
|
||||||
|
cinder = osc.cinder()
|
||||||
|
cinder_cached = osc.cinder()
|
||||||
|
self.assertEqual(cinder, cinder_cached)
|
||||||
|
|
||||||
|
@mock.patch.object(ceclient, 'Client')
|
||||||
|
@mock.patch.object(clients.OpenStackClients, 'session')
|
||||||
|
def test_clients_ceilometer(self, mock_session, mock_call):
|
||||||
|
osc = clients.OpenStackClients()
|
||||||
|
osc._ceilometer = None
|
||||||
|
osc.ceilometer()
|
||||||
|
mock_call.assert_called_once_with(
|
||||||
|
cfg.CONF.ceilometer_client.api_version,
|
||||||
|
session=mock_session)
|
||||||
|
|
||||||
|
@mock.patch.object(clients.OpenStackClients, 'session')
|
||||||
|
@mock.patch.object(ceclient_v2.Client, '_get_alarm_client')
|
||||||
|
def test_clients_ceilometer_diff_vers(self, mock_get_alarm_client,
|
||||||
|
mock_session):
|
||||||
|
'''ceilometerclient currently only has one version (v2)'''
|
||||||
|
cfg.CONF.set_override('api_version', '2',
|
||||||
|
group='ceilometer_client')
|
||||||
|
osc = clients.OpenStackClients()
|
||||||
|
osc._ceilometer = None
|
||||||
|
osc.ceilometer()
|
||||||
|
self.assertEqual(ceclient_v2.Client,
|
||||||
|
type(osc.ceilometer()))
|
||||||
|
|
||||||
|
@mock.patch.object(clients.OpenStackClients, 'session')
|
||||||
|
@mock.patch.object(ceclient_v2.Client, '_get_alarm_client')
|
||||||
|
def test_clients_ceilometer_cached(self, mock_get_alarm_client,
|
||||||
|
mock_session):
|
||||||
|
osc = clients.OpenStackClients()
|
||||||
|
osc._ceilometer = None
|
||||||
|
ceilometer = osc.ceilometer()
|
||||||
|
ceilometer_cached = osc.ceilometer()
|
||||||
|
self.assertEqual(ceilometer, ceilometer_cached)
|
||||||
|
|
||||||
|
@mock.patch.object(netclient, 'Client')
|
||||||
|
@mock.patch.object(clients.OpenStackClients, 'session')
|
||||||
|
def test_clients_neutron(self, mock_session, mock_call):
|
||||||
|
osc = clients.OpenStackClients()
|
||||||
|
osc._neutron = None
|
||||||
|
osc.neutron()
|
||||||
|
mock_call.assert_called_once_with(cfg.CONF.neutron_client.api_version,
|
||||||
|
session=mock_session)
|
||||||
|
|
||||||
|
@mock.patch.object(clients.OpenStackClients, 'session')
|
||||||
|
def test_clients_neutron_diff_vers(self, mock_session):
|
||||||
|
'''neutronclient currently only has one version (v2)'''
|
||||||
|
cfg.CONF.set_override('api_version', '2',
|
||||||
|
group='neutron_client')
|
||||||
|
osc = clients.OpenStackClients()
|
||||||
|
osc._neutron = None
|
||||||
|
osc.neutron()
|
||||||
|
self.assertEqual(netclient_v2.Client,
|
||||||
|
type(osc.neutron()))
|
||||||
|
|
||||||
|
@mock.patch.object(clients.OpenStackClients, 'session')
|
||||||
|
def test_clients_neutron_cached(self, mock_session):
|
||||||
|
osc = clients.OpenStackClients()
|
||||||
|
osc._neutron = None
|
||||||
|
neutron = osc.neutron()
|
||||||
|
neutron_cached = osc.neutron()
|
||||||
|
self.assertEqual(neutron, neutron_cached)
|
@ -1,68 +0,0 @@
|
|||||||
# -*- encoding: utf-8 -*-
|
|
||||||
# Copyright (c) 2015 b<>com
|
|
||||||
#
|
|
||||||
# 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.
|
|
||||||
|
|
||||||
from __future__ import absolute_import
|
|
||||||
from __future__ import unicode_literals
|
|
||||||
from keystoneclient.auth.identity import Password
|
|
||||||
from keystoneclient.session import Session
|
|
||||||
from mock import mock
|
|
||||||
from oslo_config import cfg
|
|
||||||
from watcher.common.keystone import KeystoneClient
|
|
||||||
from watcher.tests.base import BaseTestCase
|
|
||||||
|
|
||||||
CONF = cfg.CONF
|
|
||||||
|
|
||||||
|
|
||||||
class TestKeystone(BaseTestCase):
|
|
||||||
def setUp(self):
|
|
||||||
super(TestKeystone, self).setUp()
|
|
||||||
self.ckeystone = KeystoneClient()
|
|
||||||
|
|
||||||
@mock.patch('keystoneclient.v2_0.client.Client', autospec=True)
|
|
||||||
def test_get_endpoint_v2(self, keystone):
|
|
||||||
expected_endpoint = "http://ip:port/v2"
|
|
||||||
cfg.CONF.set_override(
|
|
||||||
'auth_uri', expected_endpoint, group="keystone_authtoken",
|
|
||||||
enforce_type=True
|
|
||||||
)
|
|
||||||
ks = mock.Mock()
|
|
||||||
ks.service_catalog.url_for.return_value = expected_endpoint
|
|
||||||
keystone.return_value = ks
|
|
||||||
ep = self.ckeystone.get_endpoint(service_type='metering',
|
|
||||||
endpoint_type='publicURL',
|
|
||||||
region_name='RegionOne')
|
|
||||||
|
|
||||||
self.assertEqual(ep, expected_endpoint)
|
|
||||||
|
|
||||||
@mock.patch('watcher.common.keystone.KeystoneClient._is_apiv3')
|
|
||||||
def test_get_session(self, mock_apiv3):
|
|
||||||
mock_apiv3.return_value = True
|
|
||||||
k = KeystoneClient()
|
|
||||||
session = k.get_session()
|
|
||||||
self.assertIsInstance(session.auth, Password)
|
|
||||||
self.assertIsInstance(session, Session)
|
|
||||||
|
|
||||||
@mock.patch('watcher.common.keystone.KeystoneClient._is_apiv3')
|
|
||||||
def test_get_credentials(self, mock_apiv3):
|
|
||||||
mock_apiv3.return_value = True
|
|
||||||
expected_creds = {'auth_url': None,
|
|
||||||
'password': None,
|
|
||||||
'project_domain_name': 'default',
|
|
||||||
'project_name': 'admin',
|
|
||||||
'user_domain_name': 'default',
|
|
||||||
'username': None}
|
|
||||||
creds = self.ckeystone.get_credentials()
|
|
||||||
self.assertEqual(creds, expected_creds)
|
|
@ -1,153 +0,0 @@
|
|||||||
# -*- encoding: utf-8 -*-
|
|
||||||
# Copyright (c) 2015 b<>com
|
|
||||||
#
|
|
||||||
# Authors: Jean-Emile DARTOIS <jean-emile.dartois@b-com.com>
|
|
||||||
#
|
|
||||||
# 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.
|
|
||||||
#
|
|
||||||
|
|
||||||
import time
|
|
||||||
|
|
||||||
import glanceclient.v2.client as glclient
|
|
||||||
import mock
|
|
||||||
import novaclient.client as nvclient
|
|
||||||
|
|
||||||
from watcher.common import keystone
|
|
||||||
from watcher.common.nova import NovaClient
|
|
||||||
from watcher.common import utils
|
|
||||||
from watcher.tests import base
|
|
||||||
|
|
||||||
|
|
||||||
class TestNovaClient(base.TestCase):
|
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
super(TestNovaClient, self).setUp()
|
|
||||||
self.instance_uuid = "fb5311b7-37f3-457e-9cde-6494a3c59bfe"
|
|
||||||
self.source_hypervisor = "ldev-indeedsrv005"
|
|
||||||
self.destination_hypervisor = "ldev-indeedsrv006"
|
|
||||||
|
|
||||||
self.creds = mock.MagicMock()
|
|
||||||
self.session = mock.MagicMock()
|
|
||||||
|
|
||||||
@mock.patch.object(keystone, 'KeystoneClient', mock.Mock())
|
|
||||||
@mock.patch.object(nvclient, "Client", mock.Mock())
|
|
||||||
def test_stop_instance(self):
|
|
||||||
nova_client = NovaClient(creds=self.creds, session=self.session)
|
|
||||||
instance_id = utils.generate_uuid()
|
|
||||||
server = mock.MagicMock()
|
|
||||||
server.id = instance_id
|
|
||||||
setattr(server, 'OS-EXT-STS:vm_state', 'stopped')
|
|
||||||
nova_client.nova.servers = mock.MagicMock()
|
|
||||||
nova_client.nova.servers.find.return_value = server
|
|
||||||
nova_client.nova.servers.list.return_value = [server]
|
|
||||||
|
|
||||||
result = nova_client.stop_instance(instance_id)
|
|
||||||
self.assertEqual(result, True)
|
|
||||||
|
|
||||||
@mock.patch.object(keystone, 'KeystoneClient', mock.Mock())
|
|
||||||
@mock.patch.object(nvclient, "Client", mock.Mock())
|
|
||||||
def test_set_host_offline(self):
|
|
||||||
nova_client = NovaClient(creds=self.creds, session=self.session)
|
|
||||||
host = mock.MagicMock()
|
|
||||||
nova_client.nova.hosts = mock.MagicMock()
|
|
||||||
nova_client.nova.hosts.get.return_value = host
|
|
||||||
result = nova_client.set_host_offline("rennes")
|
|
||||||
self.assertEqual(result, True)
|
|
||||||
|
|
||||||
@mock.patch.object(time, 'sleep', mock.Mock())
|
|
||||||
@mock.patch.object(keystone, 'KeystoneClient', mock.Mock())
|
|
||||||
@mock.patch.object(nvclient, "Client", mock.Mock())
|
|
||||||
def test_live_migrate_instance(self):
|
|
||||||
nova_client = NovaClient(creds=self.creds, session=self.session)
|
|
||||||
server = mock.MagicMock()
|
|
||||||
server.id = self.instance_uuid
|
|
||||||
nova_client.nova.servers = mock.MagicMock()
|
|
||||||
nova_client.nova.servers.list.return_value = [server]
|
|
||||||
instance = nova_client.live_migrate_instance(
|
|
||||||
self.instance_uuid, self.destination_hypervisor
|
|
||||||
)
|
|
||||||
self.assertIsNotNone(instance)
|
|
||||||
|
|
||||||
@mock.patch.object(keystone, 'KeystoneClient', mock.Mock())
|
|
||||||
@mock.patch.object(nvclient, "Client", mock.Mock())
|
|
||||||
def test_watcher_non_live_migrate_instance_not_found(self):
|
|
||||||
nova_client = NovaClient(creds=self.creds, session=self.session)
|
|
||||||
nova_client.nova.servers.list.return_value = []
|
|
||||||
nova_client.nova.servers.find.return_value = None
|
|
||||||
|
|
||||||
is_success = nova_client.watcher_non_live_migrate_instance(
|
|
||||||
self.instance_uuid,
|
|
||||||
self.destination_hypervisor)
|
|
||||||
|
|
||||||
self.assertEqual(is_success, False)
|
|
||||||
|
|
||||||
@mock.patch.object(time, 'sleep', mock.Mock())
|
|
||||||
@mock.patch.object(keystone, 'KeystoneClient', mock.Mock())
|
|
||||||
@mock.patch.object(nvclient, "Client", mock.Mock())
|
|
||||||
def test_watcher_non_live_migrate_instance_volume(self):
|
|
||||||
nova_client = NovaClient(creds=self.creds, session=self.session)
|
|
||||||
instance = mock.MagicMock(id=self.instance_uuid)
|
|
||||||
setattr(instance, 'OS-EXT-SRV-ATTR:host', self.source_hypervisor)
|
|
||||||
nova_client.nova.servers.list.return_value = [instance]
|
|
||||||
nova_client.nova.servers.find.return_value = instance
|
|
||||||
instance = nova_client.watcher_non_live_migrate_instance(
|
|
||||||
self.instance_uuid,
|
|
||||||
self.destination_hypervisor)
|
|
||||||
self.assertIsNotNone(instance)
|
|
||||||
|
|
||||||
@mock.patch.object(time, 'sleep', mock.Mock())
|
|
||||||
@mock.patch.object(keystone, 'KeystoneClient', mock.Mock())
|
|
||||||
@mock.patch.object(nvclient, "Client", mock.Mock())
|
|
||||||
def test_watcher_non_live_migrate_keep_image(self):
|
|
||||||
nova_client = NovaClient(creds=self.creds, session=self.session)
|
|
||||||
instance = mock.MagicMock(id=self.instance_uuid)
|
|
||||||
setattr(instance, 'OS-EXT-SRV-ATTR:host', self.source_hypervisor)
|
|
||||||
addresses = mock.MagicMock()
|
|
||||||
type = mock.MagicMock()
|
|
||||||
networks = []
|
|
||||||
networks.append(("lan", type))
|
|
||||||
addresses.items.return_value = networks
|
|
||||||
attached_volumes = mock.MagicMock()
|
|
||||||
setattr(instance, 'addresses', addresses)
|
|
||||||
setattr(instance, "os-extended-volumes:volumes_attached",
|
|
||||||
attached_volumes)
|
|
||||||
nova_client.nova.servers.list.return_value = [instance]
|
|
||||||
nova_client.nova.servers.find.return_value = instance
|
|
||||||
instance = nova_client.watcher_non_live_migrate_instance(
|
|
||||||
self.instance_uuid,
|
|
||||||
self.destination_hypervisor, keep_original_image_name=False)
|
|
||||||
self.assertIsNotNone(instance)
|
|
||||||
|
|
||||||
@mock.patch.object(time, 'sleep', mock.Mock())
|
|
||||||
@mock.patch.object(keystone, 'KeystoneClient', mock.Mock())
|
|
||||||
@mock.patch.object(nvclient, "Client", mock.Mock())
|
|
||||||
@mock.patch.object(glclient, "Client")
|
|
||||||
def test_create_image_from_instance(self, m_glance_cls):
|
|
||||||
nova_client = NovaClient(creds=self.creds, session=self.session)
|
|
||||||
instance = mock.MagicMock()
|
|
||||||
image = mock.MagicMock()
|
|
||||||
setattr(instance, 'OS-EXT-SRV-ATTR:host', self.source_hypervisor)
|
|
||||||
nova_client.nova.servers.list.return_value = [instance]
|
|
||||||
nova_client.nova.servers.find.return_value = instance
|
|
||||||
image_uuid = 'fake-image-uuid'
|
|
||||||
nova_client.nova.servers.create_image.return_value = image
|
|
||||||
|
|
||||||
m_glance = mock.MagicMock()
|
|
||||||
m_glance_cls.return_value = m_glance
|
|
||||||
|
|
||||||
m_glance.images = {image_uuid: image}
|
|
||||||
instance = nova_client.create_image_from_instance(
|
|
||||||
self.instance_uuid, "Cirros"
|
|
||||||
)
|
|
||||||
self.assertIsNone(instance)
|
|
144
watcher/tests/common/test_nova_helper.py
Normal file
144
watcher/tests/common/test_nova_helper.py
Normal file
@ -0,0 +1,144 @@
|
|||||||
|
# -*- encoding: utf-8 -*-
|
||||||
|
# Copyright (c) 2015 b<>com
|
||||||
|
#
|
||||||
|
# Authors: Jean-Emile DARTOIS <jean-emile.dartois@b-com.com>
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
#
|
||||||
|
|
||||||
|
import time
|
||||||
|
|
||||||
|
import mock
|
||||||
|
|
||||||
|
from watcher.common import clients
|
||||||
|
from watcher.common import nova_helper
|
||||||
|
from watcher.common import utils
|
||||||
|
from watcher.tests import base
|
||||||
|
|
||||||
|
|
||||||
|
@mock.patch.object(clients.OpenStackClients, 'nova')
|
||||||
|
@mock.patch.object(clients.OpenStackClients, 'neutron')
|
||||||
|
@mock.patch.object(clients.OpenStackClients, 'cinder')
|
||||||
|
@mock.patch.object(clients.OpenStackClients, 'glance')
|
||||||
|
class TestNovaHelper(base.TestCase):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super(TestNovaHelper, self).setUp()
|
||||||
|
self.instance_uuid = "fb5311b7-37f3-457e-9cde-6494a3c59bfe"
|
||||||
|
self.source_hypervisor = "ldev-indeedsrv005"
|
||||||
|
self.destination_hypervisor = "ldev-indeedsrv006"
|
||||||
|
|
||||||
|
def test_stop_instance(self, mock_glance, mock_cinder, mock_neutron,
|
||||||
|
mock_nova):
|
||||||
|
nova_util = nova_helper.NovaHelper()
|
||||||
|
instance_id = utils.generate_uuid()
|
||||||
|
server = mock.MagicMock()
|
||||||
|
server.id = instance_id
|
||||||
|
setattr(server, 'OS-EXT-STS:vm_state', 'stopped')
|
||||||
|
nova_util.nova.servers = mock.MagicMock()
|
||||||
|
nova_util.nova.servers.find.return_value = server
|
||||||
|
nova_util.nova.servers.list.return_value = [server]
|
||||||
|
|
||||||
|
result = nova_util.stop_instance(instance_id)
|
||||||
|
self.assertEqual(result, True)
|
||||||
|
|
||||||
|
def test_set_host_offline(self, mock_glance, mock_cinder, mock_neutron,
|
||||||
|
mock_nova):
|
||||||
|
nova_util = nova_helper.NovaHelper()
|
||||||
|
host = mock.MagicMock()
|
||||||
|
nova_util.nova.hosts = mock.MagicMock()
|
||||||
|
nova_util.nova.hosts.get.return_value = host
|
||||||
|
result = nova_util.set_host_offline("rennes")
|
||||||
|
self.assertEqual(result, True)
|
||||||
|
|
||||||
|
@mock.patch.object(time, 'sleep', mock.Mock())
|
||||||
|
def test_live_migrate_instance(self, mock_glance, mock_cinder,
|
||||||
|
mock_neutron, mock_nova):
|
||||||
|
nova_util = nova_helper.NovaHelper()
|
||||||
|
server = mock.MagicMock()
|
||||||
|
server.id = self.instance_uuid
|
||||||
|
nova_util.nova.servers = mock.MagicMock()
|
||||||
|
nova_util.nova.servers.list.return_value = [server]
|
||||||
|
instance = nova_util.live_migrate_instance(
|
||||||
|
self.instance_uuid, self.destination_hypervisor
|
||||||
|
)
|
||||||
|
self.assertIsNotNone(instance)
|
||||||
|
|
||||||
|
def test_watcher_non_live_migrate_instance_not_found(
|
||||||
|
self, mock_glance, mock_cinder, mock_neutron, mock_nova):
|
||||||
|
nova_util = nova_helper.NovaHelper()
|
||||||
|
nova_util.nova.servers.list.return_value = []
|
||||||
|
nova_util.nova.servers.find.return_value = None
|
||||||
|
|
||||||
|
is_success = nova_util.watcher_non_live_migrate_instance(
|
||||||
|
self.instance_uuid,
|
||||||
|
self.destination_hypervisor)
|
||||||
|
|
||||||
|
self.assertEqual(is_success, False)
|
||||||
|
|
||||||
|
@mock.patch.object(time, 'sleep', mock.Mock())
|
||||||
|
def test_watcher_non_live_migrate_instance_volume(
|
||||||
|
self, mock_glance, mock_cinder, mock_neutron, mock_nova):
|
||||||
|
nova_util = nova_helper.NovaHelper()
|
||||||
|
instance = mock.MagicMock(id=self.instance_uuid)
|
||||||
|
setattr(instance, 'OS-EXT-SRV-ATTR:host', self.source_hypervisor)
|
||||||
|
nova_util.nova.servers.list.return_value = [instance]
|
||||||
|
nova_util.nova.servers.find.return_value = instance
|
||||||
|
instance = nova_util.watcher_non_live_migrate_instance(
|
||||||
|
self.instance_uuid,
|
||||||
|
self.destination_hypervisor)
|
||||||
|
self.assertIsNotNone(instance)
|
||||||
|
|
||||||
|
@mock.patch.object(time, 'sleep', mock.Mock())
|
||||||
|
def test_watcher_non_live_migrate_keep_image(
|
||||||
|
self, mock_glance, mock_cinder, mock_neutron, mock_nova):
|
||||||
|
nova_util = nova_helper.NovaHelper()
|
||||||
|
instance = mock.MagicMock(id=self.instance_uuid)
|
||||||
|
setattr(instance, 'OS-EXT-SRV-ATTR:host', self.source_hypervisor)
|
||||||
|
addresses = mock.MagicMock()
|
||||||
|
type = mock.MagicMock()
|
||||||
|
networks = []
|
||||||
|
networks.append(("lan", type))
|
||||||
|
addresses.items.return_value = networks
|
||||||
|
attached_volumes = mock.MagicMock()
|
||||||
|
setattr(instance, 'addresses', addresses)
|
||||||
|
setattr(instance, "os-extended-volumes:volumes_attached",
|
||||||
|
attached_volumes)
|
||||||
|
nova_util.nova.servers.list.return_value = [instance]
|
||||||
|
nova_util.nova.servers.find.return_value = instance
|
||||||
|
instance = nova_util.watcher_non_live_migrate_instance(
|
||||||
|
self.instance_uuid,
|
||||||
|
self.destination_hypervisor, keep_original_image_name=False)
|
||||||
|
self.assertIsNotNone(instance)
|
||||||
|
|
||||||
|
@mock.patch.object(time, 'sleep', mock.Mock())
|
||||||
|
def test_create_image_from_instance(self, mock_glance, mock_cinder,
|
||||||
|
mock_neutron, mock_nova):
|
||||||
|
nova_util = nova_helper.NovaHelper()
|
||||||
|
instance = mock.MagicMock()
|
||||||
|
image = mock.MagicMock()
|
||||||
|
setattr(instance, 'OS-EXT-SRV-ATTR:host', self.source_hypervisor)
|
||||||
|
nova_util.nova.servers.list.return_value = [instance]
|
||||||
|
nova_util.nova.servers.find.return_value = instance
|
||||||
|
image_uuid = 'fake-image-uuid'
|
||||||
|
nova_util.nova.servers.create_image.return_value = image
|
||||||
|
|
||||||
|
glance_client = mock.MagicMock()
|
||||||
|
mock_glance.return_value = glance_client
|
||||||
|
|
||||||
|
glance_client.images = {image_uuid: image}
|
||||||
|
instance = nova_util.create_image_from_instance(
|
||||||
|
self.instance_uuid, "Cirros"
|
||||||
|
)
|
||||||
|
self.assertIsNone(instance)
|
@ -36,8 +36,8 @@ class TestStrategySelector(TestCase):
|
|||||||
enforce_type=True)
|
enforce_type=True)
|
||||||
expected_goal = 'DUMMY'
|
expected_goal = 'DUMMY'
|
||||||
expected_strategy = CONF.watcher_goals.goals[expected_goal]
|
expected_strategy = CONF.watcher_goals.goals[expected_goal]
|
||||||
self.strategy_selector.define_from_goal(expected_goal)
|
self.strategy_selector.define_from_goal(expected_goal, osc=None)
|
||||||
mock_call.assert_called_once_with(expected_strategy)
|
mock_call.assert_called_once_with(expected_strategy, osc=None)
|
||||||
|
|
||||||
@patch.object(DefaultStrategyLoader, 'load')
|
@patch.object(DefaultStrategyLoader, 'load')
|
||||||
def test_define_from_goal_with_incorrect_mapping(self, mock_call):
|
def test_define_from_goal_with_incorrect_mapping(self, mock_call):
|
||||||
|
Loading…
Reference in New Issue
Block a user