diff --git a/bin/heat-api-cloudwatch b/bin/heat-api-cloudwatch deleted file mode 100755 index fc3129998a..0000000000 --- a/bin/heat-api-cloudwatch +++ /dev/null @@ -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() diff --git a/doc/source/configuration/api.rst b/doc/source/configuration/api.rst index 2b4f718e0c..7105876c03 100644 --- a/doc/source/configuration/api.rst +++ b/doc/source/configuration/api.rst @@ -11,6 +11,5 @@ and CloudWatch and a native API. .. include:: ./tables/heat-api.rst .. include:: ./tables/heat-cfn_api.rst -.. include:: ./tables/heat-cloudwatch_api.rst .. include:: ./tables/heat-metadata_api.rst .. include:: ./tables/heat-waitcondition_api.rst diff --git a/doc/source/configuration/tables/heat-cloudwatch_api.rst b/doc/source/configuration/tables/heat-cloudwatch_api.rst deleted file mode 100644 index e1594b8851..0000000000 --- a/doc/source/configuration/tables/heat-cloudwatch_api.rst +++ /dev/null @@ -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. diff --git a/etc/heat/api-paste.ini b/etc/heat/api-paste.ini index 986a4a2528..ad5b3112a0 100644 --- a/etc/heat/api-paste.ini +++ b/etc/heat/api-paste.ini @@ -38,15 +38,6 @@ pipeline = cors http_proxy_to_wsgi cfnversionnegotiation osprofiler ec2authtoken [pipeline:heat-api-cfn-standalone] 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] paste.app_factory = heat.common.wsgi:app_factory 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 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] paste.filter_factory = heat.common.wsgi:filter_factory heat.filter_factory = heat.api.openstack:version_negotiation_filter @@ -77,7 +64,6 @@ heat.filter_factory = heat.api.cfn:version_negotiation_filter [filter:cwversionnegotiation] paste.filter_factory = heat.common.wsgi:filter_factory -heat.filter_factory = heat.api.cloudwatch:version_negotiation_filter [filter:context] paste.filter_factory = heat.common.context:ContextMiddleware_filter_factory diff --git a/heat/api/cloudwatch/__init__.py b/heat/api/cloudwatch/__init__.py deleted file mode 100644 index 69f0744c6c..0000000000 --- a/heat/api/cloudwatch/__init__.py +++ /dev/null @@ -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) diff --git a/heat/api/cloudwatch/watch.py b/heat/api/cloudwatch/watch.py deleted file mode 100644 index 5c4dd2edaa..0000000000 --- a/heat/api/cloudwatch/watch.py +++ /dev/null @@ -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) diff --git a/heat/cmd/all.py b/heat/cmd/all.py index d5ea33f4ea..423ef853ba 100644 --- a/heat/cmd/all.py +++ b/heat/cmd/all.py @@ -25,7 +25,6 @@ import sys from heat.cmd import api from heat.cmd import api_cfn -from heat.cmd import api_cloudwatch from heat.cmd import engine from heat.common import config from heat.common import messaging @@ -45,7 +44,6 @@ LAUNCH_SERVICES = { 'engine': [engine.launch_engine, {'setup_logging': False}], 'api': [api.launch_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( @@ -53,7 +51,7 @@ services_opt = cfg.ListOpt( default=['engine', 'api', 'api_cfn'], help='Specifies the heat services that are enabled when running heat-all. ' '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') diff --git a/heat/cmd/api_cloudwatch.py b/heat/cmd/api_cloudwatch.py deleted file mode 100644 index f20eaeb4ff..0000000000 --- a/heat/cmd/api_cloudwatch.py +++ /dev/null @@ -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) diff --git a/heat/common/wsgi.py b/heat/common/wsgi.py index c2a81f85d5..7bba27abbf 100644 --- a/heat/common/wsgi.py +++ b/heat/common/wsgi.py @@ -136,34 +136,58 @@ api_cw_opts = [ cfg.IPOpt('bind_host', default='0.0.0.0', help=_('Address to bind the server. Useful when ' '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, 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, help=_("Number of backlog requests " "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', help=_("Location of the SSL certificate file " "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', help=_("Location of the SSL key file to use " "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, 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, help=_('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.)')), + '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, help=_('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.')), + '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') cfg.CONF.register_group(api_cw_group) diff --git a/heat/httpd/files/heat-api-cloudwatch-uwsgi.ini b/heat/httpd/files/heat-api-cloudwatch-uwsgi.ini deleted file mode 100644 index 1726e667aa..0000000000 --- a/heat/httpd/files/heat-api-cloudwatch-uwsgi.ini +++ /dev/null @@ -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 diff --git a/heat/httpd/files/heat-api-cloudwatch.conf b/heat/httpd/files/heat-api-cloudwatch.conf deleted file mode 100644 index c86d9ee459..0000000000 --- a/heat/httpd/files/heat-api-cloudwatch.conf +++ /dev/null @@ -1,28 +0,0 @@ -Listen %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 - = 2.4> - ErrorLogFormat "%{cu}t %M" - - ErrorLog /var/log/%APACHE_NAME%/heat_api_cloudwatch.log - CustomLog /var/log/%APACHE_NAME%/heat_api_cloudwatch_access.log combined - %SSLENGINE% - %SSLCERTFILE% - %SSLKEYFILE% - - - = 2.4> - Require all granted - - - Order allow,deny - Allow from all - - - diff --git a/heat/httpd/files/uwsgi-heat-api-cloudwatch.conf b/heat/httpd/files/uwsgi-heat-api-cloudwatch.conf deleted file mode 100644 index bc1789708b..0000000000 --- a/heat/httpd/files/uwsgi-heat-api-cloudwatch.conf +++ /dev/null @@ -1,2 +0,0 @@ -KeepAlive Off -ProxyPass "/heat-api-cloudwatch" "http://127.0.0.1:80997" retry=0 diff --git a/heat/httpd/heat_api_cloudwatch.py b/heat/httpd/heat_api_cloudwatch.py deleted file mode 100644 index 646545e587..0000000000 --- a/heat/httpd/heat_api_cloudwatch.py +++ /dev/null @@ -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() diff --git a/heat/policies/__init__.py b/heat/policies/__init__.py index e36fcfed11..1afb736e16 100644 --- a/heat/policies/__init__.py +++ b/heat/policies/__init__.py @@ -17,7 +17,6 @@ from heat.policies import actions from heat.policies import base from heat.policies import build_info from heat.policies import cloudformation -from heat.policies import cloudwatch from heat.policies import events from heat.policies import resource from heat.policies import resource_types @@ -33,7 +32,6 @@ def list_rules(): actions.list_rules(), build_info.list_rules(), cloudformation.list_rules(), - cloudwatch.list_rules(), events.list_rules(), resource.list_rules(), resource_types.list_rules(), diff --git a/heat/policies/cloudwatch.py b/heat/policies/cloudwatch.py deleted file mode 100644 index 37cb54013f..0000000000 --- a/heat/policies/cloudwatch.py +++ /dev/null @@ -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 diff --git a/heat/tests/api/cloudwatch/__init__.py b/heat/tests/api/cloudwatch/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/heat/tests/api/cloudwatch/test_api_cloudwatch.py b/heat/tests/api/cloudwatch/test_api_cloudwatch.py deleted file mode 100644 index 065737ccba..0000000000 --- a/heat/tests/api/cloudwatch/test_api_cloudwatch.py +++ /dev/null @@ -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) diff --git a/heat/tests/policy/deny_stack_user.json b/heat/tests/policy/deny_stack_user.json index 6c0fec87fa..c20d2673fa 100644 --- a/heat/tests/policy/deny_stack_user.json +++ b/heat/tests/policy/deny_stack_user.json @@ -12,16 +12,4 @@ "cloudformation:DescribeStackResource": "", "cloudformation:DescribeStackResources": "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" } diff --git a/heat/tests/test_common_policy.py b/heat/tests/test_common_policy.py index 0157d66b3b..8055b5312b 100644 --- a/heat/tests/test_common_policy.py +++ b/heat/tests/test_common_policy.py @@ -87,26 +87,6 @@ class TestPolicyEnforcer(common.HeatTestCase): # Everything should be allowed 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): enforcer = policy.Enforcer() enforcer.load_rules(True) diff --git a/heat_integrationtests/pre_test_hook.sh b/heat_integrationtests/pre_test_hook.sh index fd869ebcc1..b96f761ce2 100755 --- a/heat_integrationtests/pre_test_hook.sh +++ b/heat_integrationtests/pre_test_hook.sh @@ -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_cfn]\nworkers=2\n' >> $localconf -echo -e '[heat_api_cloudwatch]\nworkers=2\n' >> $localconf echo -e '[cache]\nenabled=True\n' >> $localconf diff --git a/releasenotes/notes/remove-cloudwatch-api-149403251da97b41.yaml b/releasenotes/notes/remove-cloudwatch-api-149403251da97b41.yaml new file mode 100644 index 0000000000..d8fac64b38 --- /dev/null +++ b/releasenotes/notes/remove-cloudwatch-api-149403251da97b41.yaml @@ -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. diff --git a/setup.cfg b/setup.cfg index 80efdfd29c..34a9f1abcd 100644 --- a/setup.cfg +++ b/setup.cfg @@ -37,14 +37,12 @@ console_scripts = heat-all = heat.cmd.all:main heat-api = heat.cmd.api:main heat-api-cfn = heat.cmd.api_cfn:main - heat-api-cloudwatch = heat.cmd.api_cloudwatch:main heat-engine = heat.cmd.engine:main heat-manage = heat.cmd.manage:main wsgi_scripts = heat-wsgi-api = heat.httpd.heat_api: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 = heat.common.config = heat.common.config:list_opts diff --git a/tox.ini b/tox.ini index 1fa7d27ddb..3d091d7a15 100644 --- a/tox.ini +++ b/tox.ini @@ -28,7 +28,7 @@ commands = [testenv:pep8] 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 # The following bandit tests are being skipped: # B101: Test for use of assert