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-10-03 14:26:55 -06:00
|
|
|
import platform
|
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-02-09 14:19:48 -07:00
|
|
|
from detection.plugins import DETECTION_PLUGINS
|
2015-01-14 17:22:48 -07:00
|
|
|
import service.sysv as sysv
|
2014-06-09 10:34:16 -06:00
|
|
|
|
2015-02-06 10:05:43 -07:00
|
|
|
from detection.utils import check_output
|
|
|
|
|
2014-06-09 10:34:16 -06:00
|
|
|
# List of all detection plugins to run
|
|
|
|
# Map OS to service type
|
2014-08-08 09:16:00 -06:00
|
|
|
OS_SERVICE_MAP = {'Linux': sysv.SysV}
|
2014-06-09 10:34:16 -06:00
|
|
|
|
|
|
|
log = logging.getLogger(__name__)
|
|
|
|
|
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])))
|
|
|
|
|
|
|
|
|
|
|
|
def write_template(template_path, out_path, variables, group):
|
|
|
|
""" 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:
|
|
|
|
with open(out_path, 'w') as conf:
|
|
|
|
conf.write(template.read().format(**variables))
|
|
|
|
os.chown(out_path, 0, group)
|
|
|
|
os.chmod(out_path, 0640)
|
|
|
|
|
2014-06-09 10:34:16 -06:00
|
|
|
|
|
|
|
def main(argv=None):
|
2014-06-09 12:20:03 -06:00
|
|
|
parser = argparse.ArgumentParser(description='Detect running daemons then configure and start the agent.',
|
|
|
|
formatter_class=argparse.ArgumentDefaultsHelpFormatter)
|
2014-07-01 14:27:12 -07:00
|
|
|
parser.add_argument(
|
2014-11-06 17:42:13 -07:00
|
|
|
'-u', '--username', help="Username used for keystone authentication", required=True)
|
2014-07-01 14:27:12 -07:00
|
|
|
parser.add_argument(
|
2014-11-06 17:42:13 -07:00
|
|
|
'-p', '--password', help="Password used for keystone authentication", required=True)
|
2014-06-09 10:34:16 -06:00
|
|
|
parser.add_argument('--keystone_url', help="Keystone url", required=True)
|
2015-02-18 09:43:37 -07:00
|
|
|
parser.add_argument('--monasca_url', help="Monasca API url, if not defined the url is pulled from keystone", required=False, default='')
|
2014-11-06 17:42:13 -07:00
|
|
|
parser.add_argument('--insecure', help="Set whether certificates are used for Keystone authentication", required=False, default=False)
|
|
|
|
parser.add_argument('--project_name', help="Project name for keystone authentication", required=False, default='')
|
|
|
|
parser.add_argument('--project_domain_id', help="Project domain id for keystone authentication", required=False, default='')
|
|
|
|
parser.add_argument('--project_domain_name', help="Project domain name for keystone authentication", required=False, default='')
|
|
|
|
parser.add_argument('--project_id', help="Keystone project id for keystone authentication", required=False, default='')
|
2014-11-20 15:24:05 -07: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", required=False, default='')
|
2015-02-18 09:43:37 -07:00
|
|
|
parser.add_argument('--check_frequency', help="How often to run metric collection in seconds", type=int, default=15)
|
2014-07-17 09:17:30 -06:00
|
|
|
parser.add_argument('--config_dir', help="Configuration directory", default='/etc/monasca/agent')
|
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'")
|
2014-07-17 09:17:30 -06:00
|
|
|
parser.add_argument('--log_dir', help="monasca-agent log directory", default='/var/log/monasca/agent')
|
2015-02-09 08:53:49 -07:00
|
|
|
parser.add_argument('--log_level', help="monasca-agent logging level (ERROR, WARNING, INFO, DEBUG)", required=False, default='INFO')
|
2014-07-01 14:27:12 -07:00
|
|
|
parser.add_argument(
|
2015-02-18 09:43:37 -07:00
|
|
|
'--template_dir', help="Alternative template directory", default=os.path.join(PREFIX_DIR, 'share/monasca/agent'))
|
2014-06-09 10:34:16 -06:00
|
|
|
parser.add_argument('--headless', help="Run in a non-interactive mode", action="store_true")
|
|
|
|
parser.add_argument('--overwrite',
|
2014-11-20 15:24:05 -07:00
|
|
|
help="Overwrite existing plugin configuration. " +
|
2014-06-09 10:34:16 -06:00
|
|
|
"The default is to merge. Agent.conf is always overwritten.",
|
|
|
|
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")
|
2014-07-17 09:17:30 -06:00
|
|
|
parser.add_argument('--user', help="User name to run monasca-agent as", default='monasca-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. " +
|
2014-11-20 13:35:56 -07:00
|
|
|
"Useful for load testing; not for production use.", default=0, required=False)
|
2014-06-09 10:34:16 -06:00
|
|
|
parser.add_argument('-v', '--verbose', help="Verbose Output", action="store_true")
|
|
|
|
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")
|
|
|
|
|
|
|
|
# Detect os
|
2014-08-08 09:16:00 -06:00
|
|
|
detected_os = platform.system()
|
|
|
|
if detected_os == 'Linux':
|
2014-12-11 10:31:35 -07:00
|
|
|
linux_flavor = platform.linux_distribution()[0]
|
|
|
|
if 'Ubuntu' or 'debian' in linux_flavor:
|
2015-02-19 16:51:58 -07:00
|
|
|
for package in ['coreutils']:
|
2015-02-18 09:43:37 -07:00
|
|
|
# Check for required dependencies for system checks
|
2014-12-11 10:31:35 -07:00
|
|
|
try:
|
2015-02-06 10:05:43 -07:00
|
|
|
output = check_output('dpkg -s {0}'.format(package),
|
2015-02-18 09:43:37 -07:00
|
|
|
stderr=subprocess.STDOUT,
|
|
|
|
shell=True)
|
2014-12-11 10:31:35 -07:00
|
|
|
except subprocess.CalledProcessError:
|
2015-02-06 10:05:43 -07:00
|
|
|
log.warn("*** {0} package is not installed! ***".format(package) +
|
|
|
|
"\nNOTE: If you do not install the {0} ".format(package) +
|
2014-12-11 10:31:35 -07:00
|
|
|
"package, you will not receive all of the standard " +
|
|
|
|
"operating system type metrics!")
|
|
|
|
else:
|
|
|
|
pass
|
2014-08-08 09:16:00 -06:00
|
|
|
elif detected_os == 'Darwin':
|
|
|
|
print("Mac OS is not currently supported by the Monasca Agent")
|
|
|
|
sys.exit()
|
|
|
|
elif detected_os == 'Windows':
|
|
|
|
print("Windows is not currently supported by the Monasca Agent")
|
|
|
|
sys.exit()
|
2014-08-08 09:16:00 -06:00
|
|
|
else:
|
|
|
|
print("{0} is not currently supported by the Monasca Agent".format(detected_os))
|
2014-06-09 10:34:16 -06:00
|
|
|
|
2014-07-01 14:27:12 -07:00
|
|
|
# Service enable, includes setup of users/config directories so must be
|
|
|
|
# done before configuration
|
2015-02-18 09:43:37 -07:00
|
|
|
agent_service = OS_SERVICE_MAP[detected_os](PREFIX_DIR,
|
2014-07-17 09:17:30 -06:00
|
|
|
args.config_dir,
|
2015-02-18 09:43:37 -07:00
|
|
|
args.log_dir,
|
|
|
|
args.template_dir,
|
|
|
|
username=args.user)
|
2014-06-10 09:39:47 -06:00
|
|
|
if not args.skip_enable:
|
2014-06-09 12:16:05 -06:00
|
|
|
agent_service.enable()
|
2014-06-09 10:34:16 -06:00
|
|
|
|
2014-06-10 12:47:30 -06:00
|
|
|
gid = pwd.getpwnam(args.user).pw_gid
|
2014-06-09 10:34:16 -06:00
|
|
|
# Write the main agent.conf - Note this is always overwritten
|
|
|
|
log.info('Configuring base Agent settings.')
|
2015-02-18 09:43:37 -07:00
|
|
|
# Join service in with the dimensions
|
|
|
|
if args.service:
|
|
|
|
if args.dimensions is None:
|
|
|
|
args.dimensions = 'service:' + args.service
|
|
|
|
else:
|
|
|
|
args.dimensions = ','.join([args.dimensions, 'service:' + args.service])
|
|
|
|
write_template(os.path.join(args.template_dir, 'agent.conf.template'),
|
|
|
|
os.path.join(args.config_dir, 'agent.conf'),
|
|
|
|
{'args': args, 'hostname': socket.getfqdn() },
|
|
|
|
gid)
|
|
|
|
|
|
|
|
# Write the supervisor.conf
|
|
|
|
write_template(os.path.join(args.template_dir, 'supervisor.conf.template'),
|
|
|
|
os.path.join(args.config_dir, 'supervisor.conf'),
|
|
|
|
{'prefix': PREFIX_DIR, 'log_dir': args.log_dir},
|
|
|
|
gid)
|
2014-06-09 10:34:16 -06:00
|
|
|
|
|
|
|
# Run through detection and config building for the plugins
|
|
|
|
plugin_config = agent_config.Plugins()
|
|
|
|
for detect_class in DETECTION_PLUGINS:
|
|
|
|
detect = detect_class(args.template_dir, args.overwrite)
|
|
|
|
if detect.available:
|
|
|
|
log.info('Configuring {0}'.format(detect.name))
|
|
|
|
new_config = detect.build_config()
|
2014-06-10 15:42:20 -06:00
|
|
|
plugin_config.merge(new_config)
|
2014-06-09 10:34:16 -06:00
|
|
|
|
2014-07-01 14:27:12 -07:00
|
|
|
# todo add option to install dependencies
|
2014-06-09 10:34:16 -06:00
|
|
|
|
|
|
|
# Write out the plugin config
|
|
|
|
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
|
|
|
|
# todo add the ability to show a diff before overwriting or merging 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:
|
2014-06-10 15:45:56 -06:00
|
|
|
agent_config.deep_merge(old_config, value)
|
2014-06-09 15:35:44 -06:00
|
|
|
value = old_config
|
2014-06-09 10:34:16 -06:00
|
|
|
with open(config_path, 'w') as config_file:
|
2014-07-03 15:50:06 -07:00
|
|
|
os.chmod(config_path, 0o640)
|
2014-06-10 12:47:30 -06:00
|
|
|
os.chown(config_path, 0, gid)
|
2014-07-28 14:11:46 -06:00
|
|
|
config_file.write(yaml.safe_dump(value, encoding='utf-8', allow_unicode=True))
|
2014-06-09 10:34:16 -06:00
|
|
|
|
|
|
|
# Now that the config is build start the service
|
|
|
|
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())
|