示例#1
0
    def test_initialize(self, relation_ids, ver, atexit):
        # First initialization are done before there is a peer relation.
        relation_ids.return_value = []
        c = coordinator.BaseCoordinator()

        with patch.object(c, '_load_state') as _load_state, \
                patch.object(c, '_emit_state') as _emit_state:  # IGNORE: E127
            c.initialize()
            _load_state.assert_called_once_with()
            _emit_state.assert_called_once_with()

        self.assertEqual(c.relname, 'cluster')
        self.assertIsNone(c.relid)
        relation_ids.assert_called_once_with('cluster')

        # Methods installed to save state and release locks if the
        # hook is successful.
        atexit.assert_has_calls(
            [call(c._save_state),
             call(c._release_granted)])

        # If we have a peer relation, the id is stored.
        relation_ids.return_value = ['cluster:1']
        c = coordinator.BaseCoordinator()
        with patch.object(c, '_load_state'), patch.object(c, '_emit_state'):
            c.initialize()
        self.assertEqual(c.relid, 'cluster:1')

        # If we are already initialized, nothing happens.
        c.grants = {}
        c.requests = {}
        c.initialize()
示例#2
0
 def test_is_singleton(self):
     # BaseCoordinator and subclasses are singletons. Placing this
     # burden on charm authors is impractical, particularly if
     # libraries start wanting to use coordinator instances.
     # With singletons, we don't need to worry about sharing state
     # between instances or have them stomping on each other when they
     # need to serialize their state.
     self.assertTrue(
         coordinator.BaseCoordinator() is coordinator.BaseCoordinator())
     self.assertTrue(coordinator.Serial() is coordinator.Serial())
     self.assertFalse(coordinator.BaseCoordinator() is coordinator.Serial())
示例#3
0
    def test_granted(self):
        c = coordinator.BaseCoordinator()
        unit = hookenv.local_unit()
        lock = 'mylock'
        ts = coordinator._timestamp()
        c.grants = {}

        # Unit makes a request, but it isn't granted
        c.requests = {unit: {lock: ts}}
        self.assertFalse(c.granted(lock))

        # Once the leader has granted the request, all good.
        # It does this by mirroring the request timestamp.
        c.grants = {unit: {lock: ts}}
        self.assertTrue(c.granted(lock))

        # The unit releases the lock by removing the request.
        c.requests = {unit: {}}
        self.assertFalse(c.granted(lock))

        # If the unit makes a new request before the leader
        # has had a chance to do its housekeeping, the timestamps
        # do not match and the lock not considered granted.
        ts = coordinator._timestamp()
        c.requests = {unit: {lock: ts}}
        self.assertFalse(c.granted(lock))

        # Until the leader gets around to its duties.
        c.grants = {unit: {lock: ts}}
        self.assertTrue(c.granted(lock))
示例#4
0
    def test_save_state(self, leader_set, relation_set):
        c = coordinator.BaseCoordinator()
        unit = hookenv.local_unit()
        c.grants = {'directdump': True}
        c.requests = {unit: 'data1', 'foo/2': 'data2'}

        # grants is dumped to leadership settings, if the unit is leader.
        with patch.object(c, '_save_local_state') as save_loc:
            c._save_state()
            self.assertFalse(leader_set.called)
            hookenv.is_leader.return_value = True
            c._save_state()
            leader_set.assert_called_once_with({c.key: '{"directdump": true}'})

        # If there is no relation id, the local units requests is dumped
        # to a local stash.
        with patch.object(c, '_save_local_state') as save_loc:
            c._save_state()
            save_loc.assert_called_once_with('data1')

        # If there is a relation id, the local units requests is dumped
        # to the peer relation.
        with patch.object(c, '_save_local_state') as save_loc:
            c.relid = 'cluster:1'
            c._save_state()
            self.assertFalse(save_loc.called)
            relation_set.assert_called_once_with(
                c.relid, relation_settings={c.key: '"data1"'})  # JSON encoded
示例#5
0
    def test_load_peer_state(self, related_units, relation_get):
        # Standard relation-get loops, decoding results from JSON.
        c = coordinator.BaseCoordinator()
        c.key = sentinel.key
        c.relid = sentinel.relid
        related_units.return_value = ['foo/2', 'foo/3']
        d = {
            'foo/1': {
                'foo/1': True
            },
            'foo/2': {
                'foo/2': True
            },
            'foo/3': {
                'foo/3': True
            }
        }

        def _get(key, unit, relid):
            assert key == sentinel.key
            assert relid == sentinel.relid
            return json.dumps(d[unit])

        relation_get.side_effect = _get

        self.assertDictEqual(c._load_peer_state(), d)
示例#6
0
 def test_save_local_state(self):
     c = coordinator.BaseCoordinator()
     with tempfile.NamedTemporaryFile(mode='r') as f:
         with patch.object(c, '_local_state_filename') as fn:
             fn.return_value = f.name
             c._save_local_state('some data')
             self.assertEqual(json.load(f), 'some data')
示例#7
0
 def test_release_granted(self):
     c = coordinator.BaseCoordinator()
     unit = hookenv.local_unit()
     c.requests = {
         unit: {
             'lock1': sentinel.ts,
             'lock2': sentinel.ts
         },
         'foo/2': {
             'lock1': sentinel.ts
         }
     }
     c.grants = {
         unit: {
             'lock1': sentinel.ts
         },
         'foo/2': {
             'lock1': sentinel.ts
         }
     }
     # The granted lock for the local unit is released.
     c._release_granted()
     self.assertDictEqual(c.requests, {
         unit: {
             'lock2': sentinel.ts
         },
         'foo/2': {
             'lock1': sentinel.ts
         }
     })
示例#8
0
 def test_name(self):
     # We use the class name in a few places to avoid conflicts.
     # We assume we won't be using multiple BaseCoordinator subclasses
     # with the same name at the same time.
     c = coordinator.BaseCoordinator()
     self.assertEqual(c._name(), 'BaseCoordinator')
     c = coordinator.Serial()
     self.assertEqual(c._name(), 'Serial')
示例#9
0
    def test_requested(self):
        c = coordinator.BaseCoordinator()
        lock = 'mylock'
        c.requests = {hookenv.local_unit(): {}}
        c.grants = {}

        self.assertFalse(c.requested(lock))
        c.acquire(lock)
        self.assertTrue(c.requested(lock))
示例#10
0
    def test_grant(self):
        hookenv.is_leader.return_value = True
        c = coordinator.BaseCoordinator()
        c.default_grant = MagicMock()
        c.grant_other = MagicMock()

        ts = coordinator._timestamp
        ts1, ts2 = ts(), ts()

        c.requests = {
            'foo/1': {
                'mylock': ts1,
                'other': ts()
            },
            'foo/2': {
                'mylock': ts2
            },
            'foo/3': {
                'mylock': ts()
            }
        }
        grants = {'foo/1': {'mylock': ts1}}
        c.grants = grants.copy()

        # foo/1 already has a granted mylock, so returns True.
        self.assertTrue(c.grant('mylock', 'foo/1'))

        # foo/2 does not have a granted mylock. default_grant will
        # be called to make a decision (no)
        c.default_grant.return_value = False
        self.assertFalse(c.grant('mylock', 'foo/2'))
        self.assertDictEqual(grants, c.grants)
        c.default_grant.assert_called_once_with('mylock', 'foo/2',
                                                set(['foo/1']),
                                                ['foo/2', 'foo/3'])
        c.default_grant.reset_mock()

        # Lets say yes.
        c.default_grant.return_value = True
        self.assertTrue(c.grant('mylock', 'foo/2'))
        grants = {'foo/1': {'mylock': ts1}, 'foo/2': {'mylock': ts2}}
        self.assertDictEqual(grants, c.grants)
        c.default_grant.assert_called_once_with('mylock', 'foo/2',
                                                set(['foo/1']),
                                                ['foo/2', 'foo/3'])

        # The other lock has custom logic, in the form of the overridden
        # grant_other method.
        c.grant_other.return_value = False
        self.assertFalse(c.grant('other', 'foo/1'))
        c.grant_other.assert_called_once_with('other', 'foo/1', set(),
                                              ['foo/1'])

        # If there is no request, grant returns False
        c.grant_other.return_value = True
        self.assertFalse(c.grant('other', 'foo/2'))
示例#11
0
 def test_load_local_state(self):
     c = coordinator.BaseCoordinator()
     with tempfile.NamedTemporaryFile(mode='w') as f:
         with patch.object(c, '_local_state_filename') as fn:
             fn.return_value = f.name
             d = 'some data'
             json.dump(d, f)
             f.flush()
             d2 = c._load_local_state()
             self.assertEqual(d, d2)
示例#12
0
    def test_load_state(self, leader_get):
        c = coordinator.BaseCoordinator()
        unit = hookenv.local_unit()

        # c.granted is just the leader_get decoded.
        leader_get.return_value = '{"json": true}'
        c._load_state()
        self.assertDictEqual(c.grants, {'json': True})

        # With no relid, there is no peer relation so request state
        # is pulled from a local stash.
        with patch.object(c, '_load_local_state') as loc_state:
            loc_state.return_value = {'local': True}
            c._load_state()
            self.assertDictEqual(c.requests, {unit: {'local': True}})

        # With a relid, request details are pulled from the peer relation.
        # If there is no data in the peer relation from the local unit,
        # we still pull it from the local stash as it means this is the
        # first time we have joined.
        c.relid = 'cluster:1'
        with patch.object(c, '_load_local_state') as loc_state, \
                patch.object(c, '_load_peer_state') as peer_state:
            loc_state.return_value = {'local': True}
            peer_state.return_value = {'foo/2': {'mylock': 'whatever'}}
            c._load_state()
            self.assertDictEqual(c.requests, {
                unit: {
                    'local': True
                },
                'foo/2': {
                    'mylock': 'whatever'
                }
            })

        # If there are local details in the peer relation, the local
        # stash is ignored.
        with patch.object(c, '_load_local_state') as loc_state, \
                patch.object(c, '_load_peer_state') as peer_state:
            loc_state.return_value = {'local': True}
            peer_state.return_value = {
                unit: {},
                'foo/2': {
                    'mylock': 'whatever'
                }
            }
            c._load_state()
            self.assertDictEqual(c.requests, {
                unit: {},
                'foo/2': {
                    'mylock': 'whatever'
                }
            })
示例#13
0
    def test_request_timestamp(self):
        c = coordinator.BaseCoordinator()
        lock = 'mylock'
        unit = hookenv.local_unit()

        c.requests = {unit: {}}
        c.grants = {}
        self.assertIsNone(c.request_timestamp(lock))

        now = datetime.utcnow()
        fmt = coordinator._timestamp_format
        c.requests = {hookenv.local_unit(): {lock: now.strftime(fmt)}}

        self.assertEqual(c.request_timestamp(lock), now)
示例#14
0
 def test_emit_state(self):
     c = coordinator.BaseCoordinator()
     unit = hookenv.local_unit()
     c.requests = {
         unit: {
             'lock_a': sentinel.ts,
             'lock_b': sentinel.ts,
             'lock_c': sentinel.ts
         }
     }
     c.grants = {unit: {'lock_a': sentinel.ts, 'lock_b': sentinel.ts2}}
     with patch.object(c, 'msg') as msg:
         c._emit_state()
         msg.assert_has_calls([
             call('Granted lock_a'),
             call('Waiting on lock_b'),
             call('Waiting on lock_c')
         ],
                              any_order=True)
示例#15
0
    def test_handle(self):
        hookenv.is_leader.return_value = True
        lock = 'mylock'
        c = coordinator.BaseCoordinator()
        c.relid = 'cluster:1'

        ts = coordinator._timestamp
        ts1, ts2, ts3 = ts(), ts(), ts()

        # Grant one of these requests.
        requests = {
            'foo/1': {
                lock: ts1
            },
            'foo/2': {
                lock: ts2
            },
            'foo/3': {
                lock: ts3
            }
        }
        c.requests = requests.copy()
        # Because the existing grant should be released.
        c.grants = {'foo/2': {lock: ts()}}  # No request, release.

        with patch.object(c, 'grant') as grant:
            c.handle()

            # The requests are unchanged. This is normally state on the
            # peer relation, and only the units themselves can change it.
            self.assertDictEqual(requests, c.requests)

            # The grant without a corresponding requests was released.
            self.assertDictEqual({'foo/2': {}}, c.grants)

            # A potential grant was made for each of the outstanding requests.
            grant.assert_has_calls([
                call(lock, 'foo/1'),
                call(lock, 'foo/2'),
                call(lock, 'foo/3')
            ],
                                   any_order=True)
示例#16
0
    def test_require(self):
        c = coordinator.BaseCoordinator()
        c.acquire = MagicMock()
        c.granted = MagicMock()
        guard = MagicMock()

        wrapped = MagicMock()

        @c.require('mylock', guard)
        def func(*args, **kw):
            wrapped(*args, **kw)

        # If the lock is granted, the wrapped function is called.
        c.granted.return_value = True
        func(arg=True)
        wrapped.assert_called_once_with(arg=True)
        wrapped.reset_mock()

        # If the lock is not granted, and the guard returns False,
        # the lock is not acquired.
        c.acquire.return_value = False
        c.granted.return_value = False
        guard.return_value = False
        func()
        self.assertFalse(wrapped.called)
        self.assertFalse(c.acquire.called)

        # If the lock is not granted, and the guard returns True,
        # the lock is acquired. But the function still isn't called if
        # it cannot be acquired immediately.
        guard.return_value = True
        func()
        self.assertFalse(wrapped.called)
        c.acquire.assert_called_once_with('mylock')

        # Finally, if the lock is not granted, and the guard returns True,
        # and the lock acquired immediately, the function is called.
        c.acquire.return_value = True
        func(sentinel.arg)
        wrapped.assert_called_once_with(sentinel.arg)
示例#17
0
    def test_acquire(self):
        c = coordinator.BaseCoordinator()
        lock = 'mylock'
        c.grants = {}
        c.requests = {hookenv.local_unit(): {}}

        # We are not the leader, so first acquire will return False.
        self.assertFalse(c.acquire(lock))

        # But the request is in the queue.
        self.assertTrue(c.requested(lock))
        ts = c.request_timestamp(lock)

        # A further attempts at acquiring the lock do nothing,
        # and the timestamp of the request remains unchanged.
        self.assertFalse(c.acquire(lock))
        self.assertEqual(ts, c.request_timestamp(lock))

        # Once the leader has granted the lock, acquire returns True.
        with patch.object(c, 'granted') as granted:
            granted.return_value = True
            self.assertTrue(c.acquire(lock))
            granted.assert_called_once_with(lock)
示例#18
0
 def test_initialize_enforces_juju_version(self, has_juju_version):
     c = coordinator.BaseCoordinator()
     with self.assertRaises(AssertionError):
         c.initialize()
     has_juju_version.assert_called_once_with('1.23')
示例#19
0
 def test_implicit_initialize_and_handle(self, atstart):
     # When you construct a BaseCoordinator(), its initialize() and
     # handle() method are invoked automatically every hook. This
     # is done using hookenv.atstart
     c = coordinator.BaseCoordinator()
     atstart.assert_has_calls([call(c.initialize), call(c.handle)])
示例#20
0
 def test_handle_not_leader(self):
     c = coordinator.BaseCoordinator()
     # If we are not the leader, handle does nothing. We know this,
     # because without mocks or initialization it would otherwise crash.
     c.handle()
示例#21
0
 def test_local_state_filename(self):
     c = coordinator.BaseCoordinator()
     self.assertEqual(c._local_state_filename(),
                      '.charmhelpers.coordinator.BaseCoordinator')
示例#22
0
 def test_grant_not_leader(self):
     c = coordinator.BaseCoordinator()
     c.grant(sentinel.whatever, sentinel.whatever)  # Nothing happens.
示例#23
0
 def test_released(self):
     c = coordinator.BaseCoordinator()
     with patch.object(c, 'msg') as msg:
         c.released('foo/2', 'mylock', coordinator._utcnow())
         expected = 'Leader released mylock from foo/2, held 0:01:00'
         msg.assert_called_once_with(expected)
示例#24
0
 def test_msg(self):
     c = coordinator.BaseCoordinator()
     # Just a wrapper around hookenv.log
     c.msg('hi')
     hookenv.log.assert_called_once_with('coordinator.BaseCoordinator hi',
                                         level=hookenv.INFO)