Beispiel #1
0
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
Beispiel #2
0
 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)
Beispiel #3
0
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)
Beispiel #4
0
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"))
Beispiel #5
0
 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
Beispiel #6
0
    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")
Beispiel #7
0
    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")
Beispiel #8
0
    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
Beispiel #9
0
 def test_path_argument_loads_charm_info(self):
     info = MetaData(sample_path)
     self.assertEquals(info.name, "dummy")
Beispiel #10
0
 def setUp(self):
     self.metadata = MetaData()
     self.sample = sample_configuration
Beispiel #11
0
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")
Beispiel #12
0
 def setUp(self):
     self.metadata = MetaData()
     self.sample = sample_configuration
Beispiel #13
0
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))
Beispiel #14
0
 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)