class CharmState(object): """State of a charm registered in an environment.""" def __init__(self, client, charm_id, charm_data): self._client = client self._charm_url = CharmURL.parse(charm_id) self._charm_url.assert_revision() self._metadata = MetaData() self._metadata.parse_serialization_data(charm_data["metadata"]) self._config = ConfigOptions() self._config.parse(charm_data["config"]) # Just a health check: assert self._metadata.name == self.name self._sha256 = charm_data["sha256"] self._bundle_url = charm_data.get("url") @property def name(self): """The charm name.""" return self._charm_url.name @property def revision(self): """The monotonically increasing charm revision number. """ return self._charm_url.revision @property def bundle_url(self): """The url to the charm bundle in the provider storage.""" return self._bundle_url @property def id(self): """The charm id""" return str(self._charm_url) def get_metadata(self): """Return deferred MetaData.""" return succeed(self._metadata) def get_config(self): """Return deferred ConfigOptions.""" return succeed(self._config) def get_sha256(self): """Return deferred sha256 for the charm.""" return succeed(self._sha256) def is_subordinate(self): """Is this a subordinate charm.""" return self._metadata.is_subordinate
def test_metadata_fallback(self): metadata = MetaData() self.assertEquals(get_revision(None, metadata, None), None) metadata.parse( yaml.dump({ "name": "x", "summary": "y", "description": "z", "revision": 33 })) self.assertEquals(get_revision(None, metadata, None), 33)
class CharmState(object): """State of a charm registered in an environment.""" def __init__(self, client, charm_id, charm_data): self._client = client self._charm_url = CharmURL.parse(charm_id) self._charm_url.assert_revision() self._metadata = MetaData() self._metadata.parse_serialization_data(charm_data["metadata"]) self._config = ConfigOptions() self._config.parse(charm_data["config"]) # Just a health check: assert self._metadata.name == self.name self._sha256 = charm_data["sha256"] self._bundle_url = charm_data.get("url") @property def name(self): """The charm name.""" return self._charm_url.name @property def revision(self): """The monotonically increasing charm revision number. """ return self._charm_url.revision @property def bundle_url(self): """The url to the charm bundle in the provider storage.""" return self._bundle_url @property def id(self): """The charm id""" return str(self._charm_url) def get_metadata(self): """Return deferred MetaData.""" return succeed(self._metadata) def get_config(self): """Return deferred ConfigOptions.""" return succeed(self._config) def get_sha256(self): """Return deferred sha256 for the charm.""" return succeed(self._sha256)
class CharmBundle(CharmBase): """ZIP-archive that contains charm directory content.""" def __init__(self, path): self.path = isinstance(path, file) and path.name or path try: zf = ZipFile(path, 'r') except BadZipfile, exc: raise CharmError(path, "must be a zip file (%s)" % exc) if "metadata.yaml" not in zf.namelist(): raise CharmError( path, "charm does not contain required file 'metadata.yaml'") self.metadata = MetaData() self.metadata.parse(zf.read("metadata.yaml")) try: revision_content = zf.read("revision") except KeyError: revision_content = None self._revision = get_revision( revision_content, self.metadata, self.path) if self._revision is None: raise CharmError(self.path, "has no revision") self.config = ConfigOptions() if "config.yaml" in zf.namelist(): self.config.parse(zf.read("config.yaml"))
def get_metadata(self, charm_name): """Get the associated metadata for a given charm, eg ``wordpress``""" metadata = MetaData( os.path.join(test_repository_path, "series", charm_name, "metadata.yaml")) self.assertEqual(metadata.name, charm_name) return metadata
def __init__(self, client, charm_id, charm_data): self._client = client self._charm_url = CharmURL.parse(charm_id) self._charm_url.assert_revision() self._metadata = MetaData() self._metadata.parse_serialization_data(charm_data["metadata"]) self._config = ConfigOptions() self._config.parse(charm_data["config"]) # Just a health check: assert self._metadata.name == self.name self._sha256 = charm_data["sha256"] self._bundle_url = charm_data.get("url")
def __init__(self, path): self.path = path self.metadata = MetaData(os.path.join(path, "metadata.yaml")) revision_content = None revision_path = os.path.join(self.path, "revision") if os.path.exists(revision_path): with open(revision_path) as f: revision_content = f.read() self._revision = get_revision(revision_content, self.metadata, self.path) if self._revision is None: self.set_revision(0) elif revision_content is None: self.set_revision(self._revision) self.config = ConfigOptions() self.config.load(os.path.join(path, "config.yaml")) self._temp_bundle = None self._temp_bundle_file = None
def test_path_argument_loads_charm_info(self): info = MetaData(sample_path) self.assertEquals(info.name, "dummy")
def setUp(self): self.metadata = MetaData() self.sample = sample_configuration
class MetaDataTest(TestCase): def setUp(self): self.metadata = MetaData() self.sample = sample_configuration def change_sample(self): """Return a context manager for hacking the sample data. This should be used follows: with self.change_sample() as data: data["some-key"] = "some-data" The changed sample file content will be available in self.sample once the context is done executing. """ class HackManager(object): def __enter__(mgr): mgr.data = yaml.load(self.sample) return mgr.data def __exit__(mgr, exc_type, exc_val, exc_tb): self.sample = yaml.dump(mgr.data) return False return HackManager() def test_path_argument_loads_charm_info(self): info = MetaData(sample_path) self.assertEquals(info.name, "dummy") def test_check_basic_info_before_loading(self): """ Attributes should be set to None before anything is loaded. """ self.assertEquals(self.metadata.name, None) self.assertEquals(self.metadata.obsolete_revision, None) self.assertEquals(self.metadata.summary, None) self.assertEquals(self.metadata.description, None) def test_parse_and_check_basic_info(self): """ Parsing the content file should work. :-) Basic information will be available as attributes of the info file. """ self.metadata.parse(self.sample) self.assertEquals(self.metadata.name, "dummy") self.assertEquals(self.metadata.obsolete_revision, None) self.assertEquals(self.metadata.summary, u"That's a dummy charm.") self.assertEquals( self.metadata.description, u"This is a longer description which\n" u"potentially contains multiple lines.\n") def assert_parse_with_revision(self, with_path): """ Parsing the content file should work. :-) Basic information will be available as attributes of the info file. """ with self.change_sample() as data: data["revision"] = 123 log = self.capture_logging("juju.charm") self.metadata.parse(self.sample, "some/path" if with_path else None) if with_path: self.assertIn( "some/path: revision field is obsolete. Move it to the " "'revision' file.", log.getvalue()) self.assertEquals(self.metadata.name, "dummy") self.assertEquals(self.metadata.obsolete_revision, 123) self.assertEquals(self.metadata.summary, u"That's a dummy charm.") self.assertEquals( self.metadata.description, u"This is a longer description which\n" u"potentially contains multiple lines.\n") self.assertEquals(self.metadata.get_serialization_data()["revision"], 123) def test_parse_with_revision(self): self.assert_parse_with_revision(True) self.assert_parse_with_revision(False) def test_load_calls_parse_calls_parse_serialzation_data(self): """ We'll break the rules a little bit here and test the implementation itself just so that we don't have to test *everything* twice. If load() calls parse() which calls parse_serialzation_data(), then whatever happens with parse_serialization_data(), happens with the others. """ serialization_data = {"Hi": "there!"} yaml_data = yaml.dump(serialization_data) path = self.makeFile(yaml_data) mock = self.mocker.patch(self.metadata) mock.parse(yaml_data, path) self.mocker.passthrough() mock.parse_serialization_data(serialization_data, path) self.mocker.replay() self.metadata.load(path) # Do your magic Mocker! def test_metadata_parse_error_includes_path_with_load(self): broken = ("""\ description: helo name: hi requires: {interface: zebra revision: 0 summary: hola""") path = self.makeFile() e = self.assertRaises(yaml.YAMLError, self.metadata.parse, broken, path) self.assertIn(path, str(e)) def test_schema_error_includes_path_with_load(self): """ When using load(), the exception message should mention the path name which was attempted. """ with self.change_sample() as data: data["revision"] = "1" filename = self.makeFile(self.sample) error = self.assertRaises(MetaDataError, self.metadata.load, filename) self.assertEquals( str(error), "Bad data in charm info: %s: revision: " "expected int, got '1'" % filename) def test_load_missing_file(self): """ When using load(), the exception message should mention the path name which was attempted. """ filename = self.makeFile() error = self.assertRaises(FileNotFound, self.metadata.load, filename) self.assertEquals(error.path, filename) def test_name_summary_and_description_are_utf8(self): """ Textual fields are decoded to unicode by the schema using UTF-8. """ value = u"áéíóú" str_value = value.encode("utf-8") with self.change_sample() as data: data["name"] = str_value data["summary"] = str_value data["description"] = str_value self.metadata.parse(self.sample) self.assertEquals(self.metadata.name, value) self.assertEquals(self.metadata.summary, value) self.assertEquals(self.metadata.description, value) def test_get_serialized_data(self): """ The get_serialization_data() function should return an object which may be passed to parse_serialization_data() to restore the state of the instance. """ self.metadata.parse(self.sample) serialization_data = self.metadata.get_serialization_data() self.assertEquals(serialization_data["name"], "dummy")
class MetaDataTest(TestCase): def setUp(self): self.metadata = MetaData() self.sample = sample_configuration def change_sample(self): """Return a context manager for hacking the sample data. This should be used follows: with self.change_sample() as data: data["some-key"] = "some-data" The changed sample file content will be available in self.sample once the context is done executing. """ class HackManager(object): def __enter__(mgr): mgr.data = yaml.load(self.sample) return mgr.data def __exit__(mgr, exc_type, exc_val, exc_tb): self.sample = yaml.dump(mgr.data) return False return HackManager() def test_path_argument_loads_charm_info(self): info = MetaData(sample_path) self.assertEquals(info.name, "dummy") def test_check_basic_info_before_loading(self): """ Attributes should be set to None before anything is loaded. """ self.assertEquals(self.metadata.name, None) self.assertEquals(self.metadata.obsolete_revision, None) self.assertEquals(self.metadata.summary, None) self.assertEquals(self.metadata.description, None) self.assertEquals(self.metadata.is_subordinate, False) def test_parse_and_check_basic_info(self): """ Parsing the content file should work. :-) Basic information will be available as attributes of the info file. """ self.metadata.parse(self.sample) self.assertEquals(self.metadata.name, "dummy") self.assertEquals(self.metadata.obsolete_revision, None) self.assertEquals(self.metadata.summary, u"That's a dummy charm.") self.assertEquals(self.metadata.description, u"This is a longer description which\n" u"potentially contains multiple lines.\n") self.assertEquals(self.metadata.is_subordinate, False) def test_is_subordinate(self): """Validate rules for detecting proper subordinate charms are working""" logging_path = os.path.join( test_repository_path, "series", "logging", "metadata.yaml") logging_configuration = open(logging_path).read() self.metadata.parse(logging_configuration) self.assertTrue(self.metadata.is_subordinate) def test_subordinate_without_container_relation(self): """Validate rules for detecting proper subordinate charms are working Case where no container relation is specified. """ with self.change_sample() as data: data["subordinate"] = True error = self.assertRaises(MetaDataError, self.metadata.parse, self.sample, "some/path") self.assertIn("some/path labeled subordinate but lacking scope:container `requires` relation", str(error)) def test_scope_constraint(self): """Verify the scope constrain is parsed properly.""" logging_path = os.path.join( test_repository_path, "series", "logging", "metadata.yaml") logging_configuration = open(logging_path).read() self.metadata.parse(logging_configuration) # Verify the scope settings self.assertEqual(self.metadata.provides[u"logging-client"]["scope"], "global") self.assertEqual(self.metadata.requires[u"logging-directory"]["scope"], "container") self.assertTrue(self.metadata.is_subordinate) def assert_parse_with_revision(self, with_path): """ Parsing the content file should work. :-) Basic information will be available as attributes of the info file. """ with self.change_sample() as data: data["revision"] = 123 log = self.capture_logging("juju.charm") self.metadata.parse(self.sample, "some/path" if with_path else None) if with_path: self.assertIn( "some/path: revision field is obsolete. Move it to the " "'revision' file.", log.getvalue()) self.assertEquals(self.metadata.name, "dummy") self.assertEquals(self.metadata.obsolete_revision, 123) self.assertEquals(self.metadata.summary, u"That's a dummy charm.") self.assertEquals(self.metadata.description, u"This is a longer description which\n" u"potentially contains multiple lines.\n") self.assertEquals( self.metadata.get_serialization_data()["revision"], 123) def test_parse_with_revision(self): self.assert_parse_with_revision(True) self.assert_parse_with_revision(False) def test_load_calls_parse_calls_parse_serialzation_data(self): """ We'll break the rules a little bit here and test the implementation itself just so that we don't have to test *everything* twice. If load() calls parse() which calls parse_serialzation_data(), then whatever happens with parse_serialization_data(), happens with the others. """ serialization_data = {"Hi": "there!"} yaml_data = yaml.dump(serialization_data) path = self.makeFile(yaml_data) mock = self.mocker.patch(self.metadata) mock.parse(yaml_data, path) self.mocker.passthrough() mock.parse_serialization_data(serialization_data, path) self.mocker.replay() self.metadata.load(path) # Do your magic Mocker! def test_metadata_parse_error_includes_path_with_load(self): broken = ("""\ description: helo name: hi requires: {interface: zebra revision: 0 summary: hola""") path = self.makeFile() e = self.assertRaises( yaml.YAMLError, self.metadata.parse, broken, path) self.assertIn(path, str(e)) def test_schema_error_includes_path_with_load(self): """ When using load(), the exception message should mention the path name which was attempted. """ with self.change_sample() as data: data["revision"] = "1" filename = self.makeFile(self.sample) error = self.assertRaises(MetaDataError, self.metadata.load, filename) self.assertEquals(str(error), "Bad data in charm info: %s: revision: " "expected int, got '1'" % filename) def test_load_missing_file(self): """ When using load(), the exception message should mention the path name which was attempted. """ filename = self.makeFile() error = self.assertRaises(FileNotFound, self.metadata.load, filename) self.assertEquals(error.path, filename) def test_name_summary_and_description_are_utf8(self): """ Textual fields are decoded to unicode by the schema using UTF-8. """ value = u"áéíóú" str_value = value.encode("utf-8") with self.change_sample() as data: data["name"] = str_value data["summary"] = str_value data["description"] = str_value self.metadata.parse(self.sample) self.assertEquals(self.metadata.name, value) self.assertEquals(self.metadata.summary, value) self.assertEquals(self.metadata.description, value) def test_get_serialized_data(self): """ The get_serialization_data() function should return an object which may be passed to parse_serialization_data() to restore the state of the instance. """ self.metadata.parse(self.sample) serialization_data = self.metadata.get_serialization_data() self.assertEquals(serialization_data["name"], "dummy") def test_provide_implicit_relation(self): """Verify providing a juju-* reserved relation errors""" with self.change_sample() as data: data["provides"] = {"juju-foo": {"interface": "juju-magic", "scope": "container"}} # verify relation level error error = self.assertRaises(MetaDataError, self.metadata.parse, self.sample) self.assertIn("Charm dummy attempting to provide relation in implicit relation namespace: juju-foo", str(error)) # verify interface level error with self.change_sample() as data: data["provides"] = {"foo-rel": {"interface": "juju-magic", "scope": "container"}} error = self.assertRaises(MetaDataError, self.metadata.parse, self.sample) self.assertIn( "Charm dummy attempting to provide interface in implicit namespace: juju-magic (relation: foo-rel)", str(error))
def test_metadata_fallback(self): metadata = MetaData() self.assertEquals(get_revision(None, metadata, None), None) metadata.parse(yaml.dump({ "name": "x", "summary": "y", "description": "z","revision": 33})) self.assertEquals(get_revision(None, metadata, None), 33)