#!/usr/bin/env python # vim: tabstop=4 shiftwidth=4 softtabstop=4 # # 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. """ This is the administration program for heat-api-cloudwatch. It is simply a command-line interface for adding, modifying, and retrieving information about the cloudwatch alarms and metrics belonging to a user. It is a convenience application that talks to the heat Cloudwatch API server. """ import gettext import optparse import os import os.path import sys import time import logging # 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) scriptname = os.path.basename(sys.argv[0]) gettext.install('heat', unicode=1) from heat.cfn_client import boto_client_cloudwatch as heat_client from heat.version import version_info as version from heat.common import config from heat.common import exception from heat.cfn_client import utils DEFAULT_PORT=8003 @utils.catch_error('alarm-describe') def alarm_describe(options, arguments): ''' Describe detail for specified alarm, or all alarms if no AlarmName is specified ''' parameters={} try: parameters['AlarmName'] = arguments.pop(0) except IndexError: logging.info("No AlarmName passed, getting results for ALL alarms") c = heat_client.get_client(options.port) result = c.describe_alarm(**parameters) print c.format_metric_alarm(result) @utils.catch_error('alarm-set-state') def alarm_set_state(options, arguments): ''' Temporarily set state for specified alarm ''' usage = ('''Usage: %s alarm-set-state AlarmName StateValue [StateReason]''' % (scriptname)) parameters={} try: parameters['AlarmName'] = arguments.pop(0) parameters['StateValue'] = arguments.pop(0) except IndexError: logging.error("Must specify AlarmName and StateValue") print usage print "StateValue must be one of %s, %s or %s" % ( heat_client.BotoCWClient.ALARM_STATES) return utils.FAILURE try: parameters['StateReason'] = arguments.pop(0) except IndexError: parameters['StateReason'] = "" # We don't handle attaching data to state via this cli tool (yet) parameters['StateReasonData'] = None c = heat_client.get_client(options.port) result = c.set_alarm_state(**parameters) print result @utils.catch_error('metric-list') def metric_list(options, arguments): ''' List all metric data for a given metric (or all metrics if none specified) ''' parameters={} try: parameters['MetricName'] = arguments.pop(0) except IndexError: logging.info("No MetricName passed, getting results for ALL alarms") c = heat_client.get_client(options.port) result = c.list_metrics(**parameters) print c.format_metric(result) @utils.catch_error('metric-put-data') def metric_put_data(options, arguments): ''' Create a datapoint for a specified metric ''' usage = ('''Usage: %s metric-put-data AlarmName Namespace MetricName Units MetricValue e.g %s metric-put-data HttpFailureAlarm system/linux ServiceFailure Count 1 ''' % (scriptname, scriptname)) # NOTE : we currently only support metric datapoints associated with a # specific AlarmName, due to the current engine/db cloudwatch # implementation, we should probably revisit this so we can support # more generic metric data collection parameters={} try: parameters['AlarmName'] = arguments.pop(0) parameters['Namespace'] = arguments.pop(0) parameters['MetricName'] = arguments.pop(0) parameters['MetricUnit'] = arguments.pop(0) parameters['MetricValue'] = arguments.pop(0) except IndexError: logging.error("Please specify the alarm, metric, unit and value") print usage return utils.FAILURE c = heat_client.get_client(options.port) result = c.put_metric_data(**parameters) print result def create_options(parser): """ Sets up the CLI and config-file options that may be parsed and program commands. :param parser: The option parser """ parser.add_option('-v', '--verbose', default=False, action="store_true", help="Print more verbose output") parser.add_option('-d', '--debug', default=False, action="store_true", help="Print more verbose output") parser.add_option('-p', '--port', dest="port", type=int, default=DEFAULT_PORT, help="Port the heat API host listens on. " "Default: %default") def parse_options(parser, cli_args): """ Returns the parsed CLI options, command to run and its arguments, merged with any same-named options found in a configuration file :param parser: The option parser """ if not cli_args: cli_args.append('-h') # Show options in usage output... (options, args) = parser.parse_args(cli_args) # HACK(sirp): Make the parser available to the print_help method # print_help is a command, so it only accepts (options, args); we could # one-off have it take (parser, options, args), however, for now, I think # this little hack will suffice options.__parser = parser if not args: parser.print_usage() sys.exit(0) command_name = args.pop(0) command = lookup_command(parser, command_name) if options.debug: logging.basicConfig(format='%(levelname)s:%(message)s', level=logging.DEBUG) logging.debug("Debug level logging enabled") elif options.verbose: logging.basicConfig(format='%(levelname)s:%(message)s', level=logging.INFO) else: logging.basicConfig(format='%(levelname)s:%(message)s', level=logging.WARNING) return (options, command, args) def print_help(options, args): """ Print help specific to a command """ parser = options.__parser if not args: parser.print_usage() subst = {'prog': os.path.basename(sys.argv[0])} docs = [lookup_command(parser, cmd).__doc__ % subst for cmd in args] print '\n\n'.join(docs) def lookup_command(parser, command_name): base_commands = {'help': print_help} watch_commands = { 'describe': alarm_describe, 'set-state': alarm_set_state, 'metric-list': metric_list, 'metric-put-data': metric_put_data} commands = {} for command_set in (base_commands, watch_commands): commands.update(command_set) try: command = commands[command_name] except KeyError: parser.print_usage() sys.exit("Unknown command: %s" % command_name) return command def main(): ''' ''' usage = """ %prog [options] [args] Commands: help Output help for one of the commands below describe Describe a specified alarm (or all alarms) set-state Temporarily set the state of an alarm metric-list List data-points for specified metric metric-put-data Publish data-point for specified metric """ version_string = version.version_string() oparser = optparse.OptionParser(version=version_string, usage=usage.strip()) create_options(oparser) (opts, cmd, args) = parse_options(oparser, sys.argv[1:]) try: start_time = time.time() result = cmd(opts, args) end_time = time.time() logging.debug("Completed in %-0.4f sec." % (end_time - start_time)) sys.exit(result) except (RuntimeError, NotImplementedError, exception.ClientConfigurationError), ex: oparser.print_usage() logging.error("ERROR: %s" % ex) sys.exit(1) if __name__ == '__main__': main()