341 lines
13 KiB
Python
341 lines
13 KiB
Python
# Copyright (c) 2014 Styra, Inc. All rights reserved.
|
|
#
|
|
# 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 sys
|
|
from unittest import mock
|
|
|
|
from congress.api import base as api_base
|
|
from congress.policy_engines import agnostic
|
|
from congress.tests.api import base as tests_api_base
|
|
from congress.tests import base
|
|
from congress.tests import helper
|
|
|
|
|
|
class TestDseRuntime(base.SqlTestCase):
|
|
@mock.patch('congress.db.db_policy_rules.get_policy_rules')
|
|
def test_enable_schema(self, patched_persisted_rules):
|
|
class TestRule(object):
|
|
def __init__(self, id, name, rule_str,
|
|
policy_name, comment=None):
|
|
self.id = id
|
|
self.name = name
|
|
self.rule = rule_str
|
|
self.policy_name = policy_name
|
|
self.comment = comment
|
|
|
|
persisted_rule = [
|
|
TestRule('rule-id', 'rule-name',
|
|
"p(x):-nova:services(id=x)", 'classification'),
|
|
]
|
|
patched_persisted_rules.return_value = persisted_rule
|
|
|
|
services = tests_api_base.setup_config()
|
|
engine2 = services['engine']
|
|
node = services['node']
|
|
|
|
node.invoke_service_rpc = mock.MagicMock()
|
|
node.invoke_service_rpc.return_value = [
|
|
0, [['id1', 'name1', 'status1'], ['id2', 'name2', 'status2']]]
|
|
|
|
# loaded rule is disabled
|
|
subscriptions = engine2.subscription_list()
|
|
self.assertEqual([], subscriptions)
|
|
|
|
nova_schema = {
|
|
'services': [
|
|
{'name': 'id', 'desc': 'id of the service'},
|
|
{'name': 'name', 'desc': 'name of the service'},
|
|
{'name': 'status', 'desc': 'status of the service'}]}
|
|
|
|
engine2.initialize_datasource('nova', nova_schema)
|
|
# loaded rule is enabled and subscribes the table
|
|
subscriptions = engine2.subscription_list()
|
|
self.assertEqual([('nova', 'services')], subscriptions)
|
|
|
|
|
|
class TestAgnostic(base.TestCase):
|
|
def test_receive_data_no_sequence_num(self):
|
|
'''Test receiving data without sequence numbers'''
|
|
run = agnostic.DseRuntime(api_base.ENGINE_SERVICE_ID)
|
|
run.create_policy('datasource1')
|
|
|
|
# initialize with full table
|
|
run.receive_data_sequenced(
|
|
publisher='datasource1', table='p',
|
|
data=[[1], [2]], seqnum=None, is_snapshot=True)
|
|
actual = run.select('p(x)')
|
|
correct = 'p(1) p(2)'
|
|
self.assertTrue(helper.db_equal(actual, correct))
|
|
|
|
# add data
|
|
run.receive_data_sequenced(
|
|
publisher='datasource1', table='p',
|
|
data=[[[3], [4]], []], seqnum=None, is_snapshot=False)
|
|
actual = run.select('p(x)')
|
|
correct = 'p(1) p(2) p(3) p(4)'
|
|
self.assertTrue(helper.db_equal(actual, correct))
|
|
|
|
# remove data
|
|
run.receive_data_sequenced(
|
|
publisher='datasource1', table='p',
|
|
data=[[], [[2], [4]]], seqnum=None, is_snapshot=False)
|
|
actual = run.select('p(x)')
|
|
correct = 'p(1) p(3)'
|
|
self.assertTrue(helper.db_equal(actual, correct))
|
|
|
|
# add & remove data
|
|
run.receive_data_sequenced(
|
|
publisher='datasource1', table='p',
|
|
data=[[[4]], [[3]]], seqnum=None, is_snapshot=False)
|
|
actual = run.select('p(x)')
|
|
correct = 'p(1) p(4)'
|
|
self.assertTrue(helper.db_equal(actual, correct))
|
|
|
|
# re-initialize with full table
|
|
run.receive_data_sequenced(
|
|
publisher='datasource1', table='p',
|
|
data=[[1], [2]], seqnum=None, is_snapshot=True)
|
|
actual = run.select('p(x)')
|
|
correct = 'p(1) p(2)'
|
|
self.assertTrue(helper.db_equal(actual, correct))
|
|
|
|
def test_receive_data_in_order(self):
|
|
'''Test receiving data with sequence numbers, in order'''
|
|
run = agnostic.DseRuntime(api_base.ENGINE_SERVICE_ID)
|
|
run.create_policy('datasource1')
|
|
|
|
# initialize with full table
|
|
run.receive_data_sequenced(
|
|
publisher='datasource1', table='p',
|
|
data=[[1], [2]], seqnum=0, is_snapshot=True)
|
|
actual = run.select('p(x)')
|
|
correct = 'p(1) p(2)'
|
|
self.assertTrue(helper.db_equal(actual, correct))
|
|
|
|
# add data
|
|
run.receive_data_sequenced(
|
|
publisher='datasource1', table='p',
|
|
data=[[[3], [4]], []], seqnum=1, is_snapshot=False)
|
|
actual = run.select('p(x)')
|
|
correct = 'p(1) p(2) p(3) p(4)'
|
|
self.assertTrue(helper.db_equal(actual, correct))
|
|
|
|
# remove data
|
|
run.receive_data_sequenced(
|
|
publisher='datasource1', table='p',
|
|
data=[[], [[2], [4]]], seqnum=2, is_snapshot=False)
|
|
actual = run.select('p(x)')
|
|
correct = 'p(1) p(3)'
|
|
self.assertTrue(helper.db_equal(actual, correct))
|
|
|
|
# add & remove data
|
|
run.receive_data_sequenced(
|
|
publisher='datasource1', table='p',
|
|
data=[[[4]], [[3]]], seqnum=3, is_snapshot=False)
|
|
actual = run.select('p(x)')
|
|
correct = 'p(1) p(4)'
|
|
self.assertTrue(helper.db_equal(actual, correct))
|
|
|
|
# re-initialize with full table
|
|
run.receive_data_sequenced(
|
|
publisher='datasource1', table='p',
|
|
data=[[1], [2]], seqnum=4, is_snapshot=True)
|
|
actual = run.select('p(x)')
|
|
correct = 'p(1) p(2)'
|
|
self.assertTrue(helper.db_equal(actual, correct))
|
|
|
|
def test_receive_data_out_of_order(self):
|
|
'''Test receiving data with sequence numbers, out of order'''
|
|
run = agnostic.DseRuntime(api_base.ENGINE_SERVICE_ID)
|
|
run.create_policy('datasource1')
|
|
|
|
# update with lower seqnum than init snapshot is ignored
|
|
run.receive_data_sequenced(
|
|
publisher='datasource1', table='p',
|
|
data=[[[10]], []], seqnum=3, is_snapshot=False)
|
|
|
|
# add & remove data
|
|
run.receive_data_sequenced(
|
|
publisher='datasource1', table='p',
|
|
data=[[[4]], [[3]]], seqnum=7, is_snapshot=False)
|
|
actual = run.select('p(x)')
|
|
correct = ''
|
|
self.assertTrue(helper.db_equal(actual, correct))
|
|
|
|
# remove data
|
|
run.receive_data_sequenced(
|
|
publisher='datasource1', table='p',
|
|
data=[[], [[2], [4]]], seqnum=6, is_snapshot=False)
|
|
actual = run.select('p(x)')
|
|
correct = ''
|
|
self.assertTrue(helper.db_equal(actual, correct))
|
|
|
|
# add data
|
|
run.receive_data_sequenced(
|
|
publisher='datasource1', table='p',
|
|
data=[[[3], [4]], []], seqnum=5, is_snapshot=False)
|
|
actual = run.select('p(x)')
|
|
correct = ''
|
|
self.assertTrue(helper.db_equal(actual, correct))
|
|
|
|
# initialize with full table
|
|
run.receive_data_sequenced(
|
|
publisher='datasource1', table='p',
|
|
data=[[1], [2]], seqnum=4, is_snapshot=True)
|
|
actual = run.select('p(x)')
|
|
correct = 'p(1) p(4)'
|
|
self.assertTrue(helper.db_equal(actual, correct))
|
|
|
|
def test_receive_data_arbitrary_start(self):
|
|
'''Test receiving data with arbitrary starting sequence number'''
|
|
run = agnostic.DseRuntime(api_base.ENGINE_SERVICE_ID)
|
|
run.create_policy('datasource1')
|
|
run.receive_data_sequenced(
|
|
publisher='datasource1', table='p',
|
|
data=[[1], [2]], seqnum=1234, is_snapshot=True)
|
|
actual = run.select('p(x)')
|
|
correct = 'p(1) p(2)'
|
|
self.assertTrue(helper.db_equal(actual, correct))
|
|
|
|
def test_receive_data_duplicate_sequence_number(self):
|
|
'''Test receiving data with duplicate sequence number
|
|
|
|
Only one message (arbitrary) should be processed.
|
|
'''
|
|
run = agnostic.DseRuntime(api_base.ENGINE_SERVICE_ID)
|
|
run.create_policy('datasource1')
|
|
|
|
# send three updates with the same seqnum
|
|
run.receive_data_sequenced(
|
|
publisher='datasource1', table='p',
|
|
data=[[[1]], []], seqnum=1, is_snapshot=False)
|
|
run.receive_data_sequenced(
|
|
publisher='datasource1', table='p',
|
|
data=[[[2]], []], seqnum=1, is_snapshot=False)
|
|
run.receive_data_sequenced(
|
|
publisher='datasource1', table='p',
|
|
data=[[[3]], []], seqnum=1, is_snapshot=False)
|
|
|
|
# start with empty data
|
|
run.receive_data_sequenced(
|
|
publisher='datasource1', table='p',
|
|
data=[], seqnum=0, is_snapshot=True)
|
|
|
|
# exactly one of the three updates should be applied
|
|
actual = run.select('p(x)')
|
|
correct1 = 'p(1)'
|
|
correct2 = 'p(2)'
|
|
correct3 = 'p(3)'
|
|
self.assertTrue(
|
|
helper.db_equal(actual, correct1) or
|
|
helper.db_equal(actual, correct2) or
|
|
helper.db_equal(actual, correct3))
|
|
|
|
def test_receive_data_sequence_number_max_int(self):
|
|
'''Test receiving data when sequence number goes over max int'''
|
|
run = agnostic.DseRuntime(api_base.ENGINE_SERVICE_ID)
|
|
run.create_policy('datasource1')
|
|
|
|
run.receive_data_sequenced(
|
|
publisher='datasource1', table='p',
|
|
data=[[1], [2]], seqnum=sys.maxsize, is_snapshot=True)
|
|
actual = run.select('p(x)')
|
|
correct = 'p(1) p(2)'
|
|
self.assertTrue(helper.db_equal(actual, correct))
|
|
|
|
run.receive_data_sequenced(
|
|
publisher='datasource1', table='p',
|
|
data=[[], [[2]]], seqnum=sys.maxsize + 1, is_snapshot=False)
|
|
actual = run.select('p(x)')
|
|
correct = 'p(1)'
|
|
self.assertTrue(helper.db_equal(actual, correct))
|
|
|
|
# test out-of-sequence update ignored
|
|
run.receive_data_sequenced(
|
|
publisher='datasource1', table='p',
|
|
data=[[[2]], []], seqnum=sys.maxsize, is_snapshot=False)
|
|
actual = run.select('p(x)')
|
|
correct = 'p(1)'
|
|
self.assertTrue(helper.db_equal(actual, correct))
|
|
|
|
run.receive_data_sequenced(
|
|
publisher='datasource1', table='p',
|
|
data=[[[4]], []], seqnum=sys.maxsize + 3, is_snapshot=False)
|
|
actual = run.select('p(x)')
|
|
correct = 'p(1)'
|
|
self.assertTrue(helper.db_equal(actual, correct))
|
|
|
|
run.receive_data_sequenced(
|
|
publisher='datasource1', table='p',
|
|
data=[[[3]], []], seqnum=sys.maxsize + 2, is_snapshot=False)
|
|
actual = run.select('p(x)')
|
|
correct = 'p(1) p(3) p(4)'
|
|
self.assertTrue(helper.db_equal(actual, correct))
|
|
|
|
def test_receive_data_multiple_tables(self):
|
|
'''Test receiving data with sequence numbers, multiple tables'''
|
|
run = agnostic.DseRuntime(api_base.ENGINE_SERVICE_ID)
|
|
run.create_policy('datasource1')
|
|
|
|
# initialize p with full table
|
|
run.receive_data_sequenced(
|
|
publisher='datasource1', table='p',
|
|
data=[[1]], seqnum=0, is_snapshot=True)
|
|
actual = run.select('p(x)')
|
|
correct = 'p(1)'
|
|
self.assertTrue(helper.db_equal(actual, correct))
|
|
|
|
# add data to p
|
|
run.receive_data_sequenced(
|
|
publisher='datasource1', table='p',
|
|
data=[[[2]], []], seqnum=1, is_snapshot=False)
|
|
actual = run.select('p(x)')
|
|
correct = 'p(1) p(2)'
|
|
self.assertTrue(helper.db_equal(actual, correct))
|
|
|
|
# add data to q
|
|
run.receive_data_sequenced(
|
|
publisher='datasource1', table='q',
|
|
data=[[[2]], []], seqnum=1, is_snapshot=False)
|
|
actual = run.select('q(x)')
|
|
correct = '' # does not apply until initialize
|
|
self.assertTrue(helper.db_equal(actual, correct))
|
|
|
|
# initialize q with full table
|
|
run.receive_data_sequenced(
|
|
publisher='datasource1', table='q',
|
|
data=[[1]], seqnum=0, is_snapshot=True)
|
|
actual = run.select('q(x)')
|
|
correct = 'q(1) q(2)' # both initial data and preceding update applied
|
|
self.assertTrue(helper.db_equal(actual, correct))
|
|
|
|
# add data to q
|
|
run.receive_data_sequenced(
|
|
publisher='datasource1', table='q',
|
|
data=[[[3]], []], seqnum=2, is_snapshot=False)
|
|
actual = run.select('q(x)')
|
|
correct = 'q(1) q(2) q(3)'
|
|
self.assertTrue(helper.db_equal(actual, correct))
|
|
|
|
# add data to p
|
|
run.receive_data_sequenced(
|
|
publisher='datasource1', table='p',
|
|
data=[[[3]], []], seqnum=2, is_snapshot=False)
|
|
actual = run.select('p(x)')
|
|
correct = 'p(1) p(2) p(3)'
|
|
self.assertTrue(helper.db_equal(actual, correct))
|
|
|
|
# TODO(ekcs): receive data multiple publishers
|