Strategy requirements
This patch set adds /state resource to strategy API which allows to retrieve strategy requirements. Partially-Implements: blueprint check-strategy-requirements Change-Id: I177b443648301eb50da0da63271ecbfd9408bd4f
This commit is contained in:
@@ -0,0 +1,6 @@
|
|||||||
|
---
|
||||||
|
features:
|
||||||
|
- Added a way to check state of strategy before audit's execution.
|
||||||
|
Administrator can use "watcher strategy state <strategy_name>" command
|
||||||
|
to get information about metrics' availability, datasource's availability
|
||||||
|
and CDM's availability.
|
@@ -41,6 +41,7 @@ from watcher.api.controllers.v1 import utils as api_utils
|
|||||||
from watcher.common import exception
|
from watcher.common import exception
|
||||||
from watcher.common import policy
|
from watcher.common import policy
|
||||||
from watcher.common import utils as common_utils
|
from watcher.common import utils as common_utils
|
||||||
|
from watcher.decision_engine import rpcapi
|
||||||
from watcher import objects
|
from watcher import objects
|
||||||
|
|
||||||
|
|
||||||
@@ -205,6 +206,7 @@ class StrategiesController(rest.RestController):
|
|||||||
|
|
||||||
_custom_actions = {
|
_custom_actions = {
|
||||||
'detail': ['GET'],
|
'detail': ['GET'],
|
||||||
|
'state': ['GET'],
|
||||||
}
|
}
|
||||||
|
|
||||||
def _get_strategies_collection(self, filters, marker, limit, sort_key,
|
def _get_strategies_collection(self, filters, marker, limit, sort_key,
|
||||||
@@ -288,6 +290,26 @@ class StrategiesController(rest.RestController):
|
|||||||
return self._get_strategies_collection(
|
return self._get_strategies_collection(
|
||||||
filters, marker, limit, sort_key, sort_dir, expand, resource_url)
|
filters, marker, limit, sort_key, sort_dir, expand, resource_url)
|
||||||
|
|
||||||
|
@wsme_pecan.wsexpose(wtypes.text, wtypes.text)
|
||||||
|
def state(self, strategy):
|
||||||
|
"""Retrieve a inforamation about strategy requirements.
|
||||||
|
|
||||||
|
:param strategy: name of the strategy.
|
||||||
|
"""
|
||||||
|
context = pecan.request.context
|
||||||
|
policy.enforce(context, 'strategy:state', action='strategy:state')
|
||||||
|
parents = pecan.request.path.split('/')[:-1]
|
||||||
|
if parents[-2] != "strategies":
|
||||||
|
raise exception.HTTPNotFound
|
||||||
|
rpc_strategy = api_utils.get_resource('Strategy', strategy)
|
||||||
|
de_client = rpcapi.DecisionEngineAPI()
|
||||||
|
strategy_state = de_client.get_strategy_info(context,
|
||||||
|
rpc_strategy.name)
|
||||||
|
strategy_state.extend([{
|
||||||
|
'type': 'Name', 'state': rpc_strategy.name,
|
||||||
|
'mandatory': '', 'comment': ''}])
|
||||||
|
return strategy_state
|
||||||
|
|
||||||
@wsme_pecan.wsexpose(Strategy, wtypes.text)
|
@wsme_pecan.wsexpose(Strategy, wtypes.text)
|
||||||
def get_one(self, strategy):
|
def get_one(self, strategy):
|
||||||
"""Retrieve information about the given strategy.
|
"""Retrieve information about the given strategy.
|
||||||
|
@@ -409,6 +409,10 @@ class UnsupportedDataSource(UnsupportedError):
|
|||||||
"by strategy %(strategy)s")
|
"by strategy %(strategy)s")
|
||||||
|
|
||||||
|
|
||||||
|
class DataSourceNotAvailable(WatcherException):
|
||||||
|
msg_fmt = _("Datasource %(datasource)s is not available.")
|
||||||
|
|
||||||
|
|
||||||
class NoSuchMetricForHost(WatcherException):
|
class NoSuchMetricForHost(WatcherException):
|
||||||
msg_fmt = _("No %(metric)s metric for %(host)s found.")
|
msg_fmt = _("No %(metric)s metric for %(host)s found.")
|
||||||
|
|
||||||
|
@@ -49,6 +49,17 @@ rules = [
|
|||||||
'method': 'GET'
|
'method': 'GET'
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
),
|
||||||
|
policy.DocumentedRuleDefault(
|
||||||
|
name=STRATEGY % 'state',
|
||||||
|
check_str=base.RULE_ADMIN_API,
|
||||||
|
description='Get state of strategy.',
|
||||||
|
operations=[
|
||||||
|
{
|
||||||
|
'path': '/v1/strategies{strategy_uuid}/state',
|
||||||
|
'method': 'GET'
|
||||||
|
}
|
||||||
|
]
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@@ -57,6 +57,14 @@ class DataSourceBase(object):
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@abc.abstractmethod
|
||||||
|
def list_metrics(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
@abc.abstractmethod
|
||||||
|
def check_availability(self):
|
||||||
|
pass
|
||||||
|
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
def get_host_cpu_usage(self, resource_id, period, aggregate,
|
def get_host_cpu_usage(self, resource_id, period, aggregate,
|
||||||
granularity=None):
|
granularity=None):
|
||||||
|
@@ -115,6 +115,13 @@ class CeilometerHelper(base.DataSourceBase):
|
|||||||
except Exception:
|
except Exception:
|
||||||
raise
|
raise
|
||||||
|
|
||||||
|
def check_availability(self):
|
||||||
|
try:
|
||||||
|
self.query_retry(self.ceilometer.resources.list)
|
||||||
|
except Exception:
|
||||||
|
return 'not available'
|
||||||
|
return 'available'
|
||||||
|
|
||||||
def query_sample(self, meter_name, query, limit=1):
|
def query_sample(self, meter_name, query, limit=1):
|
||||||
return self.query_retry(f=self.ceilometer.samples.list,
|
return self.query_retry(f=self.ceilometer.samples.list,
|
||||||
meter_name=meter_name,
|
meter_name=meter_name,
|
||||||
@@ -129,11 +136,14 @@ class CeilometerHelper(base.DataSourceBase):
|
|||||||
period=period)
|
period=period)
|
||||||
return statistics
|
return statistics
|
||||||
|
|
||||||
def meter_list(self, query=None):
|
def list_metrics(self):
|
||||||
"""List the user's meters."""
|
"""List the user's meters."""
|
||||||
meters = self.query_retry(f=self.ceilometer.meters.list,
|
try:
|
||||||
query=query)
|
meters = self.query_retry(f=self.ceilometer.meters.list)
|
||||||
return meters
|
except Exception:
|
||||||
|
return set()
|
||||||
|
else:
|
||||||
|
return meters
|
||||||
|
|
||||||
def statistic_aggregation(self,
|
def statistic_aggregation(self,
|
||||||
resource_id,
|
resource_id,
|
||||||
|
@@ -49,7 +49,14 @@ class GnocchiHelper(base.DataSourceBase):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
LOG.exception(e)
|
LOG.exception(e)
|
||||||
time.sleep(CONF.gnocchi_client.query_timeout)
|
time.sleep(CONF.gnocchi_client.query_timeout)
|
||||||
raise
|
raise exception.DataSourceNotAvailable(datasource='gnocchi')
|
||||||
|
|
||||||
|
def check_availability(self):
|
||||||
|
try:
|
||||||
|
self.query_retry(self.gnocchi.status.get)
|
||||||
|
except Exception:
|
||||||
|
return 'not available'
|
||||||
|
return 'available'
|
||||||
|
|
||||||
def _statistic_aggregation(self,
|
def _statistic_aggregation(self,
|
||||||
resource_id,
|
resource_id,
|
||||||
@@ -108,8 +115,17 @@ class GnocchiHelper(base.DataSourceBase):
|
|||||||
# measure has structure [time, granularity, value]
|
# measure has structure [time, granularity, value]
|
||||||
return statistics[-1][2]
|
return statistics[-1][2]
|
||||||
|
|
||||||
def statistic_aggregation(self, resource_id, metric, period, aggregation,
|
def list_metrics(self):
|
||||||
granularity=300):
|
"""List the user's meters."""
|
||||||
|
try:
|
||||||
|
response = self.query_retry(f=self.gnocchi.metric.list)
|
||||||
|
except Exception:
|
||||||
|
return set()
|
||||||
|
else:
|
||||||
|
return set([metric['name'] for metric in response])
|
||||||
|
|
||||||
|
def statistic_aggregation(self, resource_id, metric, period, granularity,
|
||||||
|
aggregation='mean'):
|
||||||
stop_time = datetime.utcnow()
|
stop_time = datetime.utcnow()
|
||||||
start_time = stop_time - timedelta(seconds=(int(period)))
|
start_time = stop_time - timedelta(seconds=(int(period)))
|
||||||
return self._statistic_aggregation(
|
return self._statistic_aggregation(
|
||||||
|
@@ -65,6 +65,18 @@ class MonascaHelper(base.DataSourceBase):
|
|||||||
|
|
||||||
return start_timestamp, end_timestamp, period
|
return start_timestamp, end_timestamp, period
|
||||||
|
|
||||||
|
def check_availability(self):
|
||||||
|
try:
|
||||||
|
self.query_retry(self.monasca.metrics.list)
|
||||||
|
except Exception:
|
||||||
|
return 'not available'
|
||||||
|
return 'available'
|
||||||
|
|
||||||
|
def list_metrics(self):
|
||||||
|
# TODO(alexchadin): this method should be implemented in accordance to
|
||||||
|
# monasca API.
|
||||||
|
pass
|
||||||
|
|
||||||
def statistics_list(self, meter_name, dimensions, start_time=None,
|
def statistics_list(self, meter_name, dimensions, start_time=None,
|
||||||
end_time=None, period=None,):
|
end_time=None, period=None,):
|
||||||
"""List of statistics."""
|
"""List of statistics."""
|
||||||
|
@@ -40,6 +40,8 @@ See :doc:`../architecture` for more details on this component.
|
|||||||
from watcher.common import service_manager
|
from watcher.common import service_manager
|
||||||
from watcher.decision_engine.messaging import audit_endpoint
|
from watcher.decision_engine.messaging import audit_endpoint
|
||||||
from watcher.decision_engine.model.collector import manager
|
from watcher.decision_engine.model.collector import manager
|
||||||
|
from watcher.decision_engine.strategy.strategies import base \
|
||||||
|
as strategy_endpoint
|
||||||
|
|
||||||
from watcher import conf
|
from watcher import conf
|
||||||
|
|
||||||
@@ -70,7 +72,8 @@ class DecisionEngineManager(service_manager.ServiceManager):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def conductor_endpoints(self):
|
def conductor_endpoints(self):
|
||||||
return [audit_endpoint.AuditEndpoint]
|
return [audit_endpoint.AuditEndpoint,
|
||||||
|
strategy_endpoint.StrategyEndpoint]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def notification_endpoints(self):
|
def notification_endpoints(self):
|
||||||
|
@@ -40,6 +40,10 @@ class DecisionEngineAPI(service.Service):
|
|||||||
self.conductor_client.cast(
|
self.conductor_client.cast(
|
||||||
context, 'trigger_audit', audit_uuid=audit_uuid)
|
context, 'trigger_audit', audit_uuid=audit_uuid)
|
||||||
|
|
||||||
|
def get_strategy_info(self, context, strategy_name):
|
||||||
|
return self.conductor_client.call(
|
||||||
|
context, 'get_strategy_info', strategy_name=strategy_name)
|
||||||
|
|
||||||
|
|
||||||
class DecisionEngineAPIManager(service_manager.ServiceManager):
|
class DecisionEngineAPIManager(service_manager.ServiceManager):
|
||||||
|
|
||||||
|
@@ -157,6 +157,9 @@ class ComputeScope(base.BaseScope):
|
|||||||
compute_scope = []
|
compute_scope = []
|
||||||
model_hosts = list(cluster_model.get_all_compute_nodes().keys())
|
model_hosts = list(cluster_model.get_all_compute_nodes().keys())
|
||||||
|
|
||||||
|
if not self.scope:
|
||||||
|
return cluster_model
|
||||||
|
|
||||||
for scope in self.scope:
|
for scope in self.scope:
|
||||||
compute_scope = scope.get('compute')
|
compute_scope = scope.get('compute')
|
||||||
|
|
||||||
|
@@ -53,6 +53,69 @@ from watcher.decision_engine.solution import default
|
|||||||
from watcher.decision_engine.strategy.common import level
|
from watcher.decision_engine.strategy.common import level
|
||||||
|
|
||||||
|
|
||||||
|
class StrategyEndpoint(object):
|
||||||
|
def __init__(self, messaging):
|
||||||
|
self._messaging = messaging
|
||||||
|
|
||||||
|
def _collect_metrics(self, strategy, datasource):
|
||||||
|
metrics = []
|
||||||
|
if not datasource:
|
||||||
|
return {'type': 'Metrics', 'state': metrics,
|
||||||
|
'mandatory': False, 'comment': ''}
|
||||||
|
else:
|
||||||
|
ds_metrics = datasource.list_metrics()
|
||||||
|
if ds_metrics is None:
|
||||||
|
raise exception.DataSourceNotAvailable(
|
||||||
|
datasource=strategy.config.datasource)
|
||||||
|
else:
|
||||||
|
for metric in strategy.DATASOURCE_METRICS:
|
||||||
|
original_metric_name = datasource.METRIC_MAP.get(metric)
|
||||||
|
if original_metric_name in ds_metrics:
|
||||||
|
metrics.append({original_metric_name: 'available'})
|
||||||
|
else:
|
||||||
|
metrics.append({original_metric_name: 'not available'})
|
||||||
|
return {'type': 'Metrics', 'state': metrics,
|
||||||
|
'mandatory': False, 'comment': ''}
|
||||||
|
|
||||||
|
def _get_datasource_status(self, strategy, datasource):
|
||||||
|
if not datasource:
|
||||||
|
state = "Datasource is not presented for this strategy"
|
||||||
|
else:
|
||||||
|
state = "%s: %s" % (strategy.config.datasource,
|
||||||
|
datasource.check_availability())
|
||||||
|
return {'type': 'Datasource',
|
||||||
|
'state': state,
|
||||||
|
'mandatory': True, 'comment': ''}
|
||||||
|
|
||||||
|
def _get_cdm(self, strategy):
|
||||||
|
models = []
|
||||||
|
for model in ['compute_model', 'storage_model']:
|
||||||
|
try:
|
||||||
|
getattr(strategy, model)
|
||||||
|
except Exception:
|
||||||
|
models.append({model: 'not available'})
|
||||||
|
else:
|
||||||
|
models.append({model: 'available'})
|
||||||
|
return {'type': 'CDM', 'state': models,
|
||||||
|
'mandatory': True, 'comment': ''}
|
||||||
|
|
||||||
|
def get_strategy_info(self, context, strategy_name):
|
||||||
|
strategy = loading.DefaultStrategyLoader().load(strategy_name)
|
||||||
|
try:
|
||||||
|
is_datasources = getattr(strategy.config, 'datasources', None)
|
||||||
|
if is_datasources:
|
||||||
|
datasource = is_datasources[0]
|
||||||
|
else:
|
||||||
|
datasource = getattr(strategy, strategy.config.datasource)
|
||||||
|
except (AttributeError, IndexError):
|
||||||
|
datasource = []
|
||||||
|
available_datasource = self._get_datasource_status(strategy,
|
||||||
|
datasource)
|
||||||
|
available_metrics = self._collect_metrics(strategy, datasource)
|
||||||
|
available_cdm = self._get_cdm(strategy)
|
||||||
|
return [available_datasource, available_metrics, available_cdm]
|
||||||
|
|
||||||
|
|
||||||
@six.add_metaclass(abc.ABCMeta)
|
@six.add_metaclass(abc.ABCMeta)
|
||||||
class BaseStrategy(loadable.Loadable):
|
class BaseStrategy(loadable.Loadable):
|
||||||
"""A base class for all the strategies
|
"""A base class for all the strategies
|
||||||
|
@@ -52,6 +52,8 @@ class BasicConsolidation(base.ServerConsolidationBaseStrategy):
|
|||||||
HOST_CPU_USAGE_METRIC_NAME = 'compute.node.cpu.percent'
|
HOST_CPU_USAGE_METRIC_NAME = 'compute.node.cpu.percent'
|
||||||
INSTANCE_CPU_USAGE_METRIC_NAME = 'cpu_util'
|
INSTANCE_CPU_USAGE_METRIC_NAME = 'cpu_util'
|
||||||
|
|
||||||
|
DATASOURCE_METRICS = ['host_cpu_usage', 'instance_cpu_usage']
|
||||||
|
|
||||||
METRIC_NAMES = dict(
|
METRIC_NAMES = dict(
|
||||||
ceilometer=dict(
|
ceilometer=dict(
|
||||||
host_cpu_usage='compute.node.cpu.percent',
|
host_cpu_usage='compute.node.cpu.percent',
|
||||||
|
@@ -30,6 +30,9 @@ CONF = cfg.CONF
|
|||||||
class NoisyNeighbor(base.NoisyNeighborBaseStrategy):
|
class NoisyNeighbor(base.NoisyNeighborBaseStrategy):
|
||||||
|
|
||||||
MIGRATION = "migrate"
|
MIGRATION = "migrate"
|
||||||
|
|
||||||
|
DATASOURCE_METRICS = ['instance_l3_cache_usage']
|
||||||
|
|
||||||
# The meter to report L3 cache in ceilometer
|
# The meter to report L3 cache in ceilometer
|
||||||
METER_NAME_L3 = "cpu_l3_cache"
|
METER_NAME_L3 = "cpu_l3_cache"
|
||||||
DEFAULT_WATCHER_PRIORITY = 5
|
DEFAULT_WATCHER_PRIORITY = 5
|
||||||
|
@@ -77,6 +77,8 @@ class OutletTempControl(base.ThermalOptimizationBaseStrategy):
|
|||||||
# The meter to report outlet temperature in ceilometer
|
# The meter to report outlet temperature in ceilometer
|
||||||
MIGRATION = "migrate"
|
MIGRATION = "migrate"
|
||||||
|
|
||||||
|
DATASOURCE_METRICS = ['host_outlet_temp']
|
||||||
|
|
||||||
METRIC_NAMES = dict(
|
METRIC_NAMES = dict(
|
||||||
ceilometer=dict(
|
ceilometer=dict(
|
||||||
host_outlet_temp='hardware.ipmi.node.outlet_temperature'),
|
host_outlet_temp='hardware.ipmi.node.outlet_temperature'),
|
||||||
|
@@ -86,6 +86,8 @@ class UniformAirflow(base.BaseStrategy):
|
|||||||
# choose 300 seconds as the default duration of meter aggregation
|
# choose 300 seconds as the default duration of meter aggregation
|
||||||
PERIOD = 300
|
PERIOD = 300
|
||||||
|
|
||||||
|
DATASOURCE_METRICS = ['host_airflow', 'host_inlet_temp', 'host_power']
|
||||||
|
|
||||||
METRIC_NAMES = dict(
|
METRIC_NAMES = dict(
|
||||||
ceilometer=dict(
|
ceilometer=dict(
|
||||||
# The meter to report Airflow of physical server in ceilometer
|
# The meter to report Airflow of physical server in ceilometer
|
||||||
|
@@ -74,6 +74,9 @@ class VMWorkloadConsolidation(base.ServerConsolidationBaseStrategy):
|
|||||||
HOST_CPU_USAGE_METRIC_NAME = 'compute.node.cpu.percent'
|
HOST_CPU_USAGE_METRIC_NAME = 'compute.node.cpu.percent'
|
||||||
INSTANCE_CPU_USAGE_METRIC_NAME = 'cpu_util'
|
INSTANCE_CPU_USAGE_METRIC_NAME = 'cpu_util'
|
||||||
|
|
||||||
|
DATASOURCE_METRICS = ['instance_ram_allocated', 'instance_cpu_usage',
|
||||||
|
'instance_ram_usage', 'instance_root_disk_size']
|
||||||
|
|
||||||
METRIC_NAMES = dict(
|
METRIC_NAMES = dict(
|
||||||
ceilometer=dict(
|
ceilometer=dict(
|
||||||
cpu_util_metric='cpu_util',
|
cpu_util_metric='cpu_util',
|
||||||
|
@@ -95,6 +95,8 @@ class WorkloadBalance(base.WorkloadStabilizationBaseStrategy):
|
|||||||
# Unit: MB
|
# Unit: MB
|
||||||
MEM_METER_NAME = "memory.resident"
|
MEM_METER_NAME = "memory.resident"
|
||||||
|
|
||||||
|
DATASOURCE_METRICS = ['instance_cpu_usage', 'instance_ram_usage']
|
||||||
|
|
||||||
MIGRATION = "migrate"
|
MIGRATION = "migrate"
|
||||||
|
|
||||||
def __init__(self, config, osc=None):
|
def __init__(self, config, osc=None):
|
||||||
|
@@ -62,6 +62,9 @@ class WorkloadStabilization(base.WorkloadStabilizationBaseStrategy):
|
|||||||
MIGRATION = "migrate"
|
MIGRATION = "migrate"
|
||||||
MEMOIZE = _set_memoize(CONF)
|
MEMOIZE = _set_memoize(CONF)
|
||||||
|
|
||||||
|
DATASOURCE_METRICS = ['host_cpu_usage', 'instance_cpu_usage',
|
||||||
|
'instance_ram_usage', 'host_memory_usage']
|
||||||
|
|
||||||
def __init__(self, config, osc=None):
|
def __init__(self, config, osc=None):
|
||||||
"""Workload Stabilization control using live migration
|
"""Workload Stabilization control using live migration
|
||||||
|
|
||||||
|
@@ -10,11 +10,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.
|
||||||
|
|
||||||
|
import mock
|
||||||
|
|
||||||
from oslo_config import cfg
|
from oslo_config import cfg
|
||||||
from oslo_serialization import jsonutils
|
from oslo_serialization import jsonutils
|
||||||
from six.moves.urllib import parse as urlparse
|
from six.moves.urllib import parse as urlparse
|
||||||
|
|
||||||
from watcher.common import utils
|
from watcher.common import utils
|
||||||
|
from watcher.decision_engine import rpcapi as deapi
|
||||||
from watcher.tests.api import base as api_base
|
from watcher.tests.api import base as api_base
|
||||||
from watcher.tests.objects import utils as obj_utils
|
from watcher.tests.objects import utils as obj_utils
|
||||||
|
|
||||||
@@ -31,6 +34,28 @@ class TestListStrategy(api_base.FunctionalTest):
|
|||||||
for field in strategy_fields:
|
for field in strategy_fields:
|
||||||
self.assertIn(field, strategy)
|
self.assertIn(field, strategy)
|
||||||
|
|
||||||
|
@mock.patch.object(deapi.DecisionEngineAPI, 'get_strategy_info')
|
||||||
|
def test_state(self, mock_strategy_info):
|
||||||
|
strategy = obj_utils.create_test_strategy(self.context)
|
||||||
|
mock_state = [
|
||||||
|
{"type": "Datasource", "mandatory": True, "comment": "",
|
||||||
|
"state": "gnocchi: True"},
|
||||||
|
{"type": "Metrics", "mandatory": False, "comment": "",
|
||||||
|
"state": [{"compute.node.cpu.percent": "available"},
|
||||||
|
{"cpu_util": "available"}]},
|
||||||
|
{"type": "CDM", "mandatory": True, "comment": "",
|
||||||
|
"state": [{"compute_model": "available"},
|
||||||
|
{"storage_model": "not available"}]},
|
||||||
|
{"type": "Name", "mandatory": "", "comment": "",
|
||||||
|
"state": strategy.name}
|
||||||
|
]
|
||||||
|
|
||||||
|
mock_strategy_info.return_value = mock_state
|
||||||
|
response = self.get_json('/strategies/%s/state' % strategy.uuid)
|
||||||
|
strategy_name = [requirement["state"] for requirement in response
|
||||||
|
if requirement["type"] == "Name"][0]
|
||||||
|
self.assertEqual(strategy.name, strategy_name)
|
||||||
|
|
||||||
def test_one(self):
|
def test_one(self):
|
||||||
strategy = obj_utils.create_test_strategy(self.context)
|
strategy = obj_utils.create_test_strategy(self.context)
|
||||||
response = self.get_json('/strategies')
|
response = self.get_json('/strategies')
|
||||||
@@ -234,6 +259,13 @@ class TestStrategyPolicyEnforcement(api_base.FunctionalTest):
|
|||||||
'/strategies/detail',
|
'/strategies/detail',
|
||||||
expect_errors=True)
|
expect_errors=True)
|
||||||
|
|
||||||
|
def test_policy_disallow_state(self):
|
||||||
|
strategy = obj_utils.create_test_strategy(self.context)
|
||||||
|
self._common_policy_check(
|
||||||
|
"strategy:get", self.get_json,
|
||||||
|
'/strategies/%s/state' % strategy.uuid,
|
||||||
|
expect_errors=True)
|
||||||
|
|
||||||
|
|
||||||
class TestStrategyEnforcementWithAdminContext(
|
class TestStrategyEnforcementWithAdminContext(
|
||||||
TestListStrategy, api_base.AdminRoleTest):
|
TestListStrategy, api_base.AdminRoleTest):
|
||||||
@@ -245,4 +277,5 @@ class TestStrategyEnforcementWithAdminContext(
|
|||||||
"default": "rule:admin_api",
|
"default": "rule:admin_api",
|
||||||
"strategy:detail": "rule:default",
|
"strategy:detail": "rule:default",
|
||||||
"strategy:get": "rule:default",
|
"strategy:get": "rule:default",
|
||||||
"strategy:get_all": "rule:default"})
|
"strategy:get_all": "rule:default",
|
||||||
|
"strategy:state": "rule:default"})
|
||||||
|
@@ -198,3 +198,19 @@ class TestCeilometerHelper(base.BaseTestCase):
|
|||||||
mock_aggregation.assert_called_once_with(
|
mock_aggregation.assert_called_once_with(
|
||||||
'compute1', helper.METRIC_MAP['host_power'], 600,
|
'compute1', helper.METRIC_MAP['host_power'], 600,
|
||||||
aggregate='mean')
|
aggregate='mean')
|
||||||
|
|
||||||
|
def test_check_availability(self, mock_ceilometer):
|
||||||
|
ceilometer = mock.MagicMock()
|
||||||
|
ceilometer.resources.list.return_value = True
|
||||||
|
mock_ceilometer.return_value = ceilometer
|
||||||
|
helper = ceilometer_helper.CeilometerHelper()
|
||||||
|
result = helper.check_availability()
|
||||||
|
self.assertEqual('available', result)
|
||||||
|
|
||||||
|
def test_check_availability_with_failure(self, mock_ceilometer):
|
||||||
|
ceilometer = mock.MagicMock()
|
||||||
|
ceilometer.resources.list.side_effect = Exception()
|
||||||
|
mock_ceilometer.return_value = ceilometer
|
||||||
|
helper = ceilometer_helper.CeilometerHelper()
|
||||||
|
|
||||||
|
self.assertEqual('not available', helper.check_availability())
|
||||||
|
@@ -152,3 +152,40 @@ class TestGnocchiHelper(base.BaseTestCase):
|
|||||||
mock_aggregation.assert_called_once_with(
|
mock_aggregation.assert_called_once_with(
|
||||||
'compute1', helper.METRIC_MAP['host_power'], 600, 300,
|
'compute1', helper.METRIC_MAP['host_power'], 600, 300,
|
||||||
aggregation='mean')
|
aggregation='mean')
|
||||||
|
|
||||||
|
def test_gnocchi_check_availability(self, mock_gnocchi):
|
||||||
|
gnocchi = mock.MagicMock()
|
||||||
|
gnocchi.status.get.return_value = True
|
||||||
|
mock_gnocchi.return_value = gnocchi
|
||||||
|
helper = gnocchi_helper.GnocchiHelper()
|
||||||
|
result = helper.check_availability()
|
||||||
|
self.assertEqual('available', result)
|
||||||
|
|
||||||
|
def test_gnocchi_check_availability_with_failure(self, mock_gnocchi):
|
||||||
|
cfg.CONF.set_override("query_max_retries", 1,
|
||||||
|
group='gnocchi_client')
|
||||||
|
gnocchi = mock.MagicMock()
|
||||||
|
gnocchi.status.get.side_effect = Exception()
|
||||||
|
mock_gnocchi.return_value = gnocchi
|
||||||
|
helper = gnocchi_helper.GnocchiHelper()
|
||||||
|
|
||||||
|
self.assertEqual('not available', helper.check_availability())
|
||||||
|
|
||||||
|
def test_gnocchi_list_metrics(self, mock_gnocchi):
|
||||||
|
gnocchi = mock.MagicMock()
|
||||||
|
metrics = [{"name": "metric1"}, {"name": "metric2"}]
|
||||||
|
expected_metrics = set(["metric1", "metric2"])
|
||||||
|
gnocchi.metric.list.return_value = metrics
|
||||||
|
mock_gnocchi.return_value = gnocchi
|
||||||
|
helper = gnocchi_helper.GnocchiHelper()
|
||||||
|
result = helper.list_metrics()
|
||||||
|
self.assertEqual(expected_metrics, result)
|
||||||
|
|
||||||
|
def test_gnocchi_list_metrics_with_failure(self, mock_gnocchi):
|
||||||
|
cfg.CONF.set_override("query_max_retries", 1,
|
||||||
|
group='gnocchi_client')
|
||||||
|
gnocchi = mock.MagicMock()
|
||||||
|
gnocchi.metric.list.side_effect = Exception()
|
||||||
|
mock_gnocchi.return_value = gnocchi
|
||||||
|
helper = gnocchi_helper.GnocchiHelper()
|
||||||
|
self.assertFalse(helper.list_metrics())
|
||||||
|
@@ -57,6 +57,21 @@ class TestMonascaHelper(base.BaseTestCase):
|
|||||||
)
|
)
|
||||||
self.assertEqual(expected_result, result)
|
self.assertEqual(expected_result, result)
|
||||||
|
|
||||||
|
def test_check_availability(self, mock_monasca):
|
||||||
|
monasca = mock.MagicMock()
|
||||||
|
monasca.metrics.list.return_value = True
|
||||||
|
mock_monasca.return_value = monasca
|
||||||
|
helper = monasca_helper.MonascaHelper()
|
||||||
|
result = helper.check_availability()
|
||||||
|
self.assertEqual('available', result)
|
||||||
|
|
||||||
|
def test_check_availability_with_failure(self, mock_monasca):
|
||||||
|
monasca = mock.MagicMock()
|
||||||
|
monasca.metrics.list.side_effect = Exception()
|
||||||
|
mock_monasca.return_value = monasca
|
||||||
|
helper = monasca_helper.MonascaHelper()
|
||||||
|
self.assertEqual('not available', helper.check_availability())
|
||||||
|
|
||||||
def test_monasca_statistic_list(self, mock_monasca):
|
def test_monasca_statistic_list(self, mock_monasca):
|
||||||
monasca = mock.MagicMock()
|
monasca = mock.MagicMock()
|
||||||
expected_result = [{
|
expected_result = [{
|
||||||
|
@@ -0,0 +1,64 @@
|
|||||||
|
# -*- encoding: utf-8 -*-
|
||||||
|
# Copyright (c) 2017 Servionica
|
||||||
|
#
|
||||||
|
# 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 mock
|
||||||
|
|
||||||
|
from watcher.decision_engine.strategy.strategies import base as strategy_base
|
||||||
|
from watcher.tests import base
|
||||||
|
|
||||||
|
|
||||||
|
class TestStrategyEndpoint(base.BaseTestCase):
|
||||||
|
|
||||||
|
def test_collect_metrics(self):
|
||||||
|
datasource = mock.MagicMock()
|
||||||
|
datasource.list_metrics.return_value = ["m1", "m2"]
|
||||||
|
datasource.METRIC_MAP = {"metric1": "m1", "metric2": "m2",
|
||||||
|
"metric3": "m3"}
|
||||||
|
strategy = mock.MagicMock()
|
||||||
|
strategy.DATASOURCE_METRICS = ["metric1", "metric2", "metric3"]
|
||||||
|
strategy.config.datasource = "gnocchi"
|
||||||
|
se = strategy_base.StrategyEndpoint(mock.MagicMock())
|
||||||
|
result = se._collect_metrics(strategy, datasource)
|
||||||
|
expected_result = {'type': 'Metrics',
|
||||||
|
'state': [{"m1": "available"},
|
||||||
|
{"m2": "available"},
|
||||||
|
{"m3": "not available"}],
|
||||||
|
'mandatory': False, 'comment': ''}
|
||||||
|
self.assertEqual(expected_result, result)
|
||||||
|
|
||||||
|
def test_get_datasource_status(self):
|
||||||
|
strategy = mock.MagicMock()
|
||||||
|
datasource = mock.MagicMock()
|
||||||
|
strategy.config.datasource = "gnocchi"
|
||||||
|
datasource.check_availability.return_value = "available"
|
||||||
|
se = strategy_base.StrategyEndpoint(mock.MagicMock())
|
||||||
|
result = se._get_datasource_status(strategy, datasource)
|
||||||
|
expected_result = {'type': 'Datasource',
|
||||||
|
'state': "gnocchi: available",
|
||||||
|
'mandatory': True, 'comment': ''}
|
||||||
|
self.assertEqual(expected_result, result)
|
||||||
|
|
||||||
|
def test_get_cdm(self):
|
||||||
|
strategy = mock.MagicMock()
|
||||||
|
strategy.compute_model = mock.MagicMock()
|
||||||
|
del strategy.storage_model
|
||||||
|
se = strategy_base.StrategyEndpoint(mock.MagicMock())
|
||||||
|
result = se._get_cdm(strategy)
|
||||||
|
expected_result = {'type': 'CDM',
|
||||||
|
'state': [{"compute_model": "available"},
|
||||||
|
{"storage_model": "not available"}],
|
||||||
|
'mandatory': True, 'comment': ''}
|
||||||
|
self.assertEqual(expected_result, result)
|
@@ -46,3 +46,9 @@ class TestDecisionEngineAPI(base.TestCase):
|
|||||||
self.api.trigger_audit(self.context, audit_uuid)
|
self.api.trigger_audit(self.context, audit_uuid)
|
||||||
mock_cast.assert_called_once_with(
|
mock_cast.assert_called_once_with(
|
||||||
self.context, 'trigger_audit', audit_uuid=audit_uuid)
|
self.context, 'trigger_audit', audit_uuid=audit_uuid)
|
||||||
|
|
||||||
|
def test_get_strategy_info(self):
|
||||||
|
with mock.patch.object(om.RPCClient, 'call') as mock_call:
|
||||||
|
self.api.get_strategy_info(self.context, "dummy")
|
||||||
|
mock_call.assert_called_once_with(
|
||||||
|
self.context, 'get_strategy_info', strategy_name="dummy")
|
||||||
|
@@ -54,6 +54,7 @@ policy_data = """
|
|||||||
"strategy:detail": "",
|
"strategy:detail": "",
|
||||||
"strategy:get": "",
|
"strategy:get": "",
|
||||||
"strategy:get_all": "",
|
"strategy:get_all": "",
|
||||||
|
"strategy:state": "",
|
||||||
|
|
||||||
"service:detail": "",
|
"service:detail": "",
|
||||||
"service:get": "",
|
"service:get": "",
|
||||||
|
Reference in New Issue
Block a user