Compare commits
22 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ad40c61ea9 | ||
|
|
1d74f7e3bc | ||
|
|
b7641a9311 | ||
|
|
376d669af6 | ||
|
|
25d27f0288 | ||
|
|
3f4686ce79 | ||
|
|
86c1a9d77f | ||
|
|
9a6811ae6b | ||
|
|
e520f5f452 | ||
|
|
6a25bd983c | ||
|
|
c175ef2170 | ||
|
|
28733a5f30 | ||
|
|
7f8fec1bca | ||
|
|
278b1819d6 | ||
|
|
978bb11d4a | ||
|
|
3027b28942 | ||
|
|
2f0c1c12cf | ||
|
|
e122c61840 | ||
|
|
8f6eac819f | ||
|
|
de307e536e | ||
|
|
7406a1e713 | ||
|
|
982410dd3e |
@@ -80,10 +80,7 @@ function cleanup_watcher {
|
|||||||
# configure_watcher() - Set config files, create data dirs, etc
|
# configure_watcher() - Set config files, create data dirs, etc
|
||||||
function configure_watcher {
|
function configure_watcher {
|
||||||
# Put config files in ``/etc/watcher`` for everyone to find
|
# Put config files in ``/etc/watcher`` for everyone to find
|
||||||
if [[ ! -d $WATCHER_CONF_DIR ]]; then
|
sudo install -d -o $STACK_USER $WATCHER_CONF_DIR
|
||||||
sudo mkdir -p $WATCHER_CONF_DIR
|
|
||||||
sudo chown $STACK_USER $WATCHER_CONF_DIR
|
|
||||||
fi
|
|
||||||
|
|
||||||
install_default_policy watcher
|
install_default_policy watcher
|
||||||
|
|
||||||
@@ -128,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
|
||||||
@@ -178,9 +169,8 @@ function create_watcher_conf {
|
|||||||
# create_watcher_cache_dir() - Part of the init_watcher() process
|
# create_watcher_cache_dir() - Part of the init_watcher() process
|
||||||
function create_watcher_cache_dir {
|
function create_watcher_cache_dir {
|
||||||
# Create cache dir
|
# Create cache dir
|
||||||
sudo mkdir -p $WATCHER_AUTH_CACHE_DIR
|
sudo install -d -o $STACK_USER $WATCHER_AUTH_CACHE_DIR
|
||||||
sudo chown $STACK_USER $WATCHER_AUTH_CACHE_DIR
|
rm -rf $WATCHER_AUTH_CACHE_DIR/*
|
||||||
rm -f $WATCHER_AUTH_CACHE_DIR/*
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# init_watcher() - Initialize databases, etc.
|
# init_watcher() - Initialize databases, etc.
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -237,39 +243,105 @@ so that the watcher service is configured for your needs.
|
|||||||
#rabbit_port = 5672
|
#rabbit_port = 5672
|
||||||
|
|
||||||
|
|
||||||
#. Configure the Watcher Service to use these credentials with the Identity
|
#. Watcher API shall validate the token provided by every incoming request,
|
||||||
Service. Replace IDENTITY_IP with the IP of the Identity server, and
|
via keystonemiddleware, which requires the Watcher service to be configured
|
||||||
replace WATCHER_PASSWORD with the password you chose for the ``watcher``
|
with the right credentials for the Identity service.
|
||||||
user in the Identity Service::
|
|
||||||
|
|
||||||
[keystone_authtoken]
|
In the configuration section here below:
|
||||||
|
|
||||||
# Complete public Identity API endpoint (string value)
|
* replace IDENTITY_IP with the IP of the Identity server
|
||||||
#auth_uri=<None>
|
* replace WATCHER_PASSWORD with the password you chose for the ``watcher``
|
||||||
auth_uri=http://IDENTITY_IP:5000/v3
|
user
|
||||||
|
* replace KEYSTONE_SERVICE_PROJECT_NAME with the name of project created
|
||||||
|
for OpenStack services (e.g. ``service``) ::
|
||||||
|
|
||||||
# Complete admin Identity API endpoint. This should specify the
|
[keystone_authtoken]
|
||||||
# unversioned root endpoint e.g. https://localhost:35357/ (string
|
|
||||||
# value)
|
|
||||||
#identity_uri = <None>
|
|
||||||
identity_uri = http://IDENTITY_IP:5000
|
|
||||||
|
|
||||||
# Keystone account username (string value)
|
# Authentication type to load (unknown value)
|
||||||
#admin_user=<None>
|
# Deprecated group/name - [DEFAULT]/auth_plugin
|
||||||
admin_user=watcher
|
#auth_type = <None>
|
||||||
|
auth_type = password
|
||||||
|
|
||||||
# Keystone account password (string value)
|
# Authentication URL (unknown value)
|
||||||
#admin_password=<None>
|
#auth_url = <None>
|
||||||
admin_password=WATCHER_DBPASSWORD
|
auth_url = http://IDENTITY_IP:35357
|
||||||
|
|
||||||
# Keystone service account tenant name to validate user tokens
|
# Username (unknown value)
|
||||||
# (string value)
|
# Deprecated group/name - [DEFAULT]/username
|
||||||
#admin_tenant_name=admin
|
#username = <None>
|
||||||
admin_tenant_name=KEYSTONE_SERVICE_PROJECT_NAME
|
username=watcher
|
||||||
|
|
||||||
# Directory used to cache files related to PKI tokens (string
|
# User's password (unknown value)
|
||||||
# value)
|
#password = <None>
|
||||||
#signing_dir=<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
|
||||||
|
|
||||||
|
# Project name to scope to (unknown value)
|
||||||
|
# Deprecated group/name - [DEFAULT]/tenant-name
|
||||||
|
#project_name = <None>
|
||||||
|
project_name = KEYSTONE_SERVICE_PROJECT_NAME
|
||||||
|
|
||||||
|
#. Watcher's decision engine and applier interact with other OpenStack
|
||||||
|
projects through those projects' clients. In order to instantiate these
|
||||||
|
clients, Watcher needs to request a new session from the Identity service
|
||||||
|
using the right credentials.
|
||||||
|
|
||||||
|
In the configuration section here below:
|
||||||
|
|
||||||
|
* replace IDENTITY_IP with the IP of the Identity server
|
||||||
|
* replace WATCHER_PASSWORD with the password you chose for the ``watcher``
|
||||||
|
user
|
||||||
|
* replace KEYSTONE_SERVICE_PROJECT_NAME with the name of project created
|
||||||
|
for OpenStack services (e.g. ``service``) ::
|
||||||
|
|
||||||
|
[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
|
||||||
|
|
||||||
|
# Project name to scope to (unknown value)
|
||||||
|
# Deprecated group/name - [DEFAULT]/tenant-name
|
||||||
|
#project_name = <None>
|
||||||
|
project_name = KEYSTONE_SERVICE_PROJECT_NAME
|
||||||
|
|
||||||
|
#. 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::
|
||||||
|
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ This goal should be declared in the Watcher service configuration file
|
|||||||
|
|
||||||
.. code:: bash
|
.. code:: bash
|
||||||
|
|
||||||
$ watcher audit-template-create my_first_audit SERVERS_CONSOLIDATION
|
$ watcher audit-template-create my_first_audit DUMMY
|
||||||
|
|
||||||
If you get "*You must provide a username via either --os-username or via
|
If you get "*You must provide a username via either --os-username or via
|
||||||
env[OS_USERNAME]*" you may have to verify your credentials.
|
env[OS_USERNAME]*" you may have to verify your credentials.
|
||||||
|
|||||||
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
|
||||||
|
|||||||
2
tox.ini
2
tox.ini
@@ -67,4 +67,4 @@ import_exceptions = watcher._i18n
|
|||||||
[doc8]
|
[doc8]
|
||||||
extension=.rst
|
extension=.rst
|
||||||
# todo: stop ignoring doc/source/man when https://bugs.launchpad.net/doc8/+bug/1502391 is fixed
|
# todo: stop ignoring doc/source/man when https://bugs.launchpad.net/doc8/+bug/1502391 is fixed
|
||||||
ignore-path=doc/source/image_src,doc/source/man
|
ignore-path=doc/source/image_src,doc/source/man,doc/source/api
|
||||||
|
|||||||
@@ -288,10 +288,10 @@ class ActionsController(rest.RestController):
|
|||||||
sort_key=sort_key,
|
sort_key=sort_key,
|
||||||
sort_dir=sort_dir)
|
sort_dir=sort_dir)
|
||||||
|
|
||||||
@wsme_pecan.wsexpose(ActionCollection, types.uuid, types.uuid,
|
@wsme_pecan.wsexpose(ActionCollection, types.uuid, int,
|
||||||
int, wtypes.text, wtypes.text, types.uuid,
|
wtypes.text, wtypes.text, types.uuid,
|
||||||
types.uuid)
|
types.uuid)
|
||||||
def get_all(self, action_uuid=None, marker=None, limit=None,
|
def get_all(self, marker=None, limit=None,
|
||||||
sort_key='id', sort_dir='asc', action_plan_uuid=None,
|
sort_key='id', sort_dir='asc', action_plan_uuid=None,
|
||||||
audit_uuid=None):
|
audit_uuid=None):
|
||||||
"""Retrieve a list of actions.
|
"""Retrieve a list of actions.
|
||||||
@@ -312,16 +312,14 @@ class ActionsController(rest.RestController):
|
|||||||
marker, limit, sort_key, sort_dir,
|
marker, limit, sort_key, sort_dir,
|
||||||
action_plan_uuid=action_plan_uuid, audit_uuid=audit_uuid)
|
action_plan_uuid=action_plan_uuid, audit_uuid=audit_uuid)
|
||||||
|
|
||||||
@wsme_pecan.wsexpose(ActionCollection, types.uuid,
|
@wsme_pecan.wsexpose(ActionCollection, types.uuid, int,
|
||||||
types.uuid, int, wtypes.text, wtypes.text,
|
wtypes.text, wtypes.text, types.uuid,
|
||||||
types.uuid, types.uuid)
|
types.uuid)
|
||||||
def detail(self, action_uuid=None, marker=None, limit=None,
|
def detail(self, marker=None, limit=None,
|
||||||
sort_key='id', sort_dir='asc', action_plan_uuid=None,
|
sort_key='id', sort_dir='asc', action_plan_uuid=None,
|
||||||
audit_uuid=None):
|
audit_uuid=None):
|
||||||
"""Retrieve a list of actions with detail.
|
"""Retrieve a list of actions with detail.
|
||||||
|
|
||||||
:param action_uuid: UUID of a action, to get only actions for that
|
|
||||||
action.
|
|
||||||
:param marker: pagination marker for large data sets.
|
:param marker: pagination marker for large data sets.
|
||||||
:param limit: maximum number of resources to return in a single result.
|
:param limit: maximum number of resources to return in a single result.
|
||||||
:param sort_key: column to sort results by. Default: id.
|
:param sort_key: column to sort results by. Default: id.
|
||||||
|
|||||||
@@ -181,7 +181,6 @@ class ActionPlan(base.APIBase):
|
|||||||
|
|
||||||
self.fields = []
|
self.fields = []
|
||||||
fields = list(objects.ActionPlan.fields)
|
fields = list(objects.ActionPlan.fields)
|
||||||
fields.append('audit_uuid')
|
|
||||||
for field in fields:
|
for field in fields:
|
||||||
# Skip fields we do not expose.
|
# Skip fields we do not expose.
|
||||||
if not hasattr(self, field):
|
if not hasattr(self, field):
|
||||||
@@ -189,14 +188,19 @@ class ActionPlan(base.APIBase):
|
|||||||
self.fields.append(field)
|
self.fields.append(field)
|
||||||
setattr(self, field, kwargs.get(field, wtypes.Unset))
|
setattr(self, field, kwargs.get(field, wtypes.Unset))
|
||||||
|
|
||||||
self.fields.append('audit_id')
|
self.fields.append('audit_uuid')
|
||||||
|
self.fields.append('first_action_uuid')
|
||||||
|
|
||||||
setattr(self, 'audit_uuid', kwargs.get('audit_id', wtypes.Unset))
|
setattr(self, 'audit_uuid', kwargs.get('audit_id', wtypes.Unset))
|
||||||
|
setattr(self, 'first_action_uuid',
|
||||||
|
kwargs.get('first_action_id', wtypes.Unset))
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _convert_with_links(action_plan, url, expand=True):
|
def _convert_with_links(action_plan, url, expand=True):
|
||||||
if not expand:
|
if not expand:
|
||||||
action_plan.unset_fields_except(['uuid', 'state', 'updated_at',
|
action_plan.unset_fields_except(
|
||||||
'audit_uuid'])
|
['uuid', 'state', 'updated_at',
|
||||||
|
'audit_uuid', 'first_action_uuid'])
|
||||||
|
|
||||||
action_plan.links = [link.Link.make_link(
|
action_plan.links = [link.Link.make_link(
|
||||||
'self', url,
|
'self', url,
|
||||||
|
|||||||
@@ -149,7 +149,7 @@ class AuditTemplate(base.APIBase):
|
|||||||
name='My Audit Template',
|
name='My Audit Template',
|
||||||
description='Description of my audit template',
|
description='Description of my audit template',
|
||||||
host_aggregate=5,
|
host_aggregate=5,
|
||||||
goal='SERVERS_CONSOLIDATION',
|
goal='DUMMY',
|
||||||
extra={'automatic': True},
|
extra={'automatic': True},
|
||||||
created_at=datetime.datetime.utcnow(),
|
created_at=datetime.datetime.utcnow(),
|
||||||
deleted_at=None,
|
deleted_at=None,
|
||||||
|
|||||||
@@ -43,8 +43,8 @@ class DefaultActionPlanHandler(base.BaseActionPlanHandler):
|
|||||||
ev.data = {}
|
ev.data = {}
|
||||||
payload = {'action_plan__uuid': uuid,
|
payload = {'action_plan__uuid': uuid,
|
||||||
'action_plan_state': state}
|
'action_plan_state': state}
|
||||||
self.applier_manager.topic_status.publish_event(ev.type.name,
|
self.applier_manager.status_topic_handler.publish_event(
|
||||||
payload)
|
ev.type.name, payload)
|
||||||
|
|
||||||
def execute(self):
|
def execute(self):
|
||||||
try:
|
try:
|
||||||
@@ -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,18 +44,19 @@ 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.InvalidParameterValue(err=self.migration_type)
|
raise exception.Invalid(
|
||||||
|
message=(_('Migration of type %(migration_type)s is not '
|
||||||
|
'supported.') %
|
||||||
|
{'migration_type': self.migration_type}))
|
||||||
else:
|
else:
|
||||||
raise exception.InstanceNotFound(name=self.instance_uuid)
|
raise exception.InstanceNotFound(name=self.instance_uuid)
|
||||||
|
|
||||||
|
|||||||
@@ -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):
|
||||||
|
|||||||
@@ -34,12 +34,12 @@ APPLIER_MANAGER_OPTS = [
|
|||||||
min=1,
|
min=1,
|
||||||
required=True,
|
required=True,
|
||||||
help='Number of workers for applier, default value is 1.'),
|
help='Number of workers for applier, default value is 1.'),
|
||||||
cfg.StrOpt('topic_control',
|
cfg.StrOpt('conductor_topic',
|
||||||
default='watcher.applier.control',
|
default='watcher.applier.control',
|
||||||
help='The topic name used for'
|
help='The topic name used for'
|
||||||
'control events, this topic '
|
'control events, this topic '
|
||||||
'used for rpc call '),
|
'used for rpc call '),
|
||||||
cfg.StrOpt('topic_status',
|
cfg.StrOpt('status_topic',
|
||||||
default='watcher.applier.status',
|
default='watcher.applier.status',
|
||||||
help='The topic name used for '
|
help='The topic name used for '
|
||||||
'status events, this topic '
|
'status events, this topic '
|
||||||
@@ -67,12 +67,13 @@ class ApplierManager(messaging_core.MessagingCore):
|
|||||||
def __init__(self):
|
def __init__(self):
|
||||||
super(ApplierManager, self).__init__(
|
super(ApplierManager, self).__init__(
|
||||||
CONF.watcher_applier.publisher_id,
|
CONF.watcher_applier.publisher_id,
|
||||||
CONF.watcher_applier.topic_control,
|
CONF.watcher_applier.conductor_topic,
|
||||||
CONF.watcher_applier.topic_status,
|
CONF.watcher_applier.status_topic,
|
||||||
api_version=self.API_VERSION,
|
api_version=self.API_VERSION,
|
||||||
)
|
)
|
||||||
self.topic_control.add_endpoint(trigger.TriggerActionPlan(self))
|
self.conductor_topic_handler.add_endpoint(
|
||||||
|
trigger.TriggerActionPlan(self))
|
||||||
|
|
||||||
def join(self):
|
def join(self):
|
||||||
self.topic_control.join()
|
self.conductor_topic_handler.join()
|
||||||
self.topic_status.join()
|
self.status_topic_handler.join()
|
||||||
|
|||||||
@@ -39,17 +39,17 @@ class ApplierAPI(messaging_core.MessagingCore):
|
|||||||
def __init__(self):
|
def __init__(self):
|
||||||
super(ApplierAPI, self).__init__(
|
super(ApplierAPI, self).__init__(
|
||||||
CONF.watcher_applier.publisher_id,
|
CONF.watcher_applier.publisher_id,
|
||||||
CONF.watcher_applier.topic_control,
|
CONF.watcher_applier.conductor_topic,
|
||||||
CONF.watcher_applier.topic_status,
|
CONF.watcher_applier.status_topic,
|
||||||
api_version=self.API_VERSION,
|
api_version=self.API_VERSION,
|
||||||
)
|
)
|
||||||
self.handler = notification.NotificationHandler(self.publisher_id)
|
self.handler = notification.NotificationHandler(self.publisher_id)
|
||||||
self.handler.register_observer(self)
|
self.handler.register_observer(self)
|
||||||
self.topic_status.add_endpoint(self.handler)
|
self.status_topic_handler.add_endpoint(self.handler)
|
||||||
transport = om.get_transport(CONF)
|
transport = om.get_transport(CONF)
|
||||||
|
|
||||||
target = om.Target(
|
target = om.Target(
|
||||||
topic=CONF.watcher_applier.topic_control,
|
topic=CONF.watcher_applier.conductor_topic,
|
||||||
version=self.API_VERSION,
|
version=self.API_VERSION,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -62,8 +62,8 @@ class BaseWorkFlowEngine(object):
|
|||||||
ev.data = {}
|
ev.data = {}
|
||||||
payload = {'action_uuid': action.uuid,
|
payload = {'action_uuid': action.uuid,
|
||||||
'action_state': state}
|
'action_state': state}
|
||||||
self.applier_manager.topic_status.publish_event(ev.type.name,
|
self.applier_manager.status_topic_handler.publish_event(
|
||||||
payload)
|
ev.type.name, payload)
|
||||||
|
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
def execute(self, actions):
|
def execute(self, actions):
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
@@ -133,12 +154,6 @@ class InvalidGoal(Invalid):
|
|||||||
msg_fmt = _("Goal %(goal)s is not defined in Watcher configuration file")
|
msg_fmt = _("Goal %(goal)s is not defined in Watcher configuration file")
|
||||||
|
|
||||||
|
|
||||||
# Cannot be templated as the error syntax varies.
|
|
||||||
# msg needs to be constructed when raised.
|
|
||||||
class InvalidParameterValue(Invalid):
|
|
||||||
msg_fmt = _("%(err)s")
|
|
||||||
|
|
||||||
|
|
||||||
class InvalidUUID(Invalid):
|
class InvalidUUID(Invalid):
|
||||||
msg_fmt = _("Expected a uuid but received %(uuid)s")
|
msg_fmt = _("Expected a uuid but received %(uuid)s")
|
||||||
|
|
||||||
@@ -179,7 +194,7 @@ class AuditReferenced(Invalid):
|
|||||||
|
|
||||||
|
|
||||||
class ActionPlanNotFound(ResourceNotFound):
|
class ActionPlanNotFound(ResourceNotFound):
|
||||||
msg_fmt = _("ActionPlan %(action plan)s could not be found")
|
msg_fmt = _("ActionPlan %(action_plan)s could not be found")
|
||||||
|
|
||||||
|
|
||||||
class ActionPlanAlreadyExists(Conflict):
|
class ActionPlanAlreadyExists(Conflict):
|
||||||
@@ -232,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)
|
||||||
|
|||||||
@@ -16,58 +16,100 @@
|
|||||||
|
|
||||||
from oslo_config import cfg
|
from oslo_config import cfg
|
||||||
from oslo_log import log
|
from oslo_log import log
|
||||||
from watcher.common.messaging.events.event_dispatcher import \
|
import oslo_messaging as om
|
||||||
EventDispatcher
|
|
||||||
from watcher.common.messaging.messaging_handler import \
|
|
||||||
MessagingHandler
|
|
||||||
from watcher.common.rpc import RequestContextSerializer
|
|
||||||
|
|
||||||
from watcher.objects.base import WatcherObjectSerializer
|
from watcher.common.messaging.events import event_dispatcher as dispatcher
|
||||||
|
from watcher.common.messaging import messaging_handler
|
||||||
|
from watcher.common import rpc
|
||||||
|
|
||||||
|
from watcher.objects import base
|
||||||
|
|
||||||
LOG = log.getLogger(__name__)
|
LOG = log.getLogger(__name__)
|
||||||
CONF = cfg.CONF
|
CONF = cfg.CONF
|
||||||
|
|
||||||
|
|
||||||
class MessagingCore(EventDispatcher):
|
class MessagingCore(dispatcher.EventDispatcher):
|
||||||
|
|
||||||
API_VERSION = '1.0'
|
API_VERSION = '1.0'
|
||||||
|
|
||||||
def __init__(self, publisher_id, topic_control, topic_status,
|
def __init__(self, publisher_id, conductor_topic, status_topic,
|
||||||
api_version=API_VERSION):
|
api_version=API_VERSION):
|
||||||
super(MessagingCore, self).__init__()
|
super(MessagingCore, self).__init__()
|
||||||
self.serializer = RequestContextSerializer(WatcherObjectSerializer())
|
self.serializer = rpc.RequestContextSerializer(
|
||||||
|
base.WatcherObjectSerializer())
|
||||||
self.publisher_id = publisher_id
|
self.publisher_id = publisher_id
|
||||||
self.api_version = api_version
|
self.api_version = api_version
|
||||||
self.topic_control = self.build_topic(topic_control)
|
|
||||||
self.topic_status = self.build_topic(topic_status)
|
|
||||||
|
|
||||||
def build_topic(self, topic_name):
|
self.conductor_topic = conductor_topic
|
||||||
return MessagingHandler(self.publisher_id, topic_name, self,
|
self.status_topic = status_topic
|
||||||
self.api_version, self.serializer)
|
self.conductor_topic_handler = self.build_topic_handler(
|
||||||
|
conductor_topic)
|
||||||
|
self.status_topic_handler = self.build_topic_handler(status_topic)
|
||||||
|
|
||||||
|
self._conductor_client = None
|
||||||
|
self._status_client = None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def conductor_client(self):
|
||||||
|
if self._conductor_client is None:
|
||||||
|
transport = om.get_transport(CONF)
|
||||||
|
target = om.Target(
|
||||||
|
topic=self.conductor_topic,
|
||||||
|
version=self.API_VERSION,
|
||||||
|
)
|
||||||
|
self._conductor_client = om.RPCClient(
|
||||||
|
transport, target, serializer=self.serializer)
|
||||||
|
return self._conductor_client
|
||||||
|
|
||||||
|
@conductor_client.setter
|
||||||
|
def conductor_client(self, c):
|
||||||
|
self.conductor_client = c
|
||||||
|
|
||||||
|
@property
|
||||||
|
def status_client(self):
|
||||||
|
if self._status_client is None:
|
||||||
|
transport = om.get_transport(CONF)
|
||||||
|
target = om.Target(
|
||||||
|
topic=self.status_topic,
|
||||||
|
version=self.API_VERSION,
|
||||||
|
)
|
||||||
|
self._status_client = om.RPCClient(
|
||||||
|
transport, target, serializer=self.serializer)
|
||||||
|
return self._status_client
|
||||||
|
|
||||||
|
@status_client.setter
|
||||||
|
def status_client(self, c):
|
||||||
|
self.status_client = c
|
||||||
|
|
||||||
|
def build_topic_handler(self, topic_name):
|
||||||
|
return messaging_handler.MessagingHandler(
|
||||||
|
self.publisher_id, topic_name, self,
|
||||||
|
self.api_version, self.serializer)
|
||||||
|
|
||||||
def connect(self):
|
def connect(self):
|
||||||
LOG.debug("Connecting to '%s' (%s)",
|
LOG.debug("Connecting to '%s' (%s)",
|
||||||
CONF.transport_url, CONF.rpc_backend)
|
CONF.transport_url, CONF.rpc_backend)
|
||||||
self.topic_control.start()
|
self.conductor_topic_handler.start()
|
||||||
self.topic_status.start()
|
self.status_topic_handler.start()
|
||||||
|
|
||||||
def disconnect(self):
|
def disconnect(self):
|
||||||
LOG.debug("Disconnecting from '%s' (%s)",
|
LOG.debug("Disconnecting from '%s' (%s)",
|
||||||
CONF.transport_url, CONF.rpc_backend)
|
CONF.transport_url, CONF.rpc_backend)
|
||||||
self.topic_control.stop()
|
self.conductor_topic_handler.stop()
|
||||||
self.topic_status.stop()
|
self.status_topic_handler.stop()
|
||||||
|
|
||||||
def publish_control(self, event, payload):
|
def publish_control(self, event, payload):
|
||||||
return self.topic_control.publish_event(event, payload)
|
return self.conductor_topic_handler.publish_event(event, payload)
|
||||||
|
|
||||||
def publish_status(self, event, payload, request_id=None):
|
def publish_status(self, event, payload, request_id=None):
|
||||||
return self.topic_status.publish_event(event, payload, request_id)
|
return self.status_topic_handler.publish_event(
|
||||||
|
event, payload, request_id)
|
||||||
|
|
||||||
def get_version(self):
|
def get_version(self):
|
||||||
return self.api_version
|
return self.api_version
|
||||||
|
|
||||||
def check_api_version(self, context):
|
def check_api_version(self, context):
|
||||||
api_manager_version = self.client.call(
|
api_manager_version = self.conductor_client.call(
|
||||||
context.to_dict(), 'check_api_version',
|
context.to_dict(), 'check_api_version',
|
||||||
api_version=self.api_version)
|
api_version=self.api_version)
|
||||||
return api_manager_version
|
return api_manager_version
|
||||||
|
|||||||
@@ -38,11 +38,11 @@ CONF = cfg.CONF
|
|||||||
|
|
||||||
class MessagingHandler(threading.Thread):
|
class MessagingHandler(threading.Thread):
|
||||||
|
|
||||||
def __init__(self, publisher_id, topic_watcher, endpoint, version,
|
def __init__(self, publisher_id, topic_name, endpoint, version,
|
||||||
serializer=None):
|
serializer=None):
|
||||||
super(MessagingHandler, self).__init__()
|
super(MessagingHandler, self).__init__()
|
||||||
self.publisher_id = publisher_id
|
self.publisher_id = publisher_id
|
||||||
self.topic_watcher = topic_watcher
|
self.topic_name = topic_name
|
||||||
self.__endpoints = []
|
self.__endpoints = []
|
||||||
self.__serializer = serializer
|
self.__serializer = serializer
|
||||||
self.__version = version
|
self.__version = version
|
||||||
@@ -72,7 +72,7 @@ class MessagingHandler(threading.Thread):
|
|||||||
return om.Notifier(
|
return om.Notifier(
|
||||||
self.__transport,
|
self.__transport,
|
||||||
publisher_id=self.publisher_id,
|
publisher_id=self.publisher_id,
|
||||||
topic=self.topic_watcher,
|
topic=self.topic_name,
|
||||||
serializer=serializer
|
serializer=serializer
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -87,7 +87,7 @@ class MessagingHandler(threading.Thread):
|
|||||||
self.__notifier = self.build_notifier()
|
self.__notifier = self.build_notifier()
|
||||||
if len(self.__endpoints):
|
if len(self.__endpoints):
|
||||||
target = om.Target(
|
target = om.Target(
|
||||||
topic=self.topic_watcher,
|
topic=self.topic_name,
|
||||||
# For compatibility, we can override it with 'host' opt
|
# For compatibility, we can override it with 'host' opt
|
||||||
server=CONF.host or socket.getfqdn(),
|
server=CONF.host or socket.getfqdn(),
|
||||||
version=self.__version,
|
version=self.__version,
|
||||||
@@ -101,7 +101,7 @@ class MessagingHandler(threading.Thread):
|
|||||||
LOG.error(_LE("Messaging configuration error"))
|
LOG.error(_LE("Messaging configuration error"))
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
LOG.debug("configure MessagingHandler for %s" % self.topic_watcher)
|
LOG.debug("configure MessagingHandler for %s" % self.topic_name)
|
||||||
self._configure()
|
self._configure()
|
||||||
if len(self.__endpoints) > 0:
|
if len(self.__endpoints) > 0:
|
||||||
LOG.debug("Starting up server")
|
LOG.debug("Starting up server")
|
||||||
|
|||||||
@@ -18,17 +18,16 @@ import eventlet
|
|||||||
from oslo_log import log
|
from oslo_log import log
|
||||||
import oslo_messaging as messaging
|
import oslo_messaging as messaging
|
||||||
|
|
||||||
from watcher.common.messaging.utils.observable import \
|
from watcher.common.messaging.utils import observable
|
||||||
Observable
|
|
||||||
|
|
||||||
|
|
||||||
eventlet.monkey_patch()
|
eventlet.monkey_patch()
|
||||||
LOG = log.getLogger(__name__)
|
LOG = log.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class NotificationHandler(Observable):
|
class NotificationHandler(observable.Observable):
|
||||||
def __init__(self, publisher_id):
|
def __init__(self, publisher_id):
|
||||||
Observable.__init__(self)
|
super(NotificationHandler, self).__init__()
|
||||||
self.publisher_id = publisher_id
|
self.publisher_id = publisher_id
|
||||||
|
|
||||||
def info(self, ctx, publisher_id, event_type, payload, metadata):
|
def info(self, ctx, publisher_id, event_type, payload, metadata):
|
||||||
|
|||||||
@@ -14,19 +14,14 @@
|
|||||||
# 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 oslo_log import log
|
from watcher.common.messaging.utils import synchronization
|
||||||
|
|
||||||
from watcher.common.messaging.utils.synchronization import \
|
|
||||||
Synchronization
|
|
||||||
|
|
||||||
LOG = log.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
class Observable(Synchronization):
|
class Observable(synchronization.Synchronization):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
super(Observable, self).__init__()
|
||||||
self.__observers = []
|
self.__observers = []
|
||||||
self.changed = 0
|
self.changed = 0
|
||||||
Synchronization.__init__(self)
|
|
||||||
|
|
||||||
def set_changed(self):
|
def set_changed(self):
|
||||||
self.changed = 1
|
self.changed = 1
|
||||||
|
|||||||
@@ -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)
|
||||||
@@ -71,7 +71,7 @@ class BaseConnection(object):
|
|||||||
'name': 'example',
|
'name': 'example',
|
||||||
'description': 'free text description'
|
'description': 'free text description'
|
||||||
'host_aggregate': 'nova aggregate name or id'
|
'host_aggregate': 'nova aggregate name or id'
|
||||||
'goal': 'SERVER_CONSOLiDATION'
|
'goal': 'DUMMY'
|
||||||
'extra': {'automatic': True}
|
'extra': {'automatic': True}
|
||||||
}
|
}
|
||||||
:returns: An audit template.
|
:returns: An audit template.
|
||||||
@@ -122,7 +122,7 @@ class BaseConnection(object):
|
|||||||
:param audit_template_id: The id or uuid of an audit template.
|
:param audit_template_id: The id or uuid of an audit template.
|
||||||
:returns: An audit template.
|
:returns: An audit template.
|
||||||
:raises: AuditTemplateNotFound
|
:raises: AuditTemplateNotFound
|
||||||
:raises: InvalidParameterValue
|
:raises: Invalid
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
@@ -209,7 +209,7 @@ class BaseConnection(object):
|
|||||||
:param audit_id: The id or uuid of an audit.
|
:param audit_id: The id or uuid of an audit.
|
||||||
:returns: An audit.
|
:returns: An audit.
|
||||||
:raises: AuditNotFound
|
:raises: AuditNotFound
|
||||||
:raises: InvalidParameterValue
|
:raises: Invalid
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def soft_delete_audit(self, audit_id):
|
def soft_delete_audit(self, audit_id):
|
||||||
@@ -299,6 +299,7 @@ class BaseConnection(object):
|
|||||||
:returns: A action.
|
:returns: A action.
|
||||||
:raises: ActionNotFound
|
:raises: ActionNotFound
|
||||||
:raises: ActionReferenced
|
:raises: ActionReferenced
|
||||||
|
:raises: Invalid
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
@@ -371,4 +372,5 @@ class BaseConnection(object):
|
|||||||
:returns: An action plan.
|
:returns: An action plan.
|
||||||
:raises: ActionPlanNotFound
|
:raises: ActionPlanNotFound
|
||||||
:raises: ActionPlanReferenced
|
:raises: ActionPlanReferenced
|
||||||
|
:raises: Invalid
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -274,8 +274,9 @@ class Connection(api.BaseConnection):
|
|||||||
|
|
||||||
def update_audit_template(self, audit_template_id, values):
|
def update_audit_template(self, audit_template_id, values):
|
||||||
if 'uuid' in values:
|
if 'uuid' in values:
|
||||||
msg = _("Cannot overwrite UUID for an existing AuditTemplate.")
|
raise exception.Invalid(
|
||||||
raise exception.InvalidParameterValue(err=msg)
|
message=_("Cannot overwrite UUID for an existing "
|
||||||
|
"Audit Template."))
|
||||||
|
|
||||||
return self._do_update_audit_template(audit_template_id, values)
|
return self._do_update_audit_template(audit_template_id, values)
|
||||||
|
|
||||||
@@ -383,8 +384,9 @@ class Connection(api.BaseConnection):
|
|||||||
|
|
||||||
def update_audit(self, audit_id, values):
|
def update_audit(self, audit_id, values):
|
||||||
if 'uuid' in values:
|
if 'uuid' in values:
|
||||||
msg = _("Cannot overwrite UUID for an existing Audit.")
|
raise exception.Invalid(
|
||||||
raise exception.InvalidParameterValue(err=msg)
|
message=_("Cannot overwrite UUID for an existing "
|
||||||
|
"Audit."))
|
||||||
|
|
||||||
return self._do_update_audit(audit_id, values)
|
return self._do_update_audit(audit_id, values)
|
||||||
|
|
||||||
@@ -474,8 +476,9 @@ class Connection(api.BaseConnection):
|
|||||||
def update_action(self, action_id, values):
|
def update_action(self, action_id, values):
|
||||||
# NOTE(dtantsur): this can lead to very strange errors
|
# NOTE(dtantsur): this can lead to very strange errors
|
||||||
if 'uuid' in values:
|
if 'uuid' in values:
|
||||||
msg = _("Cannot overwrite UUID for an existing Action.")
|
raise exception.Invalid(
|
||||||
raise exception.InvalidParameterValue(err=msg)
|
message=_("Cannot overwrite UUID for an existing "
|
||||||
|
"Action."))
|
||||||
|
|
||||||
return self._do_update_action(action_id, values)
|
return self._do_update_action(action_id, values)
|
||||||
|
|
||||||
@@ -583,8 +586,9 @@ class Connection(api.BaseConnection):
|
|||||||
|
|
||||||
def update_action_plan(self, action_plan_id, values):
|
def update_action_plan(self, action_plan_id, values):
|
||||||
if 'uuid' in values:
|
if 'uuid' in values:
|
||||||
msg = _("Cannot overwrite UUID for an existing Audit.")
|
raise exception.Invalid(
|
||||||
raise exception.InvalidParameterValue(err=msg)
|
message=_("Cannot overwrite UUID for an existing "
|
||||||
|
"Action Plan."))
|
||||||
|
|
||||||
return self._do_update_action_plan(action_plan_id, values)
|
return self._do_update_action_plan(action_plan_id, values)
|
||||||
|
|
||||||
|
|||||||
@@ -54,8 +54,8 @@ class DefaultAuditHandler(base.BaseAuditHandler):
|
|||||||
event.data = {}
|
event.data = {}
|
||||||
payload = {'audit_uuid': audit_uuid,
|
payload = {'audit_uuid': audit_uuid,
|
||||||
'audit_status': status}
|
'audit_status': status}
|
||||||
self.messaging.topic_status.publish_event(event.type.name,
|
self.messaging.status_topic_handler.publish_event(
|
||||||
payload)
|
event.type.name, payload)
|
||||||
|
|
||||||
def update_audit_state(self, request_context, audit_uuid, state):
|
def update_audit_state(self, request_context, audit_uuid, state):
|
||||||
LOG.debug("Update audit state: %s", state)
|
LOG.debug("Update audit state: %s", state)
|
||||||
|
|||||||
@@ -40,20 +40,20 @@ See :doc:`../architecture` for more details on this component.
|
|||||||
from oslo_config import cfg
|
from oslo_config import cfg
|
||||||
from oslo_log import log
|
from oslo_log import log
|
||||||
|
|
||||||
from watcher.common.messaging.messaging_core import MessagingCore
|
from watcher.common.messaging import messaging_core
|
||||||
from watcher.decision_engine.messaging.audit_endpoint import AuditEndpoint
|
from watcher.decision_engine.messaging import audit_endpoint
|
||||||
|
|
||||||
|
|
||||||
LOG = log.getLogger(__name__)
|
LOG = log.getLogger(__name__)
|
||||||
CONF = cfg.CONF
|
CONF = cfg.CONF
|
||||||
|
|
||||||
WATCHER_DECISION_ENGINE_OPTS = [
|
WATCHER_DECISION_ENGINE_OPTS = [
|
||||||
cfg.StrOpt('topic_control',
|
cfg.StrOpt('conductor_topic',
|
||||||
default='watcher.decision.control',
|
default='watcher.decision.control',
|
||||||
help='The topic name used for'
|
help='The topic name used for'
|
||||||
'control events, this topic '
|
'control events, this topic '
|
||||||
'used for rpc call '),
|
'used for rpc call '),
|
||||||
cfg.StrOpt('topic_status',
|
cfg.StrOpt('status_topic',
|
||||||
default='watcher.decision.status',
|
default='watcher.decision.status',
|
||||||
help='The topic name used for '
|
help='The topic name used for '
|
||||||
'status events, this topic '
|
'status events, this topic '
|
||||||
@@ -78,18 +78,18 @@ CONF.register_group(decision_engine_opt_group)
|
|||||||
CONF.register_opts(WATCHER_DECISION_ENGINE_OPTS, decision_engine_opt_group)
|
CONF.register_opts(WATCHER_DECISION_ENGINE_OPTS, decision_engine_opt_group)
|
||||||
|
|
||||||
|
|
||||||
class DecisionEngineManager(MessagingCore):
|
class DecisionEngineManager(messaging_core.MessagingCore):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super(DecisionEngineManager, self).__init__(
|
super(DecisionEngineManager, self).__init__(
|
||||||
CONF.watcher_decision_engine.publisher_id,
|
CONF.watcher_decision_engine.publisher_id,
|
||||||
CONF.watcher_decision_engine.topic_control,
|
CONF.watcher_decision_engine.conductor_topic,
|
||||||
CONF.watcher_decision_engine.topic_status,
|
CONF.watcher_decision_engine.status_topic,
|
||||||
api_version=self.API_VERSION)
|
api_version=self.API_VERSION)
|
||||||
endpoint = AuditEndpoint(self,
|
endpoint = audit_endpoint.AuditEndpoint(
|
||||||
max_workers=CONF.watcher_decision_engine.
|
self,
|
||||||
max_workers)
|
max_workers=CONF.watcher_decision_engine.max_workers)
|
||||||
self.topic_control.add_endpoint(endpoint)
|
self.conductor_topic_handler.add_endpoint(endpoint)
|
||||||
|
|
||||||
def join(self):
|
def join(self):
|
||||||
self.topic_control.join()
|
self.conductor_topic_handler.join()
|
||||||
self.topic_status.join()
|
self.status_topic_handler.join()
|
||||||
|
|||||||
@@ -19,10 +19,10 @@
|
|||||||
|
|
||||||
from oslo_config import cfg
|
from oslo_config import cfg
|
||||||
from oslo_log import log
|
from oslo_log import log
|
||||||
import oslo_messaging as om
|
|
||||||
|
|
||||||
from watcher.common import exception
|
from watcher.common import exception
|
||||||
from watcher.common.messaging.messaging_core import MessagingCore
|
from watcher.common.messaging.messaging_core import MessagingCore
|
||||||
|
from watcher.common.messaging.notification_handler import NotificationHandler
|
||||||
from watcher.common import utils
|
from watcher.common import utils
|
||||||
from watcher.decision_engine.manager import decision_engine_opt_group
|
from watcher.decision_engine.manager import decision_engine_opt_group
|
||||||
from watcher.decision_engine.manager import WATCHER_DECISION_ENGINE_OPTS
|
from watcher.decision_engine.manager import WATCHER_DECISION_ENGINE_OPTS
|
||||||
@@ -40,22 +40,16 @@ class DecisionEngineAPI(MessagingCore):
|
|||||||
def __init__(self):
|
def __init__(self):
|
||||||
super(DecisionEngineAPI, self).__init__(
|
super(DecisionEngineAPI, self).__init__(
|
||||||
CONF.watcher_decision_engine.publisher_id,
|
CONF.watcher_decision_engine.publisher_id,
|
||||||
CONF.watcher_decision_engine.topic_control,
|
CONF.watcher_decision_engine.conductor_topic,
|
||||||
CONF.watcher_decision_engine.topic_status,
|
CONF.watcher_decision_engine.status_topic,
|
||||||
api_version=self.API_VERSION,
|
api_version=self.API_VERSION,
|
||||||
)
|
)
|
||||||
|
self.handler = NotificationHandler(self.publisher_id)
|
||||||
transport = om.get_transport(CONF)
|
self.status_topic_handler.add_endpoint(self.handler)
|
||||||
target = om.Target(
|
|
||||||
topic=CONF.watcher_decision_engine.topic_control,
|
|
||||||
version=self.API_VERSION,
|
|
||||||
)
|
|
||||||
self.client = om.RPCClient(transport, target,
|
|
||||||
serializer=self.serializer)
|
|
||||||
|
|
||||||
def trigger_audit(self, context, audit_uuid=None):
|
def trigger_audit(self, context, audit_uuid=None):
|
||||||
if not utils.is_uuid_like(audit_uuid):
|
if not utils.is_uuid_like(audit_uuid):
|
||||||
raise exception.InvalidUuidOrName(name=audit_uuid)
|
raise exception.InvalidUuidOrName(name=audit_uuid)
|
||||||
|
|
||||||
return self.client.call(
|
return self.conductor_client.call(
|
||||||
context.to_dict(), 'trigger_audit', audit_uuid=audit_uuid)
|
context.to_dict(), 'trigger_audit', audit_uuid=audit_uuid)
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: python-watcher 0.21.1.dev32\n"
|
"Project-Id-Version: python-watcher 0.21.1.dev32\n"
|
||||||
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
|
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
|
||||||
"POT-Creation-Date: 2016-01-19 17:54+0100\n"
|
"POT-Creation-Date: 2016-01-26 11:26+0100\n"
|
||||||
"PO-Revision-Date: 2015-12-11 15:42+0100\n"
|
"PO-Revision-Date: 2015-12-11 15:42+0100\n"
|
||||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||||
"Language: fr\n"
|
"Language: fr\n"
|
||||||
@@ -24,25 +24,29 @@ msgstr ""
|
|||||||
msgid "Invalid state: %(state)s"
|
msgid "Invalid state: %(state)s"
|
||||||
msgstr "État invalide : %(state)s"
|
msgstr "État invalide : %(state)s"
|
||||||
|
|
||||||
#: watcher/api/controllers/v1/action_plan.py:418
|
#: watcher/api/controllers/v1/action_plan.py:420
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "State transition not allowed: (%(initial_state)s -> %(new_state)s)"
|
msgid "State transition not allowed: (%(initial_state)s -> %(new_state)s)"
|
||||||
msgstr "Transition d'état non autorisée : (%(initial_state)s -> %(new_state)s)"
|
msgstr "Transition d'état non autorisée : (%(initial_state)s -> %(new_state)s)"
|
||||||
|
|
||||||
|
#: watcher/api/controllers/v1/audit.py:359
|
||||||
|
msgid "The audit template UUID or name specified is invalid"
|
||||||
|
msgstr "Le nom ou UUID de l'audit template est invalide"
|
||||||
|
|
||||||
#: watcher/api/controllers/v1/types.py:148
|
#: watcher/api/controllers/v1/types.py:148
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "%s is not JSON serializable"
|
msgid "%s is not JSON serializable"
|
||||||
msgstr ""
|
msgstr "%s n'est pas sérialisable en JSON"
|
||||||
|
|
||||||
#: watcher/api/controllers/v1/types.py:184
|
#: watcher/api/controllers/v1/types.py:184
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Wrong type. Expected '%(type)s', got '%(value)s'"
|
msgid "Wrong type. Expected '%(type)s', got '%(value)s'"
|
||||||
msgstr ""
|
msgstr "Type incorrect. '%(type)s' attendu, '%(value)s' obtenu"
|
||||||
|
|
||||||
#: watcher/api/controllers/v1/types.py:223
|
#: watcher/api/controllers/v1/types.py:223
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "'%s' is an internal attribute and can not be updated"
|
msgid "'%s' is an internal attribute and can not be updated"
|
||||||
msgstr ""
|
msgstr "'%s' wat un attribut interne et ne peut pas être modifié"
|
||||||
|
|
||||||
#: watcher/api/controllers/v1/types.py:227
|
#: watcher/api/controllers/v1/types.py:227
|
||||||
#, python-format
|
#, python-format
|
||||||
@@ -60,7 +64,7 @@ msgstr "Limit doit être positif"
|
|||||||
#: watcher/api/controllers/v1/utils.py:47
|
#: watcher/api/controllers/v1/utils.py:47
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Invalid sort direction: %s. Acceptable values are 'asc' or 'desc'"
|
msgid "Invalid sort direction: %s. Acceptable values are 'asc' or 'desc'"
|
||||||
msgstr ""
|
msgstr "Ordre de tri invalide : %s. Les valeurs acceptées sont 'asc' or 'desc'"
|
||||||
|
|
||||||
#: watcher/api/controllers/v1/utils.py:57
|
#: watcher/api/controllers/v1/utils.py:57
|
||||||
#, python-format
|
#, python-format
|
||||||
@@ -69,14 +73,14 @@ msgstr ""
|
|||||||
|
|
||||||
#: watcher/api/middleware/auth_token.py:45
|
#: watcher/api/middleware/auth_token.py:45
|
||||||
msgid "Cannot compile public API routes"
|
msgid "Cannot compile public API routes"
|
||||||
msgstr ""
|
msgstr "Ne peut pas compiler les chemins d'API publique"
|
||||||
|
|
||||||
#: watcher/api/middleware/parsable_error.py:52
|
#: watcher/api/middleware/parsable_error.py:52
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "ErrorDocumentMiddleware received an invalid status %s"
|
msgid "ErrorDocumentMiddleware received an invalid status %s"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: watcher/api/middleware/parsable_error.py:80
|
#: watcher/api/middleware/parsable_error.py:79
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Error parsing HTTP response: %s"
|
msgid "Error parsing HTTP response: %s"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -85,17 +89,17 @@ msgstr ""
|
|||||||
msgid "The target state is not defined"
|
msgid "The target state is not defined"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: watcher/applier/workflow_engine/default.py:69
|
#: watcher/applier/workflow_engine/default.py:126
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "The WorkFlow Engine has failed to execute the action %s"
|
msgid "The WorkFlow Engine has failed to execute the action %s"
|
||||||
msgstr "Le moteur de workflow a echoué lors de l'éxécution de l'action %s"
|
msgstr "Le moteur de workflow a echoué lors de l'éxécution de l'action %s"
|
||||||
|
|
||||||
#: watcher/applier/workflow_engine/default.py:77
|
#: watcher/applier/workflow_engine/default.py:144
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Revert action %s"
|
msgid "Revert action %s"
|
||||||
msgstr "Annulation de l'action %s"
|
msgstr "Annulation de l'action %s"
|
||||||
|
|
||||||
#: watcher/applier/workflow_engine/default.py:83
|
#: watcher/applier/workflow_engine/default.py:150
|
||||||
msgid "Oops! We need disaster recover plan"
|
msgid "Oops! We need disaster recover plan"
|
||||||
msgstr "Oops! Nous avons besoin d'un plan de reprise d'activité"
|
msgstr "Oops! Nous avons besoin d'un plan de reprise d'activité"
|
||||||
|
|
||||||
@@ -115,185 +119,178 @@ msgstr "Sert sur 0.0.0.0:%(port)s, accessible à http://127.0.0.1:%(port)s"
|
|||||||
msgid "serving on http://%(host)s:%(port)s"
|
msgid "serving on http://%(host)s:%(port)s"
|
||||||
msgstr "Sert sur http://%(host)s:%(port)s"
|
msgstr "Sert sur http://%(host)s:%(port)s"
|
||||||
|
|
||||||
#: watcher/common/exception.py:56
|
#: watcher/common/exception.py:51
|
||||||
msgid "An unknown exception occurred"
|
msgid "An unknown exception occurred"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: watcher/common/exception.py:77
|
#: watcher/common/exception.py:71
|
||||||
msgid "Exception in string format operation"
|
msgid "Exception in string format operation"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: watcher/common/exception.py:107
|
#: watcher/common/exception.py:101
|
||||||
msgid "Not authorized"
|
msgid "Not authorized"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: watcher/common/exception.py:112
|
#: watcher/common/exception.py:106
|
||||||
msgid "Operation not permitted"
|
msgid "Operation not permitted"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: watcher/common/exception.py:116
|
#: watcher/common/exception.py:110
|
||||||
msgid "Unacceptable parameters"
|
msgid "Unacceptable parameters"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: watcher/common/exception.py:121
|
#: watcher/common/exception.py:115
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "The %(name)s %(id)s could not be found"
|
msgid "The %(name)s %(id)s could not be found"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: watcher/common/exception.py:125
|
#: watcher/common/exception.py:119
|
||||||
#, fuzzy
|
#, fuzzy
|
||||||
msgid "Conflict"
|
msgid "Conflict"
|
||||||
msgstr "Conflit."
|
msgstr "Conflit"
|
||||||
|
|
||||||
#: watcher/common/exception.py:130
|
#: watcher/common/exception.py:124
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "The %(name)s resource %(id)s could not be found"
|
msgid "The %(name)s resource %(id)s could not be found"
|
||||||
msgstr ""
|
msgstr "La ressource %(name)s / %(id)s est introuvable"
|
||||||
|
|
||||||
#: watcher/common/exception.py:135
|
#: watcher/common/exception.py:129
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Expected an uuid or int but received %(identity)s"
|
msgid "Expected an uuid or int but received %(identity)s"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: watcher/common/exception.py:139
|
#: watcher/common/exception.py:133
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Goal %(goal)s is not defined in Watcher configuration file"
|
msgid "Goal %(goal)s is not defined in Watcher configuration file"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: watcher/common/exception.py:145
|
#: watcher/common/exception.py:139
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "%(err)s"
|
msgid "%(err)s"
|
||||||
msgstr ""
|
msgstr "%(err)s"
|
||||||
|
|
||||||
#: watcher/common/exception.py:149
|
#: watcher/common/exception.py:143
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Expected a uuid but received %(uuid)s"
|
msgid "Expected a uuid but received %(uuid)s"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: watcher/common/exception.py:153
|
#: watcher/common/exception.py:147
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Expected a logical name but received %(name)s"
|
msgid "Expected a logical name but received %(name)s"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: watcher/common/exception.py:157
|
#: watcher/common/exception.py:151
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Expected a logical name or uuid but received %(name)s"
|
msgid "Expected a logical name or uuid but received %(name)s"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: watcher/common/exception.py:161
|
#: watcher/common/exception.py:155
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "AuditTemplate %(audit_template)s could not be found"
|
msgid "AuditTemplate %(audit_template)s could not be found"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: watcher/common/exception.py:165
|
#: watcher/common/exception.py:159
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "An audit_template with UUID %(uuid)s or name %(name)s already exists"
|
msgid "An audit_template with UUID %(uuid)s or name %(name)s already exists"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: watcher/common/exception.py:170
|
#: watcher/common/exception.py:164
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "AuditTemplate %(audit_template)s is referenced by one or multiple audit"
|
msgid "AuditTemplate %(audit_template)s is referenced by one or multiple audit"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: watcher/common/exception.py:175
|
#: watcher/common/exception.py:169
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Audit %(audit)s could not be found"
|
msgid "Audit %(audit)s could not be found"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: watcher/common/exception.py:179
|
#: watcher/common/exception.py:173
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "An audit with UUID %(uuid)s already exists"
|
msgid "An audit with UUID %(uuid)s already exists"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: watcher/common/exception.py:183
|
#: watcher/common/exception.py:177
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Audit %(audit)s is referenced by one or multiple action plans"
|
msgid "Audit %(audit)s is referenced by one or multiple action plans"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: watcher/common/exception.py:188
|
#: watcher/common/exception.py:182
|
||||||
msgid "ActionPlan %(action plan)s could not be found"
|
#, python-format
|
||||||
|
msgid "ActionPlan %(action_plan)s could not be found"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: watcher/common/exception.py:192
|
#: watcher/common/exception.py:186
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "An action plan with UUID %(uuid)s already exists"
|
msgid "An action plan with UUID %(uuid)s already exists"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: watcher/common/exception.py:196
|
#: watcher/common/exception.py:190
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Action Plan %(action_plan)s is referenced by one or multiple actions"
|
msgid "Action Plan %(action_plan)s is referenced by one or multiple actions"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: watcher/common/exception.py:201
|
#: watcher/common/exception.py:195
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Action %(action)s could not be found"
|
msgid "Action %(action)s could not be found"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: watcher/common/exception.py:205
|
#: watcher/common/exception.py:199
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "An action with UUID %(uuid)s already exists"
|
msgid "An action with UUID %(uuid)s already exists"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: watcher/common/exception.py:209
|
#: watcher/common/exception.py:203
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Action plan %(action_plan)s is referenced by one or multiple goals"
|
msgid "Action plan %(action_plan)s is referenced by one or multiple goals"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: watcher/common/exception.py:214
|
#: watcher/common/exception.py:208
|
||||||
msgid "Filtering actions on both audit and action-plan is prohibited"
|
msgid "Filtering actions on both audit and action-plan is prohibited"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: watcher/common/exception.py:223
|
#: watcher/common/exception.py:217
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Couldn't apply patch '%(patch)s'. Reason: %(reason)s"
|
msgid "Couldn't apply patch '%(patch)s'. Reason: %(reason)s"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: watcher/common/exception.py:233
|
#: watcher/common/exception.py:224
|
||||||
msgid "Description must be an instance of str"
|
msgid "Illegal argument"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: watcher/common/exception.py:243
|
#: watcher/common/exception.py:228
|
||||||
msgid "An exception occurred without a description"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: watcher/common/exception.py:251
|
|
||||||
msgid "Description cannot be empty"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: watcher/common/exception.py:260
|
|
||||||
msgid "No such metric"
|
msgid "No such metric"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: watcher/common/exception.py:269
|
#: watcher/common/exception.py:232
|
||||||
msgid "No rows were returned"
|
msgid "No rows were returned"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: watcher/common/exception.py:277
|
#: watcher/common/exception.py:236
|
||||||
msgid "'Keystone API endpoint is missing''"
|
msgid "'Keystone API endpoint is missing''"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: watcher/common/exception.py:281
|
#: watcher/common/exception.py:240
|
||||||
msgid "The list of hypervisor(s) in the cluster is empty"
|
msgid "The list of hypervisor(s) in the cluster is empty"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: watcher/common/exception.py:285
|
#: watcher/common/exception.py:244
|
||||||
msgid "The metrics resource collector is not defined"
|
msgid "The metrics resource collector is not defined"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: watcher/common/exception.py:289
|
#: watcher/common/exception.py:248
|
||||||
msgid "the cluster state is not defined"
|
msgid "the cluster state is not defined"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: watcher/common/exception.py:295
|
#: watcher/common/exception.py:254
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "The instance '%(name)s' is not found"
|
msgid "The instance '%(name)s' is not found"
|
||||||
msgstr "L'instance '%(name)s' n'a pas été trouvée"
|
msgstr "L'instance '%(name)s' n'a pas été trouvée"
|
||||||
|
|
||||||
#: watcher/common/exception.py:299
|
#: watcher/common/exception.py:258
|
||||||
msgid "The hypervisor is not found"
|
msgid "The hypervisor is not found"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: watcher/common/exception.py:303
|
#: watcher/common/exception.py:262
|
||||||
#, fuzzy, python-format
|
#, fuzzy, python-format
|
||||||
msgid "Error loading plugin '%(name)s'"
|
msgid "Error loading plugin '%(name)s'"
|
||||||
msgstr "Erreur lors du chargement du module '%(name)s'"
|
msgstr "Erreur lors du chargement du module '%(name)s'"
|
||||||
@@ -332,7 +329,7 @@ msgstr ""
|
|||||||
#: watcher/common/utils.py:53
|
#: watcher/common/utils.py:53
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid ""
|
msgid ""
|
||||||
"Failed to remove trailing character. Returning original object. Supplied "
|
"Failed to remove trailing character. Returning original object.Supplied "
|
||||||
"object is not a string: %s,"
|
"object is not a string: %s,"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -373,7 +370,7 @@ msgstr ""
|
|||||||
msgid "'obj' argument type is not valid"
|
msgid "'obj' argument type is not valid"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: watcher/decision_engine/planner/default.py:75
|
#: watcher/decision_engine/planner/default.py:76
|
||||||
msgid "The action plan is empty"
|
msgid "The action plan is empty"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -388,11 +385,11 @@ msgstr ""
|
|||||||
msgid "No values returned by %(resource_id)s for %(metric_name)s"
|
msgid "No values returned by %(resource_id)s for %(metric_name)s"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: watcher/decision_engine/strategy/strategies/basic_consolidation.py:349
|
#: watcher/decision_engine/strategy/strategies/basic_consolidation.py:424
|
||||||
msgid "Initializing Sercon Consolidation"
|
msgid "Initializing Sercon Consolidation"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: watcher/decision_engine/strategy/strategies/basic_consolidation.py:406
|
#: watcher/decision_engine/strategy/strategies/basic_consolidation.py:468
|
||||||
msgid "The workloads of the compute nodes of the cluster is zero"
|
msgid "The workloads of the compute nodes of the cluster is zero"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -573,3 +570,21 @@ msgstr ""
|
|||||||
#~ msgid "The WorkFlow Engine has failedto execute the action %s"
|
#~ msgid "The WorkFlow Engine has failedto execute the action %s"
|
||||||
#~ msgstr ""
|
#~ msgstr ""
|
||||||
|
|
||||||
|
#~ msgid "ActionPlan %(action plan)s could not be found"
|
||||||
|
#~ msgstr ""
|
||||||
|
|
||||||
|
#~ msgid "Description must be an instance of str"
|
||||||
|
#~ msgstr ""
|
||||||
|
|
||||||
|
#~ msgid "An exception occurred without a description"
|
||||||
|
#~ msgstr ""
|
||||||
|
|
||||||
|
#~ msgid "Description cannot be empty"
|
||||||
|
#~ msgstr ""
|
||||||
|
|
||||||
|
#~ msgid ""
|
||||||
|
#~ "Failed to remove trailing character. "
|
||||||
|
#~ "Returning original object. Supplied object "
|
||||||
|
#~ "is not a string: %s,"
|
||||||
|
#~ msgstr ""
|
||||||
|
|
||||||
|
|||||||
@@ -7,9 +7,9 @@
|
|||||||
#, fuzzy
|
#, fuzzy
|
||||||
msgid ""
|
msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: python-watcher 0.22.1.dev49\n"
|
"Project-Id-Version: python-watcher 0.23.2.dev1\n"
|
||||||
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
|
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
|
||||||
"POT-Creation-Date: 2016-01-22 10:43+0100\n"
|
"POT-Creation-Date: 2016-01-26 11:26+0100\n"
|
||||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||||
@@ -23,7 +23,7 @@ msgstr ""
|
|||||||
msgid "Invalid state: %(state)s"
|
msgid "Invalid state: %(state)s"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: watcher/api/controllers/v1/action_plan.py:416
|
#: watcher/api/controllers/v1/action_plan.py:420
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "State transition not allowed: (%(initial_state)s -> %(new_state)s)"
|
msgid "State transition not allowed: (%(initial_state)s -> %(new_state)s)"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -88,6 +88,10 @@ msgstr ""
|
|||||||
msgid "The target state is not defined"
|
msgid "The target state is not defined"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: watcher/applier/actions/migration.py:60
|
||||||
|
msgid "Migration of type %(migration_type)s is not supported."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: watcher/applier/workflow_engine/default.py:126
|
#: watcher/applier/workflow_engine/default.py:126
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "The WorkFlow Engine has failed to execute the action %s"
|
msgid "The WorkFlow Engine has failed to execute the action %s"
|
||||||
@@ -162,11 +166,6 @@ msgstr ""
|
|||||||
msgid "Goal %(goal)s is not defined in Watcher configuration file"
|
msgid "Goal %(goal)s is not defined in Watcher configuration file"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: watcher/common/exception.py:139
|
|
||||||
#, python-format
|
|
||||||
msgid "%(err)s"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: watcher/common/exception.py:143
|
#: watcher/common/exception.py:143
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Expected a uuid but received %(uuid)s"
|
msgid "Expected a uuid but received %(uuid)s"
|
||||||
@@ -213,7 +212,8 @@ msgid "Audit %(audit)s is referenced by one or multiple action plans"
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: watcher/common/exception.py:182
|
#: watcher/common/exception.py:182
|
||||||
msgid "ActionPlan %(action plan)s could not be found"
|
#, python-format
|
||||||
|
msgid "ActionPlan %(action_plan)s could not be found"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: watcher/common/exception.py:186
|
#: watcher/common/exception.py:186
|
||||||
|
|||||||
@@ -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)
|
|
||||||
|
|||||||
@@ -117,7 +117,7 @@ def dt_serializer(name):
|
|||||||
return serializer
|
return serializer
|
||||||
|
|
||||||
|
|
||||||
def dt_deserializer(instance, val):
|
def dt_deserializer(val):
|
||||||
"""A deserializer method for datetime attributes."""
|
"""A deserializer method for datetime attributes."""
|
||||||
if val is None:
|
if val is None:
|
||||||
return None
|
return None
|
||||||
|
|||||||
@@ -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()))
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ class TestActionPlanObject(base.TestCase):
|
|||||||
act_plan_dict = api_utils.action_plan_post_data()
|
act_plan_dict = api_utils.action_plan_post_data()
|
||||||
del act_plan_dict['state']
|
del act_plan_dict['state']
|
||||||
del act_plan_dict['audit_id']
|
del act_plan_dict['audit_id']
|
||||||
|
del act_plan_dict['first_action_id']
|
||||||
act_plan = api_action_plan.ActionPlan(**act_plan_dict)
|
act_plan = api_action_plan.ActionPlan(**act_plan_dict)
|
||||||
self.assertEqual(wtypes.Unset, act_plan.state)
|
self.assertEqual(wtypes.Unset, act_plan.state)
|
||||||
|
|
||||||
@@ -69,12 +70,20 @@ class TestListActionPlan(api_base.FunctionalTest):
|
|||||||
response = self.get_json('/action_plans')
|
response = self.get_json('/action_plans')
|
||||||
self.assertEqual([], response['action_plans'])
|
self.assertEqual([], response['action_plans'])
|
||||||
|
|
||||||
def test_get_one(self):
|
def test_get_one_ok(self):
|
||||||
action_plan = obj_utils.create_action_plan_without_audit(self.context)
|
action_plan = obj_utils.create_action_plan_without_audit(self.context)
|
||||||
response = self.get_json('/action_plans/%s' % action_plan['uuid'])
|
response = self.get_json('/action_plans/%s' % action_plan['uuid'])
|
||||||
self.assertEqual(action_plan.uuid, response['uuid'])
|
self.assertEqual(action_plan.uuid, response['uuid'])
|
||||||
self._assert_action_plans_fields(response)
|
self._assert_action_plans_fields(response)
|
||||||
|
|
||||||
|
def test_get_one_with_first_action(self):
|
||||||
|
action_plan = obj_utils.create_test_action_plan(self.context)
|
||||||
|
action = obj_utils.create_test_action(self.context, id=1)
|
||||||
|
response = self.get_json('/action_plans/%s' % action_plan['uuid'])
|
||||||
|
self.assertEqual(action_plan.uuid, response['uuid'])
|
||||||
|
self.assertEqual(action.uuid, response['first_action_uuid'])
|
||||||
|
self._assert_action_plans_fields(response)
|
||||||
|
|
||||||
def test_get_one_soft_deleted(self):
|
def test_get_one_soft_deleted(self):
|
||||||
action_plan = obj_utils.create_action_plan_without_audit(self.context)
|
action_plan = obj_utils.create_action_plan_without_audit(self.context)
|
||||||
action_plan.soft_delete()
|
action_plan.soft_delete()
|
||||||
|
|||||||
@@ -15,9 +15,8 @@
|
|||||||
# implied.
|
# implied.
|
||||||
# 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 mock import call
|
import mock
|
||||||
from mock import MagicMock
|
|
||||||
|
|
||||||
from watcher.applier.action_plan.default import DefaultActionPlanHandler
|
from watcher.applier.action_plan.default import DefaultActionPlanHandler
|
||||||
from watcher.applier.messaging.event_types import EventTypes
|
from watcher.applier.messaging.event_types import EventTypes
|
||||||
@@ -34,7 +33,7 @@ class TestDefaultActionPlanHandler(DbTestCase):
|
|||||||
self.context)
|
self.context)
|
||||||
|
|
||||||
def test_launch_action_plan(self):
|
def test_launch_action_plan(self):
|
||||||
command = DefaultActionPlanHandler(self.context, MagicMock(),
|
command = DefaultActionPlanHandler(self.context, mock.MagicMock(),
|
||||||
self.action_plan.uuid)
|
self.action_plan.uuid)
|
||||||
command.execute()
|
command.execute()
|
||||||
action_plan = ActionPlan.get_by_uuid(self.context,
|
action_plan = ActionPlan.get_by_uuid(self.context,
|
||||||
@@ -42,18 +41,19 @@ class TestDefaultActionPlanHandler(DbTestCase):
|
|||||||
self.assertEqual(ap_objects.State.SUCCEEDED, action_plan.state)
|
self.assertEqual(ap_objects.State.SUCCEEDED, action_plan.state)
|
||||||
|
|
||||||
def test_trigger_audit_send_notification(self):
|
def test_trigger_audit_send_notification(self):
|
||||||
messaging = MagicMock()
|
messaging = mock.MagicMock()
|
||||||
command = DefaultActionPlanHandler(self.context, messaging,
|
command = DefaultActionPlanHandler(self.context, messaging,
|
||||||
self.action_plan.uuid)
|
self.action_plan.uuid)
|
||||||
command.execute()
|
command.execute()
|
||||||
|
|
||||||
call_on_going = call(EventTypes.LAUNCH_ACTION_PLAN.name, {
|
call_on_going = mock.call(EventTypes.LAUNCH_ACTION_PLAN.name, {
|
||||||
'action_plan_state': ap_objects.State.ONGOING,
|
'action_plan_state': ap_objects.State.ONGOING,
|
||||||
'action_plan__uuid': self.action_plan.uuid})
|
'action_plan__uuid': self.action_plan.uuid})
|
||||||
call_succeeded = call(EventTypes.LAUNCH_ACTION_PLAN.name, {
|
call_succeeded = mock.call(EventTypes.LAUNCH_ACTION_PLAN.name, {
|
||||||
'action_plan_state': ap_objects.State.SUCCEEDED,
|
'action_plan_state': ap_objects.State.SUCCEEDED,
|
||||||
'action_plan__uuid': self.action_plan.uuid})
|
'action_plan__uuid': self.action_plan.uuid})
|
||||||
|
|
||||||
calls = [call_on_going, call_succeeded]
|
calls = [call_on_going, call_succeeded]
|
||||||
messaging.topic_status.publish_event.assert_has_calls(calls)
|
messaging.status_topic_handler.publish_event.assert_has_calls(calls)
|
||||||
self.assertEqual(2, messaging.topic_status.publish_event.call_count)
|
self.assertEqual(
|
||||||
|
2, messaging.status_topic_handler.publish_event.call_count)
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -15,59 +15,79 @@
|
|||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
|
|
||||||
from mock import patch
|
import mock
|
||||||
|
|
||||||
from watcher.common.messaging.messaging_core import MessagingCore
|
from watcher.common.messaging import messaging_core
|
||||||
from watcher.common.messaging.messaging_handler import MessagingHandler
|
from watcher.common.messaging import messaging_handler
|
||||||
from watcher.common.rpc import RequestContextSerializer
|
from watcher.common import rpc
|
||||||
from watcher.tests.base import TestCase
|
from watcher.tests import base
|
||||||
|
|
||||||
|
|
||||||
class TestMessagingCore(TestCase):
|
class TestMessagingCore(base.TestCase):
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super(TestMessagingCore, self).setUp()
|
super(TestMessagingCore, self).setUp()
|
||||||
|
|
||||||
def test_build_topic(self):
|
@mock.patch.object(messaging_handler, "MessagingHandler")
|
||||||
|
def test_connect(self, m_handler):
|
||||||
|
messaging = messaging_core.MessagingCore("", "", "")
|
||||||
|
messaging.connect()
|
||||||
|
self.assertEqual(m_handler.call_count, 2)
|
||||||
|
|
||||||
|
@mock.patch.object(messaging_handler, "MessagingHandler")
|
||||||
|
def test_disconnect(self, m_handler):
|
||||||
|
messaging = messaging_core.MessagingCore("", "", "")
|
||||||
|
messaging.disconnect()
|
||||||
|
self.assertEqual(m_handler.call_count, 2)
|
||||||
|
|
||||||
|
def test_build_topic_handler(self):
|
||||||
topic_name = "MyTopic"
|
topic_name = "MyTopic"
|
||||||
messaging = MessagingCore("", "", "")
|
messaging = messaging_core.MessagingCore("", "", "")
|
||||||
messaging_handler = messaging.build_topic(topic_name)
|
handler = messaging.build_topic_handler(topic_name)
|
||||||
self.assertIsNotNone(messaging_handler)
|
self.assertIsNotNone(handler)
|
||||||
|
|
||||||
def test_init_messaging_core(self):
|
def test_init_messaging_core(self):
|
||||||
messaging = MessagingCore("", "", "")
|
messaging = messaging_core.MessagingCore("", "", "")
|
||||||
self.assertIsInstance(messaging.serializer,
|
self.assertIsInstance(messaging.serializer,
|
||||||
RequestContextSerializer)
|
rpc.RequestContextSerializer)
|
||||||
self.assertIsInstance(messaging.topic_control, MessagingHandler)
|
self.assertIsInstance(
|
||||||
self.assertIsInstance(messaging.topic_status, MessagingHandler)
|
messaging.conductor_topic_handler,
|
||||||
|
messaging_handler.MessagingHandler)
|
||||||
|
self.assertIsInstance(
|
||||||
|
messaging.status_topic_handler,
|
||||||
|
messaging_handler.MessagingHandler)
|
||||||
|
|
||||||
@patch.object(MessagingCore, 'publish_control')
|
@mock.patch.object(messaging_handler, "MessagingHandler")
|
||||||
def test_publish_control(self, mock_call):
|
def test_publish_control(self, m_handler_cls):
|
||||||
|
m_handler = mock.Mock()
|
||||||
|
m_handler_cls.return_value = m_handler
|
||||||
payload = {
|
payload = {
|
||||||
"name": "value",
|
"name": "value",
|
||||||
}
|
}
|
||||||
event = "MyEvent"
|
event = "MyEvent"
|
||||||
messaging = MessagingCore("", "", "")
|
messaging = messaging_core.MessagingCore("", "", "")
|
||||||
messaging.publish_control(event, payload)
|
messaging.publish_control(event, payload)
|
||||||
mock_call.assert_called_once_with(event, payload)
|
m_handler.publish_event.assert_called_once_with(event, payload)
|
||||||
|
|
||||||
@patch.object(MessagingCore, 'publish_status')
|
@mock.patch.object(messaging_handler, "MessagingHandler")
|
||||||
def test_publish_status(self, mock_call):
|
def test_publish_status(self, m_handler_cls):
|
||||||
|
m_handler = mock.Mock()
|
||||||
|
m_handler_cls.return_value = m_handler
|
||||||
payload = {
|
payload = {
|
||||||
"name": "value",
|
"name": "value",
|
||||||
}
|
}
|
||||||
event = "MyEvent"
|
event = "MyEvent"
|
||||||
messaging = MessagingCore("", "", "")
|
messaging = messaging_core.MessagingCore("", "", "")
|
||||||
messaging.publish_status(event, payload)
|
messaging.publish_status(event, payload)
|
||||||
mock_call.assert_called_once_with(event, payload)
|
m_handler.publish_event.assert_called_once_with(event, payload, None)
|
||||||
|
|
||||||
@patch.object(MessagingCore, 'publish_status')
|
@mock.patch.object(messaging_core.MessagingCore, 'publish_status')
|
||||||
def test_response(self, mock_call):
|
def test_response(self, mock_call):
|
||||||
event = "My event"
|
event = "My event"
|
||||||
context = {'request_id': 12}
|
context = {'request_id': 12}
|
||||||
message = "My Message"
|
message = "My Message"
|
||||||
|
|
||||||
messaging = MessagingCore("", "", "")
|
messaging = messaging_core.MessagingCore("", "", "")
|
||||||
messaging.response(event, context, message)
|
messaging.response(event, context, message)
|
||||||
|
|
||||||
expected_payload = {
|
expected_payload = {
|
||||||
@@ -76,13 +96,15 @@ class TestMessagingCore(TestCase):
|
|||||||
}
|
}
|
||||||
mock_call.assert_called_once_with(event, expected_payload)
|
mock_call.assert_called_once_with(event, expected_payload)
|
||||||
|
|
||||||
def test_messaging_build_topic(self):
|
def test_messaging_build_topic_handler(self):
|
||||||
messaging = MessagingCore("pub_id", "test_topic", "does not matter")
|
messaging = messaging_core.MessagingCore(
|
||||||
topic = messaging.build_topic("test_topic")
|
"pub_id", "test_topic", "does not matter")
|
||||||
|
topic = messaging.build_topic_handler("test_topic")
|
||||||
|
|
||||||
self.assertIsInstance(topic, MessagingHandler)
|
self.assertIsInstance(topic, messaging_handler.MessagingHandler)
|
||||||
self.assertEqual(messaging.publisher_id, "pub_id")
|
self.assertEqual(messaging.publisher_id, "pub_id")
|
||||||
self.assertEqual(topic.publisher_id, "pub_id")
|
self.assertEqual(topic.publisher_id, "pub_id")
|
||||||
|
|
||||||
self.assertEqual(messaging.topic_control.topic_watcher, "test_topic")
|
self.assertEqual(
|
||||||
self.assertEqual(topic.topic_watcher, "test_topic")
|
messaging.conductor_topic_handler.topic_name, "test_topic")
|
||||||
|
self.assertEqual(topic.topic_name, "test_topic")
|
||||||
|
|||||||
@@ -14,17 +14,16 @@
|
|||||||
# 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 mock import Mock
|
import mock
|
||||||
from mock import patch
|
|
||||||
from oslo_config import cfg
|
from oslo_config import cfg
|
||||||
import oslo_messaging as messaging
|
import oslo_messaging as messaging
|
||||||
from watcher.common.messaging.messaging_handler import MessagingHandler
|
from watcher.common.messaging import messaging_handler
|
||||||
from watcher.tests.base import TestCase
|
from watcher.tests import base
|
||||||
|
|
||||||
CONF = cfg.CONF
|
CONF = cfg.CONF
|
||||||
|
|
||||||
|
|
||||||
class TestMessagingHandler(TestCase):
|
class TestMessagingHandler(base.TestCase):
|
||||||
|
|
||||||
PUBLISHER_ID = 'TEST_API'
|
PUBLISHER_ID = 'TEST_API'
|
||||||
TOPIC_WATCHER = 'TEST_TOPIC_WATCHER'
|
TOPIC_WATCHER = 'TEST_TOPIC_WATCHER'
|
||||||
@@ -35,20 +34,20 @@ class TestMessagingHandler(TestCase):
|
|||||||
super(TestMessagingHandler, self).setUp()
|
super(TestMessagingHandler, self).setUp()
|
||||||
CONF.set_default('host', 'fake-fqdn')
|
CONF.set_default('host', 'fake-fqdn')
|
||||||
|
|
||||||
@patch.object(messaging, "get_rpc_server")
|
@mock.patch.object(messaging, "get_rpc_server")
|
||||||
@patch.object(messaging, "Target")
|
@mock.patch.object(messaging, "Target")
|
||||||
def test_setup_messaging_handler(self, m_target_cls, m_get_rpc_server):
|
def test_setup_messaging_handler(self, m_target_cls, m_get_rpc_server):
|
||||||
m_target = Mock()
|
m_target = mock.Mock()
|
||||||
m_target_cls.return_value = m_target
|
m_target_cls.return_value = m_target
|
||||||
messaging_handler = MessagingHandler(
|
handler = messaging_handler.MessagingHandler(
|
||||||
publisher_id=self.PUBLISHER_ID,
|
publisher_id=self.PUBLISHER_ID,
|
||||||
topic_watcher=self.TOPIC_WATCHER,
|
topic_name=self.TOPIC_WATCHER,
|
||||||
endpoint=self.ENDPOINT,
|
endpoint=self.ENDPOINT,
|
||||||
version=self.VERSION,
|
version=self.VERSION,
|
||||||
serializer=None,
|
serializer=None,
|
||||||
)
|
)
|
||||||
|
|
||||||
messaging_handler.run()
|
handler.run()
|
||||||
|
|
||||||
m_target_cls.assert_called_once_with(
|
m_target_cls.assert_called_once_with(
|
||||||
server="fake-fqdn",
|
server="fake-fqdn",
|
||||||
@@ -56,23 +55,23 @@ class TestMessagingHandler(TestCase):
|
|||||||
version="1.0",
|
version="1.0",
|
||||||
)
|
)
|
||||||
m_get_rpc_server.assert_called_once_with(
|
m_get_rpc_server.assert_called_once_with(
|
||||||
messaging_handler.transport,
|
handler.transport,
|
||||||
m_target,
|
m_target,
|
||||||
[self.ENDPOINT],
|
[self.ENDPOINT],
|
||||||
serializer=None,
|
serializer=None,
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_messaging_handler_remove_endpoint(self):
|
def test_messaging_handler_remove_endpoint(self):
|
||||||
messaging_handler = MessagingHandler(
|
handler = messaging_handler.MessagingHandler(
|
||||||
publisher_id=self.PUBLISHER_ID,
|
publisher_id=self.PUBLISHER_ID,
|
||||||
topic_watcher=self.TOPIC_WATCHER,
|
topic_name=self.TOPIC_WATCHER,
|
||||||
endpoint=self.ENDPOINT,
|
endpoint=self.ENDPOINT,
|
||||||
version=self.VERSION,
|
version=self.VERSION,
|
||||||
serializer=None,
|
serializer=None,
|
||||||
)
|
)
|
||||||
|
|
||||||
self.assertEqual(messaging_handler.endpoints, [self.ENDPOINT])
|
self.assertEqual(handler.endpoints, [self.ENDPOINT])
|
||||||
|
|
||||||
messaging_handler.remove_endpoint(self.ENDPOINT)
|
handler.remove_endpoint(self.ENDPOINT)
|
||||||
|
|
||||||
self.assertEqual(messaging_handler.endpoints, [])
|
self.assertEqual(handler.endpoints, [])
|
||||||
|
|||||||
@@ -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)
|
||||||
@@ -126,7 +126,7 @@ class DbActionTestCase(base.DbTestCase):
|
|||||||
|
|
||||||
def test_update_action_uuid(self):
|
def test_update_action_uuid(self):
|
||||||
action = self._create_test_action()
|
action = self._create_test_action()
|
||||||
self.assertRaises(exception.InvalidParameterValue,
|
self.assertRaises(exception.Invalid,
|
||||||
self.dbapi.update_action, action['id'],
|
self.dbapi.update_action, action['id'],
|
||||||
{'uuid': 'hello'})
|
{'uuid': 'hello'})
|
||||||
|
|
||||||
|
|||||||
@@ -108,7 +108,7 @@ class DbActionPlanTestCase(base.DbTestCase):
|
|||||||
|
|
||||||
def test_update_action_plan_uuid(self):
|
def test_update_action_plan_uuid(self):
|
||||||
action_plan = self._create_test_action_plan()
|
action_plan = self._create_test_action_plan()
|
||||||
self.assertRaises(exception.InvalidParameterValue,
|
self.assertRaises(exception.Invalid,
|
||||||
self.dbapi.update_action_plan, action_plan['id'],
|
self.dbapi.update_action_plan, action_plan['id'],
|
||||||
{'uuid': 'hello'})
|
{'uuid': 'hello'})
|
||||||
|
|
||||||
|
|||||||
@@ -92,7 +92,7 @@ class DbAuditTestCase(base.DbTestCase):
|
|||||||
name='My Audit Template 1',
|
name='My Audit Template 1',
|
||||||
description='Description of my audit template 1',
|
description='Description of my audit template 1',
|
||||||
host_aggregate=5,
|
host_aggregate=5,
|
||||||
goal='SERVERS_CONSOLIDATION',
|
goal='DUMMY',
|
||||||
extra={'automatic': True})
|
extra={'automatic': True})
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -118,7 +118,7 @@ class DbAuditTestCase(base.DbTestCase):
|
|||||||
name='My Audit Template 1',
|
name='My Audit Template 1',
|
||||||
description='Description of my audit template 1',
|
description='Description of my audit template 1',
|
||||||
host_aggregate=5,
|
host_aggregate=5,
|
||||||
goal='SERVERS_CONSOLIDATION',
|
goal='DUMMY',
|
||||||
extra={'automatic': True})
|
extra={'automatic': True})
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -147,7 +147,7 @@ class DbAuditTestCase(base.DbTestCase):
|
|||||||
|
|
||||||
def test_update_audit_uuid(self):
|
def test_update_audit_uuid(self):
|
||||||
audit = self._create_test_audit()
|
audit = self._create_test_audit()
|
||||||
self.assertRaises(exception.InvalidParameterValue,
|
self.assertRaises(exception.Invalid,
|
||||||
self.dbapi.update_audit, audit['id'],
|
self.dbapi.update_audit, audit['id'],
|
||||||
{'uuid': 'hello'})
|
{'uuid': 'hello'})
|
||||||
|
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ class DbAuditTemplateTestCase(base.DbTestCase):
|
|||||||
name='My Audit Template 1',
|
name='My Audit Template 1',
|
||||||
description='Description of my audit template 1',
|
description='Description of my audit template 1',
|
||||||
host_aggregate=5,
|
host_aggregate=5,
|
||||||
goal='SERVERS_CONSOLIDATION',
|
goal='DUMMY',
|
||||||
extra={'automatic': True})
|
extra={'automatic': True})
|
||||||
audit_template2 = self._create_test_audit_template(
|
audit_template2 = self._create_test_audit_template(
|
||||||
id=2,
|
id=2,
|
||||||
@@ -55,7 +55,7 @@ class DbAuditTemplateTestCase(base.DbTestCase):
|
|||||||
name='My Audit Template 2',
|
name='My Audit Template 2',
|
||||||
description='Description of my audit template 2',
|
description='Description of my audit template 2',
|
||||||
host_aggregate=3,
|
host_aggregate=3,
|
||||||
goal='SERVERS_CONSOLIDATION',
|
goal='DUMMY',
|
||||||
extra={'automatic': True})
|
extra={'automatic': True})
|
||||||
|
|
||||||
res = self.dbapi.get_audit_template_list(self.context,
|
res = self.dbapi.get_audit_template_list(self.context,
|
||||||
@@ -68,7 +68,7 @@ class DbAuditTemplateTestCase(base.DbTestCase):
|
|||||||
|
|
||||||
res = self.dbapi.get_audit_template_list(
|
res = self.dbapi.get_audit_template_list(
|
||||||
self.context,
|
self.context,
|
||||||
filters={'goal': 'SERVERS_CONSOLIDATION'})
|
filters={'goal': 'DUMMY'})
|
||||||
self.assertEqual([audit_template1['id'], audit_template2['id']],
|
self.assertEqual([audit_template1['id'], audit_template2['id']],
|
||||||
[r.id for r in res])
|
[r.id for r in res])
|
||||||
|
|
||||||
@@ -106,7 +106,7 @@ class DbAuditTemplateTestCase(base.DbTestCase):
|
|||||||
|
|
||||||
def test_update_audit_template_uuid(self):
|
def test_update_audit_template_uuid(self):
|
||||||
audit_template = self._create_test_audit_template()
|
audit_template = self._create_test_audit_template()
|
||||||
self.assertRaises(exception.InvalidParameterValue,
|
self.assertRaises(exception.Invalid,
|
||||||
self.dbapi.update_audit_template,
|
self.dbapi.update_audit_template,
|
||||||
audit_template['id'],
|
audit_template['id'],
|
||||||
{'uuid': 'hello'})
|
{'uuid': 'hello'})
|
||||||
|
|||||||
@@ -63,5 +63,6 @@ class TestDefaultAuditHandler(base.DbTestCase):
|
|||||||
'audit_uuid': self.audit.uuid})
|
'audit_uuid': self.audit.uuid})
|
||||||
|
|
||||||
calls = [call_on_going, call_succeeded]
|
calls = [call_on_going, call_succeeded]
|
||||||
messaging.topic_status.publish_event.assert_has_calls(calls)
|
messaging.status_topic_handler.publish_event.assert_has_calls(calls)
|
||||||
self.assertEqual(2, messaging.topic_status.publish_event.call_count)
|
self.assertEqual(
|
||||||
|
2, messaging.status_topic_handler.publish_event.call_count)
|
||||||
|
|||||||
@@ -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):
|
||||||
|
|||||||
@@ -112,19 +112,19 @@ class TestAuditTemplateObject(base.DbTestCase):
|
|||||||
as mock_update_audit_template:
|
as mock_update_audit_template:
|
||||||
audit_template = objects.AuditTemplate.get_by_uuid(
|
audit_template = objects.AuditTemplate.get_by_uuid(
|
||||||
self.context, uuid)
|
self.context, uuid)
|
||||||
audit_template.goal = 'SERVERS_CONSOLIDATION'
|
audit_template.goal = 'DUMMY'
|
||||||
audit_template.save()
|
audit_template.save()
|
||||||
|
|
||||||
mock_get_audit_template.assert_called_once_with(
|
mock_get_audit_template.assert_called_once_with(
|
||||||
self.context, uuid)
|
self.context, uuid)
|
||||||
mock_update_audit_template.assert_called_once_with(
|
mock_update_audit_template.assert_called_once_with(
|
||||||
uuid, {'goal': 'SERVERS_CONSOLIDATION'})
|
uuid, {'goal': 'DUMMY'})
|
||||||
self.assertEqual(self.context, audit_template._context)
|
self.assertEqual(self.context, audit_template._context)
|
||||||
|
|
||||||
def test_refresh(self):
|
def test_refresh(self):
|
||||||
uuid = self.fake_audit_template['uuid']
|
uuid = self.fake_audit_template['uuid']
|
||||||
returns = [dict(self.fake_audit_template,
|
returns = [dict(self.fake_audit_template,
|
||||||
goal="SERVERS_CONSOLIDATION"),
|
goal="DUMMY"),
|
||||||
dict(self.fake_audit_template, goal="BALANCE_LOAD")]
|
dict(self.fake_audit_template, goal="BALANCE_LOAD")]
|
||||||
expected = [mock.call(self.context, uuid),
|
expected = [mock.call(self.context, uuid),
|
||||||
mock.call(self.context, uuid)]
|
mock.call(self.context, uuid)]
|
||||||
@@ -132,7 +132,7 @@ class TestAuditTemplateObject(base.DbTestCase):
|
|||||||
side_effect=returns,
|
side_effect=returns,
|
||||||
autospec=True) as mock_get_audit_template:
|
autospec=True) as mock_get_audit_template:
|
||||||
audit_template = objects.AuditTemplate.get(self.context, uuid)
|
audit_template = objects.AuditTemplate.get(self.context, uuid)
|
||||||
self.assertEqual("SERVERS_CONSOLIDATION", audit_template.goal)
|
self.assertEqual("DUMMY", audit_template.goal)
|
||||||
audit_template.refresh()
|
audit_template.refresh()
|
||||||
self.assertEqual("BALANCE_LOAD", audit_template.goal)
|
self.assertEqual("BALANCE_LOAD", audit_template.goal)
|
||||||
self.assertEqual(expected, mock_get_audit_template.call_args_list)
|
self.assertEqual(expected, mock_get_audit_template.call_args_list)
|
||||||
|
|||||||
@@ -79,7 +79,7 @@ class MyObj2(object):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class TestSubclassedObject(MyObj):
|
class DummySubclassedObject(MyObj):
|
||||||
fields = {'new_field': str}
|
fields = {'new_field': str}
|
||||||
|
|
||||||
|
|
||||||
@@ -172,10 +172,9 @@ class TestUtils(test_base.TestCase):
|
|||||||
|
|
||||||
def test_dt_deserializer(self):
|
def test_dt_deserializer(self):
|
||||||
dt = timeutils.parse_isotime('1955-11-05T00:00:00Z')
|
dt = timeutils.parse_isotime('1955-11-05T00:00:00Z')
|
||||||
self.assertEqual(utils.dt_deserializer(None, timeutils.isotime(dt)),
|
self.assertEqual(utils.dt_deserializer(timeutils.isotime(dt)), dt)
|
||||||
dt)
|
self.assertIsNone(utils.dt_deserializer(None))
|
||||||
self.assertIsNone(utils.dt_deserializer(None, None))
|
self.assertRaises(ValueError, utils.dt_deserializer, 'foo')
|
||||||
self.assertRaises(ValueError, utils.dt_deserializer, None, 'foo')
|
|
||||||
|
|
||||||
def test_obj_to_primitive_list(self):
|
def test_obj_to_primitive_list(self):
|
||||||
class MyList(base.ObjectListBase, base.WatcherObject):
|
class MyList(base.ObjectListBase, base.WatcherObject):
|
||||||
@@ -438,13 +437,13 @@ class _TestObject(object):
|
|||||||
base_fields = base.WatcherObject.fields.keys()
|
base_fields = base.WatcherObject.fields.keys()
|
||||||
myobj_fields = ['foo', 'bar', 'missing'] + base_fields
|
myobj_fields = ['foo', 'bar', 'missing'] + base_fields
|
||||||
myobj3_fields = ['new_field']
|
myobj3_fields = ['new_field']
|
||||||
self.assertTrue(issubclass(TestSubclassedObject, MyObj))
|
self.assertTrue(issubclass(DummySubclassedObject, MyObj))
|
||||||
self.assertEqual(len(myobj_fields), len(MyObj.fields))
|
self.assertEqual(len(myobj_fields), len(MyObj.fields))
|
||||||
self.assertEqual(set(myobj_fields), set(MyObj.fields.keys()))
|
self.assertEqual(set(myobj_fields), set(MyObj.fields.keys()))
|
||||||
self.assertEqual(len(myobj_fields) + len(myobj3_fields),
|
self.assertEqual(len(myobj_fields) + len(myobj3_fields),
|
||||||
len(TestSubclassedObject.fields))
|
len(DummySubclassedObject.fields))
|
||||||
self.assertEqual(set(myobj_fields) | set(myobj3_fields),
|
self.assertEqual(set(myobj_fields) | set(myobj3_fields),
|
||||||
set(TestSubclassedObject.fields.keys()))
|
set(DummySubclassedObject.fields.keys()))
|
||||||
|
|
||||||
def test_get_changes(self):
|
def test_get_changes(self):
|
||||||
obj = MyObj(self.context)
|
obj = MyObj(self.context)
|
||||||
|
|||||||
@@ -55,7 +55,8 @@ Otherwise, if you are not using a virtualenv::
|
|||||||
$ cd <TEMPEST_DIR>
|
$ cd <TEMPEST_DIR>
|
||||||
$ tempest init --config-dir ./etc watcher-cloud
|
$ tempest init --config-dir ./etc watcher-cloud
|
||||||
|
|
||||||
By default the configuration file is empty so before starting, you need to issue the following commands::
|
By default the configuration file is empty so before starting, you need to
|
||||||
|
issue the following commands::
|
||||||
|
|
||||||
$ cd <TEMPEST_DIR>/watcher-cloud/etc
|
$ cd <TEMPEST_DIR>/watcher-cloud/etc
|
||||||
$ cp tempest.conf.sample tempest.conf
|
$ cp tempest.conf.sample tempest.conf
|
||||||
@@ -67,26 +68,50 @@ Shown below is a minimal configuration you need to set within your
|
|||||||
|
|
||||||
For Keystone V3::
|
For Keystone V3::
|
||||||
|
|
||||||
|
[identity]
|
||||||
uri_v3 = http://<KEYSTONE_PUBLIC_ENDPOINT_IP>:<KEYSTONE_PORT>/v3
|
uri_v3 = http://<KEYSTONE_PUBLIC_ENDPOINT_IP>:<KEYSTONE_PORT>/v3
|
||||||
admin_tenant_name = <ADMIN_TENANT_NAME>
|
auth_version = v3
|
||||||
|
|
||||||
|
[auth]
|
||||||
admin_username = <ADMIN_USERNAME>
|
admin_username = <ADMIN_USERNAME>
|
||||||
admin_password = <ADMIN_PASSWORD>
|
admin_password = <ADMIN_PASSWORD>
|
||||||
|
admin_tenant_name = <ADMIN_TENANT_NAME>
|
||||||
admin_domain_name = <ADMIN_DOMAIN_NAME>
|
admin_domain_name = <ADMIN_DOMAIN_NAME>
|
||||||
|
|
||||||
|
[identity-feature-enabled]
|
||||||
api_v2 = false
|
api_v2 = false
|
||||||
api_v3 = true
|
api_v3 = true
|
||||||
auth_version = v3
|
|
||||||
|
|
||||||
For Keystone V2::
|
For Keystone V2::
|
||||||
|
|
||||||
|
[identity]
|
||||||
uri = http://<KEYSTONE_PUBLIC_ENDPOINT_IP>:<KEYSTONE_PORT>/v2.0
|
uri = http://<KEYSTONE_PUBLIC_ENDPOINT_IP>:<KEYSTONE_PORT>/v2.0
|
||||||
|
auth_version = v2
|
||||||
|
|
||||||
|
[auth]
|
||||||
admin_tenant_name = <ADMIN_TENANT_NAME>
|
admin_tenant_name = <ADMIN_TENANT_NAME>
|
||||||
admin_username = <ADMIN_USERNAME>
|
admin_username = <ADMIN_USERNAME>
|
||||||
admin_password = <ADMIN_PASSWORD>
|
admin_password = <ADMIN_PASSWORD>
|
||||||
auth_version = v2
|
|
||||||
|
|
||||||
In both cases::
|
In both cases::
|
||||||
|
|
||||||
|
[network]
|
||||||
public_network_id = <PUBLIC_NETWORK_ID>
|
public_network_id = <PUBLIC_NETWORK_ID>
|
||||||
|
|
||||||
|
You now have the minimum configuration for running Watcher Tempest tests on a
|
||||||
|
single node.
|
||||||
|
|
||||||
|
Since deploying Watcher with only a single compute node is not very useful, a
|
||||||
|
few more configuration have to be set in your ``tempest.conf`` file in order to
|
||||||
|
enable the execution of multi-node scenarios::
|
||||||
|
|
||||||
|
[compute]
|
||||||
|
# To indicate Tempest test that yout have provided enough compute nodes
|
||||||
|
min_compute_nodes = 2
|
||||||
|
|
||||||
|
# Image UUID you can get using the "glance image-list" command
|
||||||
|
image_ref = <IMAGE_UUID>
|
||||||
|
|
||||||
|
|
||||||
For more information, please refer to:
|
For more information, please refer to:
|
||||||
|
|
||||||
|
|||||||
@@ -54,25 +54,6 @@ class InfraOptimClientJSON(base.BaseInfraOptimClient):
|
|||||||
"""
|
"""
|
||||||
return self._show_request('audit_templates', audit_template_uuid)
|
return self._show_request('audit_templates', audit_template_uuid)
|
||||||
|
|
||||||
@base.handle_errors
|
|
||||||
def filter_audit_template_by_host_aggregate(self, host_aggregate):
|
|
||||||
"""Gets an audit template associated with given host agregate ID.
|
|
||||||
|
|
||||||
:param host_aggregate: Unique identifier of the host aggregate
|
|
||||||
:return: Serialized audit template as a dictionary.
|
|
||||||
"""
|
|
||||||
return self._list_request('/audit_templates',
|
|
||||||
host_aggregate=host_aggregate)
|
|
||||||
|
|
||||||
@base.handle_errors
|
|
||||||
def filter_audit_template_by_goal(self, goal):
|
|
||||||
"""Gets an audit template associated with given goal.
|
|
||||||
|
|
||||||
:param goal: goal identifier
|
|
||||||
:return: Serialized audit template as a dictionary.
|
|
||||||
"""
|
|
||||||
return self._list_request('/audit_templates', goal=goal)
|
|
||||||
|
|
||||||
@base.handle_errors
|
@base.handle_errors
|
||||||
def create_audit_template(self, **kwargs):
|
def create_audit_template(self, **kwargs):
|
||||||
"""Creates an audit template with the specified parameters.
|
"""Creates an audit template with the specified parameters.
|
||||||
@@ -139,12 +120,6 @@ class InfraOptimClientJSON(base.BaseInfraOptimClient):
|
|||||||
"""Lists details of all existing audit templates."""
|
"""Lists details of all existing audit templates."""
|
||||||
return self._list_request('/audits/detail', **kwargs)
|
return self._list_request('/audits/detail', **kwargs)
|
||||||
|
|
||||||
@base.handle_errors
|
|
||||||
def list_audit_by_audit_template(self, audit_template_uuid):
|
|
||||||
"""Lists all audits associated with an audit template."""
|
|
||||||
return self._list_request(
|
|
||||||
'/audits', audit_template=audit_template_uuid)
|
|
||||||
|
|
||||||
@base.handle_errors
|
@base.handle_errors
|
||||||
def show_audit(self, audit_uuid):
|
def show_audit(self, audit_uuid):
|
||||||
"""Gets a specific audit template.
|
"""Gets a specific audit template.
|
||||||
@@ -254,3 +229,24 @@ class InfraOptimClientJSON(base.BaseInfraOptimClient):
|
|||||||
:return: Serialized goal as a dictionary
|
:return: Serialized goal as a dictionary
|
||||||
"""
|
"""
|
||||||
return self._show_request('/goals', goal)
|
return self._show_request('/goals', goal)
|
||||||
|
|
||||||
|
# ### ACTIONS ### #
|
||||||
|
|
||||||
|
@base.handle_errors
|
||||||
|
def list_actions(self, **kwargs):
|
||||||
|
"""List all existing actions"""
|
||||||
|
return self._list_request('/actions', **kwargs)
|
||||||
|
|
||||||
|
@base.handle_errors
|
||||||
|
def list_actions_detail(self, **kwargs):
|
||||||
|
"""Lists details of all existing actions"""
|
||||||
|
return self._list_request('/actions/detail', **kwargs)
|
||||||
|
|
||||||
|
@base.handle_errors
|
||||||
|
def show_action(self, action_uuid):
|
||||||
|
"""Gets a specific action
|
||||||
|
|
||||||
|
:param action_uuid: Unique identifier of the action
|
||||||
|
:return: Serialized action as a dictionary
|
||||||
|
"""
|
||||||
|
return self._show_request('/actions', action_uuid)
|
||||||
|
|||||||
@@ -22,11 +22,6 @@ from tempest_lib import exceptions as lib_exc
|
|||||||
|
|
||||||
from watcher_tempest_plugin import infra_optim_clients as clients
|
from watcher_tempest_plugin import infra_optim_clients as clients
|
||||||
|
|
||||||
# Resources must be deleted in a specific order, this list
|
|
||||||
# defines the resource types to clean up, and the correct order.
|
|
||||||
RESOURCE_TYPES = ['audit_template', 'audit', 'action_plan']
|
|
||||||
# RESOURCE_TYPES = ['action', 'action_plan', 'audit', 'audit_template']
|
|
||||||
|
|
||||||
|
|
||||||
def creates(resource):
|
def creates(resource):
|
||||||
"""Decorator that adds resources to the appropriate cleanup list."""
|
"""Decorator that adds resources to the appropriate cleanup list."""
|
||||||
@@ -47,6 +42,8 @@ def creates(resource):
|
|||||||
class BaseInfraOptimTest(test.BaseTestCase):
|
class BaseInfraOptimTest(test.BaseTestCase):
|
||||||
"""Base class for Infrastructure Optimization API tests."""
|
"""Base class for Infrastructure Optimization API tests."""
|
||||||
|
|
||||||
|
RESOURCE_TYPES = ['audit_template', 'audit']
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def setup_credentials(cls):
|
def setup_credentials(cls):
|
||||||
super(BaseInfraOptimTest, cls).setup_credentials()
|
super(BaseInfraOptimTest, cls).setup_credentials()
|
||||||
@@ -62,19 +59,18 @@ class BaseInfraOptimTest(test.BaseTestCase):
|
|||||||
super(BaseInfraOptimTest, cls).resource_setup()
|
super(BaseInfraOptimTest, cls).resource_setup()
|
||||||
|
|
||||||
cls.created_objects = {}
|
cls.created_objects = {}
|
||||||
for resource in RESOURCE_TYPES:
|
for resource in cls.RESOURCE_TYPES:
|
||||||
cls.created_objects[resource] = set()
|
cls.created_objects[resource] = set()
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def resource_cleanup(cls):
|
def resource_cleanup(cls):
|
||||||
"""Ensure that all created objects get destroyed."""
|
"""Ensure that all created objects get destroyed."""
|
||||||
|
|
||||||
try:
|
try:
|
||||||
for resource in RESOURCE_TYPES:
|
for resource in cls.RESOURCE_TYPES:
|
||||||
uuids = cls.created_objects[resource]
|
obj_uuids = cls.created_objects[resource]
|
||||||
delete_method = getattr(cls.client, 'delete_%s' % resource)
|
delete_method = getattr(cls.client, 'delete_%s' % resource)
|
||||||
for u in uuids:
|
for obj_uuid in obj_uuids:
|
||||||
delete_method(u, ignore_errors=lib_exc.NotFound)
|
delete_method(obj_uuid, ignore_errors=lib_exc.NotFound)
|
||||||
finally:
|
finally:
|
||||||
super(BaseInfraOptimTest, cls).resource_cleanup()
|
super(BaseInfraOptimTest, cls).resource_cleanup()
|
||||||
|
|
||||||
@@ -178,21 +174,6 @@ class BaseInfraOptimTest(test.BaseTestCase):
|
|||||||
|
|
||||||
# ### ACTION PLANS ### #
|
# ### ACTION PLANS ### #
|
||||||
|
|
||||||
@classmethod
|
|
||||||
@creates('action_plan')
|
|
||||||
def start_action_plan(cls, audit_uuid, type='ONESHOT',
|
|
||||||
state='PENDING', deadline=None):
|
|
||||||
"""Wrapper utility for creating a test action plan
|
|
||||||
|
|
||||||
:param audit_uuid: Audit Template UUID this action plan will use
|
|
||||||
:param type: Audit type (either ONESHOT or CONTINUOUS)
|
|
||||||
:return: A tuple with The HTTP response and its body
|
|
||||||
"""
|
|
||||||
resp, body = cls.client.create_action_plan(
|
|
||||||
audit_uuid=audit_uuid, type=type,
|
|
||||||
state=state, deadline=deadline)
|
|
||||||
return resp, body
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def delete_action_plan(cls, action_plan_uuid):
|
def delete_action_plan(cls, action_plan_uuid):
|
||||||
"""Deletes an action plan having the specified UUID
|
"""Deletes an action plan having the specified UUID
|
||||||
|
|||||||
102
watcher_tempest_plugin/tests/api/admin/test_action.py
Normal file
102
watcher_tempest_plugin/tests/api/admin/test_action.py
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
# -*- encoding: utf-8 -*-
|
||||||
|
# Copyright (c) 2016 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 unicode_literals
|
||||||
|
|
||||||
|
import collections
|
||||||
|
import functools
|
||||||
|
|
||||||
|
from tempest import test
|
||||||
|
|
||||||
|
from watcher_tempest_plugin.tests.api.admin import base
|
||||||
|
|
||||||
|
|
||||||
|
class TestShowListAction(base.BaseInfraOptimTest):
|
||||||
|
"""Tests for actions"""
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def resource_setup(cls):
|
||||||
|
super(TestShowListAction, cls).resource_setup()
|
||||||
|
_, cls.audit_template = cls.create_audit_template()
|
||||||
|
_, cls.audit = cls.create_audit(cls.audit_template['uuid'])
|
||||||
|
|
||||||
|
assert test.call_until_true(
|
||||||
|
func=functools.partial(cls.has_audit_succeeded, cls.audit['uuid']),
|
||||||
|
duration=30,
|
||||||
|
sleep_for=.5
|
||||||
|
)
|
||||||
|
_, action_plans = cls.client.list_action_plan_by_audit(
|
||||||
|
cls.audit['uuid'])
|
||||||
|
cls.action_plan = action_plans['action_plans'][0]
|
||||||
|
|
||||||
|
@test.attr(type='smoke')
|
||||||
|
def test_show_one_action(self):
|
||||||
|
_, action = self.client.show_action(
|
||||||
|
self.action_plan["first_action_uuid"])
|
||||||
|
|
||||||
|
self.assertEqual(action['uuid'], self.action_plan["first_action_uuid"])
|
||||||
|
self.assertEqual(action['action_type'], "nop")
|
||||||
|
self.assertEqual(action['state'], "PENDING")
|
||||||
|
|
||||||
|
@test.attr(type='smoke')
|
||||||
|
def test_show_action_with_links(self):
|
||||||
|
_, action = self.client.show_action(
|
||||||
|
self.action_plan["first_action_uuid"])
|
||||||
|
self.assertIn('links', action.keys())
|
||||||
|
self.assertEqual(2, len(action['links']))
|
||||||
|
self.assertIn(action['uuid'], action['links'][0]['href'])
|
||||||
|
|
||||||
|
@test.attr(type="smoke")
|
||||||
|
def test_list_actions(self):
|
||||||
|
_, body = self.client.list_actions()
|
||||||
|
|
||||||
|
# Verify self links.
|
||||||
|
for action in body['actions']:
|
||||||
|
self.validate_self_link('actions', action['uuid'],
|
||||||
|
action['links'][0]['href'])
|
||||||
|
|
||||||
|
@test.attr(type="smoke")
|
||||||
|
def test_list_actions_by_action_plan(self):
|
||||||
|
_, body = self.client.list_actions(
|
||||||
|
action_plan_uuid=self.action_plan["uuid"])
|
||||||
|
|
||||||
|
for item in body['actions']:
|
||||||
|
self.assertEqual(self.action_plan["uuid"],
|
||||||
|
item['action_plan_uuid'])
|
||||||
|
|
||||||
|
action_counter = collections.Counter(
|
||||||
|
act['action_type'] for act in body['actions'])
|
||||||
|
|
||||||
|
# A dummy strategy generates 2 "nop" actions and 1 "sleep" action
|
||||||
|
self.assertEqual(len(body['actions']), 3)
|
||||||
|
self.assertEqual(action_counter.get("nop"), 2)
|
||||||
|
self.assertEqual(action_counter.get("sleep"), 1)
|
||||||
|
|
||||||
|
@test.attr(type="smoke")
|
||||||
|
def test_list_actions_by_audit(self):
|
||||||
|
_, body = self.client.list_actions(audit_uuid=self.audit["uuid"])
|
||||||
|
|
||||||
|
for item in body['actions']:
|
||||||
|
self.assertEqual(self.action_plan["uuid"],
|
||||||
|
item['action_plan_uuid'])
|
||||||
|
|
||||||
|
action_counter = collections.Counter(
|
||||||
|
act['action_type'] for act in body['actions'])
|
||||||
|
|
||||||
|
# A dummy strategy generates 2 "nop" actions and 1 "sleep" action
|
||||||
|
self.assertEqual(len(body['actions']), 3)
|
||||||
|
self.assertEqual(action_counter.get("nop"), 2)
|
||||||
|
self.assertEqual(action_counter.get("sleep"), 1)
|
||||||
@@ -34,7 +34,7 @@ class TestCreateDeleteExecuteActionPlan(base.BaseInfraOptimTest):
|
|||||||
|
|
||||||
self.assertTrue(test.call_until_true(
|
self.assertTrue(test.call_until_true(
|
||||||
func=functools.partial(self.has_audit_succeeded, audit['uuid']),
|
func=functools.partial(self.has_audit_succeeded, audit['uuid']),
|
||||||
duration=10,
|
duration=30,
|
||||||
sleep_for=.5
|
sleep_for=.5
|
||||||
))
|
))
|
||||||
_, action_plans = self.client.list_action_plan_by_audit(audit['uuid'])
|
_, action_plans = self.client.list_action_plan_by_audit(audit['uuid'])
|
||||||
@@ -52,7 +52,7 @@ class TestCreateDeleteExecuteActionPlan(base.BaseInfraOptimTest):
|
|||||||
|
|
||||||
self.assertTrue(test.call_until_true(
|
self.assertTrue(test.call_until_true(
|
||||||
func=functools.partial(self.has_audit_succeeded, audit['uuid']),
|
func=functools.partial(self.has_audit_succeeded, audit['uuid']),
|
||||||
duration=10,
|
duration=30,
|
||||||
sleep_for=.5
|
sleep_for=.5
|
||||||
))
|
))
|
||||||
_, action_plans = self.client.list_action_plan_by_audit(audit['uuid'])
|
_, action_plans = self.client.list_action_plan_by_audit(audit['uuid'])
|
||||||
@@ -72,7 +72,7 @@ class TestCreateDeleteExecuteActionPlan(base.BaseInfraOptimTest):
|
|||||||
|
|
||||||
self.assertTrue(test.call_until_true(
|
self.assertTrue(test.call_until_true(
|
||||||
func=functools.partial(self.has_audit_succeeded, audit['uuid']),
|
func=functools.partial(self.has_audit_succeeded, audit['uuid']),
|
||||||
duration=10,
|
duration=30,
|
||||||
sleep_for=.5
|
sleep_for=.5
|
||||||
))
|
))
|
||||||
_, action_plans = self.client.list_action_plan_by_audit(audit['uuid'])
|
_, action_plans = self.client.list_action_plan_by_audit(audit['uuid'])
|
||||||
@@ -89,7 +89,7 @@ class TestCreateDeleteExecuteActionPlan(base.BaseInfraOptimTest):
|
|||||||
self.assertTrue(test.call_until_true(
|
self.assertTrue(test.call_until_true(
|
||||||
func=functools.partial(
|
func=functools.partial(
|
||||||
self.has_action_plan_finished, action_plan['uuid']),
|
self.has_action_plan_finished, action_plan['uuid']),
|
||||||
duration=10,
|
duration=30,
|
||||||
sleep_for=.5
|
sleep_for=.5
|
||||||
))
|
))
|
||||||
_, finished_ap = self.client.show_action_plan(action_plan['uuid'])
|
_, finished_ap = self.client.show_action_plan(action_plan['uuid'])
|
||||||
@@ -109,7 +109,7 @@ class TestShowListActionPlan(base.BaseInfraOptimTest):
|
|||||||
|
|
||||||
assert test.call_until_true(
|
assert test.call_until_true(
|
||||||
func=functools.partial(cls.has_audit_succeeded, cls.audit['uuid']),
|
func=functools.partial(cls.has_audit_succeeded, cls.audit['uuid']),
|
||||||
duration=10,
|
duration=30,
|
||||||
sleep_for=.5
|
sleep_for=.5
|
||||||
)
|
)
|
||||||
_, action_plans = cls.client.list_action_plan_by_audit(
|
_, action_plans = cls.client.list_action_plan_by_audit(
|
||||||
|
|||||||
@@ -179,9 +179,8 @@ class TestShowListAudit(base.BaseInfraOptimTest):
|
|||||||
self.assertEqual(len(body['audits']), 3)
|
self.assertEqual(len(body['audits']), 3)
|
||||||
self.assertIn(next_marker, body['next'])
|
self.assertIn(next_marker, body['next'])
|
||||||
|
|
||||||
# @decorators.skip_because(bug="1533220")
|
|
||||||
@test.attr(type='smoke')
|
@test.attr(type='smoke')
|
||||||
def test_list_audits_related_to_given_audit_template(self):
|
def test_list_audits_related_to_given_audit_template(self):
|
||||||
_, body = self.client.list_audit_by_audit_template(
|
_, body = self.client.list_audits(
|
||||||
self.audit_template['uuid'])
|
audit_template=self.audit_template['uuid'])
|
||||||
self.assertIn(self.audit['uuid'], [n['uuid'] for n in body['audits']])
|
self.assertIn(self.audit['uuid'], [n['uuid'] for n in body['audits']])
|
||||||
|
|||||||
@@ -88,8 +88,8 @@ class TestAuditTemplate(base.BaseInfraOptimTest):
|
|||||||
@decorators.skip_because(bug="1510189")
|
@decorators.skip_because(bug="1510189")
|
||||||
@test.attr(type='smoke')
|
@test.attr(type='smoke')
|
||||||
def test_filter_audit_template_by_goal(self):
|
def test_filter_audit_template_by_goal(self):
|
||||||
_, audit_template = self.client.\
|
_, audit_template = self.client.list_audit_templates(
|
||||||
filter_audit_template_by_goal(self.audit_template['goal'])
|
goal=self.audit_template['goal'])
|
||||||
|
|
||||||
self.assert_expected(self.audit_template,
|
self.assert_expected(self.audit_template,
|
||||||
audit_template['audit_templates'][0])
|
audit_template['audit_templates'][0])
|
||||||
@@ -97,9 +97,8 @@ class TestAuditTemplate(base.BaseInfraOptimTest):
|
|||||||
@decorators.skip_because(bug="1510189")
|
@decorators.skip_because(bug="1510189")
|
||||||
@test.attr(type='smoke')
|
@test.attr(type='smoke')
|
||||||
def test_filter_audit_template_by_host_aggregate(self):
|
def test_filter_audit_template_by_host_aggregate(self):
|
||||||
_, audit_template = self.client.\
|
_, audit_template = self.client.list_audit_templates(
|
||||||
filter_audit_template_by_host_aggregate(
|
host_aggregate=self.audit_template['host_aggregate'])
|
||||||
self.audit_template['host_aggregate'])
|
|
||||||
|
|
||||||
self.assert_expected(self.audit_template,
|
self.assert_expected(self.audit_template,
|
||||||
audit_template['audit_templates'][0])
|
audit_template['audit_templates'][0])
|
||||||
|
|||||||
@@ -142,6 +142,10 @@ class BaseInfraOptimScenarioTest(manager.ScenarioTest):
|
|||||||
resp, _ = self.client.delete_audit(audit_uuid)
|
resp, _ = self.client.delete_audit(audit_uuid)
|
||||||
return resp
|
return resp
|
||||||
|
|
||||||
|
def has_audit_succeeded(self, audit_uuid):
|
||||||
|
_, audit = self.client.show_audit(audit_uuid)
|
||||||
|
return audit.get('state') == 'SUCCEEDED'
|
||||||
|
|
||||||
# ### ACTION PLANS ### #
|
# ### ACTION PLANS ### #
|
||||||
|
|
||||||
def delete_action_plan(self, action_plan_uuid):
|
def delete_action_plan(self, action_plan_uuid):
|
||||||
@@ -152,3 +156,7 @@ class BaseInfraOptimScenarioTest(manager.ScenarioTest):
|
|||||||
"""
|
"""
|
||||||
resp, _ = self.client.delete_action_plan(action_plan_uuid)
|
resp, _ = self.client.delete_action_plan(action_plan_uuid)
|
||||||
return resp
|
return resp
|
||||||
|
|
||||||
|
def has_action_plan_finished(self, action_plan_uuid):
|
||||||
|
_, action_plan = self.client.show_action_plan(action_plan_uuid)
|
||||||
|
return action_plan.get('state') in ('FAILED', 'SUCCEEDED', 'CANCELLED')
|
||||||
|
|||||||
@@ -0,0 +1,145 @@
|
|||||||
|
# -*- encoding: utf-8 -*-
|
||||||
|
# Copyright (c) 2016 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 unicode_literals
|
||||||
|
|
||||||
|
import functools
|
||||||
|
|
||||||
|
from tempest import config
|
||||||
|
from tempest import test
|
||||||
|
|
||||||
|
from watcher_tempest_plugin.tests.scenario import base
|
||||||
|
|
||||||
|
CONF = config.CONF
|
||||||
|
|
||||||
|
|
||||||
|
class TestExecuteBasicStrategy(base.BaseInfraOptimScenarioTest):
|
||||||
|
"""Tests for action plans"""
|
||||||
|
|
||||||
|
BASIC_GOAL = "BASIC_CONSOLIDATION"
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def skip_checks(cls):
|
||||||
|
super(TestExecuteBasicStrategy, cls).skip_checks()
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def resource_setup(cls):
|
||||||
|
super(TestExecuteBasicStrategy, cls).resource_setup()
|
||||||
|
if CONF.compute.min_compute_nodes < 2:
|
||||||
|
raise cls.skipException(
|
||||||
|
"Less than 2 compute nodes, skipping multinode tests.")
|
||||||
|
if not CONF.compute_feature_enabled.live_migration:
|
||||||
|
raise cls.skipException("Live migration is not enabled")
|
||||||
|
|
||||||
|
cls.initial_compute_nodes_setup = cls.get_compute_nodes_setup()
|
||||||
|
enabled_compute_nodes = [cn for cn in cls.initial_compute_nodes_setup
|
||||||
|
if cn.get('status') == 'enabled']
|
||||||
|
|
||||||
|
if len(enabled_compute_nodes) < 2:
|
||||||
|
raise cls.skipException(
|
||||||
|
"Less than 2 compute nodes are enabled, "
|
||||||
|
"skipping multinode tests.")
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_compute_nodes_setup(cls):
|
||||||
|
services_client = cls.mgr.services_client
|
||||||
|
available_services = services_client.list_services()['services']
|
||||||
|
|
||||||
|
return [srv for srv in available_services
|
||||||
|
if srv.get('binary') == 'nova-compute']
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def rollback_compute_nodes_status(cls):
|
||||||
|
current_compute_nodes_setup = cls.get_compute_nodes_setup()
|
||||||
|
for cn_setup in current_compute_nodes_setup:
|
||||||
|
cn_hostname = cn_setup.get('host')
|
||||||
|
matching_cns = [
|
||||||
|
cns for cns in cls.initial_compute_nodes_setup
|
||||||
|
if cns.get('host') == cn_hostname
|
||||||
|
]
|
||||||
|
initial_cn_setup = matching_cns[0] # Should return a single result
|
||||||
|
if cn_setup.get('status') != initial_cn_setup.get('status'):
|
||||||
|
if initial_cn_setup.get('status') == 'enabled':
|
||||||
|
rollback_func = cls.mgr.services_client.enable_service
|
||||||
|
else:
|
||||||
|
rollback_func = cls.mgr.services_client.disable_service
|
||||||
|
rollback_func(binary='nova-compute', host=cn_hostname)
|
||||||
|
|
||||||
|
def _create_one_instance_per_host(self):
|
||||||
|
"""Create 1 instance per compute node
|
||||||
|
|
||||||
|
This goes up to the min_compute_nodes threshold so that things don't
|
||||||
|
get crazy if you have 1000 compute nodes but set min to 3.
|
||||||
|
"""
|
||||||
|
host_client = self.mgr.hosts_client
|
||||||
|
all_hosts = host_client.list_hosts()['hosts']
|
||||||
|
compute_nodes = [x for x in all_hosts if x['service'] == 'compute']
|
||||||
|
|
||||||
|
for _ in compute_nodes[:CONF.compute.min_compute_nodes]:
|
||||||
|
# by getting to active state here, this means this has
|
||||||
|
# landed on the host in question.
|
||||||
|
self.create_server(image_id=CONF.compute.image_ref,
|
||||||
|
wait_until='ACTIVE',
|
||||||
|
clients=self.mgr)
|
||||||
|
|
||||||
|
@test.services('compute', 'network', 'telemetry', 'image')
|
||||||
|
def test_execute_basic_action_plan(self):
|
||||||
|
"""Execute an action plan based on the BASIC strategy
|
||||||
|
|
||||||
|
- create an audit template with the basic strategy
|
||||||
|
- run the audit to create an action plan
|
||||||
|
- get the action plan
|
||||||
|
- run the action plan
|
||||||
|
- get results and make sure it succeeded
|
||||||
|
"""
|
||||||
|
self.addCleanup(self.rollback_compute_nodes_status)
|
||||||
|
self._create_one_instance_per_host()
|
||||||
|
|
||||||
|
_, audit_template = self.create_audit_template(goal=self.BASIC_GOAL)
|
||||||
|
_, audit = self.create_audit(audit_template['uuid'])
|
||||||
|
|
||||||
|
self.assertTrue(test.call_until_true(
|
||||||
|
func=functools.partial(self.has_audit_succeeded, audit['uuid']),
|
||||||
|
duration=300,
|
||||||
|
sleep_for=2
|
||||||
|
))
|
||||||
|
_, action_plans = self.client.list_action_plan_by_audit(audit['uuid'])
|
||||||
|
action_plan = action_plans['action_plans'][0]
|
||||||
|
|
||||||
|
_, action_plan = self.client.show_action_plan(action_plan['uuid'])
|
||||||
|
|
||||||
|
# Execute the action by changing its state to TRIGGERED
|
||||||
|
_, updated_ap = self.client.update_action_plan(
|
||||||
|
action_plan['uuid'],
|
||||||
|
patch=[{'path': '/state', 'op': 'replace', 'value': 'TRIGGERED'}]
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertTrue(test.call_until_true(
|
||||||
|
func=functools.partial(
|
||||||
|
self.has_action_plan_finished, action_plan['uuid']),
|
||||||
|
duration=300,
|
||||||
|
sleep_for=2
|
||||||
|
))
|
||||||
|
_, finished_ap = self.client.show_action_plan(action_plan['uuid'])
|
||||||
|
_, action_list = self.client.list_actions(
|
||||||
|
action_plan_uuid=finished_ap["uuid"])
|
||||||
|
|
||||||
|
self.assertIn(updated_ap['state'], ('TRIGGERED', 'ONGOING'))
|
||||||
|
self.assertEqual(finished_ap['state'], 'SUCCEEDED')
|
||||||
|
|
||||||
|
for action in action_list['actions']:
|
||||||
|
self.assertEqual(action.get('state'), 'SUCCEEDED')
|
||||||
@@ -17,6 +17,7 @@
|
|||||||
|
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import collections
|
||||||
import functools
|
import functools
|
||||||
|
|
||||||
from tempest import test
|
from tempest import test
|
||||||
@@ -55,15 +56,23 @@ class TestExecuteDummyStrategy(base.BaseInfraOptimScenarioTest):
|
|||||||
patch=[{'path': '/state', 'op': 'replace', 'value': 'TRIGGERED'}]
|
patch=[{'path': '/state', 'op': 'replace', 'value': 'TRIGGERED'}]
|
||||||
)
|
)
|
||||||
|
|
||||||
def has_finished(action_plan_uuid):
|
|
||||||
return self.has_action_plan_finished(action_plan_uuid)
|
|
||||||
|
|
||||||
self.assertTrue(test.call_until_true(
|
self.assertTrue(test.call_until_true(
|
||||||
func=functools.partial(has_finished, action_plan['uuid']),
|
func=functools.partial(
|
||||||
|
self.has_action_plan_finished, action_plan['uuid']),
|
||||||
duration=30,
|
duration=30,
|
||||||
sleep_for=.5
|
sleep_for=.5
|
||||||
))
|
))
|
||||||
_, finished_ap = self.client.show_action_plan(action_plan['uuid'])
|
_, finished_ap = self.client.show_action_plan(action_plan['uuid'])
|
||||||
|
_, action_list = self.client.list_actions(
|
||||||
|
action_plan_uuid=finished_ap["uuid"])
|
||||||
|
|
||||||
|
action_counter = collections.Counter(
|
||||||
|
act['action_type'] for act in action_list['actions'])
|
||||||
|
|
||||||
self.assertIn(updated_ap['state'], ('TRIGGERED', 'ONGOING'))
|
self.assertIn(updated_ap['state'], ('TRIGGERED', 'ONGOING'))
|
||||||
self.assertEqual(finished_ap['state'], 'SUCCEEDED')
|
self.assertEqual(finished_ap['state'], 'SUCCEEDED')
|
||||||
|
|
||||||
|
# A dummy strategy generates 2 "nop" actions and 1 "sleep" action
|
||||||
|
self.assertEqual(len(action_list['actions']), 3)
|
||||||
|
self.assertEqual(action_counter.get("nop"), 2)
|
||||||
|
self.assertEqual(action_counter.get("sleep"), 1)
|
||||||
|
|||||||
Reference in New Issue
Block a user