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:
parent
5e1eee2803
commit
253d48cbe4
30
conf.d/ovs.yaml.example
Normal file
30
conf.d/ovs.yaml.example
Normal 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.
|
||||
- {}
|
167
monasca_setup/detection/plugins/ovs.py
Normal file
167
monasca_setup/detection/plugins/ovs.py
Normal 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
|
0
tests/detection/__init__.py
Normal file
0
tests/detection/__init__.py
Normal file
244
tests/detection/test_ovs.py
Normal file
244
tests/detection/test_ovs.py
Normal 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)
|
Loading…
Reference in New Issue
Block a user