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
1=== modified file 'examples/oneiric/mysql/metadata.yaml'
2--- examples/oneiric/mysql/metadata.yaml 2011-09-15 19:24:47 +0000
3+++ examples/oneiric/mysql/metadata.yaml 2011-10-05 18:13:26 +0000
4@@ -1,5 +1,4 @@
5 name: mysql
6-revision: 11
7 summary: "MySQL relational database provider"
8 description: |
9 Installs and configures the MySQL package (mysqldb), then runs it.
10
11=== added file 'examples/oneiric/mysql/revision'
12--- examples/oneiric/mysql/revision 1970-01-01 00:00:00 +0000
13+++ examples/oneiric/mysql/revision 2011-10-05 18:13:26 +0000
14@@ -0,0 +1,1 @@
15+11
16
17=== modified file 'examples/oneiric/php/metadata.yaml'
18--- examples/oneiric/php/metadata.yaml 2011-09-15 19:24:47 +0000
19+++ examples/oneiric/php/metadata.yaml 2011-10-05 18:13:26 +0000
20@@ -1,5 +1,4 @@
21 name: php
22-revision: 5
23 summary: "php container"
24 description: |
25 PHP environment for your code.
26
27=== added file 'examples/oneiric/php/revision'
28--- examples/oneiric/php/revision 1970-01-01 00:00:00 +0000
29+++ examples/oneiric/php/revision 2011-10-05 18:13:26 +0000
30@@ -0,0 +1,1 @@
31+5
32
33=== modified file 'examples/oneiric/wordpress/metadata.yaml'
34--- examples/oneiric/wordpress/metadata.yaml 2011-09-15 19:24:47 +0000
35+++ examples/oneiric/wordpress/metadata.yaml 2011-10-05 18:13:26 +0000
36@@ -1,5 +1,4 @@
37 name: wordpress
38-revision: 31
39 summary: "WordPress blog"
40 description: |
41 Installs WordPress package (wordpress). Upon the database provider
42
43=== added file 'examples/oneiric/wordpress/revision'
44--- examples/oneiric/wordpress/revision 1970-01-01 00:00:00 +0000
45+++ examples/oneiric/wordpress/revision 2011-10-05 18:13:26 +0000
46@@ -0,0 +1,1 @@
47+31
48
49=== modified file 'juju/agents/tests/test_machine.py'
50--- juju/agents/tests/test_machine.py 2011-09-28 09:48:30 +0000
51+++ juju/agents/tests/test_machine.py 2011-10-05 18:13:26 +0000
52@@ -170,7 +170,7 @@
53 self.assertTrue(os.path.exists(charm_path))
54 bundle = CharmBundle(charm_path)
55 self.assertEquals(
56- bundle.metadata.revision, self.charm.metadata.revision)
57+ bundle.get_revision(), self.charm.get_revision())
58 self.assertEquals(bundle.get_sha256(), checksum)
59 self.assertIn(
60 "Downloading charm %s" % charm_id, self.output.getvalue())
61
62=== modified file 'juju/agents/tests/test_unit.py'
63--- juju/agents/tests/test_unit.py 2011-10-04 21:20:30 +0000
64+++ juju/agents/tests/test_unit.py 2011-10-05 18:13:26 +0000
65@@ -598,7 +598,7 @@
66 os.path.join(self.agent.unit_directory, "charm"))
67
68 self.assertEqual(
69- self.charm.metadata.revision + 1, new_charm.metadata.revision)
70+ self.charm.get_revision() + 1, new_charm.get_revision())
71
72 @inlineCallbacks
73 def test_agent_upgrade_bad_unit_state(self):
74@@ -694,7 +694,7 @@
75 os.path.join(self.agent.unit_directory, "charm"))
76
77 self.assertEqual(
78- self.charm.metadata.revision + 1, new_charm.metadata.revision)
79+ self.charm.get_revision() + 1, new_charm.get_revision())
80
81 # Verify upgrade flag is cleared.
82 self.assertFalse((yield self.states["unit"].get_upgrade_flag()))
83
84=== modified file 'juju/charm/base.py'
85--- juju/charm/base.py 2011-09-15 18:50:23 +0000
86+++ juju/charm/base.py 2011-10-05 18:13:26 +0000
87@@ -1,3 +1,20 @@
88+from juju.errors import CharmError
89+
90+
91+def get_revision(file_content, metadata, path):
92+ if file_content is None:
93+ result = metadata.obsolete_revision
94+ if result is None:
95+ raise CharmError(path, "has no revision")
96+ else:
97+ message = "invalid charm revision %r" % file_content
98+ try:
99+ result = int(file_content.strip())
100+ except (ValueError, TypeError):
101+ raise CharmError(path, message)
102+ if result < 0:
103+ raise CharmError(path, message)
104+ return result
105
106
107 class CharmBase(object):
108@@ -10,6 +27,20 @@
109 raise NotImplementedError("%s.%s not supported" %
110 (self.__class__.__name__, attr))
111
112+ def get_revision(self):
113+ """Get the revision, preferably from the revision file.
114+
115+ Will fall back to metadata if not available.
116+ """
117+ self._unsupported("get_revision()")
118+
119+ def set_revision(self, revision):
120+ """Update the revision file, if possible.
121+
122+ Some subclasses may not be able to do this.
123+ """
124+ self._unsupported("set_revision()")
125+
126 def as_bundle(self):
127 """Transform this charm into a charm bundle, if possible.
128
129
130=== modified file 'juju/charm/bundle.py'
131--- juju/charm/bundle.py 2011-09-15 18:50:23 +0000
132+++ juju/charm/bundle.py 2011-10-05 18:13:26 +0000
133@@ -4,7 +4,7 @@
134
135 from zipfile import ZipFile, BadZipfile
136
137-from juju.charm.base import CharmBase
138+from juju.charm.base import CharmBase, get_revision
139 from juju.charm.config import ConfigOptions
140 from juju.charm.metadata import MetaData
141 from juju.errors import CharmError
142@@ -15,28 +15,31 @@
143 """ZIP-archive that contains charm directory content."""
144
145 def __init__(self, path):
146+ self.path = isinstance(path, file) and path.name or path
147 try:
148 zf = ZipFile(path, 'r')
149 except BadZipfile, exc:
150 raise CharmError(path, "must be a zip file (%s)" % exc)
151
152- metadata = MetaData()
153 if "metadata.yaml" not in zf.namelist():
154- raise CharmError(path,
155- "archive does not contain required file "
156- "\"metadata.yaml\"")
157-
158- content = zf.read("metadata.yaml")
159- metadata.parse(content)
160- self.metadata = metadata
161-
162- config = ConfigOptions()
163+ raise CharmError(
164+ path, "charm does not contain required file 'metadata.yaml'")
165+ self.metadata = MetaData()
166+ self.metadata.parse(zf.read("metadata.yaml"))
167+
168+ try:
169+ revision_content = zf.read("revision")
170+ except KeyError:
171+ revision_content = None
172+ self._revision = get_revision(
173+ revision_content, self.metadata, self.path)
174+
175+ self.config = ConfigOptions()
176 if "config.yaml" in zf.namelist():
177- content = zf.read("config.yaml")
178- config.parse(content)
179- self.config = config
180+ self.config.parse(zf.read("config.yaml"))
181
182- self.path = isinstance(path, file) and path.name or path
183+ def get_revision(self):
184+ return self._revision
185
186 def compute_sha256(self):
187 """Return the SHA256 digest for this charm bundle.
188@@ -49,7 +52,7 @@
189 """Extract the bundle to directory path and return a
190 CharmDirectory handle"""
191 from .directory import CharmDirectory
192- zf = ZipFile(self.path, 'r')
193+ zf = ZipFile(self.path, "r")
194 for info in zf.infolist():
195 mode = info.external_attr >> 16
196 extract_path = zf.extract(info, directory_path)
197
198=== modified file 'juju/charm/directory.py'
199--- juju/charm/directory.py 2011-09-15 19:24:47 +0000
200+++ juju/charm/directory.py 2011-10-05 18:13:26 +0000
201@@ -2,10 +2,11 @@
202 import zipfile
203 import tempfile
204
205+from juju.charm.base import CharmBase, get_revision
206+from juju.charm.bundle import CharmBundle
207 from juju.charm.config import ConfigOptions
208 from juju.charm.metadata import MetaData
209-from juju.charm.bundle import CharmBundle
210-from juju.charm.base import CharmBase
211+from juju.errors import CharmError
212
213
214 class CharmDirectory(CharmBase):
215@@ -22,11 +23,30 @@
216 def __init__(self, path):
217 self.path = path
218 self.metadata = MetaData(os.path.join(path, "metadata.yaml"))
219+
220+ revision_content = None
221+ revision_path = os.path.join(self.path, "revision")
222+ if os.path.exists(revision_path):
223+ with open(revision_path) as f:
224+ revision_content = f.read()
225+ self._revision = get_revision(
226+ revision_content, self.metadata, self.path)
227+ if revision_content is None:
228+ self.set_revision(self._revision)
229+
230 self.config = ConfigOptions()
231 self.config.load(os.path.join(path, "config.yaml"))
232 self._temp_bundle = None
233 self._temp_bundle_file = None
234
235+ def get_revision(self):
236+ return self._revision
237+
238+ def set_revision(self, revision):
239+ self._revision = revision
240+ with open(os.path.join(self.path, "revision"), "w") as f:
241+ f.write(str(revision) + "\n")
242+
243 def make_archive(self, path):
244 """Create archive of directory and write to ``path``.
245
246@@ -60,8 +80,7 @@
247
248 def as_bundle(self):
249 if self._temp_bundle is None:
250- prefix = "%s-%d.charm." % \
251- (self.metadata.name, self.metadata.revision)
252+ prefix = "%s-%d.charm." % (self.metadata.name, self.get_revision())
253 temp_file = tempfile.NamedTemporaryFile(prefix=prefix)
254 self.make_archive(temp_file.name)
255 self._temp_bundle = CharmBundle(temp_file.name)
256
257=== modified file 'juju/charm/metadata.py'
258--- juju/charm/metadata.py 2011-09-28 23:04:39 +0000
259+++ juju/charm/metadata.py 2011-10-05 18:13:26 +0000
260@@ -1,15 +1,17 @@
261+import logging
262 import os
263
264 import yaml
265
266 from juju.errors import JujuError, FileNotFound
267-from juju.charm.errors import CharmURLError
268-from juju.charm.url import CharmURL
269 from juju.lib.schema import (
270 SchemaError, Bool, Constant, Dict, Int,
271 KeyDict, OneOf, UnicodeOrString)
272
273
274+log = logging.getLogger("juju.charm")
275+
276+
277 class MetaDataError(JujuError):
278 """Raised when an error in the info file of a charm is found."""
279
280@@ -88,7 +90,7 @@
281 "peers": Dict(UTF8_SCHEMA, InterfaceExpander(limit=1)),
282 "provides": Dict(UTF8_SCHEMA, InterfaceExpander(limit=None)),
283 "requires": Dict(UTF8_SCHEMA, InterfaceExpander(limit=1)),
284- }, optional=set(["provides", "requires", "peers"]))
285+ }, optional=set(["provides", "requires", "peers", "revision"]))
286
287
288 class MetaData(object):
289@@ -110,12 +112,13 @@
290 return self._data.get("name")
291
292 @property
293- def revision(self):
294+ def obsolete_revision(self):
295 """The charm revision.
296
297 The charm revision acts as a version, but unlike e.g. package
298 versions, the charm revision is a monotonically increasing
299- integer.
300+ integer. This should not be stored in metadata any more, but remains
301+ for backward compatibility's sake.
302 """
303 return self._data.get("revision")
304
305@@ -174,7 +177,11 @@
306
307 @raise MetaDataError: When errors are found in the info data.
308 """
309- return self.parse_serialization_data(yaml.load(content), path)
310+ self.parse_serialization_data(yaml.load(content), path)
311+ if "revision" in self._data and path:
312+ log.warning(
313+ "%s: revision field is obsolete. Move it to the 'revision' "
314+ "file." % path)
315
316 def parse_serialization_data(self, serialization_data, path=None):
317 """Parse the unprocessed serialization data and load in this instance.
318
319=== modified file 'juju/charm/repository.py'
320--- juju/charm/repository.py 2011-10-05 09:51:16 +0000
321+++ juju/charm/repository.py 2011-10-05 18:13:26 +0000
322@@ -61,20 +61,20 @@
323 latest = None
324 for charm in self._collection(charm_url.collection):
325 if charm.metadata.name == charm_url.name:
326- if charm.metadata.revision == charm_url.revision:
327+ if charm.get_revision() == charm_url.revision:
328 return succeed(charm)
329 if (latest is None or
330- latest.metadata.revision < charm.metadata.revision):
331+ latest.get_revision() < charm.get_revision()):
332 latest = charm
333
334- if latest is None:
335+ if latest is None or charm_url.revision is not None:
336 return fail(CharmNotFound(self.path, charm_url))
337
338 return succeed(latest)
339
340 def latest(self, charm_url):
341 d = self.find(charm_url.with_revision(None))
342- d.addCallback(lambda c: c.metadata.revision)
343+ d.addCallback(lambda c: c.get_revision())
344 return d
345
346
347@@ -128,7 +128,7 @@
348 yield self._download(charm_url, cache_path)
349 charm = get_charm_from_path(cache_path)
350
351- assert charm.metadata.revision == revision, "bad charm revision"
352+ assert charm.get_revision() == revision, "bad charm revision"
353 if charm.get_sha256() != info["sha256"]:
354 os.remove(cache_path)
355 name = "%s (%s)" % (
356
357=== modified file 'juju/charm/tests/__init__.py'
358--- juju/charm/tests/__init__.py 2011-09-28 00:36:30 +0000
359+++ juju/charm/tests/__init__.py 2011-10-05 18:13:26 +0000
360@@ -1,3 +1,3 @@
361 def local_charm_id(charm):
362 return "local:series/%s-%s" % (
363- charm.metadata.name, charm.metadata.revision)
364+ charm.metadata.name, charm.get_revision())
365
366=== modified file 'juju/charm/tests/repository/series/dummy/metadata.yaml'
367--- juju/charm/tests/repository/series/dummy/metadata.yaml 2011-09-15 19:24:47 +0000
368+++ juju/charm/tests/repository/series/dummy/metadata.yaml 2011-10-05 18:13:26 +0000
369@@ -1,5 +1,4 @@
370 name: dummy
371-revision: 1
372 summary: "That's a dummy charm."
373 description: |
374 This is a longer description which
375
376=== added file 'juju/charm/tests/repository/series/dummy/revision'
377--- juju/charm/tests/repository/series/dummy/revision 1970-01-01 00:00:00 +0000
378+++ juju/charm/tests/repository/series/dummy/revision 2011-10-05 18:13:26 +0000
379@@ -0,0 +1,1 @@
380+1
381\ No newline at end of file
382
383=== modified file 'juju/charm/tests/repository/series/mysql-alternative/metadata.yaml'
384--- juju/charm/tests/repository/series/mysql-alternative/metadata.yaml 2011-09-28 08:55:57 +0000
385+++ juju/charm/tests/repository/series/mysql-alternative/metadata.yaml 2011-10-05 18:13:26 +0000
386@@ -1,5 +1,4 @@
387 name: mysql-alternative
388-revision: 1
389 summary: "Database engine"
390 description: "A pretty popular database"
391 provides:
392
393=== added file 'juju/charm/tests/repository/series/mysql-alternative/revision'
394--- juju/charm/tests/repository/series/mysql-alternative/revision 1970-01-01 00:00:00 +0000
395+++ juju/charm/tests/repository/series/mysql-alternative/revision 2011-10-05 18:13:26 +0000
396@@ -0,0 +1,1 @@
397+1
398\ No newline at end of file
399
400=== modified file 'juju/charm/tests/repository/series/mysql/metadata.yaml'
401--- juju/charm/tests/repository/series/mysql/metadata.yaml 2011-09-15 19:24:47 +0000
402+++ juju/charm/tests/repository/series/mysql/metadata.yaml 2011-10-05 18:13:26 +0000
403@@ -1,5 +1,4 @@
404 name: mysql
405-revision: 1
406 summary: "Database engine"
407 description: "A pretty popular database"
408 provides:
409
410=== added file 'juju/charm/tests/repository/series/mysql/revision'
411--- juju/charm/tests/repository/series/mysql/revision 1970-01-01 00:00:00 +0000
412+++ juju/charm/tests/repository/series/mysql/revision 2011-10-05 18:13:26 +0000
413@@ -0,0 +1,1 @@
414+1
415\ No newline at end of file
416
417=== modified file 'juju/charm/tests/repository/series/new/metadata.yaml'
418--- juju/charm/tests/repository/series/new/metadata.yaml 2011-09-15 19:24:47 +0000
419+++ juju/charm/tests/repository/series/new/metadata.yaml 2011-10-05 18:13:26 +0000
420@@ -1,5 +1,4 @@
421 name: sample
422-revision: 2
423 summary: "That's a sample charm."
424 description: |
425 This is a longer description which
426
427=== added file 'juju/charm/tests/repository/series/new/revision'
428--- juju/charm/tests/repository/series/new/revision 1970-01-01 00:00:00 +0000
429+++ juju/charm/tests/repository/series/new/revision 2011-10-05 18:13:26 +0000
430@@ -0,0 +1,1 @@
431+2
432
433=== modified file 'juju/charm/tests/repository/series/old/metadata.yaml'
434--- juju/charm/tests/repository/series/old/metadata.yaml 2011-09-15 19:24:47 +0000
435+++ juju/charm/tests/repository/series/old/metadata.yaml 2011-10-05 18:13:26 +0000
436@@ -1,5 +1,4 @@
437 name: sample
438-revision: 1
439 summary: "That's a sample charm."
440 description: |
441 This is a longer description which
442
443=== added file 'juju/charm/tests/repository/series/old/revision'
444--- juju/charm/tests/repository/series/old/revision 1970-01-01 00:00:00 +0000
445+++ juju/charm/tests/repository/series/old/revision 2011-10-05 18:13:26 +0000
446@@ -0,0 +1,1 @@
447+1
448
449=== modified file 'juju/charm/tests/repository/series/riak/metadata.yaml'
450--- juju/charm/tests/repository/series/riak/metadata.yaml 2011-09-15 19:24:47 +0000
451+++ juju/charm/tests/repository/series/riak/metadata.yaml 2011-10-05 18:13:26 +0000
452@@ -1,5 +1,4 @@
453 name: riak
454-revision: 7
455 summary: "K/V storage engine"
456 description: "Scalable K/V Store in Erlang with Clocks :-)"
457 provides:
458
459=== added file 'juju/charm/tests/repository/series/riak/revision'
460--- juju/charm/tests/repository/series/riak/revision 1970-01-01 00:00:00 +0000
461+++ juju/charm/tests/repository/series/riak/revision 2011-10-05 18:13:26 +0000
462@@ -0,0 +1,1 @@
463+7
464\ No newline at end of file
465
466=== modified file 'juju/charm/tests/repository/series/varnish-alternative/metadata.yaml'
467--- juju/charm/tests/repository/series/varnish-alternative/metadata.yaml 2011-09-28 08:55:57 +0000
468+++ juju/charm/tests/repository/series/varnish-alternative/metadata.yaml 2011-10-05 18:13:26 +0000
469@@ -1,5 +1,4 @@
470 name: varnish-alternative
471-revision: 1
472 summary: "Database engine"
473 description: "Another popular database"
474 provides:
475
476=== added file 'juju/charm/tests/repository/series/varnish-alternative/revision'
477--- juju/charm/tests/repository/series/varnish-alternative/revision 1970-01-01 00:00:00 +0000
478+++ juju/charm/tests/repository/series/varnish-alternative/revision 2011-10-05 18:13:26 +0000
479@@ -0,0 +1,1 @@
480+1
481\ No newline at end of file
482
483=== modified file 'juju/charm/tests/repository/series/varnish/metadata.yaml'
484--- juju/charm/tests/repository/series/varnish/metadata.yaml 2011-09-15 19:24:47 +0000
485+++ juju/charm/tests/repository/series/varnish/metadata.yaml 2011-10-05 18:13:26 +0000
486@@ -1,5 +1,4 @@
487 name: varnish
488-revision: 1
489 summary: "Database engine"
490 description: "Another popular database"
491 provides:
492
493=== added file 'juju/charm/tests/repository/series/varnish/revision'
494--- juju/charm/tests/repository/series/varnish/revision 1970-01-01 00:00:00 +0000
495+++ juju/charm/tests/repository/series/varnish/revision 2011-10-05 18:13:26 +0000
496@@ -0,0 +1,1 @@
497+1
498\ No newline at end of file
499
500=== modified file 'juju/charm/tests/repository/series/wordpress/metadata.yaml'
501--- juju/charm/tests/repository/series/wordpress/metadata.yaml 2011-09-15 19:24:47 +0000
502+++ juju/charm/tests/repository/series/wordpress/metadata.yaml 2011-10-05 18:13:26 +0000
503@@ -1,5 +1,4 @@
504 name: wordpress
505-revision: 3
506 summary: "Blog engine"
507 description: "A pretty popular blog engine"
508 provides:
509
510=== added file 'juju/charm/tests/repository/series/wordpress/revision'
511--- juju/charm/tests/repository/series/wordpress/revision 1970-01-01 00:00:00 +0000
512+++ juju/charm/tests/repository/series/wordpress/revision 2011-10-05 18:13:26 +0000
513@@ -0,0 +1,1 @@
514+3
515\ No newline at end of file
516
517=== modified file 'juju/charm/tests/test_base.py'
518--- juju/charm/tests/test_base.py 2011-09-15 18:50:23 +0000
519+++ juju/charm/tests/test_base.py 2011-10-05 18:13:26 +0000
520@@ -1,5 +1,9 @@
521+import yaml
522+
523+from juju.charm.base import CharmBase, get_revision
524+from juju.charm.metadata import MetaData
525+from juju.errors import CharmError
526 from juju.lib.testing import TestCase
527-from juju.charm.base import CharmBase
528
529
530 class MyCharm(CharmBase):
531@@ -20,21 +24,13 @@
532 else:
533 self.fail("MyCharm.%s didn't fail" % attr_name)
534
535- def test_as_bundle_is_unsupported(self):
536- self.assertUnsupported(lambda: self.charm.as_bundle(),
537- "as_bundle()")
538-
539- def test_compute_sha256_is_unsupported(self):
540- self.assertUnsupported(lambda: self.charm.compute_sha256(),
541- "compute_sha256()")
542-
543- def test_get_sha256_fails_if_not_preset(self):
544- """
545- get_sha256() will internally call compute_sha256() if
546- the digest value hasn't been previously set.
547- """
548- self.assertUnsupported(lambda: self.charm.get_sha256(),
549- "compute_sha256()")
550+ def test_unsupported(self):
551+ self.assertUnsupported(self.charm.as_bundle, "as_bundle()")
552+ self.assertUnsupported(self.charm.get_sha256, "compute_sha256()")
553+ self.assertUnsupported(self.charm.compute_sha256, "compute_sha256()")
554+ self.assertUnsupported(self.charm.get_revision, "get_revision()")
555+ self.assertUnsupported(
556+ lambda: self.charm.set_revision(1), "set_revision()")
557
558 def test_compute_and_cache_sha256(self):
559 """
560@@ -53,3 +49,33 @@
561 sha256 = ["anothervalue"]
562 # Should still be the same, since the old one was cached.
563 self.assertEquals(charm.get_sha256(), "mysha")
564+
565+
566+class GetRevisionTest(TestCase):
567+
568+ def assert_good_content(self, content, value):
569+ self.assertEquals(get_revision(content, None, None), value)
570+
571+ def assert_bad_content(self, content):
572+ err = self.assertRaises(
573+ CharmError, get_revision, content, None, "path")
574+ self.assertEquals(
575+ str(err),
576+ "Error processing 'path': invalid charm revision %r" % content)
577+
578+ def test_with_content(self):
579+ self.assert_good_content("0\n", 0)
580+ self.assert_good_content("123\n", 123)
581+ self.assert_bad_content("")
582+ self.assert_bad_content("-1\n")
583+ self.assert_bad_content("three hundred and six or so")
584+
585+ def test_metadata_fallback(self):
586+ metadata = MetaData()
587+ err = self.assertRaises(
588+ CharmError, get_revision, None, metadata, "path")
589+ self.assertEquals(
590+ str(err), "Error processing 'path': has no revision")
591+ metadata.parse(yaml.dump({
592+ "name": "x", "summary": "y", "description": "z","revision": 33}))
593+ self.assertEquals(get_revision(None, metadata, None), 33)
594
595=== modified file 'juju/charm/tests/test_bundle.py'
596--- juju/charm/tests/test_bundle.py 2011-09-28 08:55:57 +0000
597+++ juju/charm/tests/test_bundle.py 2011-10-05 18:13:26 +0000
598@@ -1,7 +1,7 @@
599 import os
600-import stat
601 import hashlib
602 import inspect
603+import yaml
604 import zipfile
605
606 from juju.lib.testing import TestCase
607@@ -35,16 +35,11 @@
608
609 def test_error_not_zip(self):
610 filename = self.makeFile("@#$@$")
611- try:
612- CharmBundle(filename)
613- except CharmError, exc:
614- self.assertEquals(
615- str(exc),
616- ("Error processing %r: " % filename) +
617- "must be a zip file (File is not a zip file)")
618- return
619-
620- self.fail("Expected charm error.")
621+ err = self.assertRaises(CharmError, CharmBundle, filename)
622+ self.assertEquals(
623+ str(err),
624+ "Error processing %r: must be a zip file (File is not a zip file)"
625+ % filename)
626
627 def test_error_zip_but_doesnt_have_metadata_file(self):
628 filename = self.makeFile()
629@@ -52,16 +47,60 @@
630 zf.writestr("README.txt", "This is not a valid charm.")
631 zf.close()
632
633- try:
634- CharmBundle(filename)
635- except CharmError, exc:
636- self.assertEquals(
637- str(exc),
638- ("Error processing %r: " % filename) +
639- "archive does not contain required file \"metadata.yaml\"")
640- return
641-
642- self.fail("Expected charm error.")
643+ err = self.assertRaises(CharmError, CharmBundle, filename)
644+ self.assertEquals(
645+ str(err),
646+ "Error processing %r: charm does not contain required "
647+ "file 'metadata.yaml'" % filename)
648+
649+ def test_no_revision_at_all(self):
650+ filename = self.makeFile()
651+ zf_dst = zipfile.ZipFile(filename, "w")
652+ zf_src = zipfile.ZipFile(self.filename, "r")
653+ for name in zf_src.namelist():
654+ if name == "revision":
655+ continue
656+ zf_dst.writestr(name, zf_src.read(name))
657+ zf_src.close()
658+ zf_dst.close()
659+
660+ err = self.assertRaises(CharmError, CharmBundle, filename)
661+ self.assertEquals(
662+ str(err), "Error processing %r: has no revision" % filename)
663+
664+ def test_revision_in_metadata(self):
665+ filename = self.makeFile()
666+ zf_dst = zipfile.ZipFile(filename, "w")
667+ zf_src = zipfile.ZipFile(self.filename, "r")
668+ for name in zf_src.namelist():
669+ if name == "revision":
670+ continue
671+ content = zf_src.read(name)
672+ if name == "metadata.yaml":
673+ data = yaml.load(content)
674+ data["revision"] = 303
675+ content = yaml.dump(data)
676+ zf_dst.writestr(name, content)
677+ zf_src.close()
678+ zf_dst.close()
679+
680+ charm = CharmBundle(filename)
681+ self.assertEquals(charm.get_revision(), 303)
682+
683+ def test_competing_revisions(self):
684+ zf = zipfile.ZipFile(self.filename, "a")
685+ zf.writestr("revision", "999")
686+ data = yaml.load(zf.read("metadata.yaml"))
687+ data["revision"] = 303
688+ zf.writestr("metadata.yaml", yaml.dump(data))
689+ zf.close()
690+
691+ charm = CharmBundle(self.filename)
692+ self.assertEquals(charm.get_revision(), 999)
693+
694+ def test_cannot_set_revision(self):
695+ charm = CharmBundle(self.filename)
696+ self.assertRaises(NotImplementedError, charm.set_revision, 123)
697
698 def test_bundled_config(self):
699 """Make sure that config is accessible from a bundle."""
700
701=== modified file 'juju/charm/tests/test_directory.py'
702--- juju/charm/tests/test_directory.py 2011-09-28 10:38:16 +0000
703+++ juju/charm/tests/test_directory.py 2011-10-05 18:13:26 +0000
704@@ -1,11 +1,13 @@
705+import gc
706 import os
707 import hashlib
708 import inspect
709+import shutil
710 import tempfile
711+import yaml
712 import zipfile
713-import gc
714
715-from juju.errors import FileNotFound
716+from juju.errors import CharmError, FileNotFound
717 from juju.charm.metadata import MetaData
718 from juju.charm.directory import CharmDirectory
719 from juju.charm.bundle import CharmBundle
720@@ -31,10 +33,62 @@
721 if not os.path.isdir(empty_dir):
722 os.mkdir(empty_dir)
723
724+ def copy_charm(self):
725+ dir_ = os.path.join(tempfile.mkdtemp(), "sample")
726+ shutil.copytree(sample_directory, dir_)
727+ return dir_
728+
729+ def delete_revision(self, dir_):
730+ os.remove(os.path.join(dir_, "revision"))
731+
732+ def set_metadata_revision(self, dir_, revision):
733+ metadata_path = os.path.join(dir_, "metadata.yaml")
734+ with open(metadata_path) as f:
735+ data = yaml.load(f.read())
736+ data["revision"] = 999
737+ with open(metadata_path, "w") as f:
738+ f.write(yaml.dump(data))
739+
740 def test_metadata_is_required(self):
741 directory = self.makeDir()
742 self.assertRaises(FileNotFound, CharmDirectory, directory)
743
744+ def test_no_revision(self):
745+ dir_ = self.copy_charm()
746+ self.delete_revision(dir_)
747+ err = self.assertRaises(CharmError, CharmDirectory, dir_)
748+ self.assertEquals(
749+ str(err), "Error processing %r: has no revision" % dir_)
750+
751+ def test_revision_in_metadata(self):
752+ dir_ = self.copy_charm()
753+ self.delete_revision(dir_)
754+ self.set_metadata_revision(dir_, 999)
755+ log = self.capture_logging("juju.charm")
756+ charm = CharmDirectory(dir_)
757+ self.assertEquals(charm.get_revision(), 999)
758+ self.assertIn(
759+ "revision field is obsolete. Move it to the 'revision' file.",
760+ log.getvalue())
761+
762+ def test_competing_revisions(self):
763+ dir_ = self.copy_charm()
764+ self.set_metadata_revision(dir_, 999)
765+ log = self.capture_logging("juju.charm")
766+ charm = CharmDirectory(dir_)
767+ self.assertEquals(charm.get_revision(), 1)
768+ self.assertIn(
769+ "revision field is obsolete. Move it to the 'revision' file.",
770+ log.getvalue())
771+
772+ def test_set_revision(self):
773+ dir_ = self.copy_charm()
774+ charm = CharmDirectory(dir_)
775+ charm.set_revision(123)
776+ self.assertEquals(charm.get_revision(), 123)
777+ with open(os.path.join(dir_, "revision")) as f:
778+ self.assertEquals(f.read(), "123\n")
779+
780 def test_info(self):
781 directory = CharmDirectory(sample_directory)
782 self.assertTrue(directory.metadata is not None)
783@@ -57,7 +111,7 @@
784 self.assertEqual(
785 set(included),
786 set(("metadata.yaml", "empty/", "src/", "src/hello.c",
787- "config.yaml", "hooks/", "hooks/install")))
788+ "config.yaml", "hooks/", "hooks/install", "revision")))
789
790 def test_as_bundle(self):
791 directory = CharmDirectory(self.sample_dir1)
792@@ -66,10 +120,14 @@
793 self.assertEquals(charm_bundle.metadata.name, "sample")
794 self.assertIn("sample-1.charm", charm_bundle.path)
795
796+ total_compressed = 0
797+ total_uncompressed = 0
798 zip_file = zipfile.ZipFile(charm_bundle.path)
799 for n in zip_file.namelist():
800 info = zip_file.getinfo(n)
801- self.assertTrue(info.compress_size < info.file_size)
802+ total_compressed += info.compress_size
803+ total_uncompressed += info.file_size
804+ self.assertTrue(total_compressed < total_uncompressed)
805
806 def test_as_bundle_file_lifetime(self):
807 """
808
809=== modified file 'juju/charm/tests/test_metadata.py'
810--- juju/charm/tests/test_metadata.py 2011-09-28 23:04:39 +0000
811+++ juju/charm/tests/test_metadata.py 2011-10-05 18:13:26 +0000
812@@ -5,7 +5,6 @@
813 import inspect
814
815 from juju.charm import tests
816-from juju.charm.errors import CharmURLError
817 from juju.charm.metadata import (
818 MetaData, MetaDataError, InterfaceExpander, SchemaError)
819 from juju.errors import FileNotFound
820@@ -57,7 +56,7 @@
821 Attributes should be set to None before anything is loaded.
822 """
823 self.assertEquals(self.metadata.name, None)
824- self.assertEquals(self.metadata.revision, None)
825+ self.assertEquals(self.metadata.obsolete_revision, None)
826 self.assertEquals(self.metadata.summary, None)
827 self.assertEquals(self.metadata.description, None)
828
829@@ -68,11 +67,38 @@
830 """
831 self.metadata.parse(self.sample)
832 self.assertEquals(self.metadata.name, "dummy")
833- self.assertEquals(self.metadata.revision, 1)
834- self.assertEquals(self.metadata.summary, u"That's a dummy charm.")
835- self.assertEquals(self.metadata.description,
836- u"This is a longer description which\n"
837- u"potentially contains multiple lines.\n")
838+ self.assertEquals(self.metadata.obsolete_revision, None)
839+ self.assertEquals(self.metadata.summary, u"That's a dummy charm.")
840+ self.assertEquals(self.metadata.description,
841+ u"This is a longer description which\n"
842+ u"potentially contains multiple lines.\n")
843+
844+ def assert_parse_with_revision(self, with_path):
845+ """
846+ Parsing the content file should work. :-) Basic information will
847+ be available as attributes of the info file.
848+ """
849+ with self.change_sample() as data:
850+ data["revision"] = 123
851+ log = self.capture_logging("juju.charm")
852+ self.metadata.parse(self.sample, "some/path" if with_path else None)
853+ if with_path:
854+ self.assertIn(
855+ "some/path: revision field is obsolete. Move it to the "
856+ "'revision' file.",
857+ log.getvalue())
858+ self.assertEquals(self.metadata.name, "dummy")
859+ self.assertEquals(self.metadata.obsolete_revision, 123)
860+ self.assertEquals(self.metadata.summary, u"That's a dummy charm.")
861+ self.assertEquals(self.metadata.description,
862+ u"This is a longer description which\n"
863+ u"potentially contains multiple lines.\n")
864+ self.assertEquals(
865+ self.metadata.get_serialization_data()["revision"], 123)
866+
867+ def test_parse_with_revision(self):
868+ self.assert_parse_with_revision(True)
869+ self.assert_parse_with_revision(False)
870
871 def test_load_calls_parse_calls_parse_serialzation_data(self):
872 """
873@@ -118,18 +144,6 @@
874 self.metadata.load, filename)
875 self.assertEquals(error.path, filename)
876
877- def test_revision_is_int(self):
878- """
879- Revision numbers are *integers*. Yep, no 1.2.3 versioning.
880- """
881- with self.change_sample() as data:
882- data["revision"] = "1"
883- error = self.assertRaises(MetaDataError,
884- self.metadata.parse, self.sample)
885- self.assertEquals(str(error),
886- "Bad data in charm info: revision: "
887- "expected int, got '1'")
888-
889 def test_name_summary_and_description_are_utf8(self):
890 """
891 Textual fields are decoded to unicode by the schema using UTF-8.
892
893=== modified file 'juju/charm/tests/test_repository.py'
894--- juju/charm/tests/test_repository.py 2011-10-05 09:51:16 +0000
895+++ juju/charm/tests/test_repository.py 2011-10-05 18:13:26 +0000
896@@ -63,16 +63,15 @@
897 return CharmURL.parse("local:series/" + name)
898
899 @inlineCallbacks
900- def assert_not_there(self, name):
901+ def assert_not_there(self, name, repo, revision=None):
902 url = self.charm_url(name)
903 msg = "Charm 'local:series/%s' not found in repository %s" % (
904- name, self.unbundled_repo_path)
905- err = yield self.assertFailure(
906- self.repository1.find(url), CharmNotFound)
907- self.assertEquals(str(err), msg)
908- err = yield self.assertFailure(
909- self.repository1.latest(url), CharmNotFound)
910- self.assertEquals(str(err), msg)
911+ name, repo.path)
912+ err = yield self.assertFailure(repo.find(url), CharmNotFound)
913+ self.assertEquals(str(err), msg)
914+ if revision is None:
915+ err = yield self.assertFailure(repo.latest(url), CharmNotFound)
916+ self.assertEquals(str(err), msg)
917
918 def test_find_inappropriate_url(self):
919 url = CharmURL.parse("cs:foo/bar")
920@@ -80,18 +79,18 @@
921 self.assertEquals(str(err), "schema mismatch")
922
923 def test_completely_missing(self):
924- return self.assert_not_there("zebra")
925+ return self.assert_not_there("zebra", self.repository1)
926
927 def test_unkown_files_ignored(self):
928 self.makeFile(
929 "Foobar",
930 path=os.path.join(self.repository1.path, "series", "zebra"))
931- return self.assert_not_there("zebra")
932+ return self.assert_not_there("zebra", self.repository1)
933
934 def test_unknown_directories_ignored(self):
935 self.makeDir(
936 path=os.path.join(self.repository1.path, "series", "zebra"))
937- return self.assert_not_there("zebra")
938+ return self.assert_not_there("zebra", self.repository1)
939
940 def test_broken_charms_ignored(self):
941 charm_path = self.makeDir(
942@@ -104,37 +103,45 @@
943 revision: 0
944 summary: hola""")
945 fh.close()
946- return self.assert_not_there("zebra")
947+ return self.assert_not_there("zebra", self.repository1)
948
949 def assert_there(self, name, repo, revision, latest_revision=None):
950 url = self.charm_url(name)
951 charm = yield repo.find(url)
952- self.assertEquals(charm.metadata.revision, revision)
953+ self.assertEquals(charm.get_revision(), revision)
954 latest = yield repo.latest(url)
955 self.assertEquals(latest, latest_revision or revision)
956
957+ @inlineCallbacks
958 def test_success_unbundled(self):
959- return self.assert_there("sample", self.repository1, 2)
960- return self.assert_there("sample-2", self.repository1, 2)
961+ yield self.assert_there("sample", self.repository1, 2)
962+ yield self.assert_there("sample-1", self.repository1, 1, 2)
963+ yield self.assert_there("sample-2", self.repository1, 2)
964+ yield self.assert_not_there("sample-3", self.repository1, 2)
965
966+ @inlineCallbacks
967 def test_success_bundled(self):
968- return self.assert_there("sample", self.repository2, 2)
969- return self.assert_there("sample-2", self.repository2, 2)
970+ yield self.assert_there("sample", self.repository2, 2)
971+ yield self.assert_there("sample-1", self.repository2, 1, 2)
972+ yield self.assert_there("sample-2", self.repository2, 2)
973+ yield self.assert_not_there("sample-3", self.repository2, 2)
974
975 @inlineCallbacks
976 def test_no_revision_gets_latest(self):
977 yield self.assert_there("sample", self.repository1, 2)
978+ yield self.assert_there("sample-1", self.repository1, 1, 2)
979+ yield self.assert_there("sample-2", self.repository1, 2)
980+ yield self.assert_not_there("sample-3", self.repository1, 2)
981
982- file = open(os.path.join(
983- self.repository1.path, "series/old/metadata.yaml"), "rw+")
984- data = yaml.load(file.read())
985- data["revision"] = 3
986- file.seek(0)
987- file.write(yaml.dump(data))
988- file.close()
989+ revision_path = os.path.join(
990+ self.repository1.path, "series/old/revision")
991+ with open(revision_path, "w") as f:
992+ f.write("3")
993
994 yield self.assert_there("sample", self.repository1, 3)
995+ yield self.assert_not_there("sample-1", self.repository1, 3)
996 yield self.assert_there("sample-2", self.repository1, 2, 3)
997+ yield self.assert_there("sample-3", self.repository1, 3)
998
999
1000 class RemoteRepositoryTest(RepositoryTestBase):
1001
1002=== modified file 'juju/control/deploy.py'
1003--- juju/control/deploy.py 2011-09-30 11:25:41 +0000
1004+++ juju/control/deploy.py 2011-10-05 18:13:26 +0000
1005@@ -95,7 +95,7 @@
1006 service_options = parse_config_options(config_file, service_name)
1007
1008 charm = yield repo.find(charm_url)
1009- charm_id = str(charm_url.with_revision(charm.metadata.revision))
1010+ charm_id = str(charm_url.with_revision(charm.get_revision()))
1011
1012 provider = environment.get_machine_provider()
1013 placement_policy = provider.get_placement_policy()
1014
1015=== modified file 'juju/control/tests/test_upgrade_charm.py'
1016--- juju/control/tests/test_upgrade_charm.py 2011-09-29 03:07:26 +0000
1017+++ juju/control/tests/test_upgrade_charm.py 2011-10-05 18:13:26 +0000
1018@@ -15,23 +15,23 @@
1019
1020 class CharmUpgradeTestBase(object):
1021
1022- def add_charm(self, metadata, repository_dir=None):
1023+ def add_charm(self, metadata, revision, repository_dir=None):
1024 if repository_dir is None:
1025 repository_dir = self.makeDir()
1026 series_dir = os.path.join(repository_dir, "series")
1027 os.mkdir(series_dir)
1028 charm_dir = os.path.join(series_dir, metadata["name"])
1029 os.mkdir(charm_dir)
1030- fh = open(os.path.join(charm_dir, "metadata.yaml"), "w")
1031- fh.write(dump(metadata))
1032- fh.close()
1033+ with open(os.path.join(charm_dir, "metadata.yaml"), "w") as f:
1034+ f.write(dump(metadata))
1035+ with open(os.path.join(charm_dir, "revision"), "w") as f:
1036+ f.write(str(revision))
1037 return LocalCharmRepository(repository_dir)
1038
1039 def increment_charm(self, charm):
1040 metadata = charm.metadata.get_serialization_data()
1041 metadata["name"] = "mysql"
1042- metadata["revision"] = 2
1043- repository = self.add_charm(metadata)
1044+ repository = self.add_charm(metadata, charm.get_revision() + 1)
1045 return repository
1046
1047
1048@@ -201,7 +201,7 @@
1049
1050 metadata = self.charm.metadata.get_serialization_data()
1051 metadata["name"] = "mysql"
1052- repository = self.add_charm(metadata)
1053+ repository = self.add_charm(metadata, 1)
1054 main(["upgrade-charm", "--dry-run",
1055 "--repository", repository.path, "mysql"])
1056 yield finished
1057@@ -224,7 +224,7 @@
1058
1059 metadata = self.charm.metadata.get_serialization_data()
1060 metadata["name"] = "mysql"
1061- repository = self.add_charm(metadata)
1062+ repository = self.add_charm(metadata, 1)
1063 main(["upgrade-charm", "--repository", repository.path, "mysql"])
1064
1065 yield finished
1066
1067=== modified file 'juju/machine/tests/test_unit_deployment.py'
1068--- juju/machine/tests/test_unit_deployment.py 2011-10-01 04:32:56 +0000
1069+++ juju/machine/tests/test_unit_deployment.py 2011-10-05 18:13:26 +0000
1070@@ -265,7 +265,7 @@
1071 self.assertTrue(os.path.exists(charm_path))
1072 charm = get_charm_from_path(charm_path)
1073 self.assertEqual(
1074- charm.metadata.revision, self.charm.metadata.revision)
1075+ charm.get_revision(), self.charm.get_revision())
1076
1077 def test_unpack_charm_exception_invalid_charm(self):
1078 """
1079
1080=== modified file 'juju/state/charm.py'
1081--- juju/state/charm.py 2011-09-28 23:54:04 +0000
1082+++ juju/state/charm.py 2011-10-05 18:13:26 +0000
1083@@ -73,7 +73,6 @@
1084
1085 # Just a health check:
1086 assert self._metadata.name == self.name
1087- assert self._metadata.revision == self.revision
1088
1089 self._sha256 = charm_data["sha256"]
1090
1091
1092=== modified file 'juju/state/tests/test_charm.py'
1093--- juju/state/tests/test_charm.py 2011-09-28 00:36:30 +0000
1094+++ juju/state/tests/test_charm.py 2011-10-05 18:13:26 +0000
1095@@ -79,7 +79,6 @@
1096 "local:series/dummy-1")
1097 metadata = yield charm_state.get_metadata()
1098 self.assertEquals(metadata.name, "dummy")
1099- self.assertEquals(metadata.revision, 1)
1100
1101 @inlineCallbacks
1102 def test_charm_state_config_options(self):
1103
1104=== modified file 'juju/unit/tests/test_charm.py'
1105--- juju/unit/tests/test_charm.py 2011-09-28 23:04:39 +0000
1106+++ juju/unit/tests/test_charm.py 2011-10-05 18:13:26 +0000
1107@@ -84,7 +84,7 @@
1108
1109 self.assertTrue(os.path.exists(charm_path))
1110 bundle = CharmBundle(charm_path)
1111- self.assertEquals(bundle.metadata.revision, charm.metadata.revision)
1112+ self.assertEquals(bundle.get_revision(), charm.get_revision())
1113
1114 self.assertEqual(checksum, bundle.get_sha256())
1115

Subscribers

People subscribed via source and target branches

to status/vote changes: