# (C) Copyright 2016 Hewlett Packard Enterprise Development Company LP # 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 configparser import logging import os import re from oslo_config import cfg from oslo_utils import importutils from monasca_setup import agent_config from monasca_setup import detection from monasca_setup.detection import utils log = logging.getLogger(__name__) # Maximum time to wait before updating neutron router cache(in seconds) neutron_refresh = 60 * 60 * 4 # Four hours # Directory to use for metric caches cache_dir = "/dev/shm" # ovs-vsctl command needs sudo privileges to connect to # /var/run/openvswitch/db.sock which is created by ovsdb-server. ovs_cmd = "sudo /usr/bin/ovs-vsctl" # If set, will submit network metrics in bits network_use_bits = False # Regular expression for interface types included_interface_re = 'qg.*|vhu.*|sg.*' # If set, will submit raw counters from ovs-vsctl command output for the given # network interface use_absolute_metrics = True # If set, will submit the rate metrics use_rate_metrics = True # If set, will submit the health metrics use_health_metrics = True # If set, router max capacity metrics will be published publish_router_capacity = False # Acceptable arguments acceptable_args = ['username', 'password', 'project_name', 'user_domain_name', 'user_domain_id', 'project_domain_name', 'project_domain_id' 'auth_url', 'cache_dir', 'neutron_refresh', 'ovs_cmd', 'network_use_bits', 'check_router_ha', 'region_name', 'included_interface_re', 'conf_file_path', 'use_absolute_metrics', 'use_rate_metrics', 'use_health_metrics', 'publish_router_capacity'] # Arguments which must be ignored if provided ignorable_args = ['username', 'password', 'project_name', 'user_domain_name', 'user_domain_id', 'project_domain_name', 'project_domain_id', 'auth_url', 'region_name', 'conf_file_path'] class Ovs(detection.Plugin): """Detect OVS daemons and setup configuration to monitor """ PROC_NAME = 'neutron-openvsw' """Name of the ovs process to look for""" REQUIRED_CONF_SECTIONS = 'keystone_authtoken', """Tuple of sections that must be part of neutron configuration file""" def _detect(self): process_exist = utils.find_process_cmdline(Ovs.PROC_NAME) has_dependencies = self.dependencies_installed() neutron_conf = self._get_ovs_config_file() if process_exist else '' neutron_conf_exists = os.path.isfile(neutron_conf) neutron_conf_valid = (neutron_conf_exists and self._is_neutron_conf_valid(neutron_conf)) self.available = (process_exist is not None and neutron_conf_valid and has_dependencies) self.cmd = '' if process_exist: self.cmd = process_exist.as_dict(attrs=['cmdline'])['cmdline'] if not self.available: if not process_exist: log.info('OVS daemon process [%s] does not exist.', Ovs.PROC_NAME) elif not neutron_conf_exists: log.warning(('OVS daemon process exists but configuration ' 'file was not found. Path to file does not exist ' 'as a process parameter or was not ' 'passed via args.')) elif not neutron_conf_valid: log.error(('OVS daemon process exists, configuration file was ' 'found but it looks like it does not contain ' 'one of following sections=%s. ' 'Check if neutron was not configured to load ' 'configuration from /etc/neutron/neutron.conf.d/.'), Ovs.REQUIRED_CONF_SECTIONS) # NOTE(trebskit) the problem with that approach is that # each setting that Ovs plugin require might be scattered # through multiple files located inside # /etc/neutron/neutron.conf.d/ # not to mention that it is still possible to have # yet another configuration file passed as neutron CLI # argument elif not has_dependencies: log.error(('OVS daemon process exists but required ' 'dependencies were not found.\n' 'Run pip install monasca-agent[ovs] ' 'to install all dependencies.')) else: for_opts = [{'opt': cfg.StrOpt('region', default='RegionOne'), 'group': 'service_auth'}, {'opt': cfg.StrOpt('region_name'), 'group': 'nova'}, {'opt': cfg.StrOpt('nova_region_name'), 'group': 'DEFAULT'}, {'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('user_domain_name'), 'group': 'keystone_authtoken'}, {'opt': cfg.StrOpt('user_domain_id'), 'group': 'keystone_authtoken'}, {'opt': cfg.StrOpt('project_domain_name'), 'group': 'keystone_authtoken'}, {'opt': cfg.StrOpt('project_domain_id'), 'group': 'keystone_authtoken'}, {'opt': cfg.StrOpt('auth_url'), 'group': 'keystone_authtoken'}, {'opt': cfg.StrOpt('identity_uri'), 'group': 'keystone_authtoken'}] self.conf = utils.load_oslo_configuration(from_cmd=self.cmd, in_project='neutron', for_opts=for_opts ) log.info("\tUsing neutron configuration file {0} for detection".format(neutron_conf)) self.neutron_conf = neutron_conf def _is_neutron_conf_valid(self, conf_file): neutron_cfg = configparser.SafeConfigParser() neutron_cfg.read(conf_file) for section in self.REQUIRED_CONF_SECTIONS: if not neutron_cfg.has_section(section=section): return False return True def _get_ovs_config_file(self): neutron_conf = None if self.args: for arg in self.args: if arg == 'conf_file_path': neutron_conf = self.args[arg] # Try to detect the location of the Neutron configuration file. # Walk through the list of processes, searching for 'neutron' # process with 'neutron.conf' inside one of the parameters. if not neutron_conf: proc = utils.find_process_name(Ovs.PROC_NAME) proc_dict = proc.as_dict(attrs=['cmdline']) cmd = proc_dict['cmdline'] neutron_config_params = [param for param in cmd if 'neutron.conf' in param] if neutron_config_params: if '=' in neutron_config_params[0]: neutron_conf = neutron_config_params[0].split('=')[1] else: neutron_conf = neutron_config_params[0] return neutron_conf def build_config(self): """Build the config as a Plugins object and return. """ config = agent_config.Plugins() log.info("\tUsing neutron configuration file {0} and configuration dir" " {1}".format(self.conf.default_config_files, self.conf.default_config_dirs)) cfg_section = 'keystone_authtoken' cfg_needed = {'username': 'username', 'password': 'password', 'project_name': 'project_name'} cfg_domain = ['user_domain_name', 'user_domain_id', 'project_domain_name', 'project_domain_id'] # Start with plugin-specific configuration parameters init_config = {'cache_dir': cache_dir, 'neutron_refresh': neutron_refresh, 'ovs_cmd': ovs_cmd, 'network_use_bits': network_use_bits, 'included_interface_re': included_interface_re, 'use_absolute_metrics': use_absolute_metrics, 'use_rate_metrics': use_rate_metrics, 'use_health_metrics': use_health_metrics, 'publish_router_capacity': publish_router_capacity} for option in cfg_needed: init_config[option] = self.get_option(cfg_section, cfg_needed[option]) for option in cfg_domain: if self.has_option(cfg_section, option): init_config[option] = self.get_option(cfg_section, option) # Create an identity URI (again, slightly different for Devstack) if self.has_option(cfg_section, 'auth_url'): init_config['auth_url'] = self.get_option(cfg_section, 'auth_url') else: init_config['auth_url'] = self.get_option(cfg_section, 'identity_uri') # Create an region_name (again, slightly different for Devstack) if self.has_option('service_auth', 'region'): init_config['region_name'] = str(self.get_option('service_auth', 'region')) else: try: init_config['region_name'] = str(self.get_option('nova', 'region_name')) except configparser.NoOptionError: log.debug(('Option region_name was not found in nova ' 'section, nova_region_name option from ' 'DEFAULT section will be used.')) init_config['region_name'] = str(self.get_option('DEFAULT', 'nova_region_name')) # Handle monasca-setup detection arguments, which take precedence if self.args: for arg in self.args: if arg in acceptable_args and arg not in ignorable_args: if arg == 'included_interface_re': try: re.compile(self.args[arg]) except re.error as e: exception_msg = ( "Invalid regular expression given for " "'included_interface_re'. {0}.".format(e)) log.exception(exception_msg) raise Exception(exception_msg) init_config[arg] = self.literal_eval(self.args[arg]) elif arg in ignorable_args: log.warn("Argument '{0}' is ignored; Fetching {0} from " "neutron configuration file.".format(arg)) else: log.warn("Invalid argument '{0}' " "has been provided!!!".format(arg)) config['ovs'] = {'init_config': init_config, 'instances': []} return config def dependencies_installed(self): return (importutils.try_import('neutronclient', False) and importutils.try_import('novaclient', False)) def has_option(self, section, option): return option in self.conf.get(section) def get_option(self, section, option): return self.conf.get(section).get(option)