Added per-datasource configuration
Previously, all datasources had the same username/password/tenant configuration information. Additionally, the name to module mapping was stored in a different place (but each data source could have a different module). This change puts all of the configuration information for datasources into a single file, and enables the configuration information to be different for each datasource. Closes-bug: #1356615 Change-Id: I0fc4274c452ff91663eded7828dc8d8679935b4e
This commit is contained in:
parent
e81e63966b
commit
f5b3209e39
|
@ -38,6 +38,8 @@ core_opts = [
|
|||
'true. Not supported on OS X.'),
|
||||
cfg.StrOpt('policy_path', default=None,
|
||||
help="The path to the latest policy dump"),
|
||||
cfg.StrOpt('datasource_file', default=None,
|
||||
help="The file containing datasource configuration"),
|
||||
cfg.IntOpt('api_workers', default=1,
|
||||
help='The number of worker processes to serve the congress '
|
||||
'API application.'),
|
||||
|
|
|
@ -22,17 +22,23 @@ import datetime
|
|||
|
||||
|
||||
class DataSourceDriver(deepsix.deepSix):
|
||||
def __init__(self, name, keys, inbox=None, datapath=None,
|
||||
poll_time=None, **creds):
|
||||
if poll_time is None:
|
||||
poll_time = 10
|
||||
def __init__(self, name, keys, inbox, datapath, args):
|
||||
# TODO(thinrichs): not all datasources poll, though for now that's
|
||||
# the only option. Create PollingDataSourceDriver subclass
|
||||
# to handle the polling logic.
|
||||
if args is None:
|
||||
args = dict()
|
||||
if 'poll_time' in args:
|
||||
self.poll_time = args['poll_time']
|
||||
else:
|
||||
self.poll_time = 10
|
||||
# default to open-stack credentials, since that's the common case
|
||||
self.creds = self.get_credentials(name, args)
|
||||
self.last_poll_time = None
|
||||
# a dictionary from tablename to the SET of tuples, both currently
|
||||
# and in the past.
|
||||
self.prior_state = dict()
|
||||
self.state = dict()
|
||||
self.poll_time = poll_time
|
||||
self.creds = creds
|
||||
self.last_poll_time = None
|
||||
# Make sure all data structures above are set up *before* calling
|
||||
# this because it will publish info to the bus.
|
||||
super(DataSourceDriver, self).__init__(name, keys, inbox, datapath)
|
||||
|
@ -162,3 +168,29 @@ class DataSourceDriver(deepsix.deepSix):
|
|||
seconds = diff.seconds + diff.days * 24 * 3600
|
||||
if seconds > self.poll_time:
|
||||
self.poll()
|
||||
|
||||
def get_credentials(self, name, config_args):
|
||||
# TODO(thinrichs): Create OpenStack mixin that implements
|
||||
# OpenStack-specific credential gathering, etc.
|
||||
d = {}
|
||||
missing = []
|
||||
for field in ['username', 'password', 'auth_url', 'tenant_name']:
|
||||
if field in config_args:
|
||||
d[field] = config_args[field]
|
||||
else:
|
||||
missing.append(field)
|
||||
if missing:
|
||||
raise DataSourceConfigException(
|
||||
"Service {} is missing configuration data for {}".format(
|
||||
name, missing))
|
||||
return d
|
||||
|
||||
def empty_credentials(self):
|
||||
return {'username': '',
|
||||
'password': '',
|
||||
'auth_url': '',
|
||||
'tenant_name': ''}
|
||||
|
||||
|
||||
class DataSourceConfigException(Exception):
|
||||
pass
|
||||
|
|
|
@ -17,8 +17,6 @@ import neutronclient.v2_0.client
|
|||
import uuid
|
||||
|
||||
from congress.datasources.datasource_driver import DataSourceDriver
|
||||
from congress.datasources.settings import OS_USERNAME, \
|
||||
OS_PASSWORD, OS_AUTH_URL, OS_TENANT_NAME
|
||||
from congress.openstack.common import log as logging
|
||||
|
||||
|
||||
|
@ -31,25 +29,10 @@ def d6service(name, keys, inbox, datapath, args):
|
|||
to add to that call, so we included them here instead of
|
||||
modifying d6cage (and all the d6cage.createservice calls).
|
||||
"""
|
||||
if 'client' in args:
|
||||
client = args['client']
|
||||
del args['client']
|
||||
else:
|
||||
client = None
|
||||
if 'poll_time' in args:
|
||||
poll_time = args['poll_time']
|
||||
del args['poll_time']
|
||||
else:
|
||||
poll_time = None
|
||||
return NeutronDriver(name, keys, inbox=inbox, datapath=datapath,
|
||||
client=client, poll_time=poll_time, **args)
|
||||
return NeutronDriver(name, keys, inbox, datapath, args)
|
||||
|
||||
|
||||
class NeutronDriver(DataSourceDriver):
|
||||
USERNAME = OS_USERNAME
|
||||
PASSWORD = OS_PASSWORD
|
||||
AUTH_URL = OS_AUTH_URL
|
||||
TENANT_NAME = OS_TENANT_NAME
|
||||
NEUTRON_NETWORKS = "networks"
|
||||
NEUTRON_NETWORKS_SUBNETS = "networks.subnets"
|
||||
NEUTRON_PORTS = "ports"
|
||||
|
@ -64,17 +47,14 @@ class NeutronDriver(DataSourceDriver):
|
|||
NEUTRON_SUBNETS = "subnets"
|
||||
last_updated = -1
|
||||
|
||||
def __init__(self, name='', keys='', inbox=None, datapath=None,
|
||||
client=None, poll_time=None, **creds):
|
||||
super(NeutronDriver, self).__init__(name, keys, inbox=inbox,
|
||||
datapath=datapath,
|
||||
poll_time=poll_time,
|
||||
**creds)
|
||||
credentials = self._get_credentials()
|
||||
if client is None:
|
||||
self.neutron = neutronclient.v2_0.client.Client(**credentials)
|
||||
def __init__(self, name='', keys='', inbox=None, datapath=None, args=None):
|
||||
if args is None:
|
||||
args = self.empty_credentials()
|
||||
super(NeutronDriver, self).__init__(name, keys, inbox, datapath, args)
|
||||
if 'client' in args:
|
||||
self.neutron = args['client']
|
||||
else:
|
||||
self.neutron = client
|
||||
self.neutron = neutronclient.v2_0.client.Client(**self.creds)
|
||||
self.raw_state = {}
|
||||
|
||||
# TODO(thinrichs): refactor this and the logic in datasource_driver.
|
||||
|
@ -360,14 +340,6 @@ class NeutronDriver(DataSourceDriver):
|
|||
LOG.debug("NEUTRON_SECURITY_GROUPS: %s",
|
||||
str(self.security_groups))
|
||||
|
||||
def _get_credentials(self):
|
||||
d = {}
|
||||
d['username'] = self.USERNAME
|
||||
d['password'] = self.PASSWORD
|
||||
d['auth_url'] = self.AUTH_URL
|
||||
d['tenant_name'] = self.TENANT_NAME
|
||||
return d
|
||||
|
||||
|
||||
# Sample Mapping
|
||||
# Network :
|
||||
|
|
|
@ -17,8 +17,6 @@ import datetime
|
|||
import novaclient.client
|
||||
|
||||
from congress.datasources.datasource_driver import DataSourceDriver
|
||||
from congress.datasources.settings import OS_USERNAME, \
|
||||
OS_PASSWORD, OS_AUTH_URL, OS_TENANT_NAME
|
||||
|
||||
|
||||
def d6service(name, keys, inbox, datapath, args):
|
||||
|
@ -27,18 +25,7 @@ def d6service(name, keys, inbox, datapath, args):
|
|||
to add to that call, so we included them here instead of
|
||||
modifying d6cage (and all the d6cage.createservice calls).
|
||||
"""
|
||||
if 'client' in args:
|
||||
client = args['client']
|
||||
del args['client']
|
||||
else:
|
||||
client = None
|
||||
if 'poll_time' in args:
|
||||
poll_time = args['poll_time']
|
||||
del args['poll_time']
|
||||
else:
|
||||
poll_time = None
|
||||
return NovaDriver(name, keys, inbox=inbox, datapath=datapath,
|
||||
client=client, poll_time=poll_time, **args)
|
||||
return NovaDriver(name, keys, inbox, datapath, args)
|
||||
|
||||
|
||||
# TODO(thinrichs): figure out how to move even more of this boilerplate
|
||||
|
@ -46,10 +33,6 @@ def d6service(name, keys, inbox, datapath, args):
|
|||
# NeutronDriver, NovaDriver, etc. and move the d6instantiate function to
|
||||
# DataSourceDriver.
|
||||
class NovaDriver(DataSourceDriver):
|
||||
USERNAME = OS_USERNAME
|
||||
PASSWORD = OS_PASSWORD
|
||||
AUTH_URL = OS_AUTH_URL
|
||||
TENANT_NAME = OS_TENANT_NAME
|
||||
SERVERS = "servers"
|
||||
FLAVORS = "flavors"
|
||||
HOSTS = "hosts"
|
||||
|
@ -57,18 +40,15 @@ class NovaDriver(DataSourceDriver):
|
|||
|
||||
last_updated = -1
|
||||
|
||||
def __init__(self, name='', keys='', inbox=None, datapath=None,
|
||||
client=None, poll_time=None, **creds):
|
||||
super(NovaDriver, self).__init__(name, keys, inbox=inbox,
|
||||
datapath=datapath,
|
||||
poll_time=poll_time,
|
||||
**creds)
|
||||
credentials = self.get_nova_credentials_v2()
|
||||
if client is None:
|
||||
self.nova_client = novaclient.client.Client(**credentials)
|
||||
def __init__(self, name='', keys='', inbox=None, datapath=None, args=None):
|
||||
if args is None:
|
||||
args = self.empty_credentials()
|
||||
super(NovaDriver, self).__init__(name, keys, inbox, datapath, args)
|
||||
if 'client' in args:
|
||||
self.nova_client = args['client']
|
||||
else:
|
||||
self.nova_client = client
|
||||
self.state = {}
|
||||
self.creds = self.get_nova_credentials_v2(name, args)
|
||||
self.nova_client = novaclient.client.Client(**self.creds)
|
||||
|
||||
def update_from_datasource(self):
|
||||
self.state = {}
|
||||
|
@ -123,13 +103,14 @@ class NovaDriver(DataSourceDriver):
|
|||
def get_last_updated_time(self):
|
||||
return self.last_updated
|
||||
|
||||
def get_nova_credentials_v2(self):
|
||||
def get_nova_credentials_v2(self, name, args):
|
||||
creds = self.get_credentials(name, args)
|
||||
d = {}
|
||||
d['version'] = '2'
|
||||
d['username'] = self.USERNAME
|
||||
d['api_key'] = self.PASSWORD
|
||||
d['auth_url'] = self.AUTH_URL
|
||||
d['project_id'] = self.TENANT_NAME
|
||||
d['username'] = creds['username']
|
||||
d['api_key'] = creds['password']
|
||||
d['auth_url'] = creds['auth_url']
|
||||
d['project_id'] = creds['tenant_name']
|
||||
return d
|
||||
|
||||
def _get_tuple_list(self, obj, type):
|
||||
|
|
|
@ -27,24 +27,14 @@ def d6service(name, keys, inbox, datapath, args):
|
|||
to add to that call, so we included them here instead of
|
||||
modifying d6cage (and all the d6cage.createservice calls).
|
||||
"""
|
||||
if 'client' in args:
|
||||
del args['client']
|
||||
if 'poll_time' in args:
|
||||
poll_time = args['poll_time']
|
||||
del args['poll_time']
|
||||
else:
|
||||
poll_time = 0
|
||||
return TestDriver(name, keys, inbox=inbox, datapath=datapath,
|
||||
poll_time=poll_time, **args)
|
||||
return TestDriver(name, keys, inbox, datapath, args)
|
||||
|
||||
|
||||
class TestDriver(DataSourceDriver):
|
||||
def __init__(self, name='', keys='', inbox=None, datapath=None,
|
||||
poll_time=None, **creds):
|
||||
super(TestDriver, self).__init__(name, keys, inbox=inbox,
|
||||
datapath=datapath,
|
||||
poll_time=poll_time,
|
||||
**creds)
|
||||
def __init__(self, name='', keys='', inbox=None, datapath=None, args=None):
|
||||
if args is None:
|
||||
args = self._empty_openstack_credentials()
|
||||
super(TestDriver, self).__init__(name, keys, inbox, datapath, args)
|
||||
self.msg = None
|
||||
|
||||
def receive_msg(self, msg):
|
||||
|
|
|
@ -17,11 +17,12 @@ import mock
|
|||
import mox
|
||||
import neutronclient.v2_0.client
|
||||
|
||||
from congress.datasources.datasource_driver import DataSourceConfigException
|
||||
from congress.datasources.neutron_driver import NeutronDriver
|
||||
import congress.dse.d6cage
|
||||
import congress.policy.compile as compile
|
||||
from congress.dse import d6cage
|
||||
from congress.policy import compile
|
||||
from congress.tests import base
|
||||
import congress.tests.helper as helper
|
||||
from congress.tests import helper
|
||||
|
||||
|
||||
class TestNeutronDriver(base.TestCase):
|
||||
|
@ -33,7 +34,9 @@ class TestNeutronDriver(base.TestCase):
|
|||
self.ports = port_response
|
||||
self.neutron_client.list_networks.return_value = self.network
|
||||
self.neutron_client.list_ports.return_value = self.ports
|
||||
self.driver = NeutronDriver(poll_time=0)
|
||||
args = helper.datasource_openstack_args()
|
||||
args['poll_time'] = 0
|
||||
self.driver = NeutronDriver(args=args)
|
||||
|
||||
def test_list_networks(self):
|
||||
"""Test conversion of complex network objects to tables."""
|
||||
|
@ -183,18 +186,62 @@ class TestNeutronDriver(base.TestCase):
|
|||
self.assertEqual("5cef03d0-1d02-40bb-8c99-2f442aac6ab0", subnet0)
|
||||
self.assertEqual("100.0.0.1", ip0)
|
||||
|
||||
#### Tests for DataSourceDriver
|
||||
# Note: these tests are really testing the functionality of the class
|
||||
# DataSourceDriver, but it's useful to use an actual subclass so
|
||||
# we can test the functionality end-to-end. We use Neutron for
|
||||
# that subclass. Leaving it in this file so that it is clear
|
||||
# that when the Neutron driver changes, these tests may need
|
||||
# to change as well. Tried to minimize the number of changes
|
||||
# necessary.
|
||||
|
||||
#### Tests for DataSourceDriver
|
||||
# Note: these tests are really testing the functionality of the class
|
||||
# DataSourceDriver, but it's useful to use an actual subclass so
|
||||
# we can test the functionality end-to-end. We use Neutron for
|
||||
# that subclass. Leaving it in this file so that it is clear
|
||||
# that when the Neutron driver changes, these tests may need
|
||||
# to change as well. Tried to minimize the number of changes
|
||||
# necessary.
|
||||
|
||||
class TestDataSourceDriver(base.TestCase):
|
||||
|
||||
def test_config(self):
|
||||
"""Test that Neutron throws an error when improperly configured."""
|
||||
# username
|
||||
args = helper.datasource_openstack_args()
|
||||
del args['username']
|
||||
try:
|
||||
self.driver = NeutronDriver(args=args)
|
||||
except DataSourceConfigException:
|
||||
pass
|
||||
else:
|
||||
self.fail('NeutronDriver failed to throw username exception')
|
||||
|
||||
# password
|
||||
args = helper.datasource_openstack_args()
|
||||
del args['password']
|
||||
try:
|
||||
self.driver = NeutronDriver(args=args)
|
||||
except DataSourceConfigException:
|
||||
pass
|
||||
else:
|
||||
self.fail('NeutronDriver failed to throw password exception')
|
||||
|
||||
# auth_url
|
||||
args = helper.datasource_openstack_args()
|
||||
del args['auth_url']
|
||||
try:
|
||||
self.driver = NeutronDriver(args=args)
|
||||
except DataSourceConfigException:
|
||||
pass
|
||||
else:
|
||||
self.fail('NeutronDriver failed to throw auth_url exception')
|
||||
|
||||
args = helper.datasource_openstack_args()
|
||||
del args['tenant_name']
|
||||
try:
|
||||
self.driver = NeutronDriver(args=args)
|
||||
except DataSourceConfigException:
|
||||
pass
|
||||
else:
|
||||
self.fail('NeutronDriver failed to throw tenant_name exception')
|
||||
|
||||
def setup_polling(self, debug_mode=False):
|
||||
"""Setup polling tests."""
|
||||
cage = congress.dse.d6cage.d6Cage()
|
||||
cage = d6cage.d6Cage()
|
||||
# so that we exit once test finishes; all other threads are forced
|
||||
# to be daemons
|
||||
cage.daemon = True
|
||||
|
@ -220,10 +267,14 @@ class TestNeutronDriver(base.TestCase):
|
|||
cage.loadModule("NeutronDriver",
|
||||
helper.data_module_path("neutron_driver.py"))
|
||||
cage.loadModule("PolicyDriver", helper.policy_module_path())
|
||||
cage.createservice(name="policy", moduleName="PolicyDriver")
|
||||
cage.createservice(name="policy", moduleName="PolicyDriver",
|
||||
args={'d6cage': cage,
|
||||
'rootdir': helper.data_module_path('')})
|
||||
args = helper.datasource_openstack_args()
|
||||
args['poll_time'] = 0
|
||||
args['client'] = neutron_client
|
||||
cage.createservice(name="neutron", moduleName="NeutronDriver",
|
||||
args={'poll_time': 0,
|
||||
'client': neutron_client})
|
||||
args=args)
|
||||
policy = cage.service_object('policy')
|
||||
|
||||
# Make it so that we get detailed info from policy engine
|
||||
|
|
|
@ -30,10 +30,10 @@ class TestNovaDriver(base.TestCase):
|
|||
super(TestNovaDriver, self).setUp()
|
||||
nova_client = MagicMock()
|
||||
self.cs = fakes.NovaFakeClient()
|
||||
|
||||
with patch.object(novaclient.client.Client, '__init__',
|
||||
return_value=nova_client):
|
||||
self.driver = NovaDriver()
|
||||
self.driver = NovaDriver(name='nova',
|
||||
args=helper.datasource_openstack_args())
|
||||
|
||||
def test_driver_called(self):
|
||||
self.assertIsNotNone(self.driver.nova_client)
|
||||
|
@ -183,12 +183,15 @@ class TestNovaDriver(base.TestCase):
|
|||
|
||||
# Create modules.
|
||||
# Turn off polling so we don't need to deal with real data.
|
||||
args = helper.datasource_openstack_args()
|
||||
args['poll_time'] = 0
|
||||
cage.loadModule("NovaDriver",
|
||||
helper.data_module_path("nova_driver.py"))
|
||||
cage.loadModule("PolicyDriver", helper.policy_module_path())
|
||||
cage.createservice(name="policy", moduleName="PolicyDriver")
|
||||
cage.createservice(name="nova", moduleName="NovaDriver",
|
||||
args={'poll_time': 0})
|
||||
cage.createservice(name="policy", moduleName="PolicyDriver",
|
||||
args={'d6cage': cage,
|
||||
'rootdir': helper.data_module_path('')})
|
||||
cage.createservice(name="nova", moduleName="NovaDriver", args=args)
|
||||
|
||||
# Check that data gets sent from nova to policy as expected
|
||||
nova = cage.service_object('nova')
|
||||
|
|
|
@ -30,6 +30,7 @@ import pprint
|
|||
from Queue import Queue
|
||||
import sys
|
||||
import threading
|
||||
import traceback
|
||||
|
||||
from congress.dse.d6message import d6msg
|
||||
from congress.dse.deepsix import deepSix
|
||||
|
@ -175,10 +176,10 @@ class d6Cage(deepSix):
|
|||
try:
|
||||
self.log_info("loading module: %s" % (name))
|
||||
imp.load_source(name, filename)
|
||||
except Exception, errmsg:
|
||||
except Exception:
|
||||
raise DataServiceError(
|
||||
"error loading module '%s' from '%s': %s" %
|
||||
(name, filename, errmsg))
|
||||
(name, filename, traceback.format_exc()))
|
||||
|
||||
def load_modules_from_config(self):
|
||||
for section in self.config['modules'].keys():
|
||||
|
@ -224,10 +225,10 @@ class d6Cage(deepSix):
|
|||
inbox,
|
||||
self.dataPath,
|
||||
args)
|
||||
except Exception, errmsg:
|
||||
except Exception:
|
||||
raise DataServiceError(
|
||||
"Error loading service '%s' of module '%s': %s"
|
||||
% (name, module, errmsg))
|
||||
"Error loading service '%s' of module '%s':: \n%s"
|
||||
% (name, module, traceback.format_exc()))
|
||||
|
||||
if svcObject:
|
||||
self.log_info("created service: {}".format(name))
|
||||
|
|
|
@ -35,10 +35,10 @@ class TestDSE(unittest.TestCase):
|
|||
cage.start()
|
||||
cage.loadModule("TestDriver",
|
||||
helper.data_module_path("test_driver.py"))
|
||||
cage.createservice(name="test1", moduleName="TestDriver",
|
||||
args={'poll_time': 0})
|
||||
cage.createservice(name="test2", moduleName="TestDriver",
|
||||
args={'poll_time': 0})
|
||||
args = helper.datasource_openstack_args()
|
||||
args['poll_time'] = 0
|
||||
cage.createservice(name="test1", moduleName="TestDriver", args=args)
|
||||
cage.createservice(name="test2", moduleName="TestDriver", args=args)
|
||||
test1 = cage.service_object('test1')
|
||||
test2 = cage.service_object('test2')
|
||||
test1.subscribe('test2', 'p', callback=test1.receive_msg)
|
||||
|
@ -65,8 +65,10 @@ class TestDSE(unittest.TestCase):
|
|||
cage.loadModule("TestDriver",
|
||||
helper.data_module_path("test_driver.py"))
|
||||
cage.loadModule("TestPolicy", helper.policy_module_path())
|
||||
cage.createservice(name="data", moduleName="TestDriver")
|
||||
cage.createservice(name="policy", moduleName="TestPolicy")
|
||||
cage.createservice(name="data", moduleName="TestDriver",
|
||||
args=helper.datasource_openstack_args())
|
||||
cage.createservice(name="policy", moduleName="TestPolicy",
|
||||
args={'d6cage': cage, 'rootdir': ''})
|
||||
data = cage.services['data']['object']
|
||||
policy = cage.services['policy']['object']
|
||||
policy.subscribe('data', 'p', callback=policy.receive_msg)
|
||||
|
@ -84,8 +86,10 @@ class TestDSE(unittest.TestCase):
|
|||
cage.loadModule("TestDriver",
|
||||
helper.data_module_path("test_driver.py"))
|
||||
cage.loadModule("TestPolicy", helper.policy_module_path())
|
||||
cage.createservice(name="data", moduleName="TestDriver")
|
||||
cage.createservice(name="policy", moduleName="TestPolicy")
|
||||
cage.createservice(name="data", moduleName="TestDriver",
|
||||
args=helper.datasource_openstack_args())
|
||||
cage.createservice(name="policy", moduleName="TestPolicy",
|
||||
args={'d6cage': cage, 'rootdir': ''})
|
||||
data = cage.services['data']['object']
|
||||
policy = cage.services['policy']['object']
|
||||
policy.subscribe('data', 'p', callback=policy.receive_data)
|
||||
|
@ -106,10 +110,13 @@ class TestDSE(unittest.TestCase):
|
|||
cage.loadModule("TestDriver",
|
||||
helper.data_module_path("test_driver.py"))
|
||||
cage.loadModule("TestPolicy", helper.policy_module_path())
|
||||
cage.createservice(name="data", moduleName="TestDriver")
|
||||
cage.createservice(name="data", moduleName="TestDriver",
|
||||
args=helper.datasource_openstack_args())
|
||||
# using regular testdriver as API for now
|
||||
cage.createservice(name="api", moduleName="TestDriver")
|
||||
cage.createservice(name="policy", moduleName="TestPolicy")
|
||||
cage.createservice(name="api", moduleName="TestDriver",
|
||||
args=helper.datasource_openstack_args())
|
||||
cage.createservice(name="policy", moduleName="TestPolicy",
|
||||
args={'d6cage': cage, 'rootdir': ''})
|
||||
data = cage.services['data']['object']
|
||||
api = cage.services['api']['object']
|
||||
policy = cage.services['policy']['object']
|
||||
|
|
|
@ -13,6 +13,8 @@
|
|||
# under the License.
|
||||
#
|
||||
|
||||
import ConfigParser
|
||||
import os
|
||||
import os.path
|
||||
|
||||
from congress.dse import d6cage
|
||||
|
@ -22,12 +24,13 @@ from congress.openstack.common import log as logging
|
|||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def create(rootdir, statedir):
|
||||
def create(rootdir, statedir, datasource_config):
|
||||
"""Get Congress up and running when src is installed in rootdir,
|
||||
i.e. ROOTDIR=/path/to/congress/congress.
|
||||
"""
|
||||
LOG.debug("Starting Congress with rootdir={} and statedir={}".format(
|
||||
rootdir, statedir))
|
||||
LOG.debug("Starting Congress with rootdir={}, statedir={}, "
|
||||
"datasource_config={}".format(
|
||||
rootdir, statedir, datasource_config))
|
||||
|
||||
# create message bus
|
||||
cage = d6cage.d6Cage()
|
||||
|
@ -35,6 +38,9 @@ def create(rootdir, statedir):
|
|||
cage.start()
|
||||
cage.system_service_names.add(cage.name)
|
||||
|
||||
# read in datasource configurations
|
||||
cage.config = initialize_config(datasource_config)
|
||||
|
||||
# add policy engine
|
||||
engine_path = os.path.join(rootdir, "policy/dsepolicy.py")
|
||||
LOG.info("main::start() engine_path: " + str(engine_path))
|
||||
|
@ -116,3 +122,20 @@ def create(rootdir, statedir):
|
|||
engine.subscribe('api-rule', 'policy-update',
|
||||
callback=engine.receive_policy_update)
|
||||
return cage
|
||||
|
||||
|
||||
def initialize_config(config_file):
|
||||
"""Turn config_file into a dictionary of dictionaries, and in so
|
||||
doing insulate rest of code from idiosyncracies of ConfigParser.
|
||||
"""
|
||||
config = ConfigParser.ConfigParser()
|
||||
config.readfp(open(config_file))
|
||||
d = {}
|
||||
for section in config.sections():
|
||||
e = {}
|
||||
for opt in config.options(section):
|
||||
e[opt] = config.get(section, opt)
|
||||
d[section] = e
|
||||
LOG.info("Configuration found for {} services: {}".format(
|
||||
len(d.keys()), ";".join(d.keys())))
|
||||
return d
|
||||
|
|
|
@ -13,19 +13,21 @@
|
|||
# under the License.
|
||||
#
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
|
||||
import congress.dse.deepsix as deepsix
|
||||
import congress.policy.compile as compile
|
||||
import congress.policy.runtime as runtime
|
||||
from congress.datasources.datasource_driver import DataSourceConfigException
|
||||
from congress.dse import deepsix
|
||||
from congress.openstack.common import log as logging
|
||||
from congress.policy import compile
|
||||
from congress.policy import runtime
|
||||
|
||||
|
||||
class PolicyServiceMismatch (Exception):
|
||||
pass
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def d6service(name, keys, inbox, datapath, args):
|
||||
return DseRuntime(name, keys, inbox=inbox, dataPath=datapath, **args)
|
||||
return DseRuntime(name, keys, inbox, datapath, args)
|
||||
|
||||
|
||||
def parse_tablename(tablename):
|
||||
|
@ -38,14 +40,13 @@ def parse_tablename(tablename):
|
|||
|
||||
|
||||
class DseRuntime (runtime.Runtime, deepsix.deepSix):
|
||||
def __init__(self, name, keys, inbox=None, dataPath=None, d6cage=None,
|
||||
rootdir=None):
|
||||
def __init__(self, name, keys, inbox, datapath, args):
|
||||
runtime.Runtime.__init__(self)
|
||||
deepsix.deepSix.__init__(self, name, keys, inbox=inbox,
|
||||
dataPath=dataPath)
|
||||
dataPath=datapath)
|
||||
self.msg = None
|
||||
self.d6cage = d6cage
|
||||
self.rootdir = rootdir
|
||||
self.d6cage = args['d6cage']
|
||||
self.rootdir = args['rootdir']
|
||||
|
||||
def receive_msg(self, msg):
|
||||
self.log("received msg " + str(msg))
|
||||
|
@ -170,33 +171,25 @@ class DseRuntime (runtime.Runtime, deepsix.deepSix):
|
|||
been loaded. Also loads module if that has not already been
|
||||
loaded.
|
||||
"""
|
||||
# TODO(thinrichs): work in d6cage's ability to reload a module,
|
||||
# so that driver updates can be handled without shutting
|
||||
# everything down. A separate API call?
|
||||
# TODO(thinrichs): Move all this functionality to a different
|
||||
# component whose responsibility is spinning these up,
|
||||
# checking they are still alive, restarting, reporting status, etc.
|
||||
# Probably d6cage (or a subclass of it).
|
||||
if self.d6cage is None:
|
||||
# policy engine is running without ability to create services
|
||||
return
|
||||
if service_name in self.d6cage.services:
|
||||
return
|
||||
# service("service1", "modulename")
|
||||
# module("modulename", "/path/to/code.py")
|
||||
query = ('ans(name, path) :- service("{}", name), '
|
||||
' module(name, path)').format(service_name)
|
||||
modules = self.select(query, self.SERVICE_THEORY)
|
||||
modules = compile.parse(modules)
|
||||
# TODO(thinrichs): figure out what to do if we can't load the right
|
||||
# data service. For now we reject the policy update.
|
||||
# Would be better to have runtime accept policy and ignore rules
|
||||
# that rely on unknown data. Then if there's a modification
|
||||
# to the SERVICES policy later, we create the service, and as
|
||||
# soon as it publishes data, the ignored rules become active.
|
||||
# Deletions from SERVICES policy should have the opposite effect.
|
||||
if len(modules) != 1:
|
||||
raise PolicyServiceMismatch(
|
||||
"Could not find module for service " + service_name)
|
||||
module = modules[0] # instance of QUERY above
|
||||
module_name = module.head.arguments[0].name
|
||||
module_path = module.head.arguments[1].name
|
||||
if service_name not in self.d6cage.config:
|
||||
raise DataSourceConfigException(
|
||||
"Service %s used in rule but not configured; "
|
||||
"tables will be empty" % service_name)
|
||||
service_config = self.d6cage.config[service_name]
|
||||
if 'module' not in service_config:
|
||||
raise DataSourceConfigException(
|
||||
"Service %s config missing 'module' entry" % service_name)
|
||||
module_path = service_config['module']
|
||||
module_name = re.sub('[^a-zA-Z0-9_]', '_', module_path)
|
||||
if not os.path.isabs(module_path) and self.rootdir is not None:
|
||||
module_path = os.path.join(self.rootdir, module_path)
|
||||
if module_name not in sys.modules:
|
||||
|
@ -205,7 +198,8 @@ class DseRuntime (runtime.Runtime, deepsix.deepSix):
|
|||
self.d6cage.loadModule(module_name, module_path)
|
||||
self.log("Trying to create service {} with module {}".format(
|
||||
service_name, module_name))
|
||||
self.d6cage.createservice(name=service_name, moduleName=module_name)
|
||||
self.d6cage.createservice(name=service_name, moduleName=module_name,
|
||||
args=service_config)
|
||||
|
||||
# since both deepSix and Runtime define log (and differently),
|
||||
# need to switch between them explicitly
|
||||
|
|
|
@ -49,7 +49,12 @@ def congress_app_factory(global_conf, **local_conf):
|
|||
policy_path = cfg.CONF.policy_path
|
||||
if policy_path is None:
|
||||
policy_path = src_path
|
||||
cage = harness.create(src_path, policy_path)
|
||||
data_path = cfg.CONF.datasource_file
|
||||
if data_path is None:
|
||||
data_path = os.path.dirname(src_path)
|
||||
data_path = os.path.join(data_path, 'etc', 'datasources.conf')
|
||||
|
||||
cage = harness.create(src_path, policy_path, data_path)
|
||||
|
||||
api_resource_mgr = application.ResourceManager()
|
||||
congress_server.initialize_resources(api_resource_mgr, cage)
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
|
||||
[neutron]
|
||||
module: datasources/neutron_driver.py
|
||||
username: demo
|
||||
password: password
|
||||
auth_url: http://127.0.0.1:5000/v2.0
|
||||
tenant_name: demo
|
||||
|
||||
[neutron2]
|
||||
module: datasources/neutron_driver.py
|
||||
username: demo
|
||||
password: password
|
||||
auth_url: http://127.0.0.1:5000/v2.0
|
||||
tenant_name: demo
|
||||
|
||||
[nova]
|
||||
module: datasources/nova_driver.py
|
||||
username: demo
|
||||
password: password
|
||||
auth_url: http://127.0.0.1:5000/v2.0
|
||||
tenant_name: demo
|
||||
|
|
@ -26,6 +26,7 @@ LOG = logging.getLogger(__name__)
|
|||
|
||||
|
||||
def source_path():
|
||||
"""Return path to root of source code."""
|
||||
x = os.path.realpath(__file__)
|
||||
x, y = os.path.split(x) # drop "helper.py"
|
||||
x, y = os.path.split(x) # drop "tests"
|
||||
|
@ -49,13 +50,42 @@ def policy_module_path():
|
|||
|
||||
|
||||
def api_module_path():
|
||||
"""Return path to policy engine module."""
|
||||
"""Return path to api module."""
|
||||
path = source_path()
|
||||
path = os.path.join(path, "datasources")
|
||||
path = os.path.join(path, "test_driver.py")
|
||||
return path
|
||||
|
||||
|
||||
def test_path():
|
||||
"""Return path to root of top-level tests."""
|
||||
path = source_path()
|
||||
path = os.path.join(path, "tests")
|
||||
return path
|
||||
|
||||
|
||||
def datasource_config_path():
|
||||
"""Return path to configuration info for datasources."""
|
||||
path = test_path()
|
||||
path = os.path.join(path, "tests", "datasources.conf")
|
||||
return path
|
||||
|
||||
|
||||
def state_path():
|
||||
"""Return path to policy logs for testing."""
|
||||
path = test_path()
|
||||
path = os.path.join(path, "snapshot")
|
||||
return path
|
||||
|
||||
|
||||
def datasource_openstack_args():
|
||||
"""Return basic args for creating an openstack datasource."""
|
||||
return {'username': '',
|
||||
'password': '',
|
||||
'auth_url': '',
|
||||
'tenant_name': ''}
|
||||
|
||||
|
||||
def pause(factor=1):
|
||||
"""Timeout so other threads can run."""
|
||||
time.sleep(factor * 1)
|
||||
|
|
|
@ -91,10 +91,19 @@ class TestCongress(base.TestCase):
|
|||
os.makedirs(path)
|
||||
return path
|
||||
|
||||
@classmethod
|
||||
def config_path(cls):
|
||||
"""Return path to the filename for datasource config."""
|
||||
path = os.path.realpath(__file__)
|
||||
path = os.path.dirname(path) # drop off file
|
||||
path = os.path.join(path, "datasources.conf")
|
||||
return path
|
||||
|
||||
def setUp(self):
|
||||
super(TestCongress, self).setUp()
|
||||
logging.getLogger().setLevel(logging.DEBUG)
|
||||
cage = harness.create(helper.source_path(), self.state_path())
|
||||
cage = harness.create(helper.source_path(), self.state_path(),
|
||||
self.config_path())
|
||||
engine = cage.service_object('engine')
|
||||
api = {'policy': cage.service_object('api-policy'),
|
||||
'rule': cage.service_object('api-rule'),
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
|
||||
[neutron]
|
||||
module: datasources/neutron_driver.py
|
||||
username: demo
|
||||
password: password
|
||||
auth_url: http://127.0.0.1:5000/v2.0
|
||||
tenant_name: demo
|
||||
|
Loading…
Reference in New Issue