#!/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 json import logging from urlparse import urlparse # 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 import boto_client_cloudwatch as heat_client from heat import version from heat.common import config from heat.common import exception from heat 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 """ oparser = optparse.OptionParser(version='%%prog %s' % 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()