2014-06-09 10:34:16 -06:00
|
|
|
#!/usr/bin/env python
|
|
|
|
""" Detect running daemons then configure and start the agent.
|
|
|
|
"""
|
|
|
|
|
|
|
|
import argparse
|
|
|
|
import logging
|
|
|
|
import os
|
2014-06-09 11:07:26 -06:00
|
|
|
import pwd
|
2014-06-09 10:34:16 -06:00
|
|
|
import socket
|
|
|
|
import subprocess
|
|
|
|
import sys
|
2014-07-28 14:11:46 -06:00
|
|
|
import yaml
|
2014-12-11 10:31:35 -07:00
|
|
|
|
2015-01-14 17:22:48 -07:00
|
|
|
import agent_config
|
2015-05-01 11:49:47 -06:00
|
|
|
from detection.utils import find_plugins
|
2015-04-21 14:00:05 -06:00
|
|
|
from service.detection import detect_init
|
2014-06-09 10:34:16 -06:00
|
|
|
|
|
|
|
|
|
|
|
log = logging.getLogger(__name__)
|
|
|
|
|
2015-05-01 11:49:47 -06:00
|
|
|
CUSTOM_PLUGIN_PATH = '/usr/lib/monasca/agent/custom_detect.d'
|
2015-02-18 09:43:37 -07:00
|
|
|
# dirname is called twice to get the dir 1 above the location of the script
|
|
|
|
PREFIX_DIR = os.path.dirname(os.path.dirname(os.path.realpath(sys.argv[0])))
|
|
|
|
|
|
|
|
|
2015-02-26 09:56:38 -07:00
|
|
|
def write_template(template_path, out_path, variables, group, is_yaml=False):
|
2015-02-18 09:43:37 -07:00
|
|
|
""" Write a file using a simple python string template.
|
|
|
|
Assumes 640 for the permissions and root:group for ownership.
|
|
|
|
:param template_path: Location of the Template to use
|
|
|
|
:param out_path: Location of the file to write
|
|
|
|
:param variables: dictionary with key/value pairs to use in writing the template
|
|
|
|
:return: None
|
|
|
|
"""
|
|
|
|
if not os.path.exists(template_path):
|
|
|
|
print("Error no template found at {0}".format(template_path))
|
|
|
|
sys.exit(1)
|
|
|
|
with open(template_path, 'r') as template:
|
2015-02-26 09:56:38 -07:00
|
|
|
contents = template.read().format(**variables)
|
2015-02-18 09:43:37 -07:00
|
|
|
with open(out_path, 'w') as conf:
|
2015-02-26 09:56:38 -07:00
|
|
|
if is_yaml:
|
|
|
|
conf.write(yaml.safe_dump(yaml.safe_load(contents),
|
|
|
|
encoding='utf-8',
|
|
|
|
allow_unicode=True,
|
|
|
|
default_flow_style=False))
|
|
|
|
else:
|
|
|
|
conf.write(contents)
|
2015-02-18 09:43:37 -07:00
|
|
|
os.chown(out_path, 0, group)
|
|
|
|
os.chmod(out_path, 0640)
|
|
|
|
|
2014-06-09 10:34:16 -06:00
|
|
|
|
|
|
|
def main(argv=None):
|
2015-04-24 11:50:06 -06:00
|
|
|
parser = argparse.ArgumentParser(description='Configure and setup the agent. In a full run it will detect running' +
|
|
|
|
' daemons then configure and start the agent.',
|
2014-06-09 12:20:03 -06:00
|
|
|
formatter_class=argparse.ArgumentDefaultsHelpFormatter)
|
2014-07-01 14:27:12 -07:00
|
|
|
parser.add_argument(
|
2015-04-24 11:50:06 -06:00
|
|
|
'-u', '--username', help="Username used for keystone authentication. Required for basic configuration.")
|
2014-07-01 14:27:12 -07:00
|
|
|
parser.add_argument(
|
2015-04-24 11:50:06 -06:00
|
|
|
'-p', '--password', help="Password used for keystone authentication. Required for basic configuration.")
|
2015-07-06 16:30:09 +00:00
|
|
|
parser.add_argument('--user_domain_id', help="User domain id for keystone authentication", default='')
|
|
|
|
parser.add_argument('--user_domain_name', help="User domain name for keystone authentication", default='')
|
2015-04-24 11:50:06 -06:00
|
|
|
parser.add_argument('--keystone_url', help="Keystone url. Required for basic configuration.")
|
|
|
|
parser.add_argument('--project_name', help="Project name for keystone authentication", default='')
|
|
|
|
parser.add_argument('--project_domain_id', help="Project domain id for keystone authentication", default='')
|
|
|
|
parser.add_argument('--project_domain_name', help="Project domain name for keystone authentication", default='')
|
|
|
|
parser.add_argument('--project_id', help="Keystone project id for keystone authentication", default='')
|
|
|
|
parser.add_argument('--monasca_url', help="Monasca API url, if not defined the url is pulled from keystone",
|
|
|
|
default='')
|
|
|
|
parser.add_argument('--system_only', help="Setup the service but only configure the base config and system " +
|
|
|
|
"metrics (cpu, disk, load, memory, network).",
|
|
|
|
action="store_true", default=False)
|
|
|
|
parser.add_argument('-d', '--detection_plugins', nargs='*',
|
2015-05-29 09:26:36 -06:00
|
|
|
help="Skip base config and service setup and only configure this space separated list. " +
|
2015-04-24 11:50:06 -06:00
|
|
|
"This assumes the base config has already run.")
|
2015-05-12 13:43:08 -06:00
|
|
|
parser.add_argument('-a', '--detection_args', help="A string of arguments that will be passed to detection " +
|
|
|
|
"plugins. Only certain detection plugins use arguments.")
|
2015-06-25 13:12:15 -06:00
|
|
|
parser.add_argument('--check_frequency', help="How often to run metric collection in seconds", type=int, default=30)
|
2014-11-10 10:51:22 -07:00
|
|
|
parser.add_argument('--dimensions', help="Additional dimensions to set for all metrics. A comma seperated list " +
|
|
|
|
"of name/value pairs, 'name:value,name2:value2'")
|
2015-04-24 11:50:06 -06:00
|
|
|
parser.add_argument('--ca_file', help="Sets the path to the ca certs file if using certificates. " +
|
|
|
|
"Required only if insecure is set to False", default='')
|
|
|
|
parser.add_argument('--insecure', help="Set whether certificates are used for Keystone authentication",
|
|
|
|
default=False)
|
|
|
|
parser.add_argument('--config_dir', help="Configuration directory", default='/etc/monasca/agent')
|
2014-07-17 09:17:30 -06:00
|
|
|
parser.add_argument('--log_dir', help="monasca-agent log directory", default='/var/log/monasca/agent')
|
2015-05-27 10:39:15 -06:00
|
|
|
parser.add_argument('--log_level', help="monasca-agent logging level (ERROR, WARNING, INFO, DEBUG)", required=False,
|
|
|
|
default='WARN')
|
2015-07-14 09:41:49 -06:00
|
|
|
parser.add_argument('--template_dir', help="Alternative template directory",
|
2015-05-27 10:39:15 -06:00
|
|
|
default=os.path.join(PREFIX_DIR, 'share/monasca/agent'))
|
2014-06-09 10:34:16 -06:00
|
|
|
parser.add_argument('--overwrite',
|
2014-11-20 15:24:05 -07:00
|
|
|
help="Overwrite existing plugin configuration. " +
|
2015-02-26 09:56:38 -07:00
|
|
|
"The default is to merge. agent.yaml is always overwritten.",
|
2014-06-09 10:34:16 -06:00
|
|
|
action="store_true")
|
2014-11-20 15:24:05 -07:00
|
|
|
parser.add_argument('--skip_enable', help="By default the service is enabled, " +
|
|
|
|
"which requires the script run as root. Set this to skip that step.",
|
2014-06-09 12:16:05 -06:00
|
|
|
action="store_true")
|
2015-05-06 11:41:28 -06:00
|
|
|
parser.add_argument('--user', help="User name to run monasca-agent as", default='mon-agent')
|
2014-11-10 10:51:22 -07:00
|
|
|
parser.add_argument('-s', '--service', help="Service this node is associated with, added as a dimension.")
|
2014-11-20 15:24:05 -07:00
|
|
|
parser.add_argument('--amplifier', help="Integer for the number of additional measurements to create. " +
|
|
|
|
"Additional measurements contain the 'amplifier' dimension. " +
|
2015-04-24 11:50:06 -06:00
|
|
|
"Useful for load testing; not for production use.", default=0)
|
2014-06-09 10:34:16 -06:00
|
|
|
parser.add_argument('-v', '--verbose', help="Verbose Output", action="store_true")
|
2015-07-14 09:41:49 -06:00
|
|
|
parser.add_argument('--dry_run', help="Make no changes just report on changes", action="store_true")
|
2014-06-09 10:34:16 -06:00
|
|
|
args = parser.parse_args()
|
|
|
|
|
|
|
|
if args.verbose:
|
|
|
|
logging.basicConfig(level=logging.DEBUG, format="%(levelname)s: %(message)s")
|
|
|
|
else:
|
|
|
|
logging.basicConfig(level=logging.INFO, format="%(levelname)s: %(message)s")
|
|
|
|
|
2015-07-14 09:41:49 -06:00
|
|
|
if args.dry_run:
|
|
|
|
log.info("Running in dry run mode, no changes will be made only reported")
|
|
|
|
|
2015-04-21 14:00:05 -06:00
|
|
|
# Detect and if possibly enable the agent service
|
|
|
|
agent_service = detect_init(PREFIX_DIR, args.config_dir, args.log_dir, args.template_dir, username=args.user)
|
2015-04-24 11:50:06 -06:00
|
|
|
|
|
|
|
if args.detection_plugins is None: # Skip base setup if running specific detection plugins
|
|
|
|
# Verify required options
|
|
|
|
if args.username is None or args.password is None or args.keystone_url is None:
|
|
|
|
log.error('Username, password and keystone_url are required when running full configuration.')
|
|
|
|
parser.print_help()
|
|
|
|
sys.exit(1)
|
|
|
|
if not args.skip_enable:
|
|
|
|
agent_service.enable()
|
|
|
|
|
2015-05-05 09:04:28 -06:00
|
|
|
gid = pwd.getpwnam(args.user).pw_gid
|
2015-04-24 11:50:06 -06:00
|
|
|
# Write the main agent.yaml - Note this is always overwritten
|
|
|
|
log.info('Configuring base Agent settings.')
|
|
|
|
dimensions = {}
|
|
|
|
# Join service in with the dimensions
|
|
|
|
if args.service:
|
|
|
|
dimensions.update({'service': args.service})
|
|
|
|
if args.dimensions:
|
|
|
|
dimensions.update(dict(item.strip().split(":") for item in args.dimensions.split(",")))
|
|
|
|
|
|
|
|
args.dimensions = dict((name, value) for (name, value) in dimensions.iteritems())
|
|
|
|
write_template(os.path.join(args.template_dir, 'agent.yaml.template'),
|
|
|
|
os.path.join(args.config_dir, 'agent.yaml'),
|
|
|
|
{'args': args, 'hostname': socket.getfqdn()},
|
|
|
|
gid,
|
|
|
|
is_yaml=True)
|
|
|
|
|
|
|
|
# Write the supervisor.conf
|
|
|
|
write_template(os.path.join(args.template_dir, 'supervisor.conf.template'),
|
|
|
|
os.path.join(args.config_dir, 'supervisor.conf'),
|
2015-05-06 11:41:28 -06:00
|
|
|
{'prefix': PREFIX_DIR, 'log_dir': args.log_dir, 'monasca_user': args.user},
|
2015-04-24 11:50:06 -06:00
|
|
|
gid)
|
2014-06-09 10:34:16 -06:00
|
|
|
|
|
|
|
# Run through detection and config building for the plugins
|
|
|
|
plugin_config = agent_config.Plugins()
|
2015-05-01 11:49:47 -06:00
|
|
|
detected_plugins = find_plugins(CUSTOM_PLUGIN_PATH)
|
2015-03-20 13:50:06 -06:00
|
|
|
if args.system_only:
|
|
|
|
from detection.plugins.system import System
|
|
|
|
plugins = [System]
|
2015-04-24 11:50:06 -06:00
|
|
|
elif args.detection_plugins is not None:
|
|
|
|
lower_plugins = [p.lower() for p in args.detection_plugins]
|
|
|
|
plugins = []
|
2015-05-01 11:49:47 -06:00
|
|
|
for plugin in detected_plugins:
|
2015-04-24 11:50:06 -06:00
|
|
|
if plugin.__name__.lower() in lower_plugins:
|
|
|
|
plugins.append(plugin)
|
|
|
|
|
|
|
|
if len(plugins) != len(args.detection_plugins):
|
2015-05-01 11:49:47 -06:00
|
|
|
plugin_names = [p.__name__ for p in detected_plugins]
|
2015-04-24 11:50:06 -06:00
|
|
|
log.warn("Not all plugins found, discovered plugins {0}\nAvailable plugins{1}".format(plugins,
|
|
|
|
plugin_names))
|
2015-03-20 13:50:06 -06:00
|
|
|
else:
|
2015-05-01 11:49:47 -06:00
|
|
|
plugins = detected_plugins
|
2015-04-24 11:50:06 -06:00
|
|
|
|
2015-03-20 13:50:06 -06:00
|
|
|
for detect_class in plugins:
|
2015-05-12 13:43:08 -06:00
|
|
|
detect = detect_class(args.template_dir, args.overwrite, args.detection_args)
|
2014-06-09 10:34:16 -06:00
|
|
|
if detect.available:
|
|
|
|
log.info('Configuring {0}'.format(detect.name))
|
|
|
|
new_config = detect.build_config()
|
2015-06-26 09:59:07 -06:00
|
|
|
if new_config is not None:
|
|
|
|
plugin_config.merge(new_config)
|
2015-05-14 15:02:32 -06:00
|
|
|
elif args.detection_plugins is not None: # Give a warning on failed detection when a plugin is called out
|
|
|
|
log.warn('Failed detection of plugin {0}.'.format(detect.name) +
|
|
|
|
"\n\tPossible causes: Service not found or missing arguments.")
|
2015-07-14 09:41:49 -06:00
|
|
|
return 1 # Others could still run but I want to make sure there is a non-zero exit code
|
2015-05-14 15:02:32 -06:00
|
|
|
|
2015-05-20 15:51:44 -06:00
|
|
|
# todo add option to install dependencies
|
2014-06-09 10:34:16 -06:00
|
|
|
|
|
|
|
# Write out the plugin config
|
2015-04-30 14:02:22 -06:00
|
|
|
changes = False
|
2015-05-05 09:04:28 -06:00
|
|
|
# The gid is created on service activation which we assume has happened before this step or before running with -d
|
|
|
|
gid = pwd.getpwnam(args.user).pw_gid
|
2014-06-09 10:34:16 -06:00
|
|
|
for key, value in plugin_config.iteritems():
|
|
|
|
# todo if overwrite is set I should either warn or just delete any config files not in the new config
|
|
|
|
config_path = os.path.join(args.config_dir, 'conf.d', key + '.yaml')
|
2014-07-01 14:27:12 -07:00
|
|
|
# merge old and new config, new has precedence
|
|
|
|
if (not args.overwrite) and os.path.exists(config_path):
|
2014-06-09 10:34:16 -06:00
|
|
|
with open(config_path, 'r') as config_file:
|
|
|
|
old_config = yaml.load(config_file.read())
|
|
|
|
if old_config is not None:
|
2015-04-30 14:02:22 -06:00
|
|
|
agent_config.merge_by_name(value['instances'], old_config['instances'])
|
2015-05-01 11:49:47 -06:00
|
|
|
# Sort before compare, if instances have no name the sort will fail making order changes significant
|
|
|
|
try:
|
|
|
|
value['instances'].sort(key=lambda k: k['name'])
|
|
|
|
old_config['instances'].sort(key=lambda k: k['name'])
|
|
|
|
except Exception:
|
|
|
|
pass
|
2015-04-30 14:02:22 -06:00
|
|
|
if value == old_config: # Don't write config if no change
|
|
|
|
continue
|
2015-07-14 09:41:49 -06:00
|
|
|
changes = True
|
|
|
|
if args.dry_run:
|
|
|
|
log.info("Changes would be made to the config file at {0}".format(config_path))
|
|
|
|
else:
|
|
|
|
with open(config_path, 'w') as config_file:
|
|
|
|
os.chmod(config_path, 0640)
|
|
|
|
os.chown(config_path, 0, gid)
|
|
|
|
config_file.write(yaml.safe_dump(value,
|
|
|
|
encoding='utf-8',
|
|
|
|
allow_unicode=True,
|
|
|
|
default_flow_style=False))
|
2014-06-09 10:34:16 -06:00
|
|
|
|
2015-04-30 14:02:22 -06:00
|
|
|
# Don't restart if only doing detection plugins and no changes found
|
|
|
|
if args.detection_plugins is not None and not changes:
|
|
|
|
plugin_names = [p.__name__ for p in plugins]
|
|
|
|
log.info('No changes found for plugins {0}, skipping restart of Monasca Agent'.format(plugin_names))
|
|
|
|
return 0
|
2015-07-14 09:41:49 -06:00
|
|
|
elif args.dry_run:
|
|
|
|
log.info('Running in dry mode, skipping changes and restart of Monasca Agent')
|
|
|
|
return 0
|
2015-04-30 14:02:22 -06:00
|
|
|
|
2015-02-26 09:56:38 -07:00
|
|
|
# Now that the config is built, start the service
|
2014-06-09 10:34:16 -06:00
|
|
|
try:
|
|
|
|
agent_service.start(restart=True)
|
|
|
|
except subprocess.CalledProcessError:
|
2014-06-09 12:16:05 -06:00
|
|
|
log.error('The service did not startup correctly see %s' % args.log_dir)
|
2014-06-09 10:34:16 -06:00
|
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
2014-06-30 17:24:27 -06:00
|
|
|
sys.exit(main())
|