Merge lp:~fwereade/pyjuju/isolate-formula-revisions into lp:pyjuju

Proposed by William Reade
Status: Merged
Approved by: Kapil Thangavelu
Approved revision: 386
Merged at revision: 389
Proposed branch: lp:~fwereade/pyjuju/isolate-formula-revisions
Merge into: lp:pyjuju
Prerequisite: lp:~fwereade/pyjuju/check-latest-formulas
Diff against target: 1114 lines (+346/-144)
43 files modified
examples/oneiric/mysql/metadata.yaml (+0/-1)
examples/oneiric/mysql/revision (+1/-0)
examples/oneiric/php/metadata.yaml (+0/-1)
examples/oneiric/php/revision (+1/-0)
examples/oneiric/wordpress/metadata.yaml (+0/-1)
examples/oneiric/wordpress/revision (+1/-0)
juju/agents/tests/test_machine.py (+1/-1)
juju/agents/tests/test_unit.py (+2/-2)
juju/charm/base.py (+31/-0)
juju/charm/bundle.py (+19/-16)
juju/charm/directory.py (+23/-4)
juju/charm/metadata.py (+13/-6)
juju/charm/repository.py (+5/-5)
juju/charm/tests/__init__.py (+1/-1)
juju/charm/tests/repository/series/dummy/metadata.yaml (+0/-1)
juju/charm/tests/repository/series/dummy/revision (+1/-0)
juju/charm/tests/repository/series/mysql-alternative/metadata.yaml (+0/-1)
juju/charm/tests/repository/series/mysql-alternative/revision (+1/-0)
juju/charm/tests/repository/series/mysql/metadata.yaml (+0/-1)
juju/charm/tests/repository/series/mysql/revision (+1/-0)
juju/charm/tests/repository/series/new/metadata.yaml (+0/-1)
juju/charm/tests/repository/series/new/revision (+1/-0)
juju/charm/tests/repository/series/old/metadata.yaml (+0/-1)
juju/charm/tests/repository/series/old/revision (+1/-0)
juju/charm/tests/repository/series/riak/metadata.yaml (+0/-1)
juju/charm/tests/repository/series/riak/revision (+1/-0)
juju/charm/tests/repository/series/varnish-alternative/metadata.yaml (+0/-1)
juju/charm/tests/repository/series/varnish-alternative/revision (+1/-0)
juju/charm/tests/repository/series/varnish/metadata.yaml (+0/-1)
juju/charm/tests/repository/series/varnish/revision (+1/-0)
juju/charm/tests/repository/series/wordpress/metadata.yaml (+0/-1)
juju/charm/tests/repository/series/wordpress/revision (+1/-0)
juju/charm/tests/test_base.py (+42/-16)
juju/charm/tests/test_bundle.py (+60/-21)
juju/charm/tests/test_directory.py (+62/-4)
juju/charm/tests/test_metadata.py (+33/-19)
juju/charm/tests/test_repository.py (+31/-24)
juju/control/deploy.py (+1/-1)
juju/control/tests/test_upgrade_charm.py (+8/-8)
juju/machine/tests/test_unit_deployment.py (+1/-1)
juju/state/charm.py (+0/-1)
juju/state/tests/test_charm.py (+0/-1)
juju/unit/tests/test_charm.py (+1/-1)
To merge this branch: bzr merge lp:~fwereade/pyjuju/isolate-formula-revisions
Reviewer Review Type Date Requested Status
Kapil Thangavelu (community) Approve
Gustavo Niemeyer Approve
Review via email: mp+78164@code.launchpad.net

Description of the change

Notable points:

* CharmBundle.set_revision doesn't work: the use cases for revision-bumping all seem to me to be targeted at the source-code form of a charm (ie a CharmDirectory).
* charms with revision still in metadata.yaml are log.warning()ed
* charms with revision in both places are log.warning()ed in the same way, and the standalone revision file (if valid) is treated as the single point of truth
* charms with no revision at all can't be initialised
* charm metadata prefers to get revision information by calling a function passed in at construction time, to accommodate actual Charms (whose revision could, in theory, change during the lifetime of a Charm object; the result should always be up to date), but will fall back to the value in _data if no getter is present (or sane)
* MetaData is responsible for the warning, which only triggers if we're going through .parse(); when we're storing charm state in ZK, we still need the revision (which is therefore included in get_serialization_data()'s result), and so we need to treat revision-in-yaml as normal and expected when we're calling parse_serialization_data().
* CharmDirectory will, upon initialization, create the appropriate revision file inside the charm's directory, so all the average charm author should have to do is delete one line from metadata.yaml; if they fail to do this, the apparent revision will be frozen even when they change metadata.yaml... BUT, as soon as local auto-updating lands (tomorrow morning(?)), this will be resolved, and they should be free from ever worrying about manually tracking revisions again

...phew. It's not that much code in the end, but I think it warrants fairly close and pedantic examination ;).

To post a comment you must log in.
Revision history for this message
Gustavo Niemeyer (niemeyer) wrote :

This is looking like a good start, but there is one important
conceptual issue we should probably address while we have time.

[1]

+ metadata = MetaData(get_revision_content=self._get_revision_content)
+ metadata.parse(zf.read("metadata.yaml"))
+ if metadata.revision is None:
+ raise CharmError(path, "has no revision")

Hmmm.. this is feeling a bit like a hack. Right now the concepts within
the charm are well separated in the API as well.. we have the config
with the contents of config.yaml and the metadata with the
contents of metadata.yaml. We're changing the location of the
revision information so that we separate logically the bits that are
human-editable from the bits that are automatically changed. If this
is a good idea, we should do this across the board, and take the
revision out of the metadata as well within the code base.

So, let's remove metadata.revision, and introduce something like a
metadata.obsolete_revision which is only accessed from the charm
implementation itself. Then, inside the charm implementation let's
introduce a method called get_revision() which either picks the
revision from the revision file or from the metadata.obsolete_revision
if the former is missing. Every other place that looks at
metadata.revision today should be changed to look at
charm.get_revision() instead, or to something contextually equivalent.
The storage and API in juju/state/charm.py will also have to change
accordingly, for instance.

This is certainly what we would have done if we started with the
revision separated in the first place, for instance.

Perhaps this will turn out to be too painful, but if we cannot do this
on time, I'd rather not change the revision location, otherwise we'll
be introducing a half-baked feature that looks like one thing in some
places and like something else in others, and forever have that hackish
taste when dealing with it.

[2]

+ def set_revision(self, revision):
+ with open(os.path.join(self.path, "revision"), "w") as f:
+ f.write(str(revision))

That's nice, and it shows the above point. set_revision is in the
charm, not in the metadata. That's where we need get_revision to
be as well.

[3]

+ try:
+ return int(self._get_revision_content())
+ except (ValueError, TypeError):
+ pass

If the file exists, and its content is invalid, we need to raise a
real error about the problem than pretending it doesn't exist.

[4]

+ if "revision" in self._data:
+ suffix = (" from %r" % path) if path else ""
+ log.warning(
+ "metadata file schema has changed: revision should be "
+ "removed" + suffix)

I suggest only logging the warning if the file path is known, as I
believe this will mean the user has a better chance of being able to
fix the problem rather than being just a consumer. Is that the case?

If so, I suggest something like this:

    if "revision" in self._data and path:
        log.warning(
            "%s: metadata.yaml: revision field is obsolete. "
            "Move it to the 'revision' file." % path)

review: Needs Fixing
382. By William Reade

merge trunk

Revision history for this message
William Reade (fwereade) wrote :

[1,2]

> Hmmm.. this is feeling a bit like a hack.

Guilty as charged, but I'd say it's quite well bounded.

> The storage and API in juju/state/charm.py will also have to change
> accordingly, for instance.

That's the core of the problem: that I thought I'd done enough damage in the last week or so, and that it would be important *not* to break charm-state storage and thus have to bump topology.VERSION. I'll go ahead with the nicer but more disruptive approach you propose in a stacked branch.

[3,4]

Good points; I'll fix them in this branch.

383. By William Reade

easy review points

Revision history for this message
William Reade (fwereade) wrote :

> That's the core of the problem: that I thought I'd done enough damage in the
> last week or so, and that it would be important *not* to break charm-state
> storage and thus have to bump topology.VERSION. I'll go ahead with the nicer
> but more disruptive approach you propose in a stacked branch.

But... just possibly... we don't actually need .revision on metadata *at all*, because CharmState takes if from a CharmURL. Stacking it anyway, just to be safe.

384. By William Reade

merge trunk

Revision history for this message
William Reade (fwereade) wrote :

Merged back from lp:~fwereade/juju/really-isolate-formula-revisions

MetaData no longer has .revision; just .obsolete_revision. Charms have .get_revision() to compensate, which uses metadata.obsolete_revision only when a real revision file is not present.

385. By William Reade

merge back from temporary really-isolate-formula-revision branch

Revision history for this message
Gustavo Niemeyer (niemeyer) wrote :

This looks fantastic. Just a few trivials, and let's try to get a +1 from Kapil.

[5]

+ revision = self.get_revision()
+ if revision is None:
+ raise CharmError(path, "has no revision")

Hmmm.. both implementations are doing this, so maybe the common
get_revision function itself could take care of it if neither
options have a valid revision.

[6]

+ revision = self.get_revision()
+ if revision is None:
+ raise CharmError(path, "has no revision")
+ if self._get_revision_file_content() is None:
+ self.set_revision(revision)

_get_revision_file_content is being called necessarily and at
least twice every time. Plus when the revision is actually
wanted outside of the implementation.

Maybe we should cache in a private attibute instead?

[7]

+ def set_revision(self, revision):
+ with open(os.path.join(self.path, "revision"), "w") as f:
+ f.write(str(revision))

A "\n" at the end would be nice to deal with it from the shell.

review: Approve
386. By William Reade

addressed review points

Revision history for this message
Kapil Thangavelu (hazmat) wrote :

Looks good to me

review: Approve
387. By William Reade

strip file contents before int()ing (explicit > implicit)

388. By William Reade

just read charm revisions once, inline in __init__

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'examples/oneiric/mysql/metadata.yaml'
--- examples/oneiric/mysql/metadata.yaml 2011-09-15 19:24:47 +0000
+++ examples/oneiric/mysql/metadata.yaml 2011-10-05 18:13:26 +0000
@@ -1,5 +1,4 @@
1name: mysql1name: mysql
2revision: 11
3summary: "MySQL relational database provider"2summary: "MySQL relational database provider"
4description: |3description: |
5 Installs and configures the MySQL package (mysqldb), then runs it.4 Installs and configures the MySQL package (mysqldb), then runs it.
65
=== added file 'examples/oneiric/mysql/revision'
--- examples/oneiric/mysql/revision 1970-01-01 00:00:00 +0000
+++ examples/oneiric/mysql/revision 2011-10-05 18:13:26 +0000
@@ -0,0 +1,1 @@
111
02
=== modified file 'examples/oneiric/php/metadata.yaml'
--- examples/oneiric/php/metadata.yaml 2011-09-15 19:24:47 +0000
+++ examples/oneiric/php/metadata.yaml 2011-10-05 18:13:26 +0000
@@ -1,5 +1,4 @@
1name: php1name: php
2revision: 5
3summary: "php container"2summary: "php container"
4description: |3description: |
5 PHP environment for your code.4 PHP environment for your code.
65
=== added file 'examples/oneiric/php/revision'
--- examples/oneiric/php/revision 1970-01-01 00:00:00 +0000
+++ examples/oneiric/php/revision 2011-10-05 18:13:26 +0000
@@ -0,0 +1,1 @@
15
02
=== modified file 'examples/oneiric/wordpress/metadata.yaml'
--- examples/oneiric/wordpress/metadata.yaml 2011-09-15 19:24:47 +0000
+++ examples/oneiric/wordpress/metadata.yaml 2011-10-05 18:13:26 +0000
@@ -1,5 +1,4 @@
1name: wordpress1name: wordpress
2revision: 31
3summary: "WordPress blog"2summary: "WordPress blog"
4description: |3description: |
5 Installs WordPress package (wordpress). Upon the database provider4 Installs WordPress package (wordpress). Upon the database provider
65
=== added file 'examples/oneiric/wordpress/revision'
--- examples/oneiric/wordpress/revision 1970-01-01 00:00:00 +0000
+++ examples/oneiric/wordpress/revision 2011-10-05 18:13:26 +0000
@@ -0,0 +1,1 @@
131
02
=== modified file 'juju/agents/tests/test_machine.py'
--- juju/agents/tests/test_machine.py 2011-09-28 09:48:30 +0000
+++ juju/agents/tests/test_machine.py 2011-10-05 18:13:26 +0000
@@ -170,7 +170,7 @@
170 self.assertTrue(os.path.exists(charm_path))170 self.assertTrue(os.path.exists(charm_path))
171 bundle = CharmBundle(charm_path)171 bundle = CharmBundle(charm_path)
172 self.assertEquals(172 self.assertEquals(
173 bundle.metadata.revision, self.charm.metadata.revision)173 bundle.get_revision(), self.charm.get_revision())
174 self.assertEquals(bundle.get_sha256(), checksum)174 self.assertEquals(bundle.get_sha256(), checksum)
175 self.assertIn(175 self.assertIn(
176 "Downloading charm %s" % charm_id, self.output.getvalue())176 "Downloading charm %s" % charm_id, self.output.getvalue())
177177
=== modified file 'juju/agents/tests/test_unit.py'
--- juju/agents/tests/test_unit.py 2011-10-04 21:20:30 +0000
+++ juju/agents/tests/test_unit.py 2011-10-05 18:13:26 +0000
@@ -598,7 +598,7 @@
598 os.path.join(self.agent.unit_directory, "charm"))598 os.path.join(self.agent.unit_directory, "charm"))
599599
600 self.assertEqual(600 self.assertEqual(
601 self.charm.metadata.revision + 1, new_charm.metadata.revision)601 self.charm.get_revision() + 1, new_charm.get_revision())
602602
603 @inlineCallbacks603 @inlineCallbacks
604 def test_agent_upgrade_bad_unit_state(self):604 def test_agent_upgrade_bad_unit_state(self):
@@ -694,7 +694,7 @@
694 os.path.join(self.agent.unit_directory, "charm"))694 os.path.join(self.agent.unit_directory, "charm"))
695695
696 self.assertEqual(696 self.assertEqual(
697 self.charm.metadata.revision + 1, new_charm.metadata.revision)697 self.charm.get_revision() + 1, new_charm.get_revision())
698698
699 # Verify upgrade flag is cleared.699 # Verify upgrade flag is cleared.
700 self.assertFalse((yield self.states["unit"].get_upgrade_flag()))700 self.assertFalse((yield self.states["unit"].get_upgrade_flag()))
701701
=== modified file 'juju/charm/base.py'
--- juju/charm/base.py 2011-09-15 18:50:23 +0000
+++ juju/charm/base.py 2011-10-05 18:13:26 +0000
@@ -1,3 +1,20 @@
1from juju.errors import CharmError
2
3
4def get_revision(file_content, metadata, path):
5 if file_content is None:
6 result = metadata.obsolete_revision
7 if result is None:
8 raise CharmError(path, "has no revision")
9 else:
10 message = "invalid charm revision %r" % file_content
11 try:
12 result = int(file_content.strip())
13 except (ValueError, TypeError):
14 raise CharmError(path, message)
15 if result < 0:
16 raise CharmError(path, message)
17 return result
118
219
3class CharmBase(object):20class CharmBase(object):
@@ -10,6 +27,20 @@
10 raise NotImplementedError("%s.%s not supported" %27 raise NotImplementedError("%s.%s not supported" %
11 (self.__class__.__name__, attr))28 (self.__class__.__name__, attr))
1229
30 def get_revision(self):
31 """Get the revision, preferably from the revision file.
32
33 Will fall back to metadata if not available.
34 """
35 self._unsupported("get_revision()")
36
37 def set_revision(self, revision):
38 """Update the revision file, if possible.
39
40 Some subclasses may not be able to do this.
41 """
42 self._unsupported("set_revision()")
43
13 def as_bundle(self):44 def as_bundle(self):
14 """Transform this charm into a charm bundle, if possible.45 """Transform this charm into a charm bundle, if possible.
1546
1647
=== modified file 'juju/charm/bundle.py'
--- juju/charm/bundle.py 2011-09-15 18:50:23 +0000
+++ juju/charm/bundle.py 2011-10-05 18:13:26 +0000
@@ -4,7 +4,7 @@
44
5from zipfile import ZipFile, BadZipfile5from zipfile import ZipFile, BadZipfile
66
7from juju.charm.base import CharmBase7from juju.charm.base import CharmBase, get_revision
8from juju.charm.config import ConfigOptions8from juju.charm.config import ConfigOptions
9from juju.charm.metadata import MetaData9from juju.charm.metadata import MetaData
10from juju.errors import CharmError10from juju.errors import CharmError
@@ -15,28 +15,31 @@
15 """ZIP-archive that contains charm directory content."""15 """ZIP-archive that contains charm directory content."""
1616
17 def __init__(self, path):17 def __init__(self, path):
18 self.path = isinstance(path, file) and path.name or path
18 try:19 try:
19 zf = ZipFile(path, 'r')20 zf = ZipFile(path, 'r')
20 except BadZipfile, exc:21 except BadZipfile, exc:
21 raise CharmError(path, "must be a zip file (%s)" % exc)22 raise CharmError(path, "must be a zip file (%s)" % exc)
2223
23 metadata = MetaData()
24 if "metadata.yaml" not in zf.namelist():24 if "metadata.yaml" not in zf.namelist():
25 raise CharmError(path,25 raise CharmError(
26 "archive does not contain required file "26 path, "charm does not contain required file 'metadata.yaml'")
27 "\"metadata.yaml\"")27 self.metadata = MetaData()
2828 self.metadata.parse(zf.read("metadata.yaml"))
29 content = zf.read("metadata.yaml")29
30 metadata.parse(content)30 try:
31 self.metadata = metadata31 revision_content = zf.read("revision")
3232 except KeyError:
33 config = ConfigOptions()33 revision_content = None
34 self._revision = get_revision(
35 revision_content, self.metadata, self.path)
36
37 self.config = ConfigOptions()
34 if "config.yaml" in zf.namelist():38 if "config.yaml" in zf.namelist():
35 content = zf.read("config.yaml")39 self.config.parse(zf.read("config.yaml"))
36 config.parse(content)
37 self.config = config
3840
39 self.path = isinstance(path, file) and path.name or path41 def get_revision(self):
42 return self._revision
4043
41 def compute_sha256(self):44 def compute_sha256(self):
42 """Return the SHA256 digest for this charm bundle.45 """Return the SHA256 digest for this charm bundle.
@@ -49,7 +52,7 @@
49 """Extract the bundle to directory path and return a52 """Extract the bundle to directory path and return a
50 CharmDirectory handle"""53 CharmDirectory handle"""
51 from .directory import CharmDirectory54 from .directory import CharmDirectory
52 zf = ZipFile(self.path, 'r')55 zf = ZipFile(self.path, "r")
53 for info in zf.infolist():56 for info in zf.infolist():
54 mode = info.external_attr >> 1657 mode = info.external_attr >> 16
55 extract_path = zf.extract(info, directory_path)58 extract_path = zf.extract(info, directory_path)
5659
=== modified file 'juju/charm/directory.py'
--- juju/charm/directory.py 2011-09-15 19:24:47 +0000
+++ juju/charm/directory.py 2011-10-05 18:13:26 +0000
@@ -2,10 +2,11 @@
2import zipfile2import zipfile
3import tempfile3import tempfile
44
5from juju.charm.base import CharmBase, get_revision
6from juju.charm.bundle import CharmBundle
5from juju.charm.config import ConfigOptions7from juju.charm.config import ConfigOptions
6from juju.charm.metadata import MetaData8from juju.charm.metadata import MetaData
7from juju.charm.bundle import CharmBundle9from juju.errors import CharmError
8from juju.charm.base import CharmBase
910
1011
11class CharmDirectory(CharmBase):12class CharmDirectory(CharmBase):
@@ -22,11 +23,30 @@
22 def __init__(self, path):23 def __init__(self, path):
23 self.path = path24 self.path = path
24 self.metadata = MetaData(os.path.join(path, "metadata.yaml"))25 self.metadata = MetaData(os.path.join(path, "metadata.yaml"))
26
27 revision_content = None
28 revision_path = os.path.join(self.path, "revision")
29 if os.path.exists(revision_path):
30 with open(revision_path) as f:
31 revision_content = f.read()
32 self._revision = get_revision(
33 revision_content, self.metadata, self.path)
34 if revision_content is None:
35 self.set_revision(self._revision)
36
25 self.config = ConfigOptions()37 self.config = ConfigOptions()
26 self.config.load(os.path.join(path, "config.yaml"))38 self.config.load(os.path.join(path, "config.yaml"))
27 self._temp_bundle = None39 self._temp_bundle = None
28 self._temp_bundle_file = None40 self._temp_bundle_file = None
2941
42 def get_revision(self):
43 return self._revision
44
45 def set_revision(self, revision):
46 self._revision = revision
47 with open(os.path.join(self.path, "revision"), "w") as f:
48 f.write(str(revision) + "\n")
49
30 def make_archive(self, path):50 def make_archive(self, path):
31 """Create archive of directory and write to ``path``.51 """Create archive of directory and write to ``path``.
3252
@@ -60,8 +80,7 @@
6080
61 def as_bundle(self):81 def as_bundle(self):
62 if self._temp_bundle is None:82 if self._temp_bundle is None:
63 prefix = "%s-%d.charm." % \83 prefix = "%s-%d.charm." % (self.metadata.name, self.get_revision())
64 (self.metadata.name, self.metadata.revision)
65 temp_file = tempfile.NamedTemporaryFile(prefix=prefix)84 temp_file = tempfile.NamedTemporaryFile(prefix=prefix)
66 self.make_archive(temp_file.name)85 self.make_archive(temp_file.name)
67 self._temp_bundle = CharmBundle(temp_file.name)86 self._temp_bundle = CharmBundle(temp_file.name)
6887
=== modified file 'juju/charm/metadata.py'
--- juju/charm/metadata.py 2011-09-28 23:04:39 +0000
+++ juju/charm/metadata.py 2011-10-05 18:13:26 +0000
@@ -1,15 +1,17 @@
1import logging
1import os2import os
23
3import yaml4import yaml
45
5from juju.errors import JujuError, FileNotFound6from juju.errors import JujuError, FileNotFound
6from juju.charm.errors import CharmURLError
7from juju.charm.url import CharmURL
8from juju.lib.schema import (7from juju.lib.schema import (
9 SchemaError, Bool, Constant, Dict, Int,8 SchemaError, Bool, Constant, Dict, Int,
10 KeyDict, OneOf, UnicodeOrString)9 KeyDict, OneOf, UnicodeOrString)
1110
1211
12log = logging.getLogger("juju.charm")
13
14
13class MetaDataError(JujuError):15class MetaDataError(JujuError):
14 """Raised when an error in the info file of a charm is found."""16 """Raised when an error in the info file of a charm is found."""
1517
@@ -88,7 +90,7 @@
88 "peers": Dict(UTF8_SCHEMA, InterfaceExpander(limit=1)),90 "peers": Dict(UTF8_SCHEMA, InterfaceExpander(limit=1)),
89 "provides": Dict(UTF8_SCHEMA, InterfaceExpander(limit=None)),91 "provides": Dict(UTF8_SCHEMA, InterfaceExpander(limit=None)),
90 "requires": Dict(UTF8_SCHEMA, InterfaceExpander(limit=1)),92 "requires": Dict(UTF8_SCHEMA, InterfaceExpander(limit=1)),
91 }, optional=set(["provides", "requires", "peers"]))93 }, optional=set(["provides", "requires", "peers", "revision"]))
9294
9395
94class MetaData(object):96class MetaData(object):
@@ -110,12 +112,13 @@
110 return self._data.get("name")112 return self._data.get("name")
111113
112 @property114 @property
113 def revision(self):115 def obsolete_revision(self):
114 """The charm revision.116 """The charm revision.
115117
116 The charm revision acts as a version, but unlike e.g. package118 The charm revision acts as a version, but unlike e.g. package
117 versions, the charm revision is a monotonically increasing119 versions, the charm revision is a monotonically increasing
118 integer.120 integer. This should not be stored in metadata any more, but remains
121 for backward compatibility's sake.
119 """122 """
120 return self._data.get("revision")123 return self._data.get("revision")
121124
@@ -174,7 +177,11 @@
174177
175 @raise MetaDataError: When errors are found in the info data.178 @raise MetaDataError: When errors are found in the info data.
176 """179 """
177 return self.parse_serialization_data(yaml.load(content), path)180 self.parse_serialization_data(yaml.load(content), path)
181 if "revision" in self._data and path:
182 log.warning(
183 "%s: revision field is obsolete. Move it to the 'revision' "
184 "file." % path)
178185
179 def parse_serialization_data(self, serialization_data, path=None):186 def parse_serialization_data(self, serialization_data, path=None):
180 """Parse the unprocessed serialization data and load in this instance.187 """Parse the unprocessed serialization data and load in this instance.
181188
=== modified file 'juju/charm/repository.py'
--- juju/charm/repository.py 2011-10-05 09:51:16 +0000
+++ juju/charm/repository.py 2011-10-05 18:13:26 +0000
@@ -61,20 +61,20 @@
61 latest = None61 latest = None
62 for charm in self._collection(charm_url.collection):62 for charm in self._collection(charm_url.collection):
63 if charm.metadata.name == charm_url.name:63 if charm.metadata.name == charm_url.name:
64 if charm.metadata.revision == charm_url.revision:64 if charm.get_revision() == charm_url.revision:
65 return succeed(charm)65 return succeed(charm)
66 if (latest is None or66 if (latest is None or
67 latest.metadata.revision < charm.metadata.revision):67 latest.get_revision() < charm.get_revision()):
68 latest = charm68 latest = charm
6969
70 if latest is None:70 if latest is None or charm_url.revision is not None:
71 return fail(CharmNotFound(self.path, charm_url))71 return fail(CharmNotFound(self.path, charm_url))
7272
73 return succeed(latest)73 return succeed(latest)
7474
75 def latest(self, charm_url):75 def latest(self, charm_url):
76 d = self.find(charm_url.with_revision(None))76 d = self.find(charm_url.with_revision(None))
77 d.addCallback(lambda c: c.metadata.revision)77 d.addCallback(lambda c: c.get_revision())
78 return d78 return d
7979
8080
@@ -128,7 +128,7 @@
128 yield self._download(charm_url, cache_path)128 yield self._download(charm_url, cache_path)
129 charm = get_charm_from_path(cache_path)129 charm = get_charm_from_path(cache_path)
130130
131 assert charm.metadata.revision == revision, "bad charm revision"131 assert charm.get_revision() == revision, "bad charm revision"
132 if charm.get_sha256() != info["sha256"]:132 if charm.get_sha256() != info["sha256"]:
133 os.remove(cache_path)133 os.remove(cache_path)
134 name = "%s (%s)" % (134 name = "%s (%s)" % (
135135
=== modified file 'juju/charm/tests/__init__.py'
--- juju/charm/tests/__init__.py 2011-09-28 00:36:30 +0000
+++ juju/charm/tests/__init__.py 2011-10-05 18:13:26 +0000
@@ -1,3 +1,3 @@
1def local_charm_id(charm):1def local_charm_id(charm):
2 return "local:series/%s-%s" % (2 return "local:series/%s-%s" % (
3 charm.metadata.name, charm.metadata.revision)3 charm.metadata.name, charm.get_revision())
44
=== modified file 'juju/charm/tests/repository/series/dummy/metadata.yaml'
--- juju/charm/tests/repository/series/dummy/metadata.yaml 2011-09-15 19:24:47 +0000
+++ juju/charm/tests/repository/series/dummy/metadata.yaml 2011-10-05 18:13:26 +0000
@@ -1,5 +1,4 @@
1name: dummy1name: dummy
2revision: 1
3summary: "That's a dummy charm."2summary: "That's a dummy charm."
4description: |3description: |
5 This is a longer description which4 This is a longer description which
65
=== added file 'juju/charm/tests/repository/series/dummy/revision'
--- juju/charm/tests/repository/series/dummy/revision 1970-01-01 00:00:00 +0000
+++ juju/charm/tests/repository/series/dummy/revision 2011-10-05 18:13:26 +0000
@@ -0,0 +1,1 @@
11
0\ No newline at end of file2\ No newline at end of file
13
=== modified file 'juju/charm/tests/repository/series/mysql-alternative/metadata.yaml'
--- juju/charm/tests/repository/series/mysql-alternative/metadata.yaml 2011-09-28 08:55:57 +0000
+++ juju/charm/tests/repository/series/mysql-alternative/metadata.yaml 2011-10-05 18:13:26 +0000
@@ -1,5 +1,4 @@
1name: mysql-alternative1name: mysql-alternative
2revision: 1
3summary: "Database engine"2summary: "Database engine"
4description: "A pretty popular database"3description: "A pretty popular database"
5provides:4provides:
65
=== added file 'juju/charm/tests/repository/series/mysql-alternative/revision'
--- juju/charm/tests/repository/series/mysql-alternative/revision 1970-01-01 00:00:00 +0000
+++ juju/charm/tests/repository/series/mysql-alternative/revision 2011-10-05 18:13:26 +0000
@@ -0,0 +1,1 @@
11
0\ No newline at end of file2\ No newline at end of file
13
=== modified file 'juju/charm/tests/repository/series/mysql/metadata.yaml'
--- juju/charm/tests/repository/series/mysql/metadata.yaml 2011-09-15 19:24:47 +0000
+++ juju/charm/tests/repository/series/mysql/metadata.yaml 2011-10-05 18:13:26 +0000
@@ -1,5 +1,4 @@
1name: mysql1name: mysql
2revision: 1
3summary: "Database engine"2summary: "Database engine"
4description: "A pretty popular database"3description: "A pretty popular database"
5provides:4provides:
65
=== added file 'juju/charm/tests/repository/series/mysql/revision'
--- juju/charm/tests/repository/series/mysql/revision 1970-01-01 00:00:00 +0000
+++ juju/charm/tests/repository/series/mysql/revision 2011-10-05 18:13:26 +0000
@@ -0,0 +1,1 @@
11
0\ No newline at end of file2\ No newline at end of file
13
=== modified file 'juju/charm/tests/repository/series/new/metadata.yaml'
--- juju/charm/tests/repository/series/new/metadata.yaml 2011-09-15 19:24:47 +0000
+++ juju/charm/tests/repository/series/new/metadata.yaml 2011-10-05 18:13:26 +0000
@@ -1,5 +1,4 @@
1name: sample1name: sample
2revision: 2
3summary: "That's a sample charm."2summary: "That's a sample charm."
4description: |3description: |
5 This is a longer description which4 This is a longer description which
65
=== added file 'juju/charm/tests/repository/series/new/revision'
--- juju/charm/tests/repository/series/new/revision 1970-01-01 00:00:00 +0000
+++ juju/charm/tests/repository/series/new/revision 2011-10-05 18:13:26 +0000
@@ -0,0 +1,1 @@
12
02
=== modified file 'juju/charm/tests/repository/series/old/metadata.yaml'
--- juju/charm/tests/repository/series/old/metadata.yaml 2011-09-15 19:24:47 +0000
+++ juju/charm/tests/repository/series/old/metadata.yaml 2011-10-05 18:13:26 +0000
@@ -1,5 +1,4 @@
1name: sample1name: sample
2revision: 1
3summary: "That's a sample charm."2summary: "That's a sample charm."
4description: |3description: |
5 This is a longer description which4 This is a longer description which
65
=== added file 'juju/charm/tests/repository/series/old/revision'
--- juju/charm/tests/repository/series/old/revision 1970-01-01 00:00:00 +0000
+++ juju/charm/tests/repository/series/old/revision 2011-10-05 18:13:26 +0000
@@ -0,0 +1,1 @@
11
02
=== modified file 'juju/charm/tests/repository/series/riak/metadata.yaml'
--- juju/charm/tests/repository/series/riak/metadata.yaml 2011-09-15 19:24:47 +0000
+++ juju/charm/tests/repository/series/riak/metadata.yaml 2011-10-05 18:13:26 +0000
@@ -1,5 +1,4 @@
1name: riak1name: riak
2revision: 7
3summary: "K/V storage engine"2summary: "K/V storage engine"
4description: "Scalable K/V Store in Erlang with Clocks :-)"3description: "Scalable K/V Store in Erlang with Clocks :-)"
5provides:4provides:
65
=== added file 'juju/charm/tests/repository/series/riak/revision'
--- juju/charm/tests/repository/series/riak/revision 1970-01-01 00:00:00 +0000
+++ juju/charm/tests/repository/series/riak/revision 2011-10-05 18:13:26 +0000
@@ -0,0 +1,1 @@
17
0\ No newline at end of file2\ No newline at end of file
13
=== modified file 'juju/charm/tests/repository/series/varnish-alternative/metadata.yaml'
--- juju/charm/tests/repository/series/varnish-alternative/metadata.yaml 2011-09-28 08:55:57 +0000
+++ juju/charm/tests/repository/series/varnish-alternative/metadata.yaml 2011-10-05 18:13:26 +0000
@@ -1,5 +1,4 @@
1name: varnish-alternative1name: varnish-alternative
2revision: 1
3summary: "Database engine"2summary: "Database engine"
4description: "Another popular database"3description: "Another popular database"
5provides:4provides:
65
=== added file 'juju/charm/tests/repository/series/varnish-alternative/revision'
--- juju/charm/tests/repository/series/varnish-alternative/revision 1970-01-01 00:00:00 +0000
+++ juju/charm/tests/repository/series/varnish-alternative/revision 2011-10-05 18:13:26 +0000
@@ -0,0 +1,1 @@
11
0\ No newline at end of file2\ No newline at end of file
13
=== modified file 'juju/charm/tests/repository/series/varnish/metadata.yaml'
--- juju/charm/tests/repository/series/varnish/metadata.yaml 2011-09-15 19:24:47 +0000
+++ juju/charm/tests/repository/series/varnish/metadata.yaml 2011-10-05 18:13:26 +0000
@@ -1,5 +1,4 @@
1name: varnish1name: varnish
2revision: 1
3summary: "Database engine"2summary: "Database engine"
4description: "Another popular database"3description: "Another popular database"
5provides:4provides:
65
=== added file 'juju/charm/tests/repository/series/varnish/revision'
--- juju/charm/tests/repository/series/varnish/revision 1970-01-01 00:00:00 +0000
+++ juju/charm/tests/repository/series/varnish/revision 2011-10-05 18:13:26 +0000
@@ -0,0 +1,1 @@
11
0\ No newline at end of file2\ No newline at end of file
13
=== modified file 'juju/charm/tests/repository/series/wordpress/metadata.yaml'
--- juju/charm/tests/repository/series/wordpress/metadata.yaml 2011-09-15 19:24:47 +0000
+++ juju/charm/tests/repository/series/wordpress/metadata.yaml 2011-10-05 18:13:26 +0000
@@ -1,5 +1,4 @@
1name: wordpress1name: wordpress
2revision: 3
3summary: "Blog engine"2summary: "Blog engine"
4description: "A pretty popular blog engine"3description: "A pretty popular blog engine"
5provides:4provides:
65
=== added file 'juju/charm/tests/repository/series/wordpress/revision'
--- juju/charm/tests/repository/series/wordpress/revision 1970-01-01 00:00:00 +0000
+++ juju/charm/tests/repository/series/wordpress/revision 2011-10-05 18:13:26 +0000
@@ -0,0 +1,1 @@
13
0\ No newline at end of file2\ No newline at end of file
13
=== modified file 'juju/charm/tests/test_base.py'
--- juju/charm/tests/test_base.py 2011-09-15 18:50:23 +0000
+++ juju/charm/tests/test_base.py 2011-10-05 18:13:26 +0000
@@ -1,5 +1,9 @@
1import yaml
2
3from juju.charm.base import CharmBase, get_revision
4from juju.charm.metadata import MetaData
5from juju.errors import CharmError
1from juju.lib.testing import TestCase6from juju.lib.testing import TestCase
2from juju.charm.base import CharmBase
37
48
5class MyCharm(CharmBase):9class MyCharm(CharmBase):
@@ -20,21 +24,13 @@
20 else:24 else:
21 self.fail("MyCharm.%s didn't fail" % attr_name)25 self.fail("MyCharm.%s didn't fail" % attr_name)
2226
23 def test_as_bundle_is_unsupported(self):27 def test_unsupported(self):
24 self.assertUnsupported(lambda: self.charm.as_bundle(),28 self.assertUnsupported(self.charm.as_bundle, "as_bundle()")
25 "as_bundle()")29 self.assertUnsupported(self.charm.get_sha256, "compute_sha256()")
2630 self.assertUnsupported(self.charm.compute_sha256, "compute_sha256()")
27 def test_compute_sha256_is_unsupported(self):31 self.assertUnsupported(self.charm.get_revision, "get_revision()")
28 self.assertUnsupported(lambda: self.charm.compute_sha256(),32 self.assertUnsupported(
29 "compute_sha256()")33 lambda: self.charm.set_revision(1), "set_revision()")
30
31 def test_get_sha256_fails_if_not_preset(self):
32 """
33 get_sha256() will internally call compute_sha256() if
34 the digest value hasn't been previously set.
35 """
36 self.assertUnsupported(lambda: self.charm.get_sha256(),
37 "compute_sha256()")
3834
39 def test_compute_and_cache_sha256(self):35 def test_compute_and_cache_sha256(self):
40 """36 """
@@ -53,3 +49,33 @@
53 sha256 = ["anothervalue"]49 sha256 = ["anothervalue"]
54 # Should still be the same, since the old one was cached.50 # Should still be the same, since the old one was cached.
55 self.assertEquals(charm.get_sha256(), "mysha")51 self.assertEquals(charm.get_sha256(), "mysha")
52
53
54class GetRevisionTest(TestCase):
55
56 def assert_good_content(self, content, value):
57 self.assertEquals(get_revision(content, None, None), value)
58
59 def assert_bad_content(self, content):
60 err = self.assertRaises(
61 CharmError, get_revision, content, None, "path")
62 self.assertEquals(
63 str(err),
64 "Error processing 'path': invalid charm revision %r" % content)
65
66 def test_with_content(self):
67 self.assert_good_content("0\n", 0)
68 self.assert_good_content("123\n", 123)
69 self.assert_bad_content("")
70 self.assert_bad_content("-1\n")
71 self.assert_bad_content("three hundred and six or so")
72
73 def test_metadata_fallback(self):
74 metadata = MetaData()
75 err = self.assertRaises(
76 CharmError, get_revision, None, metadata, "path")
77 self.assertEquals(
78 str(err), "Error processing 'path': has no revision")
79 metadata.parse(yaml.dump({
80 "name": "x", "summary": "y", "description": "z","revision": 33}))
81 self.assertEquals(get_revision(None, metadata, None), 33)
5682
=== modified file 'juju/charm/tests/test_bundle.py'
--- juju/charm/tests/test_bundle.py 2011-09-28 08:55:57 +0000
+++ juju/charm/tests/test_bundle.py 2011-10-05 18:13:26 +0000
@@ -1,7 +1,7 @@
1import os1import os
2import stat
3import hashlib2import hashlib
4import inspect3import inspect
4import yaml
5import zipfile5import zipfile
66
7from juju.lib.testing import TestCase7from juju.lib.testing import TestCase
@@ -35,16 +35,11 @@
3535
36 def test_error_not_zip(self):36 def test_error_not_zip(self):
37 filename = self.makeFile("@#$@$")37 filename = self.makeFile("@#$@$")
38 try:38 err = self.assertRaises(CharmError, CharmBundle, filename)
39 CharmBundle(filename)39 self.assertEquals(
40 except CharmError, exc:40 str(err),
41 self.assertEquals(41 "Error processing %r: must be a zip file (File is not a zip file)"
42 str(exc),42 % filename)
43 ("Error processing %r: " % filename) +
44 "must be a zip file (File is not a zip file)")
45 return
46
47 self.fail("Expected charm error.")
4843
49 def test_error_zip_but_doesnt_have_metadata_file(self):44 def test_error_zip_but_doesnt_have_metadata_file(self):
50 filename = self.makeFile()45 filename = self.makeFile()
@@ -52,16 +47,60 @@
52 zf.writestr("README.txt", "This is not a valid charm.")47 zf.writestr("README.txt", "This is not a valid charm.")
53 zf.close()48 zf.close()
5449
55 try:50 err = self.assertRaises(CharmError, CharmBundle, filename)
56 CharmBundle(filename)51 self.assertEquals(
57 except CharmError, exc:52 str(err),
58 self.assertEquals(53 "Error processing %r: charm does not contain required "
59 str(exc),54 "file 'metadata.yaml'" % filename)
60 ("Error processing %r: " % filename) +55
61 "archive does not contain required file \"metadata.yaml\"")56 def test_no_revision_at_all(self):
62 return57 filename = self.makeFile()
6358 zf_dst = zipfile.ZipFile(filename, "w")
64 self.fail("Expected charm error.")59 zf_src = zipfile.ZipFile(self.filename, "r")
60 for name in zf_src.namelist():
61 if name == "revision":
62 continue
63 zf_dst.writestr(name, zf_src.read(name))
64 zf_src.close()
65 zf_dst.close()
66
67 err = self.assertRaises(CharmError, CharmBundle, filename)
68 self.assertEquals(
69 str(err), "Error processing %r: has no revision" % filename)
70
71 def test_revision_in_metadata(self):
72 filename = self.makeFile()
73 zf_dst = zipfile.ZipFile(filename, "w")
74 zf_src = zipfile.ZipFile(self.filename, "r")
75 for name in zf_src.namelist():
76 if name == "revision":
77 continue
78 content = zf_src.read(name)
79 if name == "metadata.yaml":
80 data = yaml.load(content)
81 data["revision"] = 303
82 content = yaml.dump(data)
83 zf_dst.writestr(name, content)
84 zf_src.close()
85 zf_dst.close()
86
87 charm = CharmBundle(filename)
88 self.assertEquals(charm.get_revision(), 303)
89
90 def test_competing_revisions(self):
91 zf = zipfile.ZipFile(self.filename, "a")
92 zf.writestr("revision", "999")
93 data = yaml.load(zf.read("metadata.yaml"))
94 data["revision"] = 303
95 zf.writestr("metadata.yaml", yaml.dump(data))
96 zf.close()
97
98 charm = CharmBundle(self.filename)
99 self.assertEquals(charm.get_revision(), 999)
100
101 def test_cannot_set_revision(self):
102 charm = CharmBundle(self.filename)
103 self.assertRaises(NotImplementedError, charm.set_revision, 123)
65104
66 def test_bundled_config(self):105 def test_bundled_config(self):
67 """Make sure that config is accessible from a bundle."""106 """Make sure that config is accessible from a bundle."""
68107
=== modified file 'juju/charm/tests/test_directory.py'
--- juju/charm/tests/test_directory.py 2011-09-28 10:38:16 +0000
+++ juju/charm/tests/test_directory.py 2011-10-05 18:13:26 +0000
@@ -1,11 +1,13 @@
1import gc
1import os2import os
2import hashlib3import hashlib
3import inspect4import inspect
5import shutil
4import tempfile6import tempfile
7import yaml
5import zipfile8import zipfile
6import gc
79
8from juju.errors import FileNotFound10from juju.errors import CharmError, FileNotFound
9from juju.charm.metadata import MetaData11from juju.charm.metadata import MetaData
10from juju.charm.directory import CharmDirectory12from juju.charm.directory import CharmDirectory
11from juju.charm.bundle import CharmBundle13from juju.charm.bundle import CharmBundle
@@ -31,10 +33,62 @@
31 if not os.path.isdir(empty_dir):33 if not os.path.isdir(empty_dir):
32 os.mkdir(empty_dir)34 os.mkdir(empty_dir)
3335
36 def copy_charm(self):
37 dir_ = os.path.join(tempfile.mkdtemp(), "sample")
38 shutil.copytree(sample_directory, dir_)
39 return dir_
40
41 def delete_revision(self, dir_):
42 os.remove(os.path.join(dir_, "revision"))
43
44 def set_metadata_revision(self, dir_, revision):
45 metadata_path = os.path.join(dir_, "metadata.yaml")
46 with open(metadata_path) as f:
47 data = yaml.load(f.read())
48 data["revision"] = 999
49 with open(metadata_path, "w") as f:
50 f.write(yaml.dump(data))
51
34 def test_metadata_is_required(self):52 def test_metadata_is_required(self):
35 directory = self.makeDir()53 directory = self.makeDir()
36 self.assertRaises(FileNotFound, CharmDirectory, directory)54 self.assertRaises(FileNotFound, CharmDirectory, directory)
3755
56 def test_no_revision(self):
57 dir_ = self.copy_charm()
58 self.delete_revision(dir_)
59 err = self.assertRaises(CharmError, CharmDirectory, dir_)
60 self.assertEquals(
61 str(err), "Error processing %r: has no revision" % dir_)
62
63 def test_revision_in_metadata(self):
64 dir_ = self.copy_charm()
65 self.delete_revision(dir_)
66 self.set_metadata_revision(dir_, 999)
67 log = self.capture_logging("juju.charm")
68 charm = CharmDirectory(dir_)
69 self.assertEquals(charm.get_revision(), 999)
70 self.assertIn(
71 "revision field is obsolete. Move it to the 'revision' file.",
72 log.getvalue())
73
74 def test_competing_revisions(self):
75 dir_ = self.copy_charm()
76 self.set_metadata_revision(dir_, 999)
77 log = self.capture_logging("juju.charm")
78 charm = CharmDirectory(dir_)
79 self.assertEquals(charm.get_revision(), 1)
80 self.assertIn(
81 "revision field is obsolete. Move it to the 'revision' file.",
82 log.getvalue())
83
84 def test_set_revision(self):
85 dir_ = self.copy_charm()
86 charm = CharmDirectory(dir_)
87 charm.set_revision(123)
88 self.assertEquals(charm.get_revision(), 123)
89 with open(os.path.join(dir_, "revision")) as f:
90 self.assertEquals(f.read(), "123\n")
91
38 def test_info(self):92 def test_info(self):
39 directory = CharmDirectory(sample_directory)93 directory = CharmDirectory(sample_directory)
40 self.assertTrue(directory.metadata is not None)94 self.assertTrue(directory.metadata is not None)
@@ -57,7 +111,7 @@
57 self.assertEqual(111 self.assertEqual(
58 set(included),112 set(included),
59 set(("metadata.yaml", "empty/", "src/", "src/hello.c",113 set(("metadata.yaml", "empty/", "src/", "src/hello.c",
60 "config.yaml", "hooks/", "hooks/install")))114 "config.yaml", "hooks/", "hooks/install", "revision")))
61115
62 def test_as_bundle(self):116 def test_as_bundle(self):
63 directory = CharmDirectory(self.sample_dir1)117 directory = CharmDirectory(self.sample_dir1)
@@ -66,10 +120,14 @@
66 self.assertEquals(charm_bundle.metadata.name, "sample")120 self.assertEquals(charm_bundle.metadata.name, "sample")
67 self.assertIn("sample-1.charm", charm_bundle.path)121 self.assertIn("sample-1.charm", charm_bundle.path)
68122
123 total_compressed = 0
124 total_uncompressed = 0
69 zip_file = zipfile.ZipFile(charm_bundle.path)125 zip_file = zipfile.ZipFile(charm_bundle.path)
70 for n in zip_file.namelist():126 for n in zip_file.namelist():
71 info = zip_file.getinfo(n)127 info = zip_file.getinfo(n)
72 self.assertTrue(info.compress_size < info.file_size)128 total_compressed += info.compress_size
129 total_uncompressed += info.file_size
130 self.assertTrue(total_compressed < total_uncompressed)
73131
74 def test_as_bundle_file_lifetime(self):132 def test_as_bundle_file_lifetime(self):
75 """133 """
76134
=== modified file 'juju/charm/tests/test_metadata.py'
--- juju/charm/tests/test_metadata.py 2011-09-28 23:04:39 +0000
+++ juju/charm/tests/test_metadata.py 2011-10-05 18:13:26 +0000
@@ -5,7 +5,6 @@
5import inspect5import inspect
66
7from juju.charm import tests7from juju.charm import tests
8from juju.charm.errors import CharmURLError
9from juju.charm.metadata import (8from juju.charm.metadata import (
10 MetaData, MetaDataError, InterfaceExpander, SchemaError)9 MetaData, MetaDataError, InterfaceExpander, SchemaError)
11from juju.errors import FileNotFound10from juju.errors import FileNotFound
@@ -57,7 +56,7 @@
57 Attributes should be set to None before anything is loaded.56 Attributes should be set to None before anything is loaded.
58 """57 """
59 self.assertEquals(self.metadata.name, None)58 self.assertEquals(self.metadata.name, None)
60 self.assertEquals(self.metadata.revision, None)59 self.assertEquals(self.metadata.obsolete_revision, None)
61 self.assertEquals(self.metadata.summary, None)60 self.assertEquals(self.metadata.summary, None)
62 self.assertEquals(self.metadata.description, None)61 self.assertEquals(self.metadata.description, None)
6362
@@ -68,11 +67,38 @@
68 """67 """
69 self.metadata.parse(self.sample)68 self.metadata.parse(self.sample)
70 self.assertEquals(self.metadata.name, "dummy")69 self.assertEquals(self.metadata.name, "dummy")
71 self.assertEquals(self.metadata.revision, 1)70 self.assertEquals(self.metadata.obsolete_revision, None)
72 self.assertEquals(self.metadata.summary, u"That's a dummy charm.")71 self.assertEquals(self.metadata.summary, u"That's a dummy charm.")
73 self.assertEquals(self.metadata.description,72 self.assertEquals(self.metadata.description,
74 u"This is a longer description which\n"73 u"This is a longer description which\n"
75 u"potentially contains multiple lines.\n")74 u"potentially contains multiple lines.\n")
75
76 def assert_parse_with_revision(self, with_path):
77 """
78 Parsing the content file should work. :-) Basic information will
79 be available as attributes of the info file.
80 """
81 with self.change_sample() as data:
82 data["revision"] = 123
83 log = self.capture_logging("juju.charm")
84 self.metadata.parse(self.sample, "some/path" if with_path else None)
85 if with_path:
86 self.assertIn(
87 "some/path: revision field is obsolete. Move it to the "
88 "'revision' file.",
89 log.getvalue())
90 self.assertEquals(self.metadata.name, "dummy")
91 self.assertEquals(self.metadata.obsolete_revision, 123)
92 self.assertEquals(self.metadata.summary, u"That's a dummy charm.")
93 self.assertEquals(self.metadata.description,
94 u"This is a longer description which\n"
95 u"potentially contains multiple lines.\n")
96 self.assertEquals(
97 self.metadata.get_serialization_data()["revision"], 123)
98
99 def test_parse_with_revision(self):
100 self.assert_parse_with_revision(True)
101 self.assert_parse_with_revision(False)
76102
77 def test_load_calls_parse_calls_parse_serialzation_data(self):103 def test_load_calls_parse_calls_parse_serialzation_data(self):
78 """104 """
@@ -118,18 +144,6 @@
118 self.metadata.load, filename)144 self.metadata.load, filename)
119 self.assertEquals(error.path, filename)145 self.assertEquals(error.path, filename)
120146
121 def test_revision_is_int(self):
122 """
123 Revision numbers are *integers*. Yep, no 1.2.3 versioning.
124 """
125 with self.change_sample() as data:
126 data["revision"] = "1"
127 error = self.assertRaises(MetaDataError,
128 self.metadata.parse, self.sample)
129 self.assertEquals(str(error),
130 "Bad data in charm info: revision: "
131 "expected int, got '1'")
132
133 def test_name_summary_and_description_are_utf8(self):147 def test_name_summary_and_description_are_utf8(self):
134 """148 """
135 Textual fields are decoded to unicode by the schema using UTF-8.149 Textual fields are decoded to unicode by the schema using UTF-8.
136150
=== modified file 'juju/charm/tests/test_repository.py'
--- juju/charm/tests/test_repository.py 2011-10-05 09:51:16 +0000
+++ juju/charm/tests/test_repository.py 2011-10-05 18:13:26 +0000
@@ -63,16 +63,15 @@
63 return CharmURL.parse("local:series/" + name)63 return CharmURL.parse("local:series/" + name)
6464
65 @inlineCallbacks65 @inlineCallbacks
66 def assert_not_there(self, name):66 def assert_not_there(self, name, repo, revision=None):
67 url = self.charm_url(name)67 url = self.charm_url(name)
68 msg = "Charm 'local:series/%s' not found in repository %s" % (68 msg = "Charm 'local:series/%s' not found in repository %s" % (
69 name, self.unbundled_repo_path)69 name, repo.path)
70 err = yield self.assertFailure(70 err = yield self.assertFailure(repo.find(url), CharmNotFound)
71 self.repository1.find(url), CharmNotFound)71 self.assertEquals(str(err), msg)
72 self.assertEquals(str(err), msg)72 if revision is None:
73 err = yield self.assertFailure(73 err = yield self.assertFailure(repo.latest(url), CharmNotFound)
74 self.repository1.latest(url), CharmNotFound)74 self.assertEquals(str(err), msg)
75 self.assertEquals(str(err), msg)
7675
77 def test_find_inappropriate_url(self):76 def test_find_inappropriate_url(self):
78 url = CharmURL.parse("cs:foo/bar")77 url = CharmURL.parse("cs:foo/bar")
@@ -80,18 +79,18 @@
80 self.assertEquals(str(err), "schema mismatch")79 self.assertEquals(str(err), "schema mismatch")
8180
82 def test_completely_missing(self):81 def test_completely_missing(self):
83 return self.assert_not_there("zebra")82 return self.assert_not_there("zebra", self.repository1)
8483
85 def test_unkown_files_ignored(self):84 def test_unkown_files_ignored(self):
86 self.makeFile(85 self.makeFile(
87 "Foobar",86 "Foobar",
88 path=os.path.join(self.repository1.path, "series", "zebra"))87 path=os.path.join(self.repository1.path, "series", "zebra"))
89 return self.assert_not_there("zebra")88 return self.assert_not_there("zebra", self.repository1)
9089
91 def test_unknown_directories_ignored(self):90 def test_unknown_directories_ignored(self):
92 self.makeDir(91 self.makeDir(
93 path=os.path.join(self.repository1.path, "series", "zebra"))92 path=os.path.join(self.repository1.path, "series", "zebra"))
94 return self.assert_not_there("zebra")93 return self.assert_not_there("zebra", self.repository1)
9594
96 def test_broken_charms_ignored(self):95 def test_broken_charms_ignored(self):
97 charm_path = self.makeDir(96 charm_path = self.makeDir(
@@ -104,37 +103,45 @@
104revision: 0103revision: 0
105summary: hola""")104summary: hola""")
106 fh.close()105 fh.close()
107 return self.assert_not_there("zebra")106 return self.assert_not_there("zebra", self.repository1)
108107
109 def assert_there(self, name, repo, revision, latest_revision=None):108 def assert_there(self, name, repo, revision, latest_revision=None):
110 url = self.charm_url(name)109 url = self.charm_url(name)
111 charm = yield repo.find(url)110 charm = yield repo.find(url)
112 self.assertEquals(charm.metadata.revision, revision)111 self.assertEquals(charm.get_revision(), revision)
113 latest = yield repo.latest(url)112 latest = yield repo.latest(url)
114 self.assertEquals(latest, latest_revision or revision)113 self.assertEquals(latest, latest_revision or revision)
115114
115 @inlineCallbacks
116 def test_success_unbundled(self):116 def test_success_unbundled(self):
117 return self.assert_there("sample", self.repository1, 2)117 yield self.assert_there("sample", self.repository1, 2)
118 return self.assert_there("sample-2", self.repository1, 2)118 yield self.assert_there("sample-1", self.repository1, 1, 2)
119 yield self.assert_there("sample-2", self.repository1, 2)
120 yield self.assert_not_there("sample-3", self.repository1, 2)
119121
122 @inlineCallbacks
120 def test_success_bundled(self):123 def test_success_bundled(self):
121 return self.assert_there("sample", self.repository2, 2)124 yield self.assert_there("sample", self.repository2, 2)
122 return self.assert_there("sample-2", self.repository2, 2)125 yield self.assert_there("sample-1", self.repository2, 1, 2)
126 yield self.assert_there("sample-2", self.repository2, 2)
127 yield self.assert_not_there("sample-3", self.repository2, 2)
123128
124 @inlineCallbacks129 @inlineCallbacks
125 def test_no_revision_gets_latest(self):130 def test_no_revision_gets_latest(self):
126 yield self.assert_there("sample", self.repository1, 2)131 yield self.assert_there("sample", self.repository1, 2)
132 yield self.assert_there("sample-1", self.repository1, 1, 2)
133 yield self.assert_there("sample-2", self.repository1, 2)
134 yield self.assert_not_there("sample-3", self.repository1, 2)
127135
128 file = open(os.path.join(136 revision_path = os.path.join(
129 self.repository1.path, "series/old/metadata.yaml"), "rw+")137 self.repository1.path, "series/old/revision")
130 data = yaml.load(file.read())138 with open(revision_path, "w") as f:
131 data["revision"] = 3139 f.write("3")
132 file.seek(0)
133 file.write(yaml.dump(data))
134 file.close()
135140
136 yield self.assert_there("sample", self.repository1, 3)141 yield self.assert_there("sample", self.repository1, 3)
142 yield self.assert_not_there("sample-1", self.repository1, 3)
137 yield self.assert_there("sample-2", self.repository1, 2, 3)143 yield self.assert_there("sample-2", self.repository1, 2, 3)
144 yield self.assert_there("sample-3", self.repository1, 3)
138145
139146
140class RemoteRepositoryTest(RepositoryTestBase):147class RemoteRepositoryTest(RepositoryTestBase):
141148
=== modified file 'juju/control/deploy.py'
--- juju/control/deploy.py 2011-09-30 11:25:41 +0000
+++ juju/control/deploy.py 2011-10-05 18:13:26 +0000
@@ -95,7 +95,7 @@
95 service_options = parse_config_options(config_file, service_name)95 service_options = parse_config_options(config_file, service_name)
9696
97 charm = yield repo.find(charm_url)97 charm = yield repo.find(charm_url)
98 charm_id = str(charm_url.with_revision(charm.metadata.revision))98 charm_id = str(charm_url.with_revision(charm.get_revision()))
9999
100 provider = environment.get_machine_provider()100 provider = environment.get_machine_provider()
101 placement_policy = provider.get_placement_policy()101 placement_policy = provider.get_placement_policy()
102102
=== modified file 'juju/control/tests/test_upgrade_charm.py'
--- juju/control/tests/test_upgrade_charm.py 2011-09-29 03:07:26 +0000
+++ juju/control/tests/test_upgrade_charm.py 2011-10-05 18:13:26 +0000
@@ -15,23 +15,23 @@
1515
16class CharmUpgradeTestBase(object):16class CharmUpgradeTestBase(object):
1717
18 def add_charm(self, metadata, repository_dir=None):18 def add_charm(self, metadata, revision, repository_dir=None):
19 if repository_dir is None:19 if repository_dir is None:
20 repository_dir = self.makeDir()20 repository_dir = self.makeDir()
21 series_dir = os.path.join(repository_dir, "series")21 series_dir = os.path.join(repository_dir, "series")
22 os.mkdir(series_dir)22 os.mkdir(series_dir)
23 charm_dir = os.path.join(series_dir, metadata["name"])23 charm_dir = os.path.join(series_dir, metadata["name"])
24 os.mkdir(charm_dir)24 os.mkdir(charm_dir)
25 fh = open(os.path.join(charm_dir, "metadata.yaml"), "w")25 with open(os.path.join(charm_dir, "metadata.yaml"), "w") as f:
26 fh.write(dump(metadata))26 f.write(dump(metadata))
27 fh.close()27 with open(os.path.join(charm_dir, "revision"), "w") as f:
28 f.write(str(revision))
28 return LocalCharmRepository(repository_dir)29 return LocalCharmRepository(repository_dir)
2930
30 def increment_charm(self, charm):31 def increment_charm(self, charm):
31 metadata = charm.metadata.get_serialization_data()32 metadata = charm.metadata.get_serialization_data()
32 metadata["name"] = "mysql"33 metadata["name"] = "mysql"
33 metadata["revision"] = 234 repository = self.add_charm(metadata, charm.get_revision() + 1)
34 repository = self.add_charm(metadata)
35 return repository35 return repository
3636
3737
@@ -201,7 +201,7 @@
201201
202 metadata = self.charm.metadata.get_serialization_data()202 metadata = self.charm.metadata.get_serialization_data()
203 metadata["name"] = "mysql"203 metadata["name"] = "mysql"
204 repository = self.add_charm(metadata)204 repository = self.add_charm(metadata, 1)
205 main(["upgrade-charm", "--dry-run",205 main(["upgrade-charm", "--dry-run",
206 "--repository", repository.path, "mysql"])206 "--repository", repository.path, "mysql"])
207 yield finished207 yield finished
@@ -224,7 +224,7 @@
224224
225 metadata = self.charm.metadata.get_serialization_data()225 metadata = self.charm.metadata.get_serialization_data()
226 metadata["name"] = "mysql"226 metadata["name"] = "mysql"
227 repository = self.add_charm(metadata)227 repository = self.add_charm(metadata, 1)
228 main(["upgrade-charm", "--repository", repository.path, "mysql"])228 main(["upgrade-charm", "--repository", repository.path, "mysql"])
229229
230 yield finished230 yield finished
231231
=== modified file 'juju/machine/tests/test_unit_deployment.py'
--- juju/machine/tests/test_unit_deployment.py 2011-10-01 04:32:56 +0000
+++ juju/machine/tests/test_unit_deployment.py 2011-10-05 18:13:26 +0000
@@ -265,7 +265,7 @@
265 self.assertTrue(os.path.exists(charm_path))265 self.assertTrue(os.path.exists(charm_path))
266 charm = get_charm_from_path(charm_path)266 charm = get_charm_from_path(charm_path)
267 self.assertEqual(267 self.assertEqual(
268 charm.metadata.revision, self.charm.metadata.revision)268 charm.get_revision(), self.charm.get_revision())
269269
270 def test_unpack_charm_exception_invalid_charm(self):270 def test_unpack_charm_exception_invalid_charm(self):
271 """271 """
272272
=== modified file 'juju/state/charm.py'
--- juju/state/charm.py 2011-09-28 23:54:04 +0000
+++ juju/state/charm.py 2011-10-05 18:13:26 +0000
@@ -73,7 +73,6 @@
7373
74 # Just a health check:74 # Just a health check:
75 assert self._metadata.name == self.name75 assert self._metadata.name == self.name
76 assert self._metadata.revision == self.revision
7776
78 self._sha256 = charm_data["sha256"]77 self._sha256 = charm_data["sha256"]
7978
8079
=== modified file 'juju/state/tests/test_charm.py'
--- juju/state/tests/test_charm.py 2011-09-28 00:36:30 +0000
+++ juju/state/tests/test_charm.py 2011-10-05 18:13:26 +0000
@@ -79,7 +79,6 @@
79 "local:series/dummy-1")79 "local:series/dummy-1")
80 metadata = yield charm_state.get_metadata()80 metadata = yield charm_state.get_metadata()
81 self.assertEquals(metadata.name, "dummy")81 self.assertEquals(metadata.name, "dummy")
82 self.assertEquals(metadata.revision, 1)
8382
84 @inlineCallbacks83 @inlineCallbacks
85 def test_charm_state_config_options(self):84 def test_charm_state_config_options(self):
8685
=== modified file 'juju/unit/tests/test_charm.py'
--- juju/unit/tests/test_charm.py 2011-09-28 23:04:39 +0000
+++ juju/unit/tests/test_charm.py 2011-10-05 18:13:26 +0000
@@ -84,7 +84,7 @@
8484
85 self.assertTrue(os.path.exists(charm_path))85 self.assertTrue(os.path.exists(charm_path))
86 bundle = CharmBundle(charm_path)86 bundle = CharmBundle(charm_path)
87 self.assertEquals(bundle.metadata.revision, charm.metadata.revision)87 self.assertEquals(bundle.get_revision(), charm.get_revision())
8888
89 self.assertEqual(checksum, bundle.get_sha256())89 self.assertEqual(checksum, bundle.get_sha256())
9090

Subscribers

People subscribed via source and target branches

to status/vote changes: