Remove CloudWatch API
This patch removes the API, the next set of patches in the series would remove stack watch service and related WatchRule implementation. Change-Id: I8b0472be862907298c8da51f435b5d8b19610ec3 Partial-Bug: #1743707
This commit is contained in:
parent
3057168f5f
commit
6d55417f80
@ -1,46 +0,0 @@
|
|||||||
#!/usr/bin/env python
|
|
||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
# you may not use this file except in compliance with the License.
|
|
||||||
# You may obtain a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
||||||
# implied.
|
|
||||||
# See the License for the specific language governing permissions and
|
|
||||||
# limitations under the License.
|
|
||||||
|
|
||||||
"""Heat cloudwatch API Server.
|
|
||||||
|
|
||||||
This implements an approximation of the Amazon CloudWatch API and translates it
|
|
||||||
into a native representation. It then calls the heat-engine via AMQP RPC to
|
|
||||||
implement them.
|
|
||||||
"""
|
|
||||||
|
|
||||||
from oslo_log import log as logging
|
|
||||||
|
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
LOG.warning('DEPRECATED: `heat-api-cloudwatch` script is deprecated. '
|
|
||||||
'Please use the system level heat binaries installed to '
|
|
||||||
'start any of the heat services.')
|
|
||||||
|
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
|
|
||||||
# If ../heat/__init__.py exists, add ../ to Python search path, so that
|
|
||||||
# it will override what happens to be installed in /usr/(local/)lib/python...
|
|
||||||
POSSIBLE_TOPDIR = os.path.normpath(os.path.join(os.path.abspath(sys.argv[0]),
|
|
||||||
os.pardir,
|
|
||||||
os.pardir))
|
|
||||||
|
|
||||||
if os.path.exists(os.path.join(POSSIBLE_TOPDIR, 'heat', '__init__.py')):
|
|
||||||
sys.path.insert(0, POSSIBLE_TOPDIR)
|
|
||||||
|
|
||||||
from heat.cmd import api_cloudwatch
|
|
||||||
|
|
||||||
api_cloudwatch.main()
|
|
@ -11,6 +11,5 @@ and CloudWatch and a native API.
|
|||||||
|
|
||||||
.. include:: ./tables/heat-api.rst
|
.. include:: ./tables/heat-api.rst
|
||||||
.. include:: ./tables/heat-cfn_api.rst
|
.. include:: ./tables/heat-cfn_api.rst
|
||||||
.. include:: ./tables/heat-cloudwatch_api.rst
|
|
||||||
.. include:: ./tables/heat-metadata_api.rst
|
.. include:: ./tables/heat-metadata_api.rst
|
||||||
.. include:: ./tables/heat-waitcondition_api.rst
|
.. include:: ./tables/heat-waitcondition_api.rst
|
||||||
|
@ -1,42 +0,0 @@
|
|||||||
..
|
|
||||||
Warning: Do not edit this file. It is automatically generated from the
|
|
||||||
software project's code and your changes will be overwritten.
|
|
||||||
|
|
||||||
The tool to generate this file lives in openstack-doc-tools repository.
|
|
||||||
|
|
||||||
Please make any changes needed in the code, then run the
|
|
||||||
autogenerate-config-doc tool from the openstack-doc-tools repository, or
|
|
||||||
ask for help on the documentation mailing list, IRC channel or meeting.
|
|
||||||
|
|
||||||
.. _heat-cloudwatch_api:
|
|
||||||
|
|
||||||
.. list-table:: Description of CloudWatch API configuration options
|
|
||||||
:header-rows: 1
|
|
||||||
:class: config-ref-table
|
|
||||||
|
|
||||||
* - Configuration option = Default value
|
|
||||||
- Description
|
|
||||||
* - **[DEFAULT]**
|
|
||||||
-
|
|
||||||
* - ``enable_cloud_watch_lite`` = ``False``
|
|
||||||
- (Boolean) Enable the legacy OS::Heat::CWLiteAlarm resource.
|
|
||||||
* - ``heat_watch_server_url`` =
|
|
||||||
- (String) URL of the Heat CloudWatch server.
|
|
||||||
* - **[heat_api_cloudwatch]**
|
|
||||||
-
|
|
||||||
* - ``backlog`` = ``4096``
|
|
||||||
- (Integer) Number of backlog requests to configure the socket with.
|
|
||||||
* - ``bind_host`` = ``0.0.0.0``
|
|
||||||
- (IP) Address to bind the server. Useful when selecting a particular network interface.
|
|
||||||
* - ``bind_port`` = ``8003``
|
|
||||||
- (Port number) The port on which the server will listen.
|
|
||||||
* - ``cert_file`` = ``None``
|
|
||||||
- (String) Location of the SSL certificate file to use for SSL mode.
|
|
||||||
* - ``key_file`` = ``None``
|
|
||||||
- (String) Location of the SSL key file to use for enabling SSL mode.
|
|
||||||
* - ``max_header_line`` = ``16384``
|
|
||||||
- (Integer) Maximum line size of message headers to be accepted. max_header_line may need to be increased when using large tokens (typically those generated by the Keystone v3 API with big service catalogs.)
|
|
||||||
* - ``tcp_keepidle`` = ``600``
|
|
||||||
- (Integer) The value for the socket option TCP_KEEPIDLE. This is the time in seconds that the connection must be idle before TCP starts sending keepalive probes.
|
|
||||||
* - ``workers`` = ``1``
|
|
||||||
- (Integer) Number of workers for Heat service.
|
|
@ -38,15 +38,6 @@ pipeline = cors http_proxy_to_wsgi cfnversionnegotiation osprofiler ec2authtoken
|
|||||||
[pipeline:heat-api-cfn-standalone]
|
[pipeline:heat-api-cfn-standalone]
|
||||||
pipeline = cors http_proxy_to_wsgi cfnversionnegotiation ec2authtoken context apicfnv1app
|
pipeline = cors http_proxy_to_wsgi cfnversionnegotiation ec2authtoken context apicfnv1app
|
||||||
|
|
||||||
# heat-api-cloudwatch pipeline
|
|
||||||
[pipeline:heat-api-cloudwatch]
|
|
||||||
pipeline = cors versionnegotiation osprofiler ec2authtoken authtoken context apicwapp
|
|
||||||
|
|
||||||
# heat-api-cloudwatch pipeline for standalone heat
|
|
||||||
# relies exclusively on authenticating with ec2 signed requests
|
|
||||||
[pipeline:heat-api-cloudwatch-standalone]
|
|
||||||
pipeline = cors versionnegotiation ec2authtoken context apicwapp
|
|
||||||
|
|
||||||
[app:apiv1app]
|
[app:apiv1app]
|
||||||
paste.app_factory = heat.common.wsgi:app_factory
|
paste.app_factory = heat.common.wsgi:app_factory
|
||||||
heat.app_factory = heat.api.openstack.v1:API
|
heat.app_factory = heat.api.openstack.v1:API
|
||||||
@ -55,10 +46,6 @@ heat.app_factory = heat.api.openstack.v1:API
|
|||||||
paste.app_factory = heat.common.wsgi:app_factory
|
paste.app_factory = heat.common.wsgi:app_factory
|
||||||
heat.app_factory = heat.api.cfn.v1:API
|
heat.app_factory = heat.api.cfn.v1:API
|
||||||
|
|
||||||
[app:apicwapp]
|
|
||||||
paste.app_factory = heat.common.wsgi:app_factory
|
|
||||||
heat.app_factory = heat.api.cloudwatch:API
|
|
||||||
|
|
||||||
[filter:versionnegotiation]
|
[filter:versionnegotiation]
|
||||||
paste.filter_factory = heat.common.wsgi:filter_factory
|
paste.filter_factory = heat.common.wsgi:filter_factory
|
||||||
heat.filter_factory = heat.api.openstack:version_negotiation_filter
|
heat.filter_factory = heat.api.openstack:version_negotiation_filter
|
||||||
@ -77,7 +64,6 @@ heat.filter_factory = heat.api.cfn:version_negotiation_filter
|
|||||||
|
|
||||||
[filter:cwversionnegotiation]
|
[filter:cwversionnegotiation]
|
||||||
paste.filter_factory = heat.common.wsgi:filter_factory
|
paste.filter_factory = heat.common.wsgi:filter_factory
|
||||||
heat.filter_factory = heat.api.cloudwatch:version_negotiation_filter
|
|
||||||
|
|
||||||
[filter:context]
|
[filter:context]
|
||||||
paste.filter_factory = heat.common.context:ContextMiddleware_filter_factory
|
paste.filter_factory = heat.common.context:ContextMiddleware_filter_factory
|
||||||
|
@ -1,67 +0,0 @@
|
|||||||
#
|
|
||||||
# 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 routes
|
|
||||||
import webob
|
|
||||||
|
|
||||||
from heat.api.cloudwatch import watch
|
|
||||||
from heat.api.middleware import version_negotiation as vn
|
|
||||||
from heat.api import versions
|
|
||||||
from heat.common import wsgi
|
|
||||||
|
|
||||||
|
|
||||||
class API(wsgi.Router):
|
|
||||||
|
|
||||||
"""WSGI router for Heat CloudWatch API."""
|
|
||||||
|
|
||||||
_actions = {
|
|
||||||
'delete_alarms': 'DeleteAlarms',
|
|
||||||
'describe_alarm_history': 'DescribeAlarmHistory',
|
|
||||||
'describe_alarms': 'DescribeAlarms',
|
|
||||||
'describe_alarms_for_metric': 'DescribeAlarmsForMetric',
|
|
||||||
'disable_alarm_actions': 'DisableAlarmActions',
|
|
||||||
'enable_alarm_actions': 'EnableAlarmActions',
|
|
||||||
'get_metric_statistics': 'GetMetricStatistics',
|
|
||||||
'list_metrics': 'ListMetrics',
|
|
||||||
'put_metric_alarm': 'PutMetricAlarm',
|
|
||||||
'put_metric_data': 'PutMetricData',
|
|
||||||
'set_alarm_state': 'SetAlarmState',
|
|
||||||
}
|
|
||||||
|
|
||||||
def __init__(self, conf, **local_conf):
|
|
||||||
self.conf = conf
|
|
||||||
mapper = routes.Mapper()
|
|
||||||
controller_resource = watch.create_resource(conf)
|
|
||||||
|
|
||||||
def conditions(action):
|
|
||||||
api_action = self._actions[action]
|
|
||||||
|
|
||||||
def action_match(environ, result):
|
|
||||||
req = webob.Request(environ)
|
|
||||||
env_action = req.params.get("Action")
|
|
||||||
return env_action == api_action
|
|
||||||
|
|
||||||
return {'function': action_match}
|
|
||||||
|
|
||||||
for action in self._actions:
|
|
||||||
mapper.connect("/", controller=controller_resource, action=action,
|
|
||||||
conditions=conditions(action))
|
|
||||||
|
|
||||||
mapper.connect("/", controller=controller_resource, action="index")
|
|
||||||
|
|
||||||
super(API, self).__init__(mapper)
|
|
||||||
|
|
||||||
|
|
||||||
def version_negotiation_filter(app, conf, **local_conf):
|
|
||||||
return vn.VersionNegotiationFilter(versions.Controller, app,
|
|
||||||
conf, **local_conf)
|
|
@ -1,321 +0,0 @@
|
|||||||
#
|
|
||||||
# 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.
|
|
||||||
|
|
||||||
"""Endpoint for heat AWS-compatible CloudWatch API."""
|
|
||||||
|
|
||||||
from oslo_log import log as logging
|
|
||||||
import oslo_messaging as messaging
|
|
||||||
import six
|
|
||||||
|
|
||||||
from heat.api.aws import exception
|
|
||||||
from heat.api.aws import utils as api_utils
|
|
||||||
from heat.common import exception as heat_exception
|
|
||||||
from heat.common.i18n import _
|
|
||||||
from heat.common import policy
|
|
||||||
from heat.common import wsgi
|
|
||||||
from heat.rpc import api as rpc_api
|
|
||||||
from heat.rpc import client as rpc_client
|
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
class WatchController(object):
|
|
||||||
|
|
||||||
"""WSGI controller for CloudWatch resource in heat API.
|
|
||||||
|
|
||||||
Implements the API actions.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, options):
|
|
||||||
self.options = options
|
|
||||||
self.rpc_client = rpc_client.EngineClient()
|
|
||||||
self.policy = policy.Enforcer(scope='cloudwatch')
|
|
||||||
|
|
||||||
def _enforce(self, req, action):
|
|
||||||
"""Authorize an action against the policy.json and policies in code."""
|
|
||||||
try:
|
|
||||||
self.policy.enforce(req.context, action, is_registered_policy=True)
|
|
||||||
except heat_exception.Forbidden:
|
|
||||||
msg = _("Action %s not allowed for user") % action
|
|
||||||
raise exception.HeatAccessDeniedError(msg)
|
|
||||||
except Exception:
|
|
||||||
# We expect policy.enforce to either pass or raise Forbidden
|
|
||||||
# however, if anything else happens, we want to raise
|
|
||||||
# HeatInternalFailureError, failure to do this results in
|
|
||||||
# the user getting a big stacktrace spew as an API response
|
|
||||||
msg = _("Error authorizing action %s") % action
|
|
||||||
raise exception.HeatInternalFailureError(msg)
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def _reformat_dimensions(dims):
|
|
||||||
"""Reformat dimensions list into AWS API format.
|
|
||||||
|
|
||||||
:param dims: a list of dicts.
|
|
||||||
"""
|
|
||||||
newdims = []
|
|
||||||
for count, d in enumerate(dims, 1):
|
|
||||||
for key, value in d.items():
|
|
||||||
newdims.append({'Name': key, 'Value': value})
|
|
||||||
return newdims
|
|
||||||
|
|
||||||
def delete_alarms(self, req):
|
|
||||||
"""Implements DeleteAlarms API action."""
|
|
||||||
self._enforce(req, 'DeleteAlarms')
|
|
||||||
return exception.HeatAPINotImplementedError()
|
|
||||||
|
|
||||||
def describe_alarm_history(self, req):
|
|
||||||
"""Implements DescribeAlarmHistory API action."""
|
|
||||||
self._enforce(req, 'DescribeAlarmHistory')
|
|
||||||
return exception.HeatAPINotImplementedError()
|
|
||||||
|
|
||||||
def describe_alarms(self, req):
|
|
||||||
"""Implements DescribeAlarms API action."""
|
|
||||||
self._enforce(req, 'DescribeAlarms')
|
|
||||||
|
|
||||||
def format_metric_alarm(a):
|
|
||||||
"""Reformat engine output into the AWS "MetricAlarm" format."""
|
|
||||||
keymap = {
|
|
||||||
rpc_api.WATCH_ACTIONS_ENABLED: 'ActionsEnabled',
|
|
||||||
rpc_api.WATCH_ALARM_ACTIONS: 'AlarmActions',
|
|
||||||
rpc_api.WATCH_TOPIC: 'AlarmArn',
|
|
||||||
rpc_api.WATCH_UPDATED_TIME:
|
|
||||||
'AlarmConfigurationUpdatedTimestamp',
|
|
||||||
rpc_api.WATCH_DESCRIPTION: 'AlarmDescription',
|
|
||||||
rpc_api.WATCH_NAME: 'AlarmName',
|
|
||||||
rpc_api.WATCH_COMPARISON: 'ComparisonOperator',
|
|
||||||
rpc_api.WATCH_DIMENSIONS: 'Dimensions',
|
|
||||||
rpc_api.WATCH_PERIODS: 'EvaluationPeriods',
|
|
||||||
rpc_api.WATCH_INSUFFICIENT_ACTIONS:
|
|
||||||
'InsufficientDataActions',
|
|
||||||
rpc_api.WATCH_METRIC_NAME: 'MetricName',
|
|
||||||
rpc_api.WATCH_NAMESPACE: 'Namespace',
|
|
||||||
rpc_api.WATCH_OK_ACTIONS: 'OKActions',
|
|
||||||
rpc_api.WATCH_PERIOD: 'Period',
|
|
||||||
rpc_api.WATCH_STATE_REASON: 'StateReason',
|
|
||||||
rpc_api.WATCH_STATE_REASON_DATA: 'StateReasonData',
|
|
||||||
rpc_api.WATCH_STATE_UPDATED_TIME: 'StateUpdatedTimestamp',
|
|
||||||
rpc_api.WATCH_STATE_VALUE: 'StateValue',
|
|
||||||
rpc_api.WATCH_STATISTIC: 'Statistic',
|
|
||||||
rpc_api.WATCH_THRESHOLD: 'Threshold',
|
|
||||||
rpc_api.WATCH_UNIT: 'Unit',
|
|
||||||
}
|
|
||||||
|
|
||||||
# AWS doesn't return StackId in the main MetricAlarm
|
|
||||||
# structure, so we add StackId as a dimension to all responses
|
|
||||||
a[rpc_api.WATCH_DIMENSIONS].append({'StackId':
|
|
||||||
a[rpc_api.WATCH_STACK_ID]})
|
|
||||||
|
|
||||||
# Reformat dimensions list into AWS API format
|
|
||||||
a[rpc_api.WATCH_DIMENSIONS] = self._reformat_dimensions(
|
|
||||||
a[rpc_api.WATCH_DIMENSIONS])
|
|
||||||
|
|
||||||
return api_utils.reformat_dict_keys(keymap, a)
|
|
||||||
|
|
||||||
con = req.context
|
|
||||||
parms = dict(req.params)
|
|
||||||
try:
|
|
||||||
name = parms['AlarmName']
|
|
||||||
except KeyError:
|
|
||||||
name = None
|
|
||||||
|
|
||||||
try:
|
|
||||||
watch_list = self.rpc_client.show_watch(con, watch_name=name)
|
|
||||||
except messaging.RemoteError as ex:
|
|
||||||
return exception.map_remote_error(ex)
|
|
||||||
|
|
||||||
res = {'MetricAlarms': [format_metric_alarm(a)
|
|
||||||
for a in watch_list]}
|
|
||||||
|
|
||||||
result = api_utils.format_response("DescribeAlarms", res)
|
|
||||||
return result
|
|
||||||
|
|
||||||
def describe_alarms_for_metric(self, req):
|
|
||||||
"""Implements DescribeAlarmsForMetric API action."""
|
|
||||||
self._enforce(req, 'DescribeAlarmsForMetric')
|
|
||||||
return exception.HeatAPINotImplementedError()
|
|
||||||
|
|
||||||
def disable_alarm_actions(self, req):
|
|
||||||
"""Implements DisableAlarmActions API action."""
|
|
||||||
self._enforce(req, 'DisableAlarmActions')
|
|
||||||
return exception.HeatAPINotImplementedError()
|
|
||||||
|
|
||||||
def enable_alarm_actions(self, req):
|
|
||||||
"""Implements EnableAlarmActions API action."""
|
|
||||||
self._enforce(req, 'EnableAlarmActions')
|
|
||||||
return exception.HeatAPINotImplementedError()
|
|
||||||
|
|
||||||
def get_metric_statistics(self, req):
|
|
||||||
"""Implements GetMetricStatistics API action."""
|
|
||||||
self._enforce(req, 'GetMetricStatistics')
|
|
||||||
return exception.HeatAPINotImplementedError()
|
|
||||||
|
|
||||||
def list_metrics(self, req):
|
|
||||||
"""Implements ListMetrics API action.
|
|
||||||
|
|
||||||
Lists metric datapoints associated with a particular alarm,
|
|
||||||
or all alarms if none specified.
|
|
||||||
"""
|
|
||||||
self._enforce(req, 'ListMetrics')
|
|
||||||
|
|
||||||
def format_metric_data(d, fil=None):
|
|
||||||
"""Reformat engine output into the AWS "Metric" format.
|
|
||||||
|
|
||||||
Takes an optional filter dict, which is traversed
|
|
||||||
so a metric dict is only returned if all keys match
|
|
||||||
the filter dict.
|
|
||||||
"""
|
|
||||||
fil = fil or {}
|
|
||||||
dimensions = [
|
|
||||||
{'AlarmName': d[rpc_api.WATCH_DATA_ALARM]},
|
|
||||||
{'Timestamp': d[rpc_api.WATCH_DATA_TIME]}
|
|
||||||
]
|
|
||||||
for key in d[rpc_api.WATCH_DATA]:
|
|
||||||
dimensions.append({key: d[rpc_api.WATCH_DATA][key]})
|
|
||||||
|
|
||||||
newdims = self._reformat_dimensions(dimensions)
|
|
||||||
|
|
||||||
result = {
|
|
||||||
'MetricName': d[rpc_api.WATCH_DATA_METRIC],
|
|
||||||
'Dimensions': newdims,
|
|
||||||
'Namespace': d[rpc_api.WATCH_DATA_NAMESPACE],
|
|
||||||
}
|
|
||||||
|
|
||||||
for f in fil:
|
|
||||||
try:
|
|
||||||
value = result[f]
|
|
||||||
if value != fil[f]:
|
|
||||||
# Filter criteria not met, return None
|
|
||||||
return
|
|
||||||
except KeyError:
|
|
||||||
LOG.warning("Invalid filter key %s, ignoring", f)
|
|
||||||
|
|
||||||
return result
|
|
||||||
|
|
||||||
con = req.context
|
|
||||||
parms = dict(req.params)
|
|
||||||
# FIXME : Don't yet handle filtering by Dimensions
|
|
||||||
filter_result = dict((k, v) for (k, v) in six.iteritems(parms) if k in
|
|
||||||
("MetricName", "Namespace"))
|
|
||||||
LOG.debug("filter parameters : %s" % filter_result)
|
|
||||||
|
|
||||||
try:
|
|
||||||
# Engine does not currently support query by namespace/metric
|
|
||||||
# so we pass None/None and do any filtering locally
|
|
||||||
null_kwargs = {'metric_namespace': None,
|
|
||||||
'metric_name': None}
|
|
||||||
watch_data = self.rpc_client.show_watch_metric(con,
|
|
||||||
**null_kwargs)
|
|
||||||
except messaging.RemoteError as ex:
|
|
||||||
return exception.map_remote_error(ex)
|
|
||||||
|
|
||||||
res = {'Metrics': []}
|
|
||||||
for d in watch_data:
|
|
||||||
metric = format_metric_data(d, filter_result)
|
|
||||||
if metric:
|
|
||||||
res['Metrics'].append(metric)
|
|
||||||
|
|
||||||
result = api_utils.format_response("ListMetrics", res)
|
|
||||||
return result
|
|
||||||
|
|
||||||
def put_metric_alarm(self, req):
|
|
||||||
"""Implements PutMetricAlarm API action."""
|
|
||||||
self._enforce(req, 'PutMetricAlarm')
|
|
||||||
return exception.HeatAPINotImplementedError()
|
|
||||||
|
|
||||||
def put_metric_data(self, req):
|
|
||||||
"""Implements PutMetricData API action."""
|
|
||||||
self._enforce(req, 'PutMetricData')
|
|
||||||
|
|
||||||
con = req.context
|
|
||||||
parms = dict(req.params)
|
|
||||||
namespace = api_utils.get_param_value(parms, 'Namespace')
|
|
||||||
|
|
||||||
# Extract data from the request so we can pass it to the engine
|
|
||||||
# We have to do this in two passes, because the AWS
|
|
||||||
# query format nests the dimensions within the MetricData
|
|
||||||
# query-parameter-list (see AWS PutMetricData docs)
|
|
||||||
# extract_param_list gives a list-of-dict, which we then
|
|
||||||
# need to process (each dict) for dimensions
|
|
||||||
metric_data = api_utils.extract_param_list(parms, prefix='MetricData')
|
|
||||||
if not len(metric_data):
|
|
||||||
LOG.error("Request does not contain required MetricData")
|
|
||||||
return exception.HeatMissingParameterError(_("MetricData list"))
|
|
||||||
|
|
||||||
watch_name = None
|
|
||||||
dimensions = []
|
|
||||||
for p in metric_data:
|
|
||||||
dimension = api_utils.extract_param_pairs(p,
|
|
||||||
prefix='Dimensions',
|
|
||||||
keyname='Name',
|
|
||||||
valuename='Value')
|
|
||||||
if 'AlarmName' in dimension:
|
|
||||||
watch_name = dimension['AlarmName']
|
|
||||||
else:
|
|
||||||
dimensions.append(dimension)
|
|
||||||
|
|
||||||
# Extract the required data from the metric_data
|
|
||||||
# and format dict to pass to engine
|
|
||||||
data = {'Namespace': namespace,
|
|
||||||
api_utils.get_param_value(metric_data[0], 'MetricName'): {
|
|
||||||
'Unit': api_utils.get_param_value(metric_data[0], 'Unit'),
|
|
||||||
'Value': api_utils.get_param_value(metric_data[0],
|
|
||||||
'Value'),
|
|
||||||
'Dimensions': dimensions}}
|
|
||||||
|
|
||||||
try:
|
|
||||||
self.rpc_client.create_watch_data(con, watch_name, data)
|
|
||||||
except messaging.RemoteError as ex:
|
|
||||||
return exception.map_remote_error(ex)
|
|
||||||
|
|
||||||
result = {'ResponseMetadata': None}
|
|
||||||
return api_utils.format_response("PutMetricData", result)
|
|
||||||
|
|
||||||
def set_alarm_state(self, req):
|
|
||||||
"""Implements SetAlarmState API action."""
|
|
||||||
self._enforce(req, 'SetAlarmState')
|
|
||||||
|
|
||||||
# Map from AWS state names to those used in the engine
|
|
||||||
state_map = {'OK': rpc_api.WATCH_STATE_OK,
|
|
||||||
'ALARM': rpc_api.WATCH_STATE_ALARM,
|
|
||||||
'INSUFFICIENT_DATA': rpc_api.WATCH_STATE_NODATA}
|
|
||||||
|
|
||||||
con = req.context
|
|
||||||
parms = dict(req.params)
|
|
||||||
|
|
||||||
# Get mandatory parameters
|
|
||||||
name = api_utils.get_param_value(parms, 'AlarmName')
|
|
||||||
state = api_utils.get_param_value(parms, 'StateValue')
|
|
||||||
|
|
||||||
if state not in state_map:
|
|
||||||
msg = _('Invalid state %(state)s, '
|
|
||||||
'expecting one of %(expect)s') % {
|
|
||||||
'state': state,
|
|
||||||
'expect': list(state_map.keys())}
|
|
||||||
LOG.error(msg)
|
|
||||||
return exception.HeatInvalidParameterValueError(msg)
|
|
||||||
|
|
||||||
LOG.debug("setting %(name)s to %(state)s" % {
|
|
||||||
'name': name, 'state': state_map[state]})
|
|
||||||
try:
|
|
||||||
self.rpc_client.set_watch_state(con, watch_name=name,
|
|
||||||
state=state_map[state])
|
|
||||||
except messaging.RemoteError as ex:
|
|
||||||
return exception.map_remote_error(ex)
|
|
||||||
|
|
||||||
return api_utils.format_response("SetAlarmState", "")
|
|
||||||
|
|
||||||
|
|
||||||
def create_resource(options):
|
|
||||||
"""Watch resource factory method."""
|
|
||||||
deserializer = wsgi.JSONRequestDeserializer()
|
|
||||||
return wsgi.Resource(WatchController(options), deserializer)
|
|
@ -25,7 +25,6 @@ import sys
|
|||||||
|
|
||||||
from heat.cmd import api
|
from heat.cmd import api
|
||||||
from heat.cmd import api_cfn
|
from heat.cmd import api_cfn
|
||||||
from heat.cmd import api_cloudwatch
|
|
||||||
from heat.cmd import engine
|
from heat.cmd import engine
|
||||||
from heat.common import config
|
from heat.common import config
|
||||||
from heat.common import messaging
|
from heat.common import messaging
|
||||||
@ -45,7 +44,6 @@ LAUNCH_SERVICES = {
|
|||||||
'engine': [engine.launch_engine, {'setup_logging': False}],
|
'engine': [engine.launch_engine, {'setup_logging': False}],
|
||||||
'api': [api.launch_api, API_LAUNCH_OPTS],
|
'api': [api.launch_api, API_LAUNCH_OPTS],
|
||||||
'api_cfn': [api_cfn.launch_cfn_api, API_LAUNCH_OPTS],
|
'api_cfn': [api_cfn.launch_cfn_api, API_LAUNCH_OPTS],
|
||||||
'api_cloudwatch': [api_cloudwatch.launch_cloudwatch_api, API_LAUNCH_OPTS],
|
|
||||||
}
|
}
|
||||||
|
|
||||||
services_opt = cfg.ListOpt(
|
services_opt = cfg.ListOpt(
|
||||||
@ -53,7 +51,7 @@ services_opt = cfg.ListOpt(
|
|||||||
default=['engine', 'api', 'api_cfn'],
|
default=['engine', 'api', 'api_cfn'],
|
||||||
help='Specifies the heat services that are enabled when running heat-all. '
|
help='Specifies the heat services that are enabled when running heat-all. '
|
||||||
'Valid options are all or any combination of '
|
'Valid options are all or any combination of '
|
||||||
'api, engine, api_cfn, or api_cloudwatch.'
|
'api, engine or api_cfn.'
|
||||||
)
|
)
|
||||||
|
|
||||||
cfg.CONF.register_opt(services_opt, group='heat_all')
|
cfg.CONF.register_opt(services_opt, group='heat_all')
|
||||||
|
@ -1,78 +0,0 @@
|
|||||||
#!/usr/bin/env python
|
|
||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
||||||
# not use this file except in compliance with the License. You may obtain
|
|
||||||
# a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
# License for the specific language governing permissions and limitations
|
|
||||||
# under the License.
|
|
||||||
|
|
||||||
"""Heat API Server.
|
|
||||||
|
|
||||||
This implements an approximation of the Amazon CloudWatch API and translates it
|
|
||||||
into a native representation. It then calls the heat-engine via AMQP RPC to
|
|
||||||
implement them.
|
|
||||||
"""
|
|
||||||
|
|
||||||
import eventlet
|
|
||||||
eventlet.monkey_patch(os=False)
|
|
||||||
|
|
||||||
import sys
|
|
||||||
|
|
||||||
from oslo_config import cfg
|
|
||||||
import oslo_i18n as i18n
|
|
||||||
from oslo_log import log as logging
|
|
||||||
from oslo_reports import guru_meditation_report as gmr
|
|
||||||
from oslo_service import systemd
|
|
||||||
import six
|
|
||||||
|
|
||||||
from heat.common import config
|
|
||||||
from heat.common import messaging
|
|
||||||
from heat.common import profiler
|
|
||||||
from heat.common import wsgi
|
|
||||||
from heat import version
|
|
||||||
|
|
||||||
i18n.enable_lazy()
|
|
||||||
|
|
||||||
LOG = logging.getLogger('heat.api.cloudwatch')
|
|
||||||
|
|
||||||
|
|
||||||
def launch_cloudwatch_api(setup_logging=True):
|
|
||||||
if setup_logging:
|
|
||||||
logging.register_options(cfg.CONF)
|
|
||||||
cfg.CONF(project='heat',
|
|
||||||
prog='heat-api-cloudwatch',
|
|
||||||
version=version.version_info.version_string())
|
|
||||||
if setup_logging:
|
|
||||||
logging.setup(cfg.CONF, 'heat-api-cloudwatch')
|
|
||||||
logging.set_defaults()
|
|
||||||
config.set_config_defaults()
|
|
||||||
messaging.setup()
|
|
||||||
|
|
||||||
app = config.load_paste_app()
|
|
||||||
|
|
||||||
port = cfg.CONF.heat_api_cloudwatch.bind_port
|
|
||||||
host = cfg.CONF.heat_api_cloudwatch.bind_host
|
|
||||||
LOG.info('Starting Heat CloudWatch API on %(host)s:%(port)s',
|
|
||||||
{'host': host, 'port': port})
|
|
||||||
profiler.setup('heat-api-cloudwatch', host)
|
|
||||||
gmr.TextGuruMeditation.setup_autorun(version)
|
|
||||||
server = wsgi.Server('heat-api-cloudwatch',
|
|
||||||
cfg.CONF.heat_api_cloudwatch)
|
|
||||||
server.start(app, default_port=port)
|
|
||||||
return server
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
try:
|
|
||||||
server = launch_cloudwatch_api()
|
|
||||||
systemd.notify_once()
|
|
||||||
server.wait()
|
|
||||||
except RuntimeError as e:
|
|
||||||
msg = six.text_type(e)
|
|
||||||
sys.exit("ERROR: %s" % msg)
|
|
@ -136,34 +136,58 @@ api_cw_opts = [
|
|||||||
cfg.IPOpt('bind_host', default='0.0.0.0',
|
cfg.IPOpt('bind_host', default='0.0.0.0',
|
||||||
help=_('Address to bind the server. Useful when '
|
help=_('Address to bind the server. Useful when '
|
||||||
'selecting a particular network interface.'),
|
'selecting a particular network interface.'),
|
||||||
deprecated_group='DEFAULT'),
|
deprecated_group='DEFAULT',
|
||||||
|
deprecated_for_removal=True,
|
||||||
|
deprecated_reason='Heat CloudWatch API has been removed.',
|
||||||
|
deprecated_since='10.0.0'),
|
||||||
cfg.PortOpt('bind_port', default=8003,
|
cfg.PortOpt('bind_port', default=8003,
|
||||||
help=_('The port on which the server will listen.'),
|
help=_('The port on which the server will listen.'),
|
||||||
deprecated_group='DEFAULT'),
|
deprecated_group='DEFAULT',
|
||||||
|
deprecated_for_removal=True,
|
||||||
|
deprecated_reason='Heat CloudWatch API has been removed.',
|
||||||
|
deprecated_since='10.0.0'),
|
||||||
cfg.IntOpt('backlog', default=4096,
|
cfg.IntOpt('backlog', default=4096,
|
||||||
help=_("Number of backlog requests "
|
help=_("Number of backlog requests "
|
||||||
"to configure the socket with."),
|
"to configure the socket with."),
|
||||||
deprecated_group='DEFAULT'),
|
deprecated_group='DEFAULT',
|
||||||
|
deprecated_for_removal=True,
|
||||||
|
deprecated_reason='Heat CloudWatch API has been removed.',
|
||||||
|
deprecated_since='10.0.0'),
|
||||||
cfg.StrOpt('cert_file',
|
cfg.StrOpt('cert_file',
|
||||||
help=_("Location of the SSL certificate file "
|
help=_("Location of the SSL certificate file "
|
||||||
"to use for SSL mode."),
|
"to use for SSL mode."),
|
||||||
deprecated_group='DEFAULT'),
|
deprecated_group='DEFAULT',
|
||||||
|
deprecated_for_removal=True,
|
||||||
|
deprecated_reason='Heat CloudWatch API has been Removed.',
|
||||||
|
deprecated_since='10.0.0'),
|
||||||
cfg.StrOpt('key_file',
|
cfg.StrOpt('key_file',
|
||||||
help=_("Location of the SSL key file to use "
|
help=_("Location of the SSL key file to use "
|
||||||
"for enabling SSL mode."),
|
"for enabling SSL mode."),
|
||||||
deprecated_group='DEFAULT'),
|
deprecated_group='DEFAULT',
|
||||||
|
deprecated_for_removal=True,
|
||||||
|
deprecated_reason='Heat CloudWatch API has been Removed.',
|
||||||
|
deprecated_since='10.0.0'),
|
||||||
cfg.IntOpt('workers', min=0, default=1,
|
cfg.IntOpt('workers', min=0, default=1,
|
||||||
help=_("Number of workers for Heat service."),
|
help=_("Number of workers for Heat service."),
|
||||||
deprecated_group='DEFAULT'),
|
deprecated_group='DEFAULT',
|
||||||
|
deprecated_for_removal=True,
|
||||||
|
deprecated_reason='Heat CloudWatch API has been Removed.',
|
||||||
|
deprecated_since='10.0.0'),
|
||||||
cfg.IntOpt('max_header_line', default=16384,
|
cfg.IntOpt('max_header_line', default=16384,
|
||||||
help=_('Maximum line size of message headers to be accepted. '
|
help=_('Maximum line size of message headers to be accepted. '
|
||||||
'max_header_line may need to be increased when using '
|
'max_header_line may need to be increased when using '
|
||||||
'large tokens (typically those generated by the '
|
'large tokens (typically those generated by the '
|
||||||
'Keystone v3 API with big service catalogs.)')),
|
'Keystone v3 API with big service catalogs.)'),
|
||||||
|
deprecated_for_removal=True,
|
||||||
|
deprecated_reason='Heat CloudWatch API has been Removed.',
|
||||||
|
deprecated_since='10.0.0'),
|
||||||
cfg.IntOpt('tcp_keepidle', default=600,
|
cfg.IntOpt('tcp_keepidle', default=600,
|
||||||
help=_('The value for the socket option TCP_KEEPIDLE. This is '
|
help=_('The value for the socket option TCP_KEEPIDLE. This is '
|
||||||
'the time in seconds that the connection must be idle '
|
'the time in seconds that the connection must be idle '
|
||||||
'before TCP starts sending keepalive probes.')),
|
'before TCP starts sending keepalive probes.'),
|
||||||
|
deprecated_for_removal=True,
|
||||||
|
deprecated_reason='Heat CloudWatch API has been Removed.',
|
||||||
|
deprecated_since='10.0.0')
|
||||||
]
|
]
|
||||||
api_cw_group = cfg.OptGroup('heat_api_cloudwatch')
|
api_cw_group = cfg.OptGroup('heat_api_cloudwatch')
|
||||||
cfg.CONF.register_group(api_cw_group)
|
cfg.CONF.register_group(api_cw_group)
|
||||||
|
@ -1,14 +0,0 @@
|
|||||||
[uwsgi]
|
|
||||||
chmod-socket = 666
|
|
||||||
lazy-apps = true
|
|
||||||
add-header = Connection: close
|
|
||||||
buffer-size = 65535
|
|
||||||
thunder-lock = true
|
|
||||||
plugins = python
|
|
||||||
enable-threads = true
|
|
||||||
exit-on-reload = true
|
|
||||||
die-on-term = true
|
|
||||||
master = true
|
|
||||||
processes = 4
|
|
||||||
http = 127.0.0.1:80997
|
|
||||||
wsgi-file = /usr/local/bin/heat-wsgi-api-cloudwatch
|
|
@ -1,28 +0,0 @@
|
|||||||
Listen %PUBLICPORT%
|
|
||||||
|
|
||||||
<VirtualHost *:%PUBLICPORT%>
|
|
||||||
WSGIDaemonProcess heat-api-cloudwatch processes=%API_WORKERS% threads=1 user=%USER% display-name=%{GROUP} %VIRTUALENV%
|
|
||||||
WSGIProcessGroup heat-api-cloudwatch
|
|
||||||
WSGIScriptAlias / %HEAT_BIN_DIR%/heat-wsgi-api-cloudwatch
|
|
||||||
WSGIApplicationGroup %{GLOBAL}
|
|
||||||
WSGIPassAuthorization On
|
|
||||||
AllowEncodedSlashes On
|
|
||||||
<IfVersion >= 2.4>
|
|
||||||
ErrorLogFormat "%{cu}t %M"
|
|
||||||
</IfVersion>
|
|
||||||
ErrorLog /var/log/%APACHE_NAME%/heat_api_cloudwatch.log
|
|
||||||
CustomLog /var/log/%APACHE_NAME%/heat_api_cloudwatch_access.log combined
|
|
||||||
%SSLENGINE%
|
|
||||||
%SSLCERTFILE%
|
|
||||||
%SSLKEYFILE%
|
|
||||||
|
|
||||||
<Directory %HEAT_BIN_DIR%>
|
|
||||||
<IfVersion >= 2.4>
|
|
||||||
Require all granted
|
|
||||||
</IfVersion>
|
|
||||||
<IfVersion < 2.4>
|
|
||||||
Order allow,deny
|
|
||||||
Allow from all
|
|
||||||
</IfVersion>
|
|
||||||
</Directory>
|
|
||||||
</VirtualHost>
|
|
@ -1,2 +0,0 @@
|
|||||||
KeepAlive Off
|
|
||||||
ProxyPass "/heat-api-cloudwatch" "http://127.0.0.1:80997" retry=0
|
|
@ -1,51 +0,0 @@
|
|||||||
#!/usr/bin/env python
|
|
||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
||||||
# not use this file except in compliance with the License. You may obtain
|
|
||||||
# a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
# License for the specific language governing permissions and limitations
|
|
||||||
# under the License.
|
|
||||||
|
|
||||||
"""WSGI script for heat-api-cloudwatch.
|
|
||||||
|
|
||||||
Script for running heat-api-cloudwatch under Apache2.
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
from oslo_config import cfg
|
|
||||||
import oslo_i18n as i18n
|
|
||||||
from oslo_log import log as logging
|
|
||||||
|
|
||||||
from heat.common import config
|
|
||||||
from heat.common import messaging
|
|
||||||
from heat.common import profiler
|
|
||||||
from heat import version
|
|
||||||
|
|
||||||
|
|
||||||
def init_application():
|
|
||||||
i18n.enable_lazy()
|
|
||||||
|
|
||||||
LOG = logging.getLogger('heat.api.cloudwatch')
|
|
||||||
|
|
||||||
logging.register_options(cfg.CONF)
|
|
||||||
cfg.CONF(project='heat',
|
|
||||||
prog='heat-api-cloudwatch',
|
|
||||||
version=version.version_info.version_string())
|
|
||||||
logging.setup(cfg.CONF, 'heat-api-cloudwatch')
|
|
||||||
logging.set_defaults()
|
|
||||||
config.set_config_defaults()
|
|
||||||
messaging.setup()
|
|
||||||
|
|
||||||
port = cfg.CONF.heat_api_cloudwatch.bind_port
|
|
||||||
host = cfg.CONF.heat_api_cloudwatch.bind_host
|
|
||||||
LOG.info('Starting Heat CloudWatch API on %(host)s:%(port)s',
|
|
||||||
{'host': host, 'port': port})
|
|
||||||
profiler.setup('heat-api-cloudwatch', host)
|
|
||||||
|
|
||||||
return config.load_paste_app()
|
|
@ -17,7 +17,6 @@ from heat.policies import actions
|
|||||||
from heat.policies import base
|
from heat.policies import base
|
||||||
from heat.policies import build_info
|
from heat.policies import build_info
|
||||||
from heat.policies import cloudformation
|
from heat.policies import cloudformation
|
||||||
from heat.policies import cloudwatch
|
|
||||||
from heat.policies import events
|
from heat.policies import events
|
||||||
from heat.policies import resource
|
from heat.policies import resource
|
||||||
from heat.policies import resource_types
|
from heat.policies import resource_types
|
||||||
@ -33,7 +32,6 @@ def list_rules():
|
|||||||
actions.list_rules(),
|
actions.list_rules(),
|
||||||
build_info.list_rules(),
|
build_info.list_rules(),
|
||||||
cloudformation.list_rules(),
|
cloudformation.list_rules(),
|
||||||
cloudwatch.list_rules(),
|
|
||||||
events.list_rules(),
|
events.list_rules(),
|
||||||
resource.list_rules(),
|
resource.list_rules(),
|
||||||
resource_types.list_rules(),
|
resource_types.list_rules(),
|
||||||
|
@ -1,63 +0,0 @@
|
|||||||
# 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 oslo_policy import policy
|
|
||||||
|
|
||||||
from heat.policies import base
|
|
||||||
|
|
||||||
|
|
||||||
# These policies are for AWS CloudWatch-like APIs, so we won't list out the URI
|
|
||||||
# paths in rules.
|
|
||||||
|
|
||||||
|
|
||||||
POLICY_ROOT = 'cloudwatch:%s'
|
|
||||||
|
|
||||||
|
|
||||||
cloudwatch_policies = [
|
|
||||||
policy.RuleDefault(
|
|
||||||
name=POLICY_ROOT % 'DeleteAlarms',
|
|
||||||
check_str=base.RULE_DENY_STACK_USER),
|
|
||||||
policy.RuleDefault(
|
|
||||||
name=POLICY_ROOT % 'DescribeAlarmHistory',
|
|
||||||
check_str=base.RULE_DENY_STACK_USER),
|
|
||||||
policy.RuleDefault(
|
|
||||||
name=POLICY_ROOT % 'DescribeAlarms',
|
|
||||||
check_str=base.RULE_DENY_STACK_USER),
|
|
||||||
policy.RuleDefault(
|
|
||||||
name=POLICY_ROOT % 'DescribeAlarmsForMetric',
|
|
||||||
check_str=base.RULE_DENY_STACK_USER),
|
|
||||||
policy.RuleDefault(
|
|
||||||
name=POLICY_ROOT % 'DisableAlarmActions',
|
|
||||||
check_str=base.RULE_DENY_STACK_USER),
|
|
||||||
policy.RuleDefault(
|
|
||||||
name=POLICY_ROOT % 'EnableAlarmActions',
|
|
||||||
check_str=base.RULE_DENY_STACK_USER),
|
|
||||||
policy.RuleDefault(
|
|
||||||
name=POLICY_ROOT % 'GetMetricStatistics',
|
|
||||||
check_str=base.RULE_DENY_STACK_USER),
|
|
||||||
policy.RuleDefault(
|
|
||||||
name=POLICY_ROOT % 'ListMetrics',
|
|
||||||
check_str=base.RULE_DENY_STACK_USER),
|
|
||||||
policy.RuleDefault(
|
|
||||||
name=POLICY_ROOT % 'PutMetricAlarm',
|
|
||||||
check_str=base.RULE_DENY_STACK_USER),
|
|
||||||
policy.RuleDefault(
|
|
||||||
name=POLICY_ROOT % 'PutMetricData',
|
|
||||||
check_str=base.RULE_ALLOW_EVERYBODY),
|
|
||||||
policy.RuleDefault(
|
|
||||||
name=POLICY_ROOT % 'SetAlarmState',
|
|
||||||
check_str=base.RULE_DENY_STACK_USER)
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
def list_rules():
|
|
||||||
return cloudwatch_policies
|
|
@ -1,539 +0,0 @@
|
|||||||
#
|
|
||||||
# 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 os
|
|
||||||
|
|
||||||
from oslo_config import fixture as config_fixture
|
|
||||||
|
|
||||||
from heat.api.aws import exception
|
|
||||||
import heat.api.cloudwatch.watch as watches
|
|
||||||
from heat.common import policy
|
|
||||||
from heat.common import wsgi
|
|
||||||
from heat.rpc import api as rpc_api
|
|
||||||
from heat.rpc import client as rpc_client
|
|
||||||
from heat.tests import common
|
|
||||||
from heat.tests import utils
|
|
||||||
|
|
||||||
|
|
||||||
class WatchControllerTest(common.HeatTestCase):
|
|
||||||
"""Tests the API class WatchController.
|
|
||||||
|
|
||||||
Tests the API class which acts as the WSGI controller,
|
|
||||||
the endpoint processing API requests after they are routed
|
|
||||||
"""
|
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
super(WatchControllerTest, self).setUp()
|
|
||||||
self.path = os.path.dirname(os.path.realpath(__file__))
|
|
||||||
self.policy_path = self.path + "/../../policy/"
|
|
||||||
self.fixture = self.useFixture(config_fixture.Config())
|
|
||||||
self.fixture.conf(args=['--config-dir', self.policy_path])
|
|
||||||
self.topic = rpc_api.ENGINE_TOPIC
|
|
||||||
self.api_version = '1.0'
|
|
||||||
|
|
||||||
# Create WSGI controller instance
|
|
||||||
class DummyConfig(object):
|
|
||||||
bind_port = 8003
|
|
||||||
cfgopts = DummyConfig()
|
|
||||||
self.controller = watches.WatchController(options=cfgopts)
|
|
||||||
self.controller.policy.enforcer.policy_path = (self.policy_path +
|
|
||||||
'deny_stack_user.json')
|
|
||||||
self.addCleanup(self.m.VerifyAll)
|
|
||||||
|
|
||||||
def _dummy_GET_request(self, params=None):
|
|
||||||
# Mangle the params dict into a query string
|
|
||||||
params = params or {}
|
|
||||||
qs = "&".join(["=".join([k, str(params[k])]) for k in params])
|
|
||||||
environ = {'REQUEST_METHOD': 'GET', 'QUERY_STRING': qs}
|
|
||||||
req = wsgi.Request(environ)
|
|
||||||
req.context = utils.dummy_context()
|
|
||||||
return req
|
|
||||||
|
|
||||||
# The tests
|
|
||||||
def test_reformat_dimensions(self):
|
|
||||||
|
|
||||||
dims = [{'StackId': u'21617058-781e-4262-97ab-5f9df371ee52',
|
|
||||||
'Foo': 'bar'}]
|
|
||||||
self.assertEqual(
|
|
||||||
[{'Name': 'Foo',
|
|
||||||
'Value': 'bar'},
|
|
||||||
{'Name': 'StackId',
|
|
||||||
'Value': u'21617058-781e-4262-97ab-5f9df371ee52'}],
|
|
||||||
sorted((self.controller._reformat_dimensions(dims)),
|
|
||||||
key=lambda k: k['Name']))
|
|
||||||
|
|
||||||
def test_enforce_default(self):
|
|
||||||
self.m.ReplayAll()
|
|
||||||
params = {'Action': 'ListMetrics'}
|
|
||||||
dummy_req = self._dummy_GET_request(params)
|
|
||||||
self.controller.policy.policy_path = None
|
|
||||||
response = self.controller._enforce(dummy_req, 'ListMetrics')
|
|
||||||
self.assertIsNone(response)
|
|
||||||
self.m.VerifyAll()
|
|
||||||
|
|
||||||
def test_enforce_denied(self):
|
|
||||||
self.m.ReplayAll()
|
|
||||||
params = {'Action': 'ListMetrics'}
|
|
||||||
dummy_req = self._dummy_GET_request(params)
|
|
||||||
dummy_req.context.roles = ['heat_stack_user']
|
|
||||||
self.controller.policy.policy_path = (self.policy_path +
|
|
||||||
'deny_stack_user.json')
|
|
||||||
self.assertRaises(exception.HeatAccessDeniedError,
|
|
||||||
self.controller._enforce, dummy_req, 'ListMetrics')
|
|
||||||
self.m.VerifyAll()
|
|
||||||
|
|
||||||
def test_enforce_ise(self):
|
|
||||||
params = {'Action': 'ListMetrics'}
|
|
||||||
dummy_req = self._dummy_GET_request(params)
|
|
||||||
dummy_req.context.roles = ['heat_stack_user']
|
|
||||||
|
|
||||||
self.m.StubOutWithMock(policy.Enforcer, 'enforce')
|
|
||||||
policy.Enforcer.enforce(dummy_req.context, 'ListMetrics'
|
|
||||||
).AndRaise(AttributeError)
|
|
||||||
self.m.ReplayAll()
|
|
||||||
|
|
||||||
self.controller.policy.policy_path = (self.policy_path +
|
|
||||||
'deny_stack_user.json')
|
|
||||||
self.assertRaises(exception.HeatInternalFailureError,
|
|
||||||
self.controller._enforce, dummy_req, 'ListMetrics')
|
|
||||||
self.m.VerifyAll()
|
|
||||||
|
|
||||||
def test_delete(self):
|
|
||||||
# Not yet implemented, should raise HeatAPINotImplementedError
|
|
||||||
params = {'Action': 'DeleteAlarms'}
|
|
||||||
dummy_req = self._dummy_GET_request(params)
|
|
||||||
result = self.controller.delete_alarms(dummy_req)
|
|
||||||
self.assertIsInstance(result, exception.HeatAPINotImplementedError)
|
|
||||||
|
|
||||||
def test_describe_alarm_history(self):
|
|
||||||
# Not yet implemented, should raise HeatAPINotImplementedError
|
|
||||||
params = {'Action': 'DescribeAlarmHistory'}
|
|
||||||
dummy_req = self._dummy_GET_request(params)
|
|
||||||
result = self.controller.describe_alarm_history(dummy_req)
|
|
||||||
self.assertIsInstance(result, exception.HeatAPINotImplementedError)
|
|
||||||
|
|
||||||
def test_describe_all(self):
|
|
||||||
watch_name = None # Get all watches
|
|
||||||
|
|
||||||
# Format a dummy GET request to pass into the WSGI handler
|
|
||||||
params = {'Action': 'DescribeAlarms'}
|
|
||||||
dummy_req = self._dummy_GET_request(params)
|
|
||||||
|
|
||||||
# Stub out the RPC call to the engine with a pre-canned response
|
|
||||||
engine_resp = [{u'state_updated_time': u'2012-08-30T14:13:21Z',
|
|
||||||
u'stack_id': u'21617058-781e-4262-97ab-5f9df371ee52',
|
|
||||||
u'period': u'300',
|
|
||||||
u'actions': [u'WebServerRestartPolicy'],
|
|
||||||
u'topic': None,
|
|
||||||
u'periods': u'1',
|
|
||||||
u'statistic': u'SampleCount',
|
|
||||||
u'threshold': u'2',
|
|
||||||
u'unit': None,
|
|
||||||
u'state_reason': None,
|
|
||||||
u'dimensions': [],
|
|
||||||
u'namespace': u'system/linux',
|
|
||||||
u'state_value': u'NORMAL',
|
|
||||||
u'ok_actions': None,
|
|
||||||
u'description': u'Restart the WikiDatabase',
|
|
||||||
u'actions_enabled': None,
|
|
||||||
u'state_reason_data': None,
|
|
||||||
u'insufficient_actions': None,
|
|
||||||
u'metric_name': u'ServiceFailure',
|
|
||||||
u'comparison': u'GreaterThanThreshold',
|
|
||||||
u'name': u'HttpFailureAlarm',
|
|
||||||
u'updated_time': u'2012-08-30T14:10:46Z'}]
|
|
||||||
|
|
||||||
self.m.StubOutWithMock(rpc_client.EngineClient, 'call')
|
|
||||||
rpc_client.EngineClient.call(
|
|
||||||
dummy_req.context,
|
|
||||||
('show_watch', {'watch_name': watch_name})
|
|
||||||
).AndReturn(engine_resp)
|
|
||||||
|
|
||||||
self.m.ReplayAll()
|
|
||||||
|
|
||||||
expected = {'DescribeAlarmsResponse': {'DescribeAlarmsResult':
|
|
||||||
{'MetricAlarms': [
|
|
||||||
{'EvaluationPeriods': u'1',
|
|
||||||
'StateReasonData': None,
|
|
||||||
'AlarmArn': None,
|
|
||||||
'StateUpdatedTimestamp': u'2012-08-30T14:13:21Z',
|
|
||||||
'AlarmConfigurationUpdatedTimestamp':
|
|
||||||
u'2012-08-30T14:10:46Z',
|
|
||||||
'AlarmActions': [u'WebServerRestartPolicy'],
|
|
||||||
'Threshold': u'2',
|
|
||||||
'AlarmDescription': u'Restart the WikiDatabase',
|
|
||||||
'Namespace': u'system/linux',
|
|
||||||
'Period': u'300',
|
|
||||||
'StateValue': u'NORMAL',
|
|
||||||
'ComparisonOperator': u'GreaterThanThreshold',
|
|
||||||
'AlarmName': u'HttpFailureAlarm',
|
|
||||||
'Unit': None,
|
|
||||||
'Statistic': u'SampleCount',
|
|
||||||
'StateReason': None,
|
|
||||||
'InsufficientDataActions': None,
|
|
||||||
'OKActions': None,
|
|
||||||
'MetricName': u'ServiceFailure',
|
|
||||||
'ActionsEnabled': None,
|
|
||||||
'Dimensions':
|
|
||||||
[{'Name': 'StackId',
|
|
||||||
'Value': u'21617058-781e-4262-97ab-5f9df371ee52'}]
|
|
||||||
}]}}}
|
|
||||||
|
|
||||||
# Call the list controller function and compare the response
|
|
||||||
self.assertEqual(expected, self.controller.describe_alarms(dummy_req))
|
|
||||||
|
|
||||||
def test_describe_alarms_for_metric(self):
|
|
||||||
# Not yet implemented, should raise HeatAPINotImplementedError
|
|
||||||
params = {'Action': 'DescribeAlarmsForMetric'}
|
|
||||||
dummy_req = self._dummy_GET_request(params)
|
|
||||||
result = self.controller.describe_alarms_for_metric(dummy_req)
|
|
||||||
self.assertIsInstance(result, exception.HeatAPINotImplementedError)
|
|
||||||
|
|
||||||
def test_disable_alarm_actions(self):
|
|
||||||
# Not yet implemented, should raise HeatAPINotImplementedError
|
|
||||||
params = {'Action': 'DisableAlarmActions'}
|
|
||||||
dummy_req = self._dummy_GET_request(params)
|
|
||||||
result = self.controller.disable_alarm_actions(dummy_req)
|
|
||||||
self.assertIsInstance(result, exception.HeatAPINotImplementedError)
|
|
||||||
|
|
||||||
def test_enable_alarm_actions(self):
|
|
||||||
# Not yet implemented, should raise HeatAPINotImplementedError
|
|
||||||
params = {'Action': 'EnableAlarmActions'}
|
|
||||||
dummy_req = self._dummy_GET_request(params)
|
|
||||||
result = self.controller.enable_alarm_actions(dummy_req)
|
|
||||||
self.assertIsInstance(result, exception.HeatAPINotImplementedError)
|
|
||||||
|
|
||||||
def test_get_metric_statistics(self):
|
|
||||||
# Not yet implemented, should raise HeatAPINotImplementedError
|
|
||||||
params = {'Action': 'GetMetricStatistics'}
|
|
||||||
dummy_req = self._dummy_GET_request(params)
|
|
||||||
result = self.controller.get_metric_statistics(dummy_req)
|
|
||||||
self.assertIsInstance(result, exception.HeatAPINotImplementedError)
|
|
||||||
|
|
||||||
def test_list_metrics_all(self):
|
|
||||||
params = {'Action': 'ListMetrics'}
|
|
||||||
dummy_req = self._dummy_GET_request(params)
|
|
||||||
|
|
||||||
# Stub out the RPC call to the engine with a pre-canned response
|
|
||||||
# We dummy three different metrics and namespaces to test
|
|
||||||
# filtering by parameter
|
|
||||||
engine_resp = [{u'timestamp': u'2012-08-30T15:09:02Z',
|
|
||||||
u'watch_name': u'HttpFailureAlarm',
|
|
||||||
u'namespace': u'system/linux',
|
|
||||||
u'metric_name': u'ServiceFailure',
|
|
||||||
u'data': {u'Units': u'Counter', u'Value': 1}},
|
|
||||||
|
|
||||||
{u'timestamp': u'2012-08-30T15:10:03Z',
|
|
||||||
u'watch_name': u'HttpFailureAlarm2',
|
|
||||||
u'namespace': u'system/linux2',
|
|
||||||
u'metric_name': u'ServiceFailure2',
|
|
||||||
u'data': {u'Units': u'Counter', u'Value': 1}},
|
|
||||||
|
|
||||||
{u'timestamp': u'2012-08-30T15:16:03Z',
|
|
||||||
u'watch_name': u'HttpFailureAlar3m',
|
|
||||||
u'namespace': u'system/linux3',
|
|
||||||
u'metric_name': u'ServiceFailure3',
|
|
||||||
u'data': {u'Units': u'Counter', u'Value': 1}}]
|
|
||||||
|
|
||||||
self.m.StubOutWithMock(rpc_client.EngineClient, 'call')
|
|
||||||
# Current engine implementation means we filter in the API
|
|
||||||
# and pass None/None for namespace/watch_name which returns
|
|
||||||
# all metric data which we post-process in the API
|
|
||||||
rpc_client.EngineClient.call(
|
|
||||||
dummy_req.context,
|
|
||||||
('show_watch_metric',
|
|
||||||
{'metric_namespace': None, 'metric_name': None})
|
|
||||||
).AndReturn(engine_resp)
|
|
||||||
|
|
||||||
self.m.ReplayAll()
|
|
||||||
|
|
||||||
expected = {'ListMetricsResponse':
|
|
||||||
{'ListMetricsResult':
|
|
||||||
{'Metrics': [{'Namespace': u'system/linux',
|
|
||||||
'Dimensions':
|
|
||||||
[{'Name': 'AlarmName',
|
|
||||||
'Value': u'HttpFailureAlarm'},
|
|
||||||
{'Name': 'Timestamp',
|
|
||||||
'Value': u'2012-08-30T15:09:02Z'},
|
|
||||||
{'Name': u'Units',
|
|
||||||
'Value': u'Counter'},
|
|
||||||
{'Name': u'Value',
|
|
||||||
'Value': 1}],
|
|
||||||
'MetricName': u'ServiceFailure'},
|
|
||||||
{'Namespace': u'system/linux2',
|
|
||||||
'Dimensions':
|
|
||||||
[{'Name': 'AlarmName',
|
|
||||||
'Value': u'HttpFailureAlarm2'},
|
|
||||||
{'Name': 'Timestamp',
|
|
||||||
'Value': u'2012-08-30T15:10:03Z'},
|
|
||||||
{'Name': u'Units',
|
|
||||||
'Value': u'Counter'},
|
|
||||||
{'Name': u'Value',
|
|
||||||
'Value': 1}],
|
|
||||||
'MetricName': u'ServiceFailure2'},
|
|
||||||
{'Namespace': u'system/linux3',
|
|
||||||
'Dimensions':
|
|
||||||
[{'Name': 'AlarmName',
|
|
||||||
'Value': u'HttpFailureAlar3m'},
|
|
||||||
{'Name': 'Timestamp',
|
|
||||||
'Value': u'2012-08-30T15:16:03Z'},
|
|
||||||
{'Name': u'Units',
|
|
||||||
'Value': u'Counter'},
|
|
||||||
{'Name': u'Value',
|
|
||||||
'Value': 1}],
|
|
||||||
'MetricName': u'ServiceFailure3'}]}}}
|
|
||||||
|
|
||||||
response = self.controller.list_metrics(dummy_req)
|
|
||||||
metrics = (response['ListMetricsResponse']['ListMetricsResult']
|
|
||||||
['Metrics'])
|
|
||||||
metrics[0]['Dimensions'] = sorted(
|
|
||||||
metrics[0]['Dimensions'], key=lambda k: k['Name'])
|
|
||||||
metrics[1]['Dimensions'] = sorted(
|
|
||||||
metrics[1]['Dimensions'], key=lambda k: k['Name'])
|
|
||||||
metrics[2]['Dimensions'] = sorted(
|
|
||||||
metrics[2]['Dimensions'], key=lambda k: k['Name'])
|
|
||||||
metrics = sorted(metrics, key=lambda k: k['MetricName'])
|
|
||||||
response['ListMetricsResponse']['ListMetricsResult'] = (
|
|
||||||
{'Metrics': metrics})
|
|
||||||
# First pass no query paramters filtering, should get all three
|
|
||||||
self.assertEqual(expected, response)
|
|
||||||
|
|
||||||
def test_list_metrics_filter_name(self):
|
|
||||||
|
|
||||||
# Add a MetricName filter, so we should only get one of the three
|
|
||||||
params = {'Action': 'ListMetrics',
|
|
||||||
'MetricName': 'ServiceFailure'}
|
|
||||||
dummy_req = self._dummy_GET_request(params)
|
|
||||||
|
|
||||||
# Stub out the RPC call to the engine with a pre-canned response
|
|
||||||
# We dummy three different metrics and namespaces to test
|
|
||||||
# filtering by parameter
|
|
||||||
engine_resp = [{u'timestamp': u'2012-08-30T15:09:02Z',
|
|
||||||
u'watch_name': u'HttpFailureAlarm',
|
|
||||||
u'namespace': u'system/linux',
|
|
||||||
u'metric_name': u'ServiceFailure',
|
|
||||||
u'data': {u'Units': u'Counter', u'Value': 1}},
|
|
||||||
|
|
||||||
{u'timestamp': u'2012-08-30T15:10:03Z',
|
|
||||||
u'watch_name': u'HttpFailureAlarm2',
|
|
||||||
u'namespace': u'system/linux2',
|
|
||||||
u'metric_name': u'ServiceFailure2',
|
|
||||||
u'data': {u'Units': u'Counter', u'Value': 1}},
|
|
||||||
|
|
||||||
{u'timestamp': u'2012-08-30T15:16:03Z',
|
|
||||||
u'watch_name': u'HttpFailureAlar3m',
|
|
||||||
u'namespace': u'system/linux3',
|
|
||||||
u'metric_name': u'ServiceFailure3',
|
|
||||||
u'data': {u'Units': u'Counter', u'Value': 1}}]
|
|
||||||
|
|
||||||
self.m.StubOutWithMock(rpc_client.EngineClient, 'call')
|
|
||||||
# Current engine implementation means we filter in the API
|
|
||||||
# and pass None/None for namespace/watch_name which returns
|
|
||||||
# all metric data which we post-process in the API
|
|
||||||
rpc_client.EngineClient.call(
|
|
||||||
dummy_req.context,
|
|
||||||
('show_watch_metric',
|
|
||||||
{'metric_namespace': None, 'metric_name': None})
|
|
||||||
).AndReturn(engine_resp)
|
|
||||||
|
|
||||||
self.m.ReplayAll()
|
|
||||||
|
|
||||||
expected = {'ListMetricsResponse':
|
|
||||||
{'ListMetricsResult':
|
|
||||||
{'Metrics':
|
|
||||||
[{'Namespace': u'system/linux',
|
|
||||||
'Dimensions':
|
|
||||||
[{'Name': 'AlarmName',
|
|
||||||
'Value': u'HttpFailureAlarm'},
|
|
||||||
{'Name': 'Timestamp',
|
|
||||||
'Value': u'2012-08-30T15:09:02Z'},
|
|
||||||
{'Name': u'Units',
|
|
||||||
'Value': u'Counter'},
|
|
||||||
{'Name': u'Value',
|
|
||||||
'Value': 1}],
|
|
||||||
'MetricName': u'ServiceFailure'}]}}}
|
|
||||||
response = self.controller.list_metrics(dummy_req)
|
|
||||||
metrics = (response['ListMetricsResponse']['ListMetricsResult']
|
|
||||||
['Metrics'])
|
|
||||||
metrics[0]['Dimensions'] = sorted(
|
|
||||||
metrics[0]['Dimensions'], key=lambda k: k['Name'])
|
|
||||||
response['ListMetricsResponse']['ListMetricsResult'] = (
|
|
||||||
{'Metrics': metrics})
|
|
||||||
# First pass no query paramters filtering, should get all three
|
|
||||||
self.assertEqual(expected, response)
|
|
||||||
|
|
||||||
def test_list_metrics_filter_namespace(self):
|
|
||||||
|
|
||||||
# Add a Namespace filter and change the engine response so
|
|
||||||
# we should get two responses
|
|
||||||
params = {'Action': 'ListMetrics',
|
|
||||||
'Namespace': 'atestnamespace/foo'}
|
|
||||||
dummy_req = self._dummy_GET_request(params)
|
|
||||||
|
|
||||||
# Stub out the RPC call to the engine with a pre-canned response
|
|
||||||
# We dummy three different metrics and namespaces to test
|
|
||||||
# filtering by parameter
|
|
||||||
engine_resp = [{u'timestamp': u'2012-08-30T15:09:02Z',
|
|
||||||
u'watch_name': u'HttpFailureAlarm',
|
|
||||||
u'namespace': u'atestnamespace/foo',
|
|
||||||
u'metric_name': u'ServiceFailure',
|
|
||||||
u'data': {u'Units': u'Counter', u'Value': 1}},
|
|
||||||
|
|
||||||
{u'timestamp': u'2012-08-30T15:10:03Z',
|
|
||||||
u'watch_name': u'HttpFailureAlarm2',
|
|
||||||
u'namespace': u'atestnamespace/foo',
|
|
||||||
u'metric_name': u'ServiceFailure2',
|
|
||||||
u'data': {u'Units': u'Counter', u'Value': 1}},
|
|
||||||
|
|
||||||
{u'timestamp': u'2012-08-30T15:16:03Z',
|
|
||||||
u'watch_name': u'HttpFailureAlar3m',
|
|
||||||
u'namespace': u'system/linux3',
|
|
||||||
u'metric_name': u'ServiceFailure3',
|
|
||||||
u'data': {u'Units': u'Counter', u'Value': 1}}]
|
|
||||||
|
|
||||||
self.m.StubOutWithMock(rpc_client.EngineClient, 'call')
|
|
||||||
# Current engine implementation means we filter in the API
|
|
||||||
# and pass None/None for namespace/watch_name which returns
|
|
||||||
# all metric data which we post-process in the API
|
|
||||||
rpc_client.EngineClient.call(
|
|
||||||
dummy_req.context,
|
|
||||||
('show_watch_metric',
|
|
||||||
{'metric_namespace': None, 'metric_name': None})
|
|
||||||
).AndReturn(engine_resp)
|
|
||||||
|
|
||||||
self.m.ReplayAll()
|
|
||||||
|
|
||||||
expected = {'ListMetricsResponse':
|
|
||||||
{'ListMetricsResult':
|
|
||||||
{'Metrics':
|
|
||||||
[{'Namespace': u'atestnamespace/foo',
|
|
||||||
'Dimensions':
|
|
||||||
[{'Name': 'AlarmName',
|
|
||||||
'Value': u'HttpFailureAlarm'},
|
|
||||||
{'Name': 'Timestamp',
|
|
||||||
'Value': u'2012-08-30T15:09:02Z'},
|
|
||||||
{'Name': u'Units',
|
|
||||||
'Value': u'Counter'},
|
|
||||||
{'Name': u'Value',
|
|
||||||
'Value': 1}],
|
|
||||||
'MetricName': u'ServiceFailure'},
|
|
||||||
{'Namespace': u'atestnamespace/foo',
|
|
||||||
'Dimensions':
|
|
||||||
[{'Name': 'AlarmName',
|
|
||||||
'Value': u'HttpFailureAlarm2'},
|
|
||||||
{'Name': 'Timestamp',
|
|
||||||
'Value': u'2012-08-30T15:10:03Z'},
|
|
||||||
{'Name': u'Units',
|
|
||||||
'Value': u'Counter'},
|
|
||||||
{'Name': u'Value',
|
|
||||||
'Value': 1}],
|
|
||||||
'MetricName': u'ServiceFailure2'}]}}}
|
|
||||||
response = self.controller.list_metrics(dummy_req)
|
|
||||||
metrics = (response['ListMetricsResponse']['ListMetricsResult']
|
|
||||||
['Metrics'])
|
|
||||||
metrics[0]['Dimensions'] = sorted(
|
|
||||||
metrics[0]['Dimensions'], key=lambda k: k['Name'])
|
|
||||||
metrics[1]['Dimensions'] = sorted(
|
|
||||||
metrics[1]['Dimensions'], key=lambda k: k['Name'])
|
|
||||||
response['ListMetricsResponse']['ListMetricsResult'] = (
|
|
||||||
{'Metrics': metrics})
|
|
||||||
# First pass no query paramters filtering, should get all three
|
|
||||||
self.assertEqual(expected, response)
|
|
||||||
|
|
||||||
def test_put_metric_alarm(self):
|
|
||||||
# Not yet implemented, should raise HeatAPINotImplementedError
|
|
||||||
params = {'Action': 'PutMetricAlarm'}
|
|
||||||
dummy_req = self._dummy_GET_request(params)
|
|
||||||
result = self.controller.put_metric_alarm(dummy_req)
|
|
||||||
self.assertIsInstance(result, exception.HeatAPINotImplementedError)
|
|
||||||
|
|
||||||
def test_put_metric_data(self):
|
|
||||||
|
|
||||||
params = {u'Namespace': u'system/linux',
|
|
||||||
u'MetricData.member.1.Unit': u'Count',
|
|
||||||
u'MetricData.member.1.Value': u'1',
|
|
||||||
u'MetricData.member.1.MetricName': u'ServiceFailure',
|
|
||||||
u'MetricData.member.1.Dimensions.member.1.Name':
|
|
||||||
u'AlarmName',
|
|
||||||
u'MetricData.member.1.Dimensions.member.1.Value':
|
|
||||||
u'HttpFailureAlarm',
|
|
||||||
u'Action': u'PutMetricData'}
|
|
||||||
|
|
||||||
dummy_req = self._dummy_GET_request(params)
|
|
||||||
|
|
||||||
# Stub out the RPC call to verify the engine call parameters
|
|
||||||
engine_resp = {}
|
|
||||||
|
|
||||||
self.m.StubOutWithMock(rpc_client.EngineClient, 'call')
|
|
||||||
rpc_client.EngineClient.call(
|
|
||||||
dummy_req.context,
|
|
||||||
('create_watch_data',
|
|
||||||
{'watch_name': u'HttpFailureAlarm',
|
|
||||||
'stats_data': {
|
|
||||||
'Namespace': u'system/linux',
|
|
||||||
'ServiceFailure': {
|
|
||||||
'Value': u'1', 'Unit': u'Count', 'Dimensions': []}}})
|
|
||||||
).AndReturn(engine_resp)
|
|
||||||
|
|
||||||
self.m.ReplayAll()
|
|
||||||
|
|
||||||
expected = {'PutMetricDataResponse': {'PutMetricDataResult':
|
|
||||||
{'ResponseMetadata': None}}}
|
|
||||||
self.assertEqual(expected, self.controller.put_metric_data(dummy_req))
|
|
||||||
|
|
||||||
def test_set_alarm_state(self):
|
|
||||||
state_map = {'OK': rpc_api.WATCH_STATE_OK,
|
|
||||||
'ALARM': rpc_api.WATCH_STATE_ALARM,
|
|
||||||
'INSUFFICIENT_DATA': rpc_api.WATCH_STATE_NODATA}
|
|
||||||
|
|
||||||
for state in state_map:
|
|
||||||
params = {u'StateValue': state,
|
|
||||||
u'StateReason': u'',
|
|
||||||
u'AlarmName': u'HttpFailureAlarm',
|
|
||||||
u'Action': u'SetAlarmState'}
|
|
||||||
|
|
||||||
dummy_req = self._dummy_GET_request(params)
|
|
||||||
|
|
||||||
# Stub out the RPC call to verify the engine call parameters
|
|
||||||
# The real engine response is the same as show_watch but with
|
|
||||||
# the state overridden, but since the API doesn't make use
|
|
||||||
# of the response at present we pass nothing back from the stub
|
|
||||||
engine_resp = {}
|
|
||||||
|
|
||||||
self.m.StubOutWithMock(rpc_client.EngineClient, 'call')
|
|
||||||
rpc_client.EngineClient.call(
|
|
||||||
dummy_req.context,
|
|
||||||
('set_watch_state',
|
|
||||||
{'state': state_map[state],
|
|
||||||
'watch_name': u'HttpFailureAlarm'})
|
|
||||||
).AndReturn(engine_resp)
|
|
||||||
|
|
||||||
self.m.ReplayAll()
|
|
||||||
|
|
||||||
expected = {'SetAlarmStateResponse': {'SetAlarmStateResult': ''}}
|
|
||||||
self.assertEqual(expected,
|
|
||||||
self.controller.set_alarm_state(dummy_req))
|
|
||||||
|
|
||||||
self.m.UnsetStubs()
|
|
||||||
self.m.VerifyAll()
|
|
||||||
|
|
||||||
def test_set_alarm_state_badstate(self):
|
|
||||||
params = {u'StateValue': "baaaaad",
|
|
||||||
u'StateReason': u'',
|
|
||||||
u'AlarmName': u'HttpFailureAlarm',
|
|
||||||
u'Action': u'SetAlarmState'}
|
|
||||||
dummy_req = self._dummy_GET_request(params)
|
|
||||||
|
|
||||||
# should raise HeatInvalidParameterValueError
|
|
||||||
result = self.controller.set_alarm_state(dummy_req)
|
|
||||||
self.assertIsInstance(result, exception.HeatInvalidParameterValueError)
|
|
@ -12,16 +12,4 @@
|
|||||||
"cloudformation:DescribeStackResource": "",
|
"cloudformation:DescribeStackResource": "",
|
||||||
"cloudformation:DescribeStackResources": "rule:deny_stack_user",
|
"cloudformation:DescribeStackResources": "rule:deny_stack_user",
|
||||||
"cloudformation:ListStackResources": "rule:deny_stack_user",
|
"cloudformation:ListStackResources": "rule:deny_stack_user",
|
||||||
|
|
||||||
"cloudwatch:DeleteAlarms": "rule:deny_stack_user",
|
|
||||||
"cloudwatch:DescribeAlarmHistory": "rule:deny_stack_user",
|
|
||||||
"cloudwatch:DescribeAlarms": "rule:deny_stack_user",
|
|
||||||
"cloudwatch:DescribeAlarmsForMetric": "rule:deny_stack_user",
|
|
||||||
"cloudwatch:DisableAlarmActions": "rule:deny_stack_user",
|
|
||||||
"cloudwatch:EnableAlarmActions": "rule:deny_stack_user",
|
|
||||||
"cloudwatch:GetMetricStatistics": "rule:deny_stack_user",
|
|
||||||
"cloudwatch:ListMetrics": "rule:deny_stack_user",
|
|
||||||
"cloudwatch:PutMetricAlarm": "rule:deny_stack_user",
|
|
||||||
"cloudwatch:PutMetricData": "",
|
|
||||||
"cloudwatch:SetAlarmState": "rule:deny_stack_user"
|
|
||||||
}
|
}
|
||||||
|
@ -87,26 +87,6 @@ class TestPolicyEnforcer(common.HeatTestCase):
|
|||||||
# Everything should be allowed
|
# Everything should be allowed
|
||||||
enforcer.enforce(ctx, action, is_registered_policy=True)
|
enforcer.enforce(ctx, action, is_registered_policy=True)
|
||||||
|
|
||||||
def test_policy_cw_deny_stack_user(self):
|
|
||||||
enforcer = policy.Enforcer(scope='cloudwatch')
|
|
||||||
|
|
||||||
ctx = utils.dummy_context(roles=['heat_stack_user'])
|
|
||||||
for action in self.cw_actions:
|
|
||||||
# Everything apart from PutMetricData should be Forbidden
|
|
||||||
if action == "PutMetricData":
|
|
||||||
enforcer.enforce(ctx, action, is_registered_policy=True)
|
|
||||||
else:
|
|
||||||
self.assertRaises(exception.Forbidden, enforcer.enforce, ctx,
|
|
||||||
action, {}, is_registered_policy=True)
|
|
||||||
|
|
||||||
def test_policy_cw_allow_non_stack_user(self):
|
|
||||||
enforcer = policy.Enforcer(scope='cloudwatch')
|
|
||||||
|
|
||||||
ctx = utils.dummy_context(roles=['not_a_stack_user'])
|
|
||||||
for action in self.cw_actions:
|
|
||||||
# Everything should be allowed
|
|
||||||
enforcer.enforce(ctx, action, is_registered_policy=True)
|
|
||||||
|
|
||||||
def test_set_rules_overwrite_true(self):
|
def test_set_rules_overwrite_true(self):
|
||||||
enforcer = policy.Enforcer()
|
enforcer = policy.Enforcer()
|
||||||
enforcer.load_rules(True)
|
enforcer.load_rules(True)
|
||||||
|
@ -31,7 +31,6 @@ echo -e 'logging_exception_prefix=%(asctime)s.%(msecs)03d %(process)d TRACE %(na
|
|||||||
|
|
||||||
echo -e '[heat_api]\nworkers=2\n' >> $localconf
|
echo -e '[heat_api]\nworkers=2\n' >> $localconf
|
||||||
echo -e '[heat_api_cfn]\nworkers=2\n' >> $localconf
|
echo -e '[heat_api_cfn]\nworkers=2\n' >> $localconf
|
||||||
echo -e '[heat_api_cloudwatch]\nworkers=2\n' >> $localconf
|
|
||||||
|
|
||||||
echo -e '[cache]\nenabled=True\n' >> $localconf
|
echo -e '[cache]\nenabled=True\n' >> $localconf
|
||||||
|
|
||||||
|
@ -0,0 +1,7 @@
|
|||||||
|
---
|
||||||
|
upgrade:
|
||||||
|
- |
|
||||||
|
The AWS compatible CloudWatch API, deprecated since long has been
|
||||||
|
finally removed. OpenStack deployments, packagers, and deployment
|
||||||
|
projects which deploy/package CloudWatch should take appropriate
|
||||||
|
action to remove support.
|
@ -37,14 +37,12 @@ console_scripts =
|
|||||||
heat-all = heat.cmd.all:main
|
heat-all = heat.cmd.all:main
|
||||||
heat-api = heat.cmd.api:main
|
heat-api = heat.cmd.api:main
|
||||||
heat-api-cfn = heat.cmd.api_cfn:main
|
heat-api-cfn = heat.cmd.api_cfn:main
|
||||||
heat-api-cloudwatch = heat.cmd.api_cloudwatch:main
|
|
||||||
heat-engine = heat.cmd.engine:main
|
heat-engine = heat.cmd.engine:main
|
||||||
heat-manage = heat.cmd.manage:main
|
heat-manage = heat.cmd.manage:main
|
||||||
|
|
||||||
wsgi_scripts =
|
wsgi_scripts =
|
||||||
heat-wsgi-api = heat.httpd.heat_api:init_application
|
heat-wsgi-api = heat.httpd.heat_api:init_application
|
||||||
heat-wsgi-api-cfn = heat.httpd.heat_api_cfn:init_application
|
heat-wsgi-api-cfn = heat.httpd.heat_api_cfn:init_application
|
||||||
heat-wsgi-api-cloudwatch = heat.httpd.heat_api_cloudwatch:init_application
|
|
||||||
|
|
||||||
oslo.config.opts =
|
oslo.config.opts =
|
||||||
heat.common.config = heat.common.config:list_opts
|
heat.common.config = heat.common.config:list_opts
|
||||||
|
2
tox.ini
2
tox.ini
@ -28,7 +28,7 @@ commands =
|
|||||||
|
|
||||||
[testenv:pep8]
|
[testenv:pep8]
|
||||||
commands =
|
commands =
|
||||||
flake8 heat bin/heat-api bin/heat-api-cfn bin/heat-api-cloudwatch bin/heat-engine bin/heat-manage contrib heat_integrationtests doc/source
|
flake8 heat bin/heat-api bin/heat-api-cfn bin/heat-engine bin/heat-manage contrib heat_integrationtests doc/source
|
||||||
python tools/custom_guidelines.py --exclude heat/engine/resources/aws
|
python tools/custom_guidelines.py --exclude heat/engine/resources/aws
|
||||||
# The following bandit tests are being skipped:
|
# The following bandit tests are being skipped:
|
||||||
# B101: Test for use of assert
|
# B101: Test for use of assert
|
||||||
|
Loading…
Reference in New Issue
Block a user