def test_merge_handler_repeated_property(self): """Merge handler won't overwrite already existing properties. First fetch returns value=1; locally change that to 2 and try to update. However server says that's a conflict, and now says value=5. Merge handler decides it can't merge and errors out. """ instance = NodeInstance('id', 'node_id', {'value': 1}) ep = mock.Mock( **{ 'get_node_instance.return_value': instance, 'update_node_instance.side_effect': self.ERR_CONFLICT }) ctx = _context_with_endpoint(ep) ctx.runtime_properties['value'] = 2 # in the meantime, server's version changed! value is now 5 ep.get_node_instance.return_value = NodeInstance( 'id', 'node_id', {'value': 5}) try: ctx.update(conflict_handlers.simple_merge_handler) except ValueError: pass else: self.fail('merge handler should fail to merge repeated properties') self.assertEqual(1, len(ep.update_node_instance.mock_calls))
def test_update_property(self): node = NodeInstance('instance_id', 'node_id') node.put('key', 'value') self.assertEqual('value', node.get('key')) props = node.runtime_properties self.assertEqual(1, len(props)) self.assertEqual('value', props['key'])
def test_merge_handler_conflict_resolved(self): """Merge handler can resolve conflicts, adding new properties. First fetch returns instance without the 'value' property. Handler adds the locally-added 'othervalue' and tries updating. That's a conflict, because now the server version has the 'value' property. Handler refetches, and is able to merge. """ instances = [ NodeInstance('id', 'node_id'), NodeInstance('id', 'node_id', {'value': 1}) ] def mock_update(instance): if 'value' not in instance.runtime_properties: raise self.ERR_CONFLICT self.assertEqual({ 'othervalue': 1, 'value': 1 }, instance.runtime_properties) ep = mock.Mock( **{ 'get_node_instance.side_effect': instances, 'update_node_instance.side_effect': mock_update }) ctx = _context_with_endpoint(ep) ctx.runtime_properties['othervalue'] = 1 # at this point we don't know about the 'value' property yet ctx.update(conflict_handlers.simple_merge_handler) self.assertEqual(2, len(ep.update_node_instance.mock_calls))
def test_put_several_properties(self): node = NodeInstance('instance_id', 'node_id', {'key0': 'value0'}) node.put('key1', 'value1') node.put('key2', 'value2') props = node.runtime_properties self.assertEqual(3, len(props)) self.assertEqual('value0', props['key0']) self.assertEqual('value1', props['key1']) self.assertEqual('value2', props['key2'])
def test_refresh_fetches(self): """Refreshing a node instance fetches new properties.""" # first .get_node_instances call returns an instance with value=1 # next call returns one with value=2 instances = [ NodeInstance('id', 'node_id', {'value': 1}), NodeInstance('id', 'node_id', {'value': 2}) ] ep = mock.Mock(**{'get_node_instance.side_effect': instances}) ctx = _context_with_endpoint(ep) self.assertEqual(1, ctx.runtime_properties['value']) ctx.refresh() self.assertEqual(2, ctx.runtime_properties['value'])
def test_setting_runtime_properties_checks_modifiable(self): """Cannot assign to .runtime_properties if modifiable is false.""" node = NodeInstance('instance_id', 'node_id', runtime_properties={'preexisting-key': 'val'}) node.runtime_properties.modifiable = False try: node.runtime_properties = {'other key': 'other val'} except exceptions.NonRecoverableError: pass else: self.fail( 'Error should be raised when assigning runtime_properties ' 'with the modifiable flag set to False')
def test_put_get(self): node = NodeInstance('instance_id', 'node_id', {}) node['key'] = 'value' self.assertEqual('value', node['key']) props = node.runtime_properties self.assertEqual(1, len(props)) self.assertEqual('value', props['key'])
def test_merge_handler_noconflict(self): """The merge builtin handler adds properties that are not present. If a property was added locally, but isn't in the storage version, it can be added. """ instance = NodeInstance('id', 'node_id', {'value': 1}) def mock_update(instance): # we got both properties merged - the locally added one # and the server one self.assertEqual({ 'othervalue': 1, 'value': 1 }, instance.runtime_properties) ep = mock.Mock( **{ 'get_node_instance.return_value': instance, 'update_node_instance.side_effect': mock_update }) ctx = _context_with_endpoint(ep) ctx.runtime_properties['othervalue'] = 1 ctx.update(conflict_handlers.simple_merge_handler) ep.update_node_instance.assert_called_once_with(instance)
def test_update_conflict_simple_handler(self): """On a conflict, the handler will be called until it succeeds. The simple handler function in this test will just increase the runtime property value by 1 each call. When the value reaches 5, the mock update method will at last allow it to save. """ # each next call of the mock .get_node_instance will return subsequent # instances: each time the runtime property is changed instances = [NodeInstance('id', 'node_id', {'value': i}) for i in range(5)] def mock_update(instance): if instance.runtime_properties.get('value', 0) < 5: raise self.ERR_CONFLICT ep = mock.Mock(**{ 'get_node_instance.side_effect': instances, 'update_node_instance.side_effect': mock_update }) ctx = _context_with_endpoint(ep) ctx.runtime_properties['value'] = 1 def _handler(previous, next_props): # the "previous" argument is always the props as they were before # .update() was called self.assertEqual(previous, {'value': 1}) return {'value': next_props['value'] + 1} handler = mock.Mock(side_effect=_handler) # Mock() for recording calls ctx.update(handler) self.assertEqual(5, len(handler.mock_calls)) self.assertEqual(5, len(ep.update_node_instance.mock_calls))
def test_put_new_property_twice(self): node = NodeInstance('id') node.put('key', 'value') node.put('key', 'v') self.assertEqual('v', node.get('key')) props = node.runtime_properties self.assertEqual(1, len(props)) self.assertEqual('v', props['key'])
def test_cant_refresh_dirty(self): """Refreshing a dirty instance throws instead of overwriting data.""" instance = NodeInstance('id', 'node_id', {'value': 1}) ep = mock.Mock(**{'get_node_instance.return_value': instance}) ctx = _context_with_endpoint(ep) ctx.runtime_properties['value'] += 5 try: ctx.refresh() except exceptions.NonRecoverableError as e: self.assertIn('dirty', str(e)) else: self.fail('NonRecoverableError was not thrown') self.assertEqual( 6, ctx.runtime_properties['value'], "Instance properties were overwritten, losing local changes.")
def test_update(self): """.update() without a handler sends the changed runtime properties.""" def mock_update(instance): self.assertEqual({'foo': 42}, instance.runtime_properties) instance = NodeInstance('id', 'node_id') ep = mock.Mock( **{ 'get_node_instance.return_value': instance, 'update_node_instance.side_effect': mock_update }) ctx = _context_with_endpoint(ep) ctx.runtime_properties['foo'] = 42 ctx.update() ep.update_node_instance.assert_called_once_with(instance)
def test_update_conflict_no_handler(self): """Version conflict without a handler function aborts the operation.""" instance = NodeInstance('id', 'node_id') ep = mock.Mock(**{ 'get_node_instance.return_value': instance, 'update_node_instance.side_effect': self.ERR_CONFLICT }) ctx = _context_with_endpoint(ep) ctx.runtime_properties['foo'] = 42 try: ctx.update() except CloudifyClientError as e: self.assertEqual(409, e.status_code) else: self.fail('ctx.update() has hidden the 409 error')
def test_delete_property(self): node = NodeInstance('instance_id', 'node_id') node.put('key', 'value') self.assertEquals('value', node.get('key')) node.delete('key') self.assertNotIn('key', node)
def test_setting_runtime_properties_sets_dirty(self): """Assignment to .runtime_properties sets the dirty flag.""" node = NodeInstance('instance_id', 'node_id', runtime_properties={'preexisting-key': 'val'}) node.runtime_properties = {'other key': 'other val'} self.assertTrue(node.runtime_properties.dirty)
def test_setting_runtime_properties(self): """Assignment to .runtime_properties is possible and stores them.""" node = NodeInstance('instance_id', 'node_id', runtime_properties={'preexisting-key': 'val'}) node.runtime_properties = {'other key': 'other val'} self.assertEqual({'other key': 'other val'}, node.runtime_properties)
def get_instance(endpoint): # we'll be mutating the instance properties, so make sure # we return a new object every time - otherwise the properties # would've been overwritten with the same object, not with fresh # values. return NodeInstance('id', 'node_id', {'value': 1})
def test_no_updates_to_empty_node(self): node = NodeInstance('instance_id', 'node_id') self.assertEqual(0, len(node.runtime_properties))
def test_delete_makes_properties_dirty(self): node = NodeInstance('instance_id', 'node_id', runtime_properties={'preexisting-key': 'val'}) self.assertFalse(node.dirty) del(node['preexisting-key']) self.assertTrue(node.dirty)
def test_delete_nonexistent_property(self): node = NodeInstance('instance_id', 'node_id') self.assertRaises(KeyError, node.delete, 'key')
def test_delete_property_sugared_syntax(self): node = NodeInstance('instance_id', 'node_id') node.put('key', 'value') self.assertEquals('value', node.get('key')) del(node['key']) self.assertNotIn('key', node)