def test_relation_data_del_key(self): fake_script(self, 'relation-ids', """[ "$1" = db1 ] && echo '["db1:4"]' || echo '[]'""") fake_script(self, 'relation-list', """[ "$2" = 4 ] && echo '["remoteapp1/0"]' || exit 2""") fake_script(self, 'relation-set', '''[ "$2" = 4 ] && exit 0 || exit 2''') fake_script( self, 'relation-get', """([ "$2" = 4 ] && [ "$4" = "myapp/0" ]) && echo '{"host": "bar"}' || exit 2""" ) rel_db1 = self.model.get_relation('db1') # Force memory cache to be loaded. self.assertIn('host', rel_db1.data[self.model.unit]) del rel_db1.data[self.model.unit]['host'] fake_script( self, 'relation-get', """([ "$2" = 4 ] && [ "$4" = "myapp/0" ]) && echo '{}' || exit 2""" ) self.assertNotIn('host', rel_db1.data[self.model.unit]) self.assertEqual( fake_script_calls(self), [['relation-ids', 'db1', '--format=json'], ['relation-list', '-r', '4', '--format=json'], [ 'relation-get', '-r', '4', '-', 'myapp/0', '--app=False', '--format=json' ], ['relation-set', '-r', '4', 'host=', '--app=False']])
def test_application_version_set_invalid(self): fake_script(self, 'application-version-set', 'exit 0') with self.assertRaises(TypeError): self.backend.application_version_set(2) with self.assertRaises(TypeError): self.backend.application_version_set() self.assertEqual(fake_script_calls(self), [])
def test_remote_app_status(self): fake_script(self, 'relation-ids', """[ "$1" = db1 ] && echo '["db1:4"]' || echo '[]'""") fake_script( self, 'relation-list', """[ "$2" = 4 ] && echo '["remoteapp1/0", "remoteapp1/1"]' || exit 2""" ) remoteapp1 = self.model.get_relation('db1').app # Remote application status is always unknown. self.assertIsInstance(remoteapp1.status, ops.model.UnknownStatus) test_statuses = ( ops.model.UnknownStatus(), ops.model.ActiveStatus(), ops.model.MaintenanceStatus('Upgrading software'), ops.model.BlockedStatus('Awaiting manual resolution'), ops.model.WaitingStatus('Awaiting related app updates'), ) for target_status in test_statuses: with self.assertRaises(RuntimeError): remoteapp1.status = target_status self.assertEqual(fake_script_calls(self, clear=True), [ ['relation-ids', 'db1', '--format=json'], ['relation-list', '-r', '4', '--format=json'], ])
def test_app_relation_data_modify_local_as_leader(self): fake_script(self, 'relation-ids', """[ "$1" = db1 ] && echo '["db1:4"]' || echo '[]'""") fake_script( self, 'relation-list', """[ "$2" = 4 ] && echo '["remoteapp1/0", "remoteapp1/1"]' || exit 2""" ) fake_script( self, 'relation-get', """[ "$2" = 4 ] && [ "$4" = myapp ] && echo '{"password": "******"}' || exit 2""" ) fake_script(self, 'relation-set', """[ "$2" = 4 ] && exit 0 || exit 2""") fake_script(self, 'is-leader', 'echo true') local_app = self.model.unit.app rel_db1 = self.model.get_relation('db1') self.assertEqual(rel_db1.data[local_app], {'password': '******'}) rel_db1.data[local_app]['password'] = '******' self.assertEqual(rel_db1.data[local_app]['password'], 'foo') self.assertEqual(fake_script_calls(self), [ ['relation-ids', 'db1', '--format=json'], ['relation-list', '-r', '4', '--format=json'], [ 'relation-get', '-r', '4', '-', 'myapp', '--app=True', '--format=json' ], ['is-leader', '--format=json'], ['relation-set', '-r', '4', 'password=foo', '--app=True'], ])
def test_action_log_error(self): fake_script(self, 'action-get', '') fake_script(self, 'action-log', f'echo fooerror >&2 ; exit 1') with self.assertRaises(ops.model.ModelError): self.backend.action_log('log-message') calls = [["action-log", "log-message"]] self.assertEqual(fake_script_calls(self, clear=True), calls)
def test_action_get_error(self): fake_script(self, 'action-get', '') fake_script(self, 'action-get', f'echo fooerror >&2 ; exit 1') with self.assertRaises(ops.model.ModelError): self.backend.action_get() calls = [['action-get', '--format=json']] self.assertEqual(fake_script_calls(self, clear=True), calls)
def test_action_set_error(self): fake_script(self, 'action-get', '') fake_script(self, 'action-set', f'echo fooerror >&2 ; exit 1') with self.assertRaises(ops.model.ModelError): self.backend.action_set({'foo': 'bar', 'dead': 'beef cafe'}) calls = [["action-set", "foo=bar", "dead=beef cafe"]] self.assertEqual(fake_script_calls(self, clear=True), calls)
def test_storage_tool_errors(self): test_cases = [( lambda: fake_script(self, 'storage-list', 'echo fooerror >&2 ; exit 1'), lambda: self.backend.storage_list('foobar'), ops.model.ModelError, [['storage-list', 'foobar', '--format=json']], ), ( lambda: fake_script(self, 'storage-get', 'echo fooerror >&2 ; exit 1'), lambda: self.backend.storage_get('foobar', 'someattr'), ops.model.ModelError, [['storage-get', '-s', 'foobar', 'someattr', '--format=json']], ), ( lambda: fake_script(self, 'storage-add', 'echo fooerror >&2 ; exit 1'), lambda: self.backend.storage_add('foobar', count=2), ops.model.ModelError, [['storage-add', 'foobar=2']], ), ( lambda: fake_script(self, 'storage-add', 'echo fooerror >&2 ; exit 1'), lambda: self.backend.storage_add('foobar', count=object), TypeError, [], ), ( lambda: fake_script(self, 'storage-add', 'echo fooerror >&2 ; exit 1'), lambda: self.backend.storage_add('foobar', count=True), TypeError, [], )] for do_fake, run, exception, calls in test_cases: do_fake() with self.assertRaises(exception): run() self.assertEqual(fake_script_calls(self, clear=True), calls)
def test_network_get_errors(self): err_no_endpoint = 'ERROR no network config found for binding "$2"' err_no_rel = 'ERROR invalid value "$3" for option -r: relation not found' test_cases = [ ( lambda: fake_script(self, 'network-get', f'echo {err_no_endpoint} >&2 ; exit 1'), lambda: self.backend.network_get("deadbeef"), ops.model.ModelError, [['network-get', 'deadbeef', '--format=json']], ), ( lambda: fake_script(self, 'network-get', f'echo {err_no_rel} >&2 ; exit 2'), lambda: self.backend.network_get("deadbeef", 3), ops.model.RelationNotFoundError, [['network-get', 'deadbeef', '-r', '3', '--format=json']], ) ] for do_fake, run, exception, calls in test_cases: do_fake() with self.assertRaises(exception): run() self.assertEqual(fake_script_calls(self, clear=True), calls)
def test_network_get(self): network_get_out = '''{ "bind-addresses": [ { "mac-address": "", "interface-name": "", "addresses": [ { "hostname": "", "value": "192.0.2.2", "cidr": "" } ] } ], "egress-subnets": [ "192.0.2.2/32" ], "ingress-addresses": [ "192.0.2.2" ] }''' fake_script( self, 'network-get', f'''[ "$1" = deadbeef ] && echo '{network_get_out}' || exit 1''') network_info = self.backend.network_get('deadbeef') self.assertEqual(network_info, json.loads(network_get_out)) self.assertEqual(fake_script_calls(self, clear=True), [['network-get', 'deadbeef', '--format=json']]) network_info = self.backend.network_get('deadbeef', 1) self.assertEqual(network_info, json.loads(network_get_out)) self.assertEqual( fake_script_calls(self, clear=True), [['network-get', 'deadbeef', '-r', '1', '--format=json']])
def test_set_app_status_non_leader_raises(self): fake_script(self, 'is-leader', 'echo false') with self.assertRaises(RuntimeError): self.model.app.status with self.assertRaises(RuntimeError): self.model.app.status = ops.model.ActiveStatus()
def test_action_get(self): fake_script(self, 'action-get', """echo '{"foo-name": "bar", "silent": false}'""") params = self.backend.action_get() self.assertEqual(params['foo-name'], 'bar') self.assertEqual(params['silent'], False) self.assertEqual(fake_script_calls(self), [['action-get', '--format=json']])
def test_binding_by_relation_name(self): fake_script(self, 'network-get', '''[ "$1" = db0 ] && echo '{}' || exit 1'''.format(self.network_get_out)) binding_name = 'db0' expected_calls = [['network-get', 'db0', '--format=json']] binding = self.model.get_binding(binding_name) self._check_binding_data(binding_name, binding) self.assertEqual(fake_script_calls(self, clear=True), expected_calls)
def test_is_leader(self): def check_remote_units(): fake_script(self, 'relation-ids', """[ "$1" = db1 ] && echo '["db1:4"]' || echo '[]'""") fake_script( self, 'relation-list', """[ "$2" = 4 ] && echo '["remoteapp1/0", "remoteapp1/1"]' || exit 2""" ) # Cannot determine leadership for remote units. for u in self.model.get_relation('db1').units: with self.assertRaises(RuntimeError): u.is_leader() fake_script(self, 'is-leader', 'echo true') self.assertTrue(self.model.unit.is_leader()) check_remote_units() # Create a new model and backend to drop a cached is-leader output. self.backend = ops.model.ModelBackend() meta = ops.charm.CharmMeta() meta.relations = { 'db0': RelationMeta('provides', 'db0', { 'interface': 'db0', 'scope': 'global' }), 'db1': RelationMeta('requires', 'db1', { 'interface': 'db1', 'scope': 'global' }), 'db2': RelationMeta('peers', 'db2', { 'interface': 'db2', 'scope': 'global' }), } self.model = ops.model.Model('myapp/0', meta, self.backend) fake_script(self, 'is-leader', 'echo false') self.assertFalse(self.model.unit.is_leader()) check_remote_units() self.assertEqual(fake_script_calls(self), [ ['is-leader', '--format=json'], ['relation-ids', 'db1', '--format=json'], ['relation-list', '-r', '4', '--format=json'], ['is-leader', '--format=json'], ['relation-ids', 'db1', '--format=json'], ['relation-list', '-r', '4', '--format=json'], ])
def test_get(self): fake_script(self, 'state-get', dedent(""" echo 'foo: "bar"' """)) backend = storage._JujuStorageBackend() value = backend.get('key') self.assertEqual(value, {'foo': 'bar'}) self.assertEqual(fake_script_calls(self, clear=True), [ ['state-get', 'key'], ])
def check_remote_units(): fake_script(self, 'relation-ids', """[ "$1" = db1 ] && echo '["db1:4"]' || echo '[]'""") fake_script( self, 'relation-list', """[ "$2" = 4 ] && echo '["remoteapp1/0", "remoteapp1/1"]' || exit 2""" ) # Cannot determine leadership for remote units. for u in self.model.get_relation('db1').units: with self.assertRaises(RuntimeError): u.is_leader()
def test_binding_by_relation(self): fake_script(self, 'network-get', '''[ "$1" = db0 ] && echo '{}' || exit 1'''.format(self.network_get_out)) binding_name = 'db0' expected_calls = [ ['relation-ids', 'db0', '--format=json'], # The two invocations below are due to the get_relation call. ['relation-list', '-r', '4', '--format=json'], ['network-get', 'db0', '-r', '4', '--format=json'], ] binding = self.model.get_binding(self.model.get_relation(binding_name)) self._check_binding_data(binding_name, binding) self.assertEqual(fake_script_calls(self, clear=True), expected_calls)
def test_config(self): fake_script(self, 'config-get', """echo '{"foo":"foo","bar":1,"qux":true}'""") self.assertEqual(self.model.config, { 'foo': 'foo', 'bar': 1, 'qux': True, }) with self.assertRaises(TypeError): # Confirm that we cannot modify config values. self.model.config['foo'] = 'bar' self.assertEqual(fake_script_calls(self), [['config-get', '--format=json']])
def test_relation_set_fail(self): fake_script(self, 'relation-ids', """[ "$1" = db2 ] && echo '["db2:5"]' || echo '[]'""") fake_script(self, 'relation-list', """[ "$2" = 5 ] && echo '["remoteapp1/0"]' || exit 2""") fake_script( self, 'relation-get', """([ "$2" = 5 ] && [ "$4" = "myapp/0" ]) && echo '{"host": "myapp-0"}' || exit 2""" ) fake_script(self, 'relation-set', 'exit 2') rel_db2 = self.model.relations['db2'][0] # Force memory cache to be loaded. self.assertIn('host', rel_db2.data[self.model.unit]) with self.assertRaises(ops.model.ModelError): rel_db2.data[self.model.unit]['host'] = 'bar' self.assertEqual(rel_db2.data[self.model.unit]['host'], 'myapp-0') with self.assertRaises(ops.model.ModelError): del rel_db2.data[self.model.unit]['host'] self.assertIn('host', rel_db2.data[self.model.unit]) self.assertEqual( fake_script_calls(self), [['relation-ids', 'db2', '--format=json'], ['relation-list', '-r', '5', '--format=json'], [ 'relation-get', '-r', '5', '-', 'myapp/0', '--app=False', '--format=json' ], ['relation-set', '-r', '5', 'host=bar', '--app=False'], ['relation-set', '-r', '5', 'host=', '--app=False']])
def test_status_is_app_forced_kwargs(self): fake_script(self, 'status-get', 'exit 1') fake_script(self, 'status-set', 'exit 1') test_cases = ( lambda: self.backend.status_get(False), lambda: self.backend.status_get(True), lambda: self.backend.status_set('active', '', False), lambda: self.backend.status_set('active', '', True), ) for case in test_cases: with self.assertRaises(TypeError): case()
def test_valid_metrics(self): fake_script(self, 'add-metric', 'exit 0') test_cases = [( OrderedDict([('foo', 42), ('b-ar', 4.5), ('ba_-z', 4.5), ('a', 1)]), OrderedDict([('de', 'ad'), ('be', 'ef_ -')]), [['add-metric', '--labels', 'de=ad,be=ef_ -', 'foo=42', 'b-ar=4.5', 'ba_-z=4.5', 'a=1']] ), ( OrderedDict([('foo1', 0), ('b2r', 4.5)]), OrderedDict([('d3', 'aд'), ('b33f', '3_ -')]), [['add-metric', '--labels', 'd3=aд,b33f=3_ -', 'foo1=0', 'b2r=4.5']], )] for metrics, labels, expected_calls in test_cases: self.backend.add_metrics(metrics, labels) self.assertEqual(fake_script_calls(self, clear=True), expected_calls)
def test_juju_log(self): fake_script(self, 'juju-log', 'exit 0') self.backend.juju_log('WARNING', 'foo') self.assertEqual(fake_script_calls(self, clear=True), [['juju-log', '--log-level', 'WARNING', 'foo']]) with self.assertRaises(TypeError): self.backend.juju_log('DEBUG') self.assertEqual(fake_script_calls(self, clear=True), []) fake_script(self, 'juju-log', 'exit 1') with self.assertRaises(ops.model.ModelError): self.backend.juju_log('BAR', 'foo') self.assertEqual(fake_script_calls(self, clear=True), [['juju-log', '--log-level', 'BAR', 'foo']])
def test_remote_units_is_our(self): fake_script(self, 'relation-ids', """[ "$1" = db1 ] && echo '["db1:4"]' || echo '[]'""") fake_script( self, 'relation-list', """[ "$2" = 4 ] && echo '["remoteapp1/0", "remoteapp1/1"]' || exit 2""" ) for u in self.model.get_relation('db1').units: self.assertFalse(u._is_our_unit) self.assertFalse(u.app._is_our_app) self.assertEqual(fake_script_calls(self), [['relation-ids', 'db1', '--format=json'], ['relation-list', '-r', '4', '--format=json']])
def test_resources(self): meta = ops.charm.CharmMeta() meta.resources = {'foo': None, 'bar': None} model = ops.model.Model('myapp/0', meta, self.backend) with self.assertRaises(RuntimeError): model.resources.fetch('qux') fake_script(self, 'resource-get', 'exit 1') with self.assertRaises(ops.model.ModelError): model.resources.fetch('foo') fake_script( self, 'resource-get', 'echo /var/lib/juju/agents/unit-test-0/resources/$1/$1.tgz') self.assertEqual(model.resources.fetch('foo').name, 'foo.tgz') self.assertEqual(model.resources.fetch('bar').name, 'bar.tgz')
def test_resources(self): # TODO: (jam) 2020-05-07 Harness doesn't yet support resource-get issue #262 meta = ops.charm.CharmMeta() meta.resources = {'foo': None, 'bar': None} model = ops.model.Model('myapp/0', meta, ops.model._ModelBackend()) with self.assertRaises(RuntimeError): model.resources.fetch('qux') fake_script(self, 'resource-get', 'exit 1') with self.assertRaises(ops.model.ModelError): model.resources.fetch('foo') fake_script(self, 'resource-get', 'echo /var/lib/juju/agents/unit-test-0/resources/$1/$1.tgz') self.assertEqual(model.resources.fetch('foo').name, 'foo.tgz') self.assertEqual(model.resources.fetch('bar').name, 'bar.tgz')
def test_dead_relations(self): fake_script( self, 'network-get', ''' if [ "$1" = db0 ] && [ "$2" = --format=json ]; then echo '{}' else echo ERROR invalid value "$2" for option -r: relation not found >&2 exit 2 fi '''.format(self.network_get_out)) # Validate the behavior for dead relations. binding = ops.model.Binding('db0', 42, self.model._backend) self.assertEqual(binding.network.bind_address, ipaddress.ip_address('192.0.2.2')) self.assertEqual(fake_script_calls(self, clear=True), [ ['network-get', 'db0', '-r', '42', '--format=json'], ['network-get', 'db0', '--format=json'], ])
def test_local_set_invalid_status(self): fake_script(self, 'status-set', 'exit 1') fake_script(self, 'is-leader', 'echo true') with self.assertRaises(ops.model.ModelError): self.model.unit.status = ops.model.UnknownStatus() self.assertEqual(fake_script_calls(self, True), [ ['status-set', '--application=False', 'unknown', ''], ]) with self.assertRaises(ops.model.ModelError): self.model.app.status = ops.model.UnknownStatus() # A leadership check is needed for application status. self.assertEqual(fake_script_calls(self, True), [ ['is-leader', '--format=json'], ['status-set', '--application=True', 'unknown', ''], ])
def test_relations_keys(self): fake_script( self, 'relation-ids', """[ "$1" = db2 ] && echo '["db2:5", "db2:6"]' || echo '[]'""") fake_script( self, 'relation-list', """([ "$2" = 5 ] && echo '["remoteapp1/0", "remoteapp1/1"]') || ([ "$2" = 6 ] && echo '["remoteapp2/0"]') || exit 2""" ) for relation in self.model.relations['db2']: self.assertIn(self.model.unit, relation.data) unit_from_rel = next( filter(lambda u: u.name == 'myapp/0', relation.data.keys())) self.assertIs(self.model.unit, unit_from_rel) self.assertEqual(fake_script_calls(self), [['relation-ids', 'db2', '--format=json'], ['relation-list', '-r', '5', '--format=json'], ['relation-list', '-r', '6', '--format=json']])
def test_set_encodes_args(self): t = tempfile.NamedTemporaryFile() fake_script( self, 'state-set', dedent(""" cat >> {} """).format(pathlib.Path(t.name).as_posix())) backend = storage._JujuStorageBackend() backend.set('key', {'foo': 2}) self.assertEqual(fake_script_calls(self, clear=True), [ ['state-set', '--file', '-'], ]) t.seek(0) content = t.read() self.assertEqual( content.decode('utf-8'), dedent("""\ "key": | {foo: 2} """))
def test_local_set_valid_unit_status(self): test_cases = [ ( ops.model.ActiveStatus(), lambda: fake_script(self, 'status-set', 'exit 0'), lambda: self.assertEqual(fake_script_calls(self, True), [[ 'status-set', '--application=False', 'active', '' ]]), ), ( ops.model.MaintenanceStatus('Yellow'), lambda: fake_script(self, 'status-set', 'exit 0'), lambda: self.assertEqual(fake_script_calls(self, True), [[ 'status-set', '--application=False', 'maintenance', 'Yellow' ]]), ), ( ops.model.BlockedStatus('Red'), lambda: fake_script(self, 'status-set', 'exit 0'), lambda: self.assertEqual(fake_script_calls(self, True), [[ 'status-set', '--application=False', 'blocked', 'Red' ]]), ), ( ops.model.WaitingStatus('White'), lambda: fake_script(self, 'status-set', 'exit 0'), lambda: self.assertEqual(fake_script_calls(self, True), [[ 'status-set', '--application=False', 'waiting', 'White' ]]), ) ] for target_status, setup_tools, check_tool_calls in test_cases: setup_tools() self.model.unit.status = target_status self.assertEqual(self.model.unit.status, target_status) check_tool_calls()