def _read_and_clear_state(self): if self.CHARM_STATE_FILE.stat().st_size: storage = SQLiteStorage(self.CHARM_STATE_FILE) with (self.JUJU_CHARM_DIR / 'metadata.yaml').open() as m: af = (self.JUJU_CHARM_DIR / 'actions.yaml') if af.exists(): with af.open() as a: meta = CharmMeta.from_yaml(m, a) else: meta = CharmMeta.from_yaml(m) framework = Framework(storage, self.JUJU_CHARM_DIR, meta, None) class ThisCharmEvents(CharmEvents): pass class Charm(self.charm_module.Charm): on = ThisCharmEvents() mycharm = Charm(framework) stored = mycharm._stored # Override the saved data with a cleared state storage.save_snapshot(stored._data.handle.path, {}) storage.commit() framework.close() else: stored = StoredStateData(None, None) return stored
def test_relation_events(self): class MyCharm(CharmBase): def __init__(self, *args): super().__init__(*args) self.seen = [] for rel in ('req1', 'req-2', 'pro1', 'pro-2', 'peer1', 'peer-2'): # Hook up relation events to generic handler. self.framework.observe(self.on[rel].relation_joined, self.on_any_relation) self.framework.observe(self.on[rel].relation_changed, self.on_any_relation) self.framework.observe(self.on[rel].relation_departed, self.on_any_relation) self.framework.observe(self.on[rel].relation_broken, self.on_any_relation) def on_any_relation(self, event): assert event.relation.name == 'req1' assert event.relation.app.name == 'remote' self.seen.append(type(event).__name__) # language=YAML self.meta = CharmMeta.from_yaml(metadata=''' name: my-charm requires: req1: interface: req1 req-2: interface: req2 provides: pro1: interface: pro1 pro-2: interface: pro2 peers: peer1: interface: peer1 peer-2: interface: peer2 ''') charm = MyCharm(self.create_framework(), None) rel = charm.framework.model.get_relation('req1', 1) unit = charm.framework.model.get_unit('remote/0') charm.on['req1'].relation_joined.emit(rel, unit) charm.on['req1'].relation_changed.emit(rel, unit) charm.on['req-2'].relation_changed.emit(rel, unit) charm.on['pro1'].relation_departed.emit(rel, unit) charm.on['pro-2'].relation_departed.emit(rel, unit) charm.on['peer1'].relation_broken.emit(rel) charm.on['peer-2'].relation_broken.emit(rel) self.assertEqual(charm.seen, [ 'RelationJoinedEvent', 'RelationChangedEvent', 'RelationChangedEvent', 'RelationDepartedEvent', 'RelationDepartedEvent', 'RelationBrokenEvent', 'RelationBrokenEvent', ])
def test_not_if_already_local(self): meta = CharmMeta.from_yaml("series: [kubernetes]") with patch.dict( os.environ, {"JUJU_VERSION": "2.8"}), tempfile.NamedTemporaryFile() as fd: self.assertFalse( _should_use_controller_storage(Path(fd.name), meta)) self.assertLogged('Using local storage: {} already exists'.format( fd.name))
def test_relations_meta_limit_type_validation(self): with self.assertRaisesRegex(TypeError, "limit should be an int, not <class 'str'>"): # language=YAML self.meta = CharmMeta.from_yaml(''' name: my-charm requires: database: interface: mongodb limit: foobar ''')
def test_relations_meta_scope_type_validation(self): with self.assertRaisesRegex(TypeError, "scope should be one of 'global', 'container'; not 'foobar'"): # language=YAML self.meta = CharmMeta.from_yaml(''' name: my-charm requires: database: interface: mongodb scope: foobar ''')
def test_containers(self): meta = CharmMeta.from_yaml(""" name: k8s-charm containers: test1: k: v test2: k: v """) self.assertIsInstance(meta.containers['test1'], ContainerMeta) self.assertIsInstance(meta.containers['test2'], ContainerMeta) self.assertEqual(meta.containers['test1'].name, 'test1') self.assertEqual(meta.containers['test2'].name, 'test2')
def _get_action_test_meta(cls): # language=YAML return CharmMeta.from_yaml(metadata=''' name: my-charm ''', actions=''' foo-bar: description: "Foos the bar." params: foo-name: description: "A foo name to bar" type: string silent: default: false description: "" type: boolean required: foo-bar title: foo-bar start: description: "Start the unit." ''')
def test_relations_meta(self): # language=YAML self.meta = CharmMeta.from_yaml(''' name: my-charm requires: database: interface: mongodb limit: 1 scope: container metrics: interface: prometheus-scraping ''') self.assertEqual(self.meta.requires['database'].interface_name, 'mongodb') self.assertEqual(self.meta.requires['database'].limit, 1) self.assertEqual(self.meta.requires['database'].scope, 'container') self.assertEqual(self.meta.requires['metrics'].interface_name, 'prometheus-scraping') self.assertIsNone(self.meta.requires['metrics'].limit) self.assertEqual(self.meta.requires['metrics'].scope, 'global') # Default value
def test_workload_events(self): class MyCharm(CharmBase): def __init__(self, *args): super().__init__(*args) self.seen = [] self.count = 0 for workload in ('container-a', 'containerb'): # Hook up relation events to generic handler. self.framework.observe( self.on[workload].pebble_ready, self.on_any_pebble_ready) def on_any_pebble_ready(self, event): self.seen.append(type(event).__name__) self.count += 1 # language=YAML self.meta = CharmMeta.from_yaml(metadata=''' name: my-charm containers: container-a: containerb: ''') charm = MyCharm(self.create_framework()) self.assertIn('container_a_pebble_ready', repr(charm.on)) self.assertIn('containerb_pebble_ready', repr(charm.on)) charm.on['container-a'].pebble_ready.emit( charm.framework.model.unit.get_container('container-a')) charm.on['containerb'].pebble_ready.emit( charm.framework.model.unit.get_container('containerb')) self.assertEqual(charm.seen, [ 'PebbleReadyEvent', 'PebbleReadyEvent' ]) self.assertEqual(charm.count, 2)
def test_containers_storage(self): meta = CharmMeta.from_yaml(""" name: k8s-charm storage: data: type: filesystem location: /test/storage other: type: filesystem location: /test/other containers: test1: mounts: - storage: data location: /test/storagemount - storage: other location: /test/otherdata """) self.assertIsInstance(meta.containers['test1'], ContainerMeta) self.assertIsInstance(meta.containers['test1'].mounts["data"], ContainerStorageMeta) self.assertEqual(meta.containers['test1'].mounts["data"].location, '/test/storagemount') self.assertEqual(meta.containers['test1'].mounts["other"].location, '/test/otherdata')
def test_containers_storage_multiple_mounts(self): meta = CharmMeta.from_yaml(""" name: k8s-charm storage: data: type: filesystem location: /test/storage containers: test1: mounts: - storage: data location: /test/storagemount - storage: data location: /test/otherdata """) self.assertIsInstance(meta.containers['test1'], ContainerMeta) self.assertIsInstance(meta.containers['test1'].mounts["data"], ContainerStorageMeta) self.assertEqual( meta.containers['test1'].mounts["data"].locations[0], '/test/storagemount') self.assertEqual(meta.containers['test1'].mounts["data"].locations[1], '/test/otherdata') with self.assertRaises(RuntimeError): meta.containers["test1"].mounts["data"].location
def test_not_if_not_in_k8s(self): meta = CharmMeta.from_yaml("series: [ecs]") with patch.dict(os.environ, {"JUJU_VERSION": "2.8"}): self.assertFalse(_should_use_controller_storage(Path("/xyzzy"), meta)) self.assertLogged('Using local storage: not a kubernetes charm')
def test_fallback_to_current_juju_version__new_enough(self): meta = CharmMeta.from_yaml("series: [kubernetes]") with patch.dict(os.environ, {"JUJU_VERSION": "2.8"}): self.assertTrue(_should_use_controller_storage(Path("/xyzzy"), meta)) self.assertLogged('Using controller storage: JUJU_VERSION=2.8.0')
def test_empty_action(self): meta = CharmMeta.from_yaml('name: my-charm', '') self.assertEqual(meta.actions, {})
def test_fallback_to_current_juju_version__too_old(self): meta = CharmMeta.from_yaml("series: [kubernetes]") with patch.dict(os.environ, {"JUJU_VERSION": "1.0"}): self.assertFalse(_should_use_controller_storage(Path("/xyzzy"), meta)) self.assertLogged('Using local storage: JUJU_VERSION=1.0.0')
def test_storage_events(self): class MyCharm(CharmBase): def __init__(self, *args): super().__init__(*args) self.seen = [] self.framework.observe(self.on['stor1'].storage_attached, self._on_stor1_attach) self.framework.observe(self.on['stor2'].storage_detaching, self._on_stor2_detach) self.framework.observe(self.on['stor3'].storage_attached, self._on_stor3_attach) self.framework.observe(self.on['stor-4'].storage_attached, self._on_stor4_attach) def _on_stor1_attach(self, event): self.seen.append(type(event).__name__) def _on_stor2_detach(self, event): self.seen.append(type(event).__name__) def _on_stor3_attach(self, event): self.seen.append(type(event).__name__) def _on_stor4_attach(self, event): self.seen.append(type(event).__name__) # language=YAML self.meta = CharmMeta.from_yaml(''' name: my-charm storage: stor-4: multiple: range: 2-4 type: filesystem stor1: type: filesystem stor2: multiple: range: "2" type: filesystem stor3: multiple: range: 2- type: filesystem ''') self.assertIsNone(self.meta.storages['stor1'].multiple_range) self.assertEqual(self.meta.storages['stor2'].multiple_range, (2, 2)) self.assertEqual(self.meta.storages['stor3'].multiple_range, (2, None)) self.assertEqual(self.meta.storages['stor-4'].multiple_range, (2, 4)) charm = MyCharm(self.create_framework()) charm.on['stor1'].storage_attached.emit() charm.on['stor2'].storage_detaching.emit() charm.on['stor3'].storage_attached.emit() charm.on['stor-4'].storage_attached.emit() self.assertEqual(charm.seen, [ 'StorageAttachedEvent', 'StorageDetachingEvent', 'StorageAttachedEvent', 'StorageAttachedEvent', ])
def test_storage_events(self): this = self class MyCharm(CharmBase): def __init__(self, *args): super().__init__(*args) self.seen = [] self.framework.observe(self.on['stor1'].storage_attached, self._on_stor1_attach) self.framework.observe(self.on['stor2'].storage_detaching, self._on_stor2_detach) self.framework.observe(self.on['stor3'].storage_attached, self._on_stor3_attach) self.framework.observe(self.on['stor-4'].storage_attached, self._on_stor4_attach) def _on_stor1_attach(self, event): self.seen.append(type(event).__name__) this.assertEqual(event.storage.location, Path("/var/srv/stor1/0")) def _on_stor2_detach(self, event): self.seen.append(type(event).__name__) def _on_stor3_attach(self, event): self.seen.append(type(event).__name__) def _on_stor4_attach(self, event): self.seen.append(type(event).__name__) # language=YAML self.meta = CharmMeta.from_yaml(''' name: my-charm storage: stor-4: multiple: range: 2-4 type: filesystem stor1: type: filesystem stor2: multiple: range: "2" type: filesystem stor3: multiple: range: 2- type: filesystem stor-multiple-dashes: multiple: range: 2- type: filesystem ''') fake_script( self, "storage-get", """ if [ "$1" = "-s" ]; then id=${2#*/} key=${2%/*} echo "\\"/var/srv/${key}/${id}\\"" # NOQA: test_quote_backslashes elif [ "$1" = '--help' ]; then printf '%s\\n' \\ 'Usage: storage-get [options] [<key>]' \\ ' ' \\ 'Summary:' \\ 'print information for storage instance with specified id' \\ ' ' \\ 'Options:' \\ '--format (= smart)' \\ ' Specify output format (json|smart|yaml)' \\ '-o, --output (= "")' \\ ' Specify an output file' \\ '-s (= test-stor/0)' \\ ' specify a storage instance by id' \\ ' ' \\ 'Details:' \\ 'When no <key> is supplied, all keys values are printed.' else # Return the same path for all disks since `storage-get` # on attach and detach takes no parameters and is not # deterministically faked with fake_script exit 1 fi """, ) fake_script( self, "storage-list", """ echo '["disks/0"]' """, ) self.assertIsNone(self.meta.storages['stor1'].multiple_range) self.assertEqual(self.meta.storages['stor2'].multiple_range, (2, 2)) self.assertEqual(self.meta.storages['stor3'].multiple_range, (2, None)) self.assertEqual(self.meta.storages['stor-4'].multiple_range, (2, 4)) charm = MyCharm(self.create_framework()) charm.on['stor1'].storage_attached.emit(Storage("stor1", 0, charm.model._backend)) charm.on['stor2'].storage_detaching.emit(Storage("stor2", 0, charm.model._backend)) charm.on['stor3'].storage_attached.emit(Storage("stor3", 0, charm.model._backend)) charm.on['stor-4'].storage_attached.emit(Storage("stor-4", 0, charm.model._backend)) charm.on['stor-multiple-dashes'].storage_attached.emit( Storage("stor-multiple-dashes", 0, charm.model._backend)) self.assertEqual(charm.seen, [ 'StorageAttachedEvent', 'StorageDetachingEvent', 'StorageAttachedEvent', 'StorageAttachedEvent', ])