c983708637
Added Apache 2.0 License for missed files Change-Id: I72217d3cf0089434a9bbe1e5dfd57224a803704e Signed-off-by: Jui Chandwaskar <jchandwaskar@op5.com>
248 lines
10 KiB
Python
248 lines
10 KiB
Python
# (c) Copyright 2015-2016 Hewlett Packard Enterprise Development LP
|
|
# Copyright 2017 Fujitsu LIMITED
|
|
# Copyright 2017 SUSE Linux GmbH
|
|
# 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.
|
|
|
|
import logging
|
|
import os
|
|
import pwd
|
|
from shutil import copy
|
|
import subprocess
|
|
import sys
|
|
|
|
from oslo_config import cfg
|
|
from oslo_utils import importutils
|
|
|
|
from monasca_setup import agent_config
|
|
from monasca_setup.detection import plugin
|
|
from monasca_setup.detection import utils
|
|
|
|
|
|
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 functional 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"],
|
|
["/usr/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']
|
|
|
|
_REQUIRED_OPTS = [
|
|
{'opt': cfg.StrOpt('username'), 'group': 'keystone_authtoken'},
|
|
{'opt': cfg.StrOpt('password'), 'group': 'keystone_authtoken'},
|
|
{'opt': cfg.StrOpt('project_name'), 'group': 'keystone_authtoken'},
|
|
{'opt': cfg.StrOpt('auth_url'), 'group': 'keystone_authtoken'}
|
|
]
|
|
"""Nova configuration opts required by this plugin"""
|
|
|
|
|
|
class Libvirt(plugin.Plugin):
|
|
"""Configures VM monitoring through Nova"""
|
|
|
|
FAILED_DETECTION_MSG = 'libvirt plugin will not not be configured.'
|
|
|
|
def _detect(self):
|
|
"""Set self.available True if the process and config file are detected
|
|
"""
|
|
|
|
# NOTE(trebskit) bind each check we execute to another one
|
|
# that way if X-one fails following won't be executed
|
|
# and detection phase will end faster
|
|
nova_proc = utils.find_process_name('nova-compute')
|
|
has_deps = self.dependencies_installed() if nova_proc else None
|
|
nova_conf = self._find_nova_conf(nova_proc) if has_deps else None
|
|
has_cache_dir = self._has_cache_dir() if nova_conf else None
|
|
agent_user = utils.get_agent_username() if has_cache_dir else None
|
|
|
|
self.available = nova_conf and has_cache_dir
|
|
if not self.available:
|
|
if not nova_proc:
|
|
detailed_message = '\tnova-compute process not found.'
|
|
log.info('%s\n%s' % (detailed_message,
|
|
self.FAILED_DETECTION_MSG))
|
|
elif not has_deps:
|
|
detailed_message = ('\tRequired dependencies were not found.\n'
|
|
'Run pip install monasca-agent[libvirt] '
|
|
'to install all dependencies.')
|
|
log.warning('%s\n%s' % (detailed_message,
|
|
self.FAILED_DETECTION_MSG))
|
|
elif not has_cache_dir:
|
|
detailed_message = '\tCache directory %s not found' % cache_dir
|
|
log.warning('%s\n%s' % (detailed_message,
|
|
self.FAILED_DETECTION_MSG))
|
|
elif not nova_conf:
|
|
detailed_message = ('\tnova-compute process was found, '
|
|
'but it was impossible to '
|
|
'read it\'s configuration.')
|
|
log.warning('%s\n%s' % (detailed_message,
|
|
self.FAILED_DETECTION_MSG))
|
|
else:
|
|
self.nova_conf = nova_conf
|
|
self._agent_user = agent_user
|
|
|
|
def build_config(self):
|
|
"""Build the config as a Plugins object and return back.
|
|
"""
|
|
config = agent_config.Plugins()
|
|
init_config = self._get_init_config()
|
|
|
|
self._configure_ping(init_config)
|
|
|
|
# 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 _configure_ping(self, init_config):
|
|
if self._agent_user is None:
|
|
log.warn("\tUnable to determine agent user. Skipping ping checks.")
|
|
return
|
|
|
|
try:
|
|
client = importutils.try_import('neutronclient.v2_0.client',
|
|
False)
|
|
if not client:
|
|
log.warning(
|
|
'\tpython-neutronclient module missing, '
|
|
'required for ping checks.')
|
|
return
|
|
|
|
# TODO(dmllr) Find a better rundir or avoid copying the binary
|
|
# alltogether. see https://storyboard.openstack.org/#!/story/2001593
|
|
monasca_rundir = sys.path[0]
|
|
monasca_ip = "{0}/monasca-agent-ip".format(monasca_rundir)
|
|
# Copy system 'ip' command to monasca_rundir
|
|
copy(ip_cmd, monasca_ip)
|
|
|
|
# Restrict permissions on the local 'ip' command
|
|
os.chown(monasca_ip, *self._get_user_uid_gid(self._agent_user))
|
|
os.chmod(monasca_ip, 0o700)
|
|
|
|
# Set capabilities on 'ip' which will allow
|
|
# self.agent_user to exec commands in namespaces
|
|
setcap_cmd = ['/sbin/setcap', 'cap_sys_admin+ep',
|
|
monasca_ip]
|
|
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} netns exec NAMESPACE {1}".format(
|
|
monasca_ip,
|
|
' '.join(ping_cmd))
|
|
log.info(
|
|
"\tEnabling ping checks using {0}".format(ping_cmd[0]))
|
|
init_config['ping_check'] = True
|
|
break
|
|
if init_config['ping_check'] is False:
|
|
log.warn('\tUnable to find suitable ping command, '
|
|
'disabling ping checks.')
|
|
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
|
|
|
|
def dependencies_installed(self):
|
|
return importutils.try_import('novaclient.client', False)
|
|
|
|
def _get_init_config(self):
|
|
keystone_auth_section = self.nova_conf['keystone_authtoken']
|
|
init_config = {
|
|
'cache_dir': cache_dir,
|
|
'nova_refresh': nova_refresh,
|
|
'metadata': metadata,
|
|
'vm_probation': vm_probation,
|
|
'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,
|
|
'vm_cpu_check_enable': True,
|
|
'vm_disks_check_enable': True,
|
|
'vm_network_check_enable': True,
|
|
'vm_ping_check_enable': True,
|
|
'vm_extended_disks_check_enable': False,
|
|
'ping_check': False,
|
|
'username': keystone_auth_section['username'],
|
|
'password': keystone_auth_section['password'],
|
|
'project_name': keystone_auth_section['project_name'],
|
|
'auth_url': keystone_auth_section['auth_url']
|
|
}
|
|
return init_config
|
|
|
|
@staticmethod
|
|
def _has_cache_dir():
|
|
return os.path.isdir(cache_dir)
|
|
|
|
@staticmethod
|
|
def _find_nova_conf(nova_process):
|
|
try:
|
|
nova_cmd = nova_process.as_dict(['cmdline'])['cmdline']
|
|
return utils.load_oslo_configuration(from_cmd=nova_cmd,
|
|
in_project='nova',
|
|
for_opts=_REQUIRED_OPTS)
|
|
except cfg.Error:
|
|
log.exception('Failed to load nova configuration')
|
|
return None
|
|
|
|
@staticmethod
|
|
def _get_user_uid_gid(username):
|
|
stat = pwd.getpwnam(username)
|
|
uid = stat.pw_uid
|
|
gid = stat.pw_gid
|
|
return uid, gid
|