def test_canceling_adds(self): proxy = self.node.get_proxy('/') tx = proxy.open_transaction() tx.add('/adapters', Adapter(id='new')) tx.add('/adapters', Adapter(id='new2')) tx.cancel() self.assertEqual(self.log_levels().keys(), ['0', '1', '2', '3', '4'])
def test_compatible_adds(self): proxy = self.node.get_proxy('/') tx1 = proxy.open_transaction() tx2 = proxy.open_transaction() tx3 = proxy.open_transaction() tx1.add('/adapters', Adapter(id='new1')) tx2.add('/adapters', Adapter(id='new2')) tx3.add('/adapters', Adapter(id='new3')) tx1.commit() tx2.commit() tx3.commit() self.assertEqual(self.log_levels().keys(), ['0', '1', '2', '3', '4', 'new1', 'new2', 'new3'])
def test_additive_changes(self): proxy = self.node.get_proxy('/') tx1 = proxy.open_transaction() tx1.add('/adapters', Adapter(id='new')) tx1.add('/adapters', Adapter(id='new2')) self.assertEqual(len(proxy.get('/adapters')), 5) self.assertEqual(len(self.node.get('/adapters')), 5) self.assertEqual(len(tx1.get('/adapters')), 7) tx1.commit() self.assertEqual(len(proxy.get('/adapters')), 7) self.assertEqual(len(self.node.get('/adapters')), 7) self.assertEqual(self.log_levels().keys(), ['0', '1', '2', '3', '4', 'new', 'new2'])
def test_update_add_mix(self): # at same nodes updates are always compatible with adds proxy = self.node.get_proxy('/') tx1 = proxy.open_transaction() tx2 = proxy.open_transaction() tx3 = proxy.open_transaction() self.make_change(tx1, '/adapters/0', 'config.log_level', 4) self.make_change(tx1, '/adapters/2', 'config.log_level', 4) tx2.add('/adapters', Adapter(id='new1')) tx3.add('/adapters', Adapter(id='new2')) tx1.commit() tx2.commit() tx3.commit() self.assertEqual(self.log_levels().keys(), ['0', '1', '2', '3', '4', 'new1', 'new2'])
def test_remove_add_mix(self): # at same node, adds are always compatible with removes proxy = self.node.get_proxy('/') tx1 = proxy.open_transaction() tx2 = proxy.open_transaction() tx3 = proxy.open_transaction() tx1.remove('/adapters/0') tx2.add('/adapters', Adapter(id='new1')) tx3.add('/adapters', Adapter(id='new2')) tx1.remove('/adapters/4') tx1.commit() tx2.commit() tx3.commit() self.assertEqual(self.log_levels().keys(), ['1', '2', '3', 'new1', 'new2'])
def make_complex_changes(self): # Plan: # Have two root proxies and two proxies on specific adapters # Make several transactions, including conflicting ones # Check as much as possible in terms of expected operations proxy1 = self.node.get_proxy('/') proxy2 = self.node.get_proxy('/') proxy3 = self.node.get_proxy('/adapters/0') proxy4 = self.node.get_proxy('/adapters/1') tx1 = proxy1.open_transaction() tx2 = proxy1.open_transaction() tx3 = proxy2.open_transaction() tx4 = proxy3.open_transaction() tx5 = proxy4.open_transaction() # Make multiple changes via tx1 self.make_change(tx1, '/adapters/0', 'config.log_level', 1) tx1.add('/adapters', Adapter(id='new1')) tx1.remove('/adapters/2') # Make a non-conflicting change from tx2 self.make_change(tx2, '/adapters/3', 'config.log_level', 0) # Make some conflicting changes via tx3 now self.make_change(tx3, '/adapters/1', 'config.log_level', 1) # Make some changes via leaf proxies my_adapter = tx4.get('/') my_adapter.version = 'zulu' my_adapter.config.log_level = 0 tx4.update('/', my_adapter) # Make some changes via leaf proxies my_adapter = tx5.get('/') my_adapter.version = 'brand new' my_adapter.config.log_level = 4 tx5.update('/', my_adapter) # Make some more changes on tx2 tx2.add('/adapters', Adapter(id='new2')) # Conflicts: # - tx4 conflicts with tx0 # - tx5 conflicts with tx3 return tx1, tx2, tx3, tx4, tx5
def test_colliding_adds(self): proxy = self.node.get_proxy('/') tx1 = proxy.open_transaction() tx2 = proxy.open_transaction() tx3 = proxy.open_transaction() tx4 = proxy.open_transaction() tx1.add('/adapters', Adapter(id='new1')) tx2.add('/adapters', Adapter(id='new2')) tx3.add('/adapters', Adapter(id='new1', version='foobar')) tx4.add('/adapters', Adapter(id='new1')) tx1.commit() tx2.commit() self.assertRaises(MergeConflictException, tx3.commit) tx4.commit() # is fine since it added the same data self.assertEqual(self.log_levels().keys(), ['0', '1', '2', '3', '4', 'new1', 'new2'])
def test_remove_event(self): data = Adapter(id='1', config=AdapterConfig(log_level=3)) self.node.remove('/adapters/1') event = ConfigEvent(type=ConfigEventType.remove, hash=self.node.latest.hash, data=dumps(MessageToDict(data, True, True))) self.event_mock.assert_called_once_with('model-change-events', event)
def test_post_update_hook(self): proxy = self.node.get_proxy('/adapters/1') callback = Mock() proxy.register_callback(CallbackType.POST_UPDATE, callback, 'zizi', 42, x=1, y='baz') data = Adapter(id='1', version='zoo') proxy.update('/', data) callback.assert_called_once_with(data, 'zizi', 42, x=1, y='baz')
def test_add_node(self): new = Adapter(id='new') self.node.add('/adapters', new) self.assertNotEqual(self.node.latest.hash, self.hash_orig) self.assertEqual(len(self.node.get('/adapters')), 6) self.assertEqual(len(self.node.get('/adapters', hash=self.hash_orig)), 5) self.assertEqual(self.node.get('/adapters/new'), new)
def test_add_event(self): data = Adapter(id='10', version='zoo') self.node.add('/adapters', data) event = ConfigEvent(type=ConfigEventType.add, hash=self.node.latest.hash, data=dumps(MessageToDict(data, True, True))) self.event_mock.assert_called_once_with('model-change-events', event)
def test_mixed_add_remove_update_changes(self): proxy = self.node.get_proxy('/') tx1 = proxy.open_transaction() self.make_change(tx1, '/adapters/2', 'config.log_level', 2) tx1.remove('/adapters/0') tx1.add('/adapters', Adapter(id='new')) tx1.remove('/adapters/4') tx1.add('/adapters', Adapter(id='new2')) tx1.add('/adapters', Adapter(id='new3')) self.assertEqual(len(proxy.get('/adapters')), 5) self.assertEqual(len(self.node.get('/adapters')), 5) self.assertEqual(len(tx1.get('/adapters')), 6) tx1.commit() self.assertEqual(len(proxy.get('/adapters')), 6) self.assertEqual(len(self.node.get('/adapters')), 6) self.assertEqual(self.log_levels(), { '1': 3, '2': 2, '3': 3, 'new': 0, 'new2': 0, 'new3': 0 })
def test_pre_and_post_add_hooks(self): proxy = self.node.get_proxy('/') pre_callback = Mock() post_callback = Mock() proxy.register_callback(CallbackType.PRE_ADD, pre_callback) proxy.register_callback(CallbackType.POST_ADD, post_callback) new_adapter = Adapter(id='99', version='12.2', vendor='ace') proxy.add('/adapters', new_adapter) pre_callback.assert_called_with(new_adapter) post_callback.assert_called_with(new_adapter)
def setUp(self): gc.collect() _rev_cache.clear() self.health = HealthStatus(state=HealthStatus.DYING) self.base_shallow = VolthaInstance(instance_id='1') self.base_deep = copy(self.base_shallow) self.base_deep.health.state = HealthStatus.DYING # = self.health for i in xrange(5): self.base_deep.adapters.add().MergeFrom( Adapter(id=str(i), config=AdapterConfig(log_level=3))) self.node = ConfigRoot(self.base_deep) self.hash_orig = self.node.latest.hash
def test_strict_read_only(self): # it shall not be possible to change a read-only field self.assertRaises(ValueError, self.node.update, '/', VolthaInstance(version='foo'), strict=True) self.assertRaises(ValueError, self.node.update, '/adapters/1', Adapter(version='foo'), strict=True)
def test_add_handle_invalid_cases(self): # invalid paths self.assertRaises(KeyError, self.node.add, 'foo', None) self.assertRaises(KeyError, self.node.add, '/foo', None) self.assertRaises(KeyError, self.node.add, '/adapters/foo', None) # cannot add to non-container nodes self.assertRaises(ValueError, self.node.add, '/health', None) self.assertRaises(ValueError, self.node.add, '/adapters/1', None) # cannot add to container data with duplicate key self.assertRaises( ValueError, self.node.add, '/adapters', Adapter(id='1'))
def test_deep_update_container(self): self.node.update('/adapters/0', Adapter(id='0', version='new')) # root hash is now different hash_new = self.node.latest.hash self.assertNotEqual(self.hash_orig, hash_new) # original tree is still intact orig = self.node.get(hash=self.hash_orig, deep=1) self.assertEqual(orig, self.base_deep) self.assertEqual(orig.adapters[0].id, '0') # but the new tree contains the change new = self.node.get(deep=1) self.assertNotEqual(new, orig) self.assertEqual(new.adapters[0].version, 'new')
def test_pruning_after_deep_change(self): self.node.update('/adapters/3', Adapter(id='3', version='changed')) # sanity check self.assertEqual(len(self.node.revisions), 2) # prune self.node.prune_untagged() self.assertEqual(len(self.node.revisions), 1) # we can nevertheless access the whole tree new = self.node.get('/', deep=1) self.assertEqual(len(new.adapters), 5) self.assertEqual(new.adapters[2], self.base_deep.adapters[2]) self.assertEqual(new.adapters[3].version, 'changed')
def pump_some_data(self, node): seed(0) node.update('/', VolthaInstance(instance_id='1', version='42', log_level=1)) node.update('/health', HealthStatus(state=HealthStatus.OVERLOADED)) for i in xrange(n_adapters): node.add( '/adapters', Adapter(id=str(i), vendor='cord', version=str(randint(1, 10)), config=AdapterConfig(log_level=0))) for i in xrange(n_logical_nodes): node.add( '/logical_devices', LogicalDevice(id=str(i), datapath_id=randint(1, 100000), desc=ofp_desc(mfr_desc='foo', hw_desc='bar', sw_desc='zoo')))
def test_update_with_bad_data(self): self.assertRaises(ValueError, self.node.update, '/', Adapter())
def test_update_handle_key_change_attempt(self): self.assertRaises( ValueError, self.node.update, '/adapters/1', Adapter(id='changed'))
def test_update_handle_invalid_type(self): self.assertRaises(ValueError, self.node.update, '/', Adapter()) self.assertRaises(ValueError, self.node.update, '/health', Adapter()) self.assertRaises(ValueError, self.node.update, '/adapters/1', VolthaInstance())
def test_reject_duplicate_keys(self): data = VolthaInstance( instance_id='42', adapters=[Adapter(id='same') for _ in xrange(5)]) self.assertRaises(ValueError, ConfigRoot, data)