Add detection plugin for enabling ovs plugin

This plugin detects the ovs exsistance and writes the configurations to
ovs.yaml in conf.d directory. The configurations will be fetched from the
neutron configuration file.

Change-Id: I0b469276231764f6c854a6b0149cd3a4269ac4a2
This commit is contained in:
Gandharva 2016-05-22 03:23:20 +05:30
parent 5e1eee2803
commit 253d48cbe4
4 changed files with 441 additions and 0 deletions

30
conf.d/ovs.yaml.example Normal file
View File

@ -0,0 +1,30 @@
# (C) Copyright 2016 Hewlett Packard Enterprise Development Company LP
init_config:
# These are Neutron credentials, [keystone_authtoken] in /etc/neutron/neutron.conf
admin_password: password
admin_tenant_name: services
admin_user: neutron
identity_uri: 'http://192.168.10.5:35357/v2.0'
# Number of seconds to wait before updating the neutron router cache file.
neutron_refresh: 14400
# The region name in /etc/neutron/neutron.conf
region_name: 'region1'
# Location of temporary files maintained by the plugin. Ramdisk preferred.
cache_dir: /dev/shm
# Specfies network metrics in bitslog.
network_use_bits: false
# If set, will submit raw counters from ovs-vsctl command output for the given
# network interface
use_absolute_metrics: true
# Installations that don't allow usage of sudo should copy the `ovs-vsctl`
# command to another location and use the `setcap` command to allow the
# monasca-agent to run that command. The new location of the `ovs-vsctl`
# command should be what is set in the config file for `ovs_cmd`.
ovs_cmd: 'sudo /usr/bin/ovs-vsctl'
# Regular expression for the interfaces type
included_interface_re: tap.*|qr.*|qg.*|vhu.*
instances:
# Instances are not used and should be empty in `ovs.yaml` because like the
# ovs plugin it runs against all routers hosted on the node at once.
- {}

View File

@ -0,0 +1,167 @@
# (C) Copyright 2016 Hewlett Packard Enterprise Development Company LP
import ConfigParser
import logging
import os
import psutil
import re
import monasca_setup.agent_config
import monasca_setup.detection
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"
# Installations that don't allow usage of sudo should copy the `ovs-vsctl`
# command to another location and use the `setcap` command to allow the
# monasca-agent to run that command. The new location of the `ovs-vsctl`
# command should be what is set in the config file for `ovs_cmd`.
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 = 'tap.*|qr.*|qg.*|vhu.*'
# If set, will submit raw counters from ovs-vsctl command output for the given
# network interface
use_absolute_metrics = True
# Acceptable arguments
acceptable_args = ['admin_user', 'admin_password', 'admin_tenant_name',
'identity_uri', 'cache_dir', 'neutron_refresh', 'ovs_cmd',
'network_use_bits', 'check_router_ha', 'region_name',
'included_interface_re', 'conf_file_path', 'use_absolute_metrics']
# Arguments which must be ignored if provided
ignorable_args = ['admin_user', 'admin_password', 'admin_tenant_name',
'identity_uri', 'region_name', 'conf_file_path']
# Regular expression to match the URI version
uri_version_re = re.compile('.*v2.0|.*v3.0|.*v1|.*v2')
class Ovs(monasca_setup.detection.Plugin):
"""Detect OVS daemons and setup configuration to monitor
"""
def _detect(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:
for proc in psutil.process_iter():
try:
proc_dict = proc.as_dict()
if proc_dict['name'] == 'neutron-openvsw':
cmd = proc_dict['cmdline']
neutron_config_params = [param for param in cmd if 'neutron.conf' in param]
if not neutron_config_params:
continue
if '=' in neutron_config_params[0]:
neutron_conf = neutron_config_params[0].split('=')[1]
else:
neutron_conf = neutron_config_params[0]
except (IOError, psutil.NoSuchProcess):
# Process has already terminated, ignore
continue
if (neutron_conf is not None and os.path.isfile(neutron_conf)):
self.available = True
self.neutron_conf = neutron_conf
else:
log.error("Neutron configuration file not found!!!")
def build_config(self):
"""Build the config as a Plugins object and return.
"""
config = monasca_setup.agent_config.Plugins()
if self.dependencies_installed():
neutron_cfg = ConfigParser.SafeConfigParser()
log.info("\tUsing neutron configuration file {0}".format(self.neutron_conf))
neutron_cfg.read(self.neutron_conf)
cfg_needed = {'admin_user': 'admin_user',
'admin_password': 'admin_password',
'admin_tenant_name': 'admin_tenant_name'}
cfg_section = 'keystone_authtoken'
# Handle Devstack's slightly different neutron.conf names
if (
neutron_cfg.has_option(cfg_section, 'username') and
neutron_cfg.has_option(cfg_section, 'password') and
neutron_cfg.has_option(cfg_section, 'project_name')):
cfg_needed = {'admin_user': 'username',
'admin_password': 'password',
'admin_tenant_name': 'project_name'}
# 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}
for option in cfg_needed:
init_config[option] = neutron_cfg.get(cfg_section, cfg_needed[option])
uri_version = 'v2.0'
if neutron_cfg.has_option(cfg_section, 'auth_version'):
uri_version = str(neutron_cfg.get(cfg_section, 'auth_version'))
# Create an identity URI (again, slightly different for Devstack)
if neutron_cfg.has_option(cfg_section, 'auth_url'):
if re.match(uri_version_re, str(neutron_cfg.get(cfg_section, 'auth_url'))):
uri_version = ''
init_config['identity_uri'] = "{0}/{1}".format(neutron_cfg.get(cfg_section, 'auth_url'), uri_version)
else:
init_config['identity_uri'] = "{0}/{1}".format(neutron_cfg.get(cfg_section, 'identity_uri'), uri_version)
# Create an region_name (again, slightly different for Devstack)
if neutron_cfg.has_option('service_auth', 'region'):
init_config['region_name'] = str(neutron_cfg.get('service_auth', 'region'))
else:
init_config['region_name'] = str(neutron_cfg.get('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):
try:
import monasca_agent.collector.virt.inspector
import neutronclient.v2_0.client
except ImportError as e:
exception_msg = (
"Dependencies not satisfied; Plugin not "
"configured. {0}.".format(e))
log.exception(exception_msg)
raise Exception(exception_msg)
return True

View File

244
tests/detection/test_ovs.py Normal file
View File

@ -0,0 +1,244 @@
# (C) Copyright 2016 Hewlett Packard Enterprise Development Company LP
import ConfigParser
import contextlib
import logging
import os
import psutil
import re
import unittest
from mock import patch
from mock.mock import MagicMock
from monasca_setup.detection.plugins.ovs import Ovs
LOG = logging.getLogger('monasca_setup.detection.plugins.ovs')
class ps_util_get_proc:
cmdLine = ['/etc/neutron/neutron.conf']
def as_dict(self):
return {'name': 'neutron-openvsw',
'cmdline': ps_util_get_proc.cmdLine}
class TestOvs(unittest.TestCase):
def setUp(self):
unittest.TestCase.setUp(self)
self.ovs_obj = Ovs('temp_dir')
self.has_option = [True, True, True, False, False, True]
self.get_value = [MagicMock(), MagicMock(), MagicMock(),
'http://10.10.10.10', 'region1']
def _detect(self, ovs_obj):
ovs_obj.neutron_conf = None
ovs_obj.available = False
with contextlib.nested(
patch.object(psutil, 'process_iter',
return_value=[ps_util_get_proc()]),
patch.object(os.path, 'isfile', return_value=True)) as (
mock_process_iter, mock_isfile):
ovs_obj._detect()
self.assertTrue(mock_process_iter.called)
if not ps_util_get_proc.cmdLine:
self.assertFalse(mock_isfile.called)
def _build_config(self, ovs_obj, dependencies_installed=True):
with patch.object(ConfigParser, 'SafeConfigParser') as mock_config_parser:
config_parser_obj = mock_config_parser.return_value
with contextlib.nested(
patch.object(Ovs, 'dependencies_installed',
return_value=dependencies_installed),
patch.object(LOG, 'info'),
patch.object(config_parser_obj, 'has_option',
side_effect=self.has_option),
patch.object(config_parser_obj, 'get',
side_effect=self.get_value)) as (
mock_dependencies_installed, mock_log_info,
mock_has_option, mock_get):
result = ovs_obj.build_config()
self.assertTrue(mock_dependencies_installed.called)
if dependencies_installed:
self.assertTrue(mock_log_info.called)
self.assertTrue(mock_has_option.called)
self.assertTrue(mock_get.called)
if not self.has_option[-1]:
self.assertIn(str(('nova', 'region_name')),
str(mock_get.call_args_list[-1]))
else:
self.assertIn(str(('service_auth', 'region')),
str(mock_get.call_args_list[-1]))
else:
self.assertFalse(mock_log_info.called)
self.assertFalse(mock_has_option.called)
return result
def _build_config_with_arg(self, ovs_obj):
result = self._build_config(ovs_obj)
self.assertEqual(result['ovs']['init_config']['neutron_refresh'],
13000)
self.assertFalse(result['ovs']['init_config']['network_use_bits'])
self.assertIsInstance(result['ovs']['init_config']['admin_user'],
MagicMock)
self.assertIsInstance(result['ovs']['init_config']['admin_password'],
MagicMock)
self.assertIsInstance(result['ovs']['init_config']['admin_tenant_name'],
MagicMock)
self.assertEqual(result['ovs']['init_config']['identity_uri'],
'http://10.10.10.10/v2.0')
self.assertEqual(result['ovs']['init_config']['region_name'],
'region1')
self.assertEqual(result['ovs']['init_config']['cache_dir'],
"/dev/shm")
self.assertEqual(result['ovs']['init_config']['ovs_cmd'],
"sudo /usr/bin/ovs-vsctl")
self.assertFalse(result['ovs']['init_config']['use_absolute_metrics'])
return result
def _build_config_without_args(self, ovs_obj):
result = self._build_config(ovs_obj)
self.assertEqual(result['ovs']['init_config']['neutron_refresh'],
14400)
self.assertFalse(result['ovs']['init_config']['network_use_bits'])
self.assertEqual(result['ovs']['init_config']['cache_dir'],
"/dev/shm")
self.assertEqual(result['ovs']['init_config']['ovs_cmd'],
"sudo /usr/bin/ovs-vsctl")
self.assertEqual(result['ovs']['init_config']['included_interface_re'],
'tap.*|qr.*|qg.*|vhu.*')
self.assertIsInstance(result['ovs']['init_config']['admin_user'],
MagicMock)
self.assertIsInstance(result['ovs']['init_config']['admin_password'],
MagicMock)
self.assertIsInstance(result['ovs']['init_config']['admin_tenant_name'],
MagicMock)
self.assertTrue(result['ovs']['init_config']['use_absolute_metrics'])
return result
def test_detect(self):
self._detect(self.ovs_obj)
self.assertTrue(self.ovs_obj.available)
self.assertEqual(self.ovs_obj.neutron_conf,
'/etc/neutron/neutron.conf')
def test_detect_devstack(self):
ps_util_get_proc.cmdLine = ['--config-file=/opt/stack/neutron.conf']
self._detect(self.ovs_obj)
self.assertTrue(self.ovs_obj.available)
self.assertEqual(self.ovs_obj.neutron_conf, '/opt/stack/neutron.conf')
def test_detect_warning(self):
with patch.object(LOG, 'error') as mock_log_warn:
ps_util_get_proc.cmdLine = ['/opt/fake.txt']
self._detect(self.ovs_obj)
self.assertFalse(self.ovs_obj.available)
self.assertEqual(self.ovs_obj.neutron_conf, None)
self.assertTrue(mock_log_warn.called)
def test_detect_conf_file_path_given(self):
self.ovs_obj.neutron_conf = None
self.ovs_obj.args = {'conf_file_path': '/opt/stack/neutron.conf'}
with patch.object(os.path, 'isfile', return_value=True) as mock_isfile:
self.ovs_obj._detect()
self.assertTrue(mock_isfile.called)
self.assertTrue(self.ovs_obj.available)
self.assertEqual(self.ovs_obj.neutron_conf,
'/opt/stack/neutron.conf')
def test_build_config(self):
self.ovs_obj.neutron_conf = 'neutron-conf'
self._build_config_without_args(self.ovs_obj)
def test_build_config_with_args(self):
with patch.object(LOG, 'warn') as mock_log_warn:
self.ovs_obj.neutron_conf = 'neutron-conf'
self.ovs_obj.args = {'admin_user': 'admin',
'admin_password': 'password',
'admin_tenant_name': 'tenant',
'identity_uri': '10.10.10.20',
'region_name': 'region0',
'neutron_refresh': 13000,
'use_absolute_metrics': False}
result = self._build_config_with_arg(self.ovs_obj)
self.assertTrue(mock_log_warn.called)
self.assertEqual(mock_log_warn.call_count, 5)
self.assertEqual(result['ovs']['init_config']['included_interface_re'],
'tap.*|qr.*|qg.*|vhu.*')
def test_build_config_dependencies_not_installed(self):
self.ovs_obj.neutron_conf = 'neutron-conf'
result = self._build_config(self.ovs_obj, False)
self.assertEqual(result, {})
def test_build_config_invalid_arg_warning(self):
with patch.object(LOG, 'warn') as mock_log_warn:
self.ovs_obj.neutron_conf = 'neutron-conf'
self.ovs_obj.args = {'admin_user': 'admin',
'admin_password': 'password',
'admin_tenant_name': 'tenant',
'identity_uri': '10.10.10.20',
'region_name': 'region0',
'neutron_refresh': 13000,
'use_absolute_metrics': False,
'invalid_arg': 'inv-arg'}
result = self._build_config_with_arg(self.ovs_obj)
self.assertTrue(mock_log_warn.called)
self.assertEqual(mock_log_warn.call_count, 6)
self.assertEqual(result['ovs']['init_config']['included_interface_re'],
'tap.*|qr.*|qg.*|vhu.*')
def test_build_config_if_auth_version(self):
self.ovs_obj.neutron_conf = 'neutron-conf'
self.has_option = [True, True, True, True, True, True]
self.get_value = [MagicMock(), MagicMock(), MagicMock(),
'v3.0', 'http://10.10.10.10',
'http://10.10.10.10', 'region1']
result = self._build_config_without_args(self.ovs_obj)
self.assertEqual(result['ovs']['init_config']['identity_uri'],
'http://10.10.10.10/v3.0')
def test_build_config_if_auth_url_has_version(self):
self.ovs_obj.neutron_conf = 'neutron-conf'
self.has_option = [True, True, True, True, True, True]
self.get_value = [MagicMock(), MagicMock(), MagicMock(),
'v3.0', 'http://10.10.10.10/v1',
'http://10.10.10.10/v1', 'region1']
result = self._build_config_without_args(self.ovs_obj)
self.assertEqual(result['ovs']['init_config']['identity_uri'],
'http://10.10.10.10/v1/')
def test_build_config_region_name_from_nova(self):
self.ovs_obj.neutron_conf = 'neutron-conf'
self.has_option = [True, True, True, False, False, False]
self.get_value = [MagicMock(), MagicMock(), MagicMock(),
'http://10.10.10.10', 'region2']
result = self._build_config_without_args(self.ovs_obj)
self.assertEqual(result['ovs']['init_config']['identity_uri'],
'http://10.10.10.10/v2.0')
self.assertEqual(result['ovs']['init_config']['region_name'],
'region2')
def test_build_config_with_valid_interface_re(self):
self.ovs_obj.neutron_conf = 'neutron-conf'
self.ovs_obj.args = {'included_interface_re': 'tap.*',
'neutron_refresh': 13000,
'use_absolute_metrics': False}
result = self._build_config_with_arg(self.ovs_obj)
self.assertEqual(result['ovs']['init_config']['included_interface_re'],
'tap.*')
def test_build_config_with_invalid_interface_re(self):
self.ovs_obj.neutron_conf = 'neutron-conf'
self.ovs_obj.args = {'included_interface_re': '[',
'neutron_refresh': 13000}
with contextlib.nested(
patch.object(re, 'compile', side_effect=re.error),
patch.object(LOG, 'exception')) as (
mock_re_error, mock_log):
self.assertRaises(Exception, self._build_config_with_arg, self.ovs_obj)
self.assertTrue(mock_re_error.called)
self.assertTrue(mock_log.called)