Merge "Remove CloudWatch API"

This commit is contained in:
Zuul 2018-01-31 19:29:02 +00:00 committed by Gerrit Code Review
commit 04be5a736e
23 changed files with 41 additions and 1315 deletions

View File

@ -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()

View File

@ -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

View File

@ -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.

View File

@ -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

View File

@ -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)

View File

@ -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)

View File

@ -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')

View File

@ -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)

View File

@ -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)

View File

@ -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

View File

@ -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>

View File

@ -1,2 +0,0 @@
KeepAlive Off
ProxyPass "/heat-api-cloudwatch" "http://127.0.0.1:80997" retry=0

View File

@ -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()

View File

@ -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(),

View File

@ -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

View File

@ -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)

View File

@ -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"
} }

View File

@ -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)

View File

@ -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

View File

@ -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.

View File

@ -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

View File

@ -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