def test_hook_scope(self): kv = Storage(':memory:') try: with kv.hook_scope('install') as rev: self.assertEqual(rev, 1) kv.set('a', 1) raise RuntimeError('x') except RuntimeError: self.assertEqual(kv.get('a'), None) with kv.hook_scope('config-changed') as rev: self.assertEqual(rev, 1) kv.set('a', 1) self.assertEqual(kv.get('a'), 1) kv.revision = None with kv.hook_scope('start') as rev: self.assertEqual(rev, 2) kv.set('a', False) kv.set('a', True) self.assertEqual(kv.get('a'), True) # History doesn't decode values by default history = [h[:-1] for h in kv.gethistory('a')] self.assertEqual( history, [(1, 'a', '1', 'config-changed'), (2, 'a', 'true', 'start')]) history = [h[:-1] for h in kv.gethistory('a', deserialize=True)] self.assertEqual( history, [(1, 'a', 1, 'config-changed'), (2, 'a', True, 'start')])
def test_init_kv_multiple(self): with tempfile.NamedTemporaryFile() as fh: kv = Storage(fh.name) with kv.hook_scope('xyz'): kv.set('x', 1) kv.close() kv = Storage(fh.name) with kv.hook_scope('abc'): self.assertEqual(kv.get('x'), 1) kv.close()
def remember_devices(devs): """Add device to local store of ringed devices.""" d = os.path.dirname(KV_DB_PATH) if not os.path.isdir(d): mkdir(d) kvstore = KVStore(KV_DB_PATH) devstore = devstore_safe_load(kvstore.get(key='devices')) or {} env_uuid = os.environ['JUJU_ENV_UUID'] for dev in devs: blk_uuid = get_device_blkid("/dev/%s" % (dev)) key = "%s@%s" % (dev, env_uuid) if key in devstore and devstore[key].get('blkid') == blk_uuid: log("Device '%s' already in devstore (status:%s)" % (dev, devstore[key].get('status')), level=DEBUG) else: existing = [(k, v) for k, v in devstore.iteritems() if v.get('blkid') == blk_uuid and re.match("^(.+)@(.+)$", k).group(1) == dev] if existing: log("Device '%s' already in devstore but has a different " "JUJU_ENV_UUID (%s)" % (dev, re.match(".+@(.+)$", existing[0][0]).group(1)), level=WARNING) else: log("Adding device '%s' with blkid='%s' to devstore" % (blk_uuid, dev), level=DEBUG) devstore[key] = {'blkid': blk_uuid, 'status': 'active'} if devstore: kvstore.set(key='devices', value=json.dumps(devstore)) kvstore.flush() kvstore.close()
def test_update(self): kv = Storage(':memory:') kv.update({'v_a': 1, 'v_b': 2.2}) self.assertEqual(kv.getrange('v_'), {'v_a': 1, 'v_b': 2.2}) kv.update({'a': False, 'b': True}, prefix='x_') self.assertEqual(kv.getrange('x_', True), {'a': False, 'b': True})
def test_delta(self): kv = Storage(':memory:') kv.update({'a': 1, 'b': 2.2}, prefix="x") delta = kv.delta({'a': 0, 'c': False}, prefix='x') self.assertEqual( delta, {'a': (1, 0), 'b': (2.2, None), 'c': (None, False)}) self.assertEqual(delta.a.previous, 1) self.assertEqual(delta.a.current, 0) self.assertEqual(delta.c.previous, None) self.assertEqual(delta.a.current, False)
def test_get_set_unset(self): kv = Storage(':memory:') kv.hook_scope('test') kv.set('hello', 'saucy') kv.set('hello', 'world') self.assertEqual(kv.get('hello'), 'world') kv.flush() kv.unset('hello') self.assertEqual(kv.get('hello'), None) kv.flush(False) self.assertEqual(kv.get('hello'), 'world')
def test_keyrange(self): kv = Storage(':memory:') kv.set('docker.net_mtu', 1) kv.set('docker.net_nack', True) kv.set('docker.net_type', 'vxlan') self.assertEqual( kv.getrange('docker'), {'docker.net_mtu': 1, 'docker.net_type': 'vxlan', 'docker.net_nack': True}) self.assertEqual( kv.getrange('docker.', True), {'net_mtu': 1, 'net_type': 'vxlan', 'net_nack': True})
def test_unset(self): kv = Storage(':memory:') with kv.hook_scope('install'): kv.set('a', True) with kv.hook_scope('start'): kv.set('a', False) with kv.hook_scope('config-changed'): kv.unset('a') history = [h[:-1] for h in kv.gethistory('a')] self.assertEqual(history, [(1, 'a', 'true', 'install'), (2, 'a', 'false', 'start'), (3, 'a', '"DELETED"', "config-changed")])
def test_record(self): kv = Storage(':memory:') kv.set('config', {'x': 1, 'b': False}) config = kv.get('config', record=True) self.assertEqual(config.b, False) self.assertEqual(config.x, 1) self.assertEqual(kv.set('config.x', 1), 1) try: config.z except AttributeError: pass else: self.fail('attribute error should fire on nonexistant')
def test_delta_no_previous_and_history(self): kv = Storage(':memory:') with kv.hook_scope('install'): data = {'a': 0, 'c': False} delta = kv.delta(data, 'settings.') self.assertEqual(delta, {'a': (None, False), 'c': (None, False)}) kv.update(data, 'settings.') with kv.hook_scope('config'): data = {'a': 1, 'c': True} delta = kv.delta(data, 'settings.') self.assertEqual(delta, {'a': (0, 1), 'c': (False, True)}) kv.update(data, 'settings.') # strip the time history = [h[:-1] for h in kv.gethistory('settings.a')] self.assertEqual(history, [(1, 'settings.a', '0', 'install'), (2, 'settings.a', '1', 'config')])
def remember_devices(devs): """Add device to local store of ringed devices.""" d = os.path.dirname(KV_DB_PATH) if not os.path.isdir(d): mkdir(d) kvstore = KVStore(KV_DB_PATH) devstore = devstore_safe_load(kvstore.get(key='devices')) or {} env_uuid = os.environ.get('JUJU_ENV_UUID', os.environ.get('JUJU_MODEL_UUID')) for dev in devs: blk_uuid = get_device_blkid("/dev/%s" % (dev)) key = "%s@%s" % (dev, env_uuid) if key in devstore and devstore[key].get('blkid') == blk_uuid: log("Device '%s' already in devstore (status:%s)" % (dev, devstore[key].get('status')), level=DEBUG) else: existing = [(k, v) for k, v in devstore.iteritems() if v.get('blkid') == blk_uuid and re.match("^(.+)@(.+)$", k).group(1) == dev] if existing: log("Device '%s' already in devstore but has a different " "JUJU_[ENV|MODEL]_UUID (%s)" % (dev, re.match(".+@(.+)$", existing[0][0]).group(1)), level=WARNING) else: log("Adding device '%s' with blkid='%s' to devstore" % (dev, blk_uuid), level=DEBUG) devstore[key] = {'blkid': blk_uuid, 'status': 'active'} if devstore: kvstore.set(key='devices', value=json.dumps(devstore)) kvstore.flush() kvstore.close()
def is_device_in_ring(dev, skip_rel_check=False, ignore_deactivated=True): """Check if device has been added to the ring. First check local KV store then check storage rel with proxy. """ d = os.path.dirname(KV_DB_PATH) if not os.path.isdir(d): mkdir(d) log("Device '%s' does not appear to be in use by Swift" % (dev), level=INFO) return False # First check local KV store kvstore = KVStore(KV_DB_PATH) devstore = devstore_safe_load(kvstore.get(key='devices')) kvstore.close() deactivated = [] if devstore: blk_uuid = get_device_blkid("/dev/%s" % (dev)) env_uuid = os.environ.get('JUJU_ENV_UUID', os.environ.get('JUJU_MODEL_UUID')) masterkey = "%s@%s" % (dev, env_uuid) if (masterkey in devstore and devstore[masterkey].get('blkid') == blk_uuid and devstore[masterkey].get('status') == 'active'): log("Device '%s' appears to be in use by Swift (found in local " "devstore)" % (dev), level=INFO) return True for key, val in devstore.iteritems(): if key != masterkey and val.get('blkid') == blk_uuid: log("Device '%s' appears to be in use by Swift (found in " "local devstore) but has a different " "JUJU_[ENV|MODEL]_UUID (current=%s, expected=%s). " "This could indicate that the device was added as part of " "a previous deployment and will require manual removal or " "updating if it needs to be reformatted." % (dev, key, masterkey), level=INFO) return True if ignore_deactivated: deactivated = [ k == masterkey and v.get('blkid') == blk_uuid and v.get('status') != 'active' for k, v in devstore.iteritems() ] if skip_rel_check: log("Device '%s' does not appear to be in use by swift (searched " "local devstore only)" % (dev), level=INFO) return False # Then check swift-storage relation with proxy for rid in relation_ids('swift-storage'): devstore = relation_get(attribute='device', rid=rid, unit=local_unit()) if devstore and dev in devstore.split(':'): if not ignore_deactivated or dev not in deactivated: log("Device '%s' appears to be in use by swift (found on " "proxy relation) but was not found in local devstore so " "will be added to the cache" % (dev), level=INFO) remember_devices([dev]) return True log("Device '%s' does not appear to be in use by swift (searched local " "devstore and proxy relation)" % (dev), level=INFO) return False
def test_configure_amqp(self, mock_config, mock_grant_permissions, mock_create_vhost, mock_create_user, mock_get_rabbit_password, mock_set_ha_mode, mock_is_leader, mock_configure_notification_ttl, mock_configure_ttl): config_data = { 'notification-ttl': 450000, 'mirroring-queues': True, } mock_is_leader.return_value = True mock_config.side_effect = lambda attribute: config_data.get(attribute) tmpdir = tempfile.mkdtemp() try: db_path = '{}/kv.db'.format(tmpdir) rid = 'amqp:1' store = Storage(db_path) with patch('charmhelpers.core.unitdata._KV', store): # Check .set with patch.object(store, 'set') as mock_set: rabbitmq_server_relations.configure_amqp('user_foo', 'vhost_blah', rid) d = {rid: {"username": "******", "vhost": "vhost_blah", "ttl": None, "mirroring-queues": True}} mock_set.assert_has_calls([call(key='amqp_config_tracker', value=d)]) for m in [mock_grant_permissions, mock_create_vhost, mock_create_user, mock_set_ha_mode]: self.assertTrue(m.called) m.reset_mock() # Check .get with patch.object(store, 'get') as mock_get: mock_get.return_value = d rabbitmq_server_relations.configure_amqp('user_foo', 'vhost_blah', rid) mock_set.assert_has_calls([call(key='amqp_config_tracker', value=d)]) for m in [mock_grant_permissions, mock_create_vhost, mock_create_user, mock_set_ha_mode]: self.assertFalse(m.called) # Check invalid relation id self.assertRaises(Exception, rabbitmq_server_relations.configure_amqp, 'user_foo', 'vhost_blah', None, admin=True) # Test writing data d = {} for rid, user in [('amqp:1', 'userA'), ('amqp:2', 'userB')]: rabbitmq_server_relations.configure_amqp(user, 'vhost_blah', rid) d.update({rid: {"username": user, "vhost": "vhost_blah", "ttl": None, "mirroring-queues": True}}) self.assertEqual(store.get('amqp_config_tracker'), d) @rabbitmq_server_relations.validate_amqp_config_tracker def fake_configure_amqp(*args, **kwargs): return rabbitmq_server_relations.configure_amqp(*args, **kwargs) # Test invalidating data mock_is_leader.return_value = False d['amqp:2']['stale'] = True for rid, user in [('amqp:1', 'userA'), ('amqp:3', 'userC')]: fake_configure_amqp(user, 'vhost_blah', rid) d[rid] = {"username": user, "vhost": "vhost_blah", "ttl": None, "mirroring-queues": True, 'stale': True} # Since this is a dummy case we need to toggle the stale # values. del d[rid]['stale'] self.assertEqual(store.get('amqp_config_tracker'), d) d[rid]['stale'] = True mock_configure_notification_ttl.assert_not_called() mock_configure_ttl.assert_not_called() # Test openstack notification workaround d = {} for rid, user in [('amqp:1', 'userA')]: rabbitmq_server_relations.configure_amqp( user, 'openstack', rid, admin=False, ttlname='heat_expiry', ttlreg='heat-engine-listener|engine_worker', ttl=45000) (mock_configure_notification_ttl. assert_called_once_with('openstack', 450000)) (mock_configure_ttl. assert_called_once_with( 'openstack', 'heat_expiry', 'heat-engine-listener|engine_worker', 45000)) finally: if os.path.exists(tmpdir): shutil.rmtree(tmpdir)
def is_device_in_ring(dev, skip_rel_check=False, ignore_deactivated=True): """Check if device has been added to the ring. First check local KV store then check storage rel with proxy. """ d = os.path.dirname(KV_DB_PATH) if not os.path.isdir(d): mkdir(d) log("Device '%s' does not appear to be in use by Swift" % (dev), level=INFO) return False # First check local KV store kvstore = KVStore(KV_DB_PATH) devstore = devstore_safe_load(kvstore.get(key='devices')) kvstore.close() deactivated = [] if devstore: blk_uuid = get_device_blkid("/dev/%s" % (dev)) env_uuid = os.environ['JUJU_ENV_UUID'] masterkey = "%s@%s" % (dev, env_uuid) if (masterkey in devstore and devstore[masterkey].get('blkid') == blk_uuid and devstore[masterkey].get('status') == 'active'): log("Device '%s' appears to be in use by Swift (found in local " "devstore)" % (dev), level=INFO) return True for key, val in devstore.iteritems(): if key != masterkey and val.get('blkid') == blk_uuid: log("Device '%s' appears to be in use by Swift (found in " "local devstore) but has a different JUJU_ENV_UUID " "(current=%s, expected=%s). " "This could indicate that the device was added as part of " "a previous deployment and will require manual removal or " "updating if it needs to be reformatted." % (dev, key, masterkey), level=INFO) return True if ignore_deactivated: deactivated = [k == masterkey and v.get('blkid') == blk_uuid and v.get('status') != 'active' for k, v in devstore.iteritems()] if skip_rel_check: log("Device '%s' does not appear to be in use by swift (searched " "local devstore only)" % (dev), level=INFO) return False # Then check swift-storage relation with proxy for rid in relation_ids('swift-storage'): devstore = relation_get(attribute='device', rid=rid, unit=local_unit()) if devstore and dev in devstore.split(':'): if not ignore_deactivated or dev not in deactivated: log("Device '%s' appears to be in use by swift (found on " "proxy relation) but was not found in local devstore so " "will be added to the cache" % (dev), level=INFO) remember_devices([dev]) return True log("Device '%s' does not appear to be in use by swift (searched local " "devstore and proxy relation)" % (dev), level=INFO) return False
def test_flush_and_close_on_closed(self): kv = Storage(':memory:') kv.close() kv.flush(False) kv.close()
def test_debug(self): # pure coverage test... io = StringIO() kv = Storage(':memory:') kv.debug(io)
def test_multi_value_set_skips(self): # pure coverage test kv = Storage(':memory:') kv.set('x', 1) self.assertEqual(kv.set('x', 1), 1)
def test_configure_amqp(self, mock_grant_permissions, mock_create_vhost, mock_create_user, mock_get_rabbit_password, mock_set_ha_mode, mock_is_leader): mock_is_leader.return_value = True tmpdir = tempfile.mkdtemp() try: db_path = '{}/kv.db'.format(tmpdir) rid = 'amqp:1' store = Storage(db_path) with patch('charmhelpers.core.unitdata._KV', store): # Check .set with patch.object(store, 'set') as mock_set: rabbitmq_server_relations.configure_amqp( 'user_foo', 'vhost_blah', rid) d = { rid: { "username": "******", "vhost": "vhost_blah", "mirroring-queues": True } } mock_set.assert_has_calls( [call(key='amqp_config_tracker', value=d)]) for m in [ mock_grant_permissions, mock_create_vhost, mock_create_user, mock_set_ha_mode ]: self.assertTrue(m.called) m.reset_mock() # Check .get with patch.object(store, 'get') as mock_get: mock_get.return_value = d rabbitmq_server_relations.configure_amqp( 'user_foo', 'vhost_blah', rid) mock_set.assert_has_calls( [call(key='amqp_config_tracker', value=d)]) for m in [ mock_grant_permissions, mock_create_vhost, mock_create_user, mock_set_ha_mode ]: self.assertFalse(m.called) # Check invalid relation id self.assertRaises(Exception, rabbitmq_server_relations.configure_amqp, 'user_foo', 'vhost_blah', None, admin=True) # Test writing data d = {} for rid, user in [('amqp:1', 'userA'), ('amqp:2', 'userB')]: rabbitmq_server_relations.configure_amqp( user, 'vhost_blah', rid) d.update({ rid: { "username": user, "vhost": "vhost_blah", "mirroring-queues": True } }) self.assertEqual(store.get('amqp_config_tracker'), d) @rabbitmq_server_relations.validate_amqp_config_tracker def fake_configure_amqp(*args, **kwargs): return rabbitmq_server_relations.configure_amqp( *args, **kwargs) # Test invalidating data mock_is_leader.return_value = False d['amqp:2']['stale'] = True for rid, user in [('amqp:1', 'userA'), ('amqp:3', 'userC')]: fake_configure_amqp(user, 'vhost_blah', rid) d[rid] = { "username": user, "vhost": "vhost_blah", "mirroring-queues": True, 'stale': True } # Since this is a dummy case we need to toggle the stale # values. del d[rid]['stale'] self.assertEqual(store.get('amqp_config_tracker'), d) d[rid]['stale'] = True finally: if os.path.exists(tmpdir): shutil.rmtree(tmpdir)
def test_configure_amqp(self, mock_config, mock_grant_permissions, mock_create_vhost, mock_create_user, mock_get_rabbit_password, mock_set_ha_mode, mock_is_leader, mock_configure_notification_ttl): config_data = { 'notification-ttl': 450000, 'mirroring-queues': True, } mock_is_leader.return_value = True mock_config.side_effect = lambda attribute: config_data.get(attribute) tmpdir = tempfile.mkdtemp() try: db_path = '{}/kv.db'.format(tmpdir) rid = 'amqp:1' store = Storage(db_path) with patch('charmhelpers.core.unitdata._KV', store): # Check .set with patch.object(store, 'set') as mock_set: rabbitmq_server_relations.configure_amqp('user_foo', 'vhost_blah', rid) d = {rid: {"username": "******", "vhost": "vhost_blah", "mirroring-queues": True}} mock_set.assert_has_calls([call(key='amqp_config_tracker', value=d)]) for m in [mock_grant_permissions, mock_create_vhost, mock_create_user, mock_set_ha_mode]: self.assertTrue(m.called) m.reset_mock() # Check .get with patch.object(store, 'get') as mock_get: mock_get.return_value = d rabbitmq_server_relations.configure_amqp('user_foo', 'vhost_blah', rid) mock_set.assert_has_calls([call(key='amqp_config_tracker', value=d)]) for m in [mock_grant_permissions, mock_create_vhost, mock_create_user, mock_set_ha_mode]: self.assertFalse(m.called) # Check invalid relation id self.assertRaises(Exception, rabbitmq_server_relations.configure_amqp, 'user_foo', 'vhost_blah', None, admin=True) # Test writing data d = {} for rid, user in [('amqp:1', 'userA'), ('amqp:2', 'userB')]: rabbitmq_server_relations.configure_amqp(user, 'vhost_blah', rid) d.update({rid: {"username": user, "vhost": "vhost_blah", "mirroring-queues": True}}) self.assertEqual(store.get('amqp_config_tracker'), d) @rabbitmq_server_relations.validate_amqp_config_tracker def fake_configure_amqp(*args, **kwargs): return rabbitmq_server_relations.configure_amqp(*args, **kwargs) # Test invalidating data mock_is_leader.return_value = False d['amqp:2']['stale'] = True for rid, user in [('amqp:1', 'userA'), ('amqp:3', 'userC')]: fake_configure_amqp(user, 'vhost_blah', rid) d[rid] = {"username": user, "vhost": "vhost_blah", "mirroring-queues": True, 'stale': True} # Since this is a dummy case we need to toggle the stale # values. del d[rid]['stale'] self.assertEqual(store.get('amqp_config_tracker'), d) d[rid]['stale'] = True mock_configure_notification_ttl.assert_not_called() # Test openstack notification workaround d = {} for rid, user in [('amqp:1', 'userA')]: rabbitmq_server_relations.configure_amqp(user, 'openstack', rid) (mock_configure_notification_ttl. assert_called_once_with('openstack', 450000)) finally: if os.path.exists(tmpdir): shutil.rmtree(tmpdir)