monasca-agent/monasca_setup/detection/plugins/libvirt.py

203 lines
9.3 KiB
Python

# (c) Copyright 2015-2016 Hewlett Packard Enterprise Development LP
import ConfigParser
import grp
import logging
import os
import pwd
from shutil import copy
import subprocess
import sys
from monasca_agent.common.psutil_wrapper import psutil
import monasca_setup.agent_config
from monasca_setup.detection import Plugin
log = logging.getLogger(__name__)
# Directory to use for instance and metric caches (preferred tmpfs "/dev/shm")
cache_dir = "/dev/shm"
# Maximum age of instance cache before automatic refresh (in seconds)
nova_refresh = 60 * 60 * 4 # Four hours
# Probation period before metrics are gathered for a VM (in seconds)
vm_probation = 60 * 5 # Five minutes
# List of instance metadata keys to be sent as dimensions
# By default 'scale_group' metadata is used here for supporting auto
# scaling in Heat.
metadata = ['scale_group']
# Include scale group dimension for customer metrics.
customer_metadata = ['scale_group']
# List 'ping' commands (paths and parameters) in order of preference.
# The plugin will use the first fuctional command. 127.0.0.1 will be appended.
ping_options = [["/usr/bin/fping", "-n", "-c1", "-t250", "-q"],
["/sbin/fping", "-n", "-c1", "-t250", "-q"],
["/bin/ping", "-n", "-c1", "-w1", "-q"]]
# Path to 'ip' command (needed to execute ping within network namespaces)
ip_cmd = "/sbin/ip"
# How many ping commands to run concurrently
default_max_ping_concurrency = 8
# Disk metrics can be collected at a larger interval than other vm metrics
default_disk_collection_period = 0
# VNIC metrics can be collected at a larger interval than other vm metrics
default_vnic_collection_period = 0
# Arguments which should be written as integers, not strings
INT_ARGS = ['disk_collection_period', 'vnic_collection_period',
'max_ping_concurrency', 'nova_refresh', 'vm_probation']
class Libvirt(Plugin):
"""Configures VM monitoring through Nova"""
def _detect(self):
"""Set self.available True if the process and config file are detected
"""
# Detect Agent's OS username by getting the group owner of confg file
try:
gid = os.stat('/etc/monasca/agent/agent.yaml').st_gid
self.agent_user = grp.getgrgid(gid)[0]
except OSError:
self.agent_user = None
# Try to detect the location of the Nova configuration file.
# Walk through the list of processes, searching for 'nova-compute'
# process with 'nova.conf' inside one of the parameters
nova_conf = None
for proc in psutil.process_iter():
try:
cmd = proc.as_dict(['cmdline'])['cmdline']
if len(cmd) > 2 and 'python' in cmd[0] and 'nova-compute' in cmd[1]:
conf_indexes = [cmd.index(y) for y in cmd if 'nova.conf' in y]
if not conf_indexes:
if os.path.exists('/etc/nova/nova.conf'):
nova_conf = "/etc/nova/nova.conf"
continue
param = conf_indexes[0]
if '=' in cmd[param]:
nova_conf = cmd[param].split('=')[1]
else:
nova_conf = cmd[param]
except (IOError, psutil.NoSuchProcess):
# Process has already terminated, ignore
continue
if (nova_conf is not None and os.path.isfile(nova_conf)):
self.available = True
self.nova_conf = nova_conf
def build_config(self):
"""Build the config as a Plugins object and return back.
"""
config = monasca_setup.agent_config.Plugins()
if self.dependencies_installed():
nova_cfg = ConfigParser.SafeConfigParser()
log.info("\tUsing nova configuration file {0}".format(self.nova_conf))
nova_cfg.read(self.nova_conf)
# Which configuration options are needed for the plugin YAML?
# Use a dict so that they can be renamed later if necessary
cfg_needed = {'username': 'username',
'password': 'password',
'project_name': 'project_name'}
cfg_section = 'keystone_authtoken'
# Start with plugin-specific configuration parameters
init_config = {'cache_dir': cache_dir,
'nova_refresh': nova_refresh,
'vm_probation': vm_probation,
'metadata': metadata,
'customer_metadata': customer_metadata,
'max_ping_concurrency': default_max_ping_concurrency,
'disk_collection_period': default_disk_collection_period,
'vnic_collection_period': default_vnic_collection_period}
# Set default parameters for included checks
init_config['vm_cpu_check_enable'] = self.literal_eval('True')
init_config['vm_disks_check_enable'] = self.literal_eval('True')
init_config['vm_network_check_enable'] = self.literal_eval('True')
init_config['vm_ping_check_enable'] = self.literal_eval('True')
init_config['vm_extended_disks_check_enable'] = self.literal_eval('False')
for option in cfg_needed:
init_config[option] = nova_cfg.get(cfg_section, cfg_needed[option])
# Create an identity URI (again, slightly different for Devstack)
if nova_cfg.has_option(cfg_section, 'auth_url'):
init_config['auth_url'] = nova_cfg.get(cfg_section, 'auth_url')
else:
init_config['auth_url'] = nova_cfg.get(cfg_section, 'identity_uri')
# Verify requirements to enable ping checks
init_config['ping_check'] = self.literal_eval('False')
if self.agent_user is None:
log.warn("\tUnable to determine agent user. Skipping ping checks.")
else:
try:
from neutronclient.v2_0 import client
# Copy system 'ip' command to local directory
copy(ip_cmd, sys.path[0])
# Restrict permissions on the local 'ip' command
os.chown("{0}/ip".format(sys.path[0]), pwd.getpwnam(self.agent_user).pw_uid, 0)
os.chmod("{0}/ip".format(sys.path[0]), 0o700)
# Set capabilities on 'ip' which will allow
# self.agent_user to exec commands in namespaces
setcap_cmd = ['/sbin/setcap', 'cap_sys_admin+ep',
"{0}/ip".format(sys.path[0])]
subprocess.Popen(setcap_cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
# Verify that the capabilities were set
setcap_cmd.extend(['-v', '-q'])
subprocess.check_call(setcap_cmd)
# Look for the best ping command
for ping_cmd in ping_options:
if os.path.isfile(ping_cmd[0]):
init_config['ping_check'] = "{0}/ip netns exec NAMESPACE {1}".format(sys.path[0],
' '.join(ping_cmd))
log.info("\tEnabling ping checks using {0}".format(ping_cmd[0]))
break
if init_config['ping_check'] is False:
log.warn("\tUnable to find suitable ping command, disabling ping checks.")
except ImportError:
log.warn("\tneutronclient module missing, required for ping checks.")
pass
except IOError:
log.warn("\tUnable to copy {0}, ping checks disabled.".format(ip_cmd))
pass
except (subprocess.CalledProcessError, OSError):
log.warn("\tUnable to set up ping checks, setcap failed ({0})".format(' '.join(setcap_cmd)))
pass
# Handle monasca-setup detection arguments, which take precedence
if self.args:
for arg in self.args:
if arg in INT_ARGS:
value = self.args[arg]
try:
init_config[arg] = int(value)
except ValueError:
log.warn("\tInvalid integer value '{0}' for parameter {1}, ignoring value"
.format(value, arg))
else:
init_config[arg] = self.literal_eval(self.args[arg])
config['libvirt'] = {'init_config': init_config,
'instances': []}
return config
def dependencies_installed(self):
try:
import json
import monasca_agent.collector.virt.inspector
import time
from netaddr import all_matching_cidrs
from novaclient import client
except ImportError:
log.warn("\tDependencies not satisfied; plugin not configured.")
return False
if os.path.isdir(cache_dir) is False:
log.warn("\tCache directory {} not found;" +
" plugin not configured.".format(cache_dir))
return False
return True