Merge lp:~bcsaller/pyjuju/subordinate-charms into lp:pyjuju

Proposed by Benjamin Saller
Status: Superseded
Proposed branch: lp:~bcsaller/pyjuju/subordinate-charms
Merge into: lp:pyjuju
Diff against target: 749 lines (+528/-22)
12 files modified
.bzrignore (+2/-2)
docs/source/charm-upgrades.rst (+1/-2)
docs/source/charm.rst (+14/-0)
docs/source/drafts/implicit-relations.rst (+62/-0)
docs/source/drafts/subordinate-internals.rst (+168/-0)
docs/source/drafts/subordinate-services.rst (+170/-0)
juju/charm/metadata.py (+44/-4)
juju/charm/tests/repository/series/logging/.ignored (+1/-0)
juju/charm/tests/repository/series/logging/hooks/install (+2/-0)
juju/charm/tests/repository/series/logging/metadata.yaml (+13/-0)
juju/charm/tests/repository/series/logging/revision (+1/-0)
juju/charm/tests/test_metadata.py (+50/-14)
To merge this branch: bzr merge lp:~bcsaller/pyjuju/subordinate-charms
Reviewer Review Type Date Requested Status
Gustavo Niemeyer Needs Information
Review via email: mp+84562@code.launchpad.net

This proposal has been superseded by a proposal from 2012-02-09.

Description of the change

charm metadata support for subordinates

Charm directory, bundle and metadata support for subordinate charms and scoped relations

https://codereview.appspot.com/5532098/

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

As agreed and discussed in advance, the changes to the APIs and ZooKeeper handling of data
must be debated in advance in the mailing list.

review: Needs Information
Revision history for this message
Benjamin Saller (bcsaller) wrote :

Reviewers: mp+84562_code.launchpad.net,

Message:
Please take a look.

Description:
Charm directory, bundle and metadata support for subordinate interfaces

Includes changes related to the first round of feedback

https://code.launchpad.net/~bcsaller/juju/subordinate-charms/+merge/84562

(do not edit description out of merge proposal)

Please review this at https://codereview.appspot.com/5532098/

Affected files:
   M .bzrignore
   M docs/source/charm-upgrades.rst
   A docs/source/drafts/implicit-interfaces.rst
   A docs/source/drafts/subordinate-internals.rst
   A docs/source/drafts/subordinate-services.rst
   M juju/charm/metadata.py
   A juju/charm/tests/repository/series/logging/.ignored
   A juju/charm/tests/repository/series/logging/hooks/install
   A juju/charm/tests/repository/series/logging/metadata.yaml
   A juju/charm/tests/repository/series/logging/revision
   M juju/charm/tests/test_metadata.py

Revision history for this message
Benjamin Saller (bcsaller) wrote :

This was pushed in error, still pending the spec review so I can get the naming to what we agree on.

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

On 2012/01/19 08:59:45, bcsaller wrote:
> Please take a look.

Please hold off on merging this while we talk in the mailing list about
the specification.

https://codereview.appspot.com/5532098/

Revision history for this message
Benjamin Saller (bcsaller) wrote :
470. By Benjamin Saller

Merged subordinate-spec into subordinate-charms.

471. By Benjamin Saller

use defines throughout, is_subordinate only checks metadata flag

472. By Benjamin Saller

Merged subordinate-spec into subordinate-charms.

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

lgtm +1, one minor. woot! first branch of subordinates.

https://codereview.appspot.com/5532098/diff/4001/juju/charm/metadata.py
File juju/charm/metadata.py (right):

https://codereview.appspot.com/5532098/diff/4001/juju/charm/metadata.py#newcode228
juju/charm/metadata.py:228: path)
Feels like this should be an error that's raised.

https://codereview.appspot.com/5532098/

473. By Benjamin Saller

Merged subordinate-spec into subordinate-charms.

474. By Benjamin Saller

Merged subordinate-spec into subordinate-charms.

475. By Benjamin Saller

Merged subordinate-spec into subordinate-charms.

476. By Benjamin Saller

raise error when subordinate is improperly used

Revision history for this message
Benjamin Saller (bcsaller) wrote :

https://codereview.appspot.com/5532098/diff/4001/juju/charm/metadata.py
File juju/charm/metadata.py (right):

https://codereview.appspot.com/5532098/diff/4001/juju/charm/metadata.py#newcode228
juju/charm/metadata.py:228: path)
On 2012/02/27 19:21:20, hazmat wrote:
> Feels like this should be an error that's raised.

It isn't currently because the other checks in the method only log.
However you're correct that this indicates a usage error and should be
corrected so I'm changing it to a MetadataError(...)

https://codereview.appspot.com/5532098/

Unmerged revisions

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file '.bzrignore'
2--- .bzrignore 2011-09-05 10:53:18 +0000
3+++ .bzrignore 2012-02-09 03:51:19 +0000
4@@ -1,6 +1,6 @@
5-/debian/files
6 /docs/build
7 /docs/source/generated
8 /_trial_temp
9 /tags
10-zookeeper.log
11+/zookeeper.log
12+/TAGS
13
14=== modified file 'docs/source/charm-upgrades.rst'
15--- docs/source/charm-upgrades.rst 2011-09-15 17:56:23 +0000
16+++ docs/source/charm-upgrades.rst 2012-02-09 03:51:19 +0000
17@@ -5,7 +5,7 @@
18 Upgrading a charm
19 -------------------
20
21-A charm_ can be upgraded via the command line using the following
22+:doc:`charm` can be upgraded via the command line using the following
23 syntax::
24
25 $ juju upgrade-charm <service-name>
26@@ -21,7 +21,6 @@
27 The unit agent will switch over to executing hooks from the new charm,
28 after executing the `upgrade-charm` hook.
29
30-.. _charm: ../charm.html
31
32
33 Charm upgrade support
34
35=== modified file 'docs/source/charm.rst'
36--- docs/source/charm.rst 2011-10-06 21:04:13 +0000
37+++ docs/source/charm.rst 2012-02-09 03:51:19 +0000
38@@ -74,6 +74,20 @@
39 means the relation is required. While you may define it, this
40 field is not yet enforced by juju.
41
42+ * **scope:** - Controls which units of related-to services can
43+ be communicated with through this relationship. Juju supports
44+ the following scopes:
45+
46+ * **global:** `default` When a traditional relation is added
47+ between two services, all the service units for the first service will
48+ receive relation events about all service units for the second
49+ service.
50+
51+ * **container**: Communication is restricted to units deployed
52+ in the same container. It is not possible to establish a
53+ container scoped relation between principal services. See
54+ :doc:`subordinate-services`.
55+
56 As a shortcut, if these properties are not defined, and instead
57 a single string value is provided next to the relation name, the
58 string is taken as the interface value, as seen in this
59
60=== added file 'docs/source/drafts/implicit-relations.rst'
61--- docs/source/drafts/implicit-relations.rst 1970-01-01 00:00:00 +0000
62+++ docs/source/drafts/implicit-relations.rst 2012-02-09 03:51:19 +0000
63@@ -0,0 +1,62 @@
64+ Implicit relations
65+===================
66+
67+Implicit relations allow for interested services to gather
68+lifecycle-oriented events and data about other services without
69+expecting or requiring any modifications on the part of the author of
70+the other service's charm.
71+
72+Implicit relationships are named in the reserved the juju-*
73+namespace. Both the relation name and interface names provided by juju
74+are prefixed with `juju-`. Charms attempting to provide new
75+relationships in this namespace will trigger an error.
76+
77+Juju currently provides one implicit relationship to all deployed
78+services.
79+
80+`juju-info`, if specified would look like::
81+
82+ provides:
83+ juju-info:
84+ interface: juju-info
85+
86+The charm author should not declare the `juju-info` relation and is
87+provided here only as an example. The `juju-info` relation is
88+implicitly provided by all charms, and enables the requiring unit to
89+obtain basic details about the related-to unit. The following settings
90+will be implicitly provided by the remote unit in a relation through its
91+`juju-info` relation ::
92+
93+ private-address
94+ public-address
95+
96+
97+Relationship resolution
98+-----------------------
99+
100+If **rsyslog** is a :doc:`subordinate charm<subordinate-services>` and
101+requires a valid `scope: container` relationship in order to
102+deploy. It can take advantage of optional support from the principal
103+charm but in the event that the principal charm doesn't provide this
104+support it will still require a `scope: container` relationship. In
105+this event the logging charm author can take advantage of the implicit
106+relationship offered by all charms, `juju-info`. ::
107+
108+ requires:
109+ logging:
110+ interface: logging-directory
111+ scope: container
112+ juju-info:
113+ interface: juju-info
114+ scope: container
115+
116+The admin then issues the following ::
117+
118+ juju add-relation wordpress rsyslog
119+
120+If the wordpress author doesn't provide the `logging-directory`
121+interface juju will use the less-specific (in the sense that it likely
122+provides less information) `juju-info` interface.
123+
124+juju always attempts to match user provided interfaces before looking
125+for possible relationship matches in the `juju-*` namespace.
126
127=== added file 'docs/source/drafts/subordinate-internals.rst'
128--- docs/source/drafts/subordinate-internals.rst 1970-01-01 00:00:00 +0000
129+++ docs/source/drafts/subordinate-internals.rst 2012-02-09 03:51:19 +0000
130@@ -0,0 +1,168 @@
131+ Subordinate service implementation details
132+===========================================
133+
134+
135+This document explains the implementation of subordinate services. For
136+a higher level understanding please refer to the primary :doc:`subordinates
137+document <subordinate-services>`.
138+
139+
140+Overview
141+-------
142+
143+Principal services can have relationships with subordinates. This is
144+modeled using extensions to the `client-server` relationship
145+type. This new relation type is used exclusively between subordinates
146+and principals and is used to limit communication to only service
147+units in the same container.
148+
149+
150+ZooKeeper state
151+---------------
152+
153+Subordinate relations use the normal `client-server` relationship type
154+but store additional information in the relation nodes to maintain a
155+1-1 mapping from the unit-id of the principal to the unit id of the
156+subordinate. ::
157+
158+ relations/
159+ relation-000000001/
160+ container_relations/
161+ | subordinate_id-00000002: principal_id-00000001
162+ | subordinate_id-00000004: principal_id-00000003
163+ ---------------------------------------------------
164+ settings/
165+ principal_id-00000001/
166+ | private-address: 10.2.2.2
167+ -------------------------------
168+ principal_id-00000003/
169+ | private-address: 10.2.2.3
170+ ---------------------------------------------------
171+
172+Elements ending with **/** refer to ZK nodes with its children
173+indented under it.. Elements preceded by **|** represent a YAML
174+encoded structure under the preceding ZK node. `container_relations`
175+is a new structure under the each relations ZK node. This contains a
176+mapping from the subordinate unit's id to the id of its principal
177+services' unit agent. This representation takes advantage of the fact
178+that while a principal can have many subordinates a subordinate can
179+only have one principal. In the context of a given relationship this
180+mapping is unique.
181+
182+Additionally there are changes related to the storage of the relation
183+in the topology. A `relation_scope` is added to the ordered list of
184+properties such that ::
185+
186+ relations
187+ relation-00000001:
188+ - relation_type
189+ - relation_scope
190+ - service-00000001: {name: name, role: role}
191+ service-00000002: {name: name, role: role}
192+
193+
194+Watch related changes
195+---------------------
196+
197+The unit agent will use watch_relation_states to see when new
198+relations are added. When a subordinate relation is present deployment
199+actions will be taken in the unit agents lifecycle.
200+
201+UnitRelationStates.watch_related_units will dispatch on the relation
202+and only establish watches between the principal and subordinate units
203+in the same container.
204+
205+Deployment
206+----------
207+
208+`juju deploy` needs to enforce that services which require `scope:
209+container` relations are not deployed until one of those relationships
210+have been satisfied.
211+
212+The UnitMachineDeployment/UnitContainerDeployment in machine/unit will
213+undergo minor refactoring to make it more easily useable by the
214+UnitAgent to do its deployment of subordinate services. The Unit Agent
215+is the only entity with direct a relationship to its subordinate unit
216+and so the UnitAgent does the deployment of its subordinate units
217+rather than the MachineAgent. This model will continue to work in
218+the expected LXCEverywhere future.
219+
220+When a subordinate unit is deployed it is assigned the public and private
221+addresses of its principal service (even though it may expose its own
222+ports). This is because networking is dependent on the container its
223+running it, i.e, that of the principal's service unit.
224+
225+One interesting caveat is that we don't assign the subordinate unit a
226+machine id. `juju-status` exposes this information in a way that is
227+clear and is outlined below. Machine assignment is currently used to
228+trigger a class of deployment activities which subordinate services do
229+not take advantage of. It is an error to assign a machine directly to
230+a subordinate unit (as this indicates a usage error).
231+
232+`juju.unit.lifecycle._process_service_changes` is currently
233+responsible for adding unit state to relations. This code will change
234+so that when relationships are added or removed we have access to the
235+unit_name of the subordinate. This information is used to annotate the
236+UnitRelationState with the mapping from principal unit_name to
237+subordinate unit name and is stored in ZooKeeper as outlined
238+above. When a relationship is removed we detect this here as well and
239+update the mapping using the unit_name of the principal.
240+
241+`juju.state.relation.ServiceRelationState.add_unit_state` will be
242+augmented to support tracking of the 1-1 mapping between principal and
243+subordinate unit names. It will take an optional `principal_unit`
244+argument. This will take the `unit_name` the of principal unit the
245+service is contained with. Error checking will validate that the
246+service unit should be subordinate to the principal service in
247+question.
248+
249+
250+Unit management
251+---------------
252+
253+`juju add-unit` must raise an error informing the admin they can not
254+add units of a subordinate service. These scale automatically with the
255+principal service.
256+
257+`juju remove-unit` produces an error on subordinate services for the
258+same reason.
259+
260+
261+Relation management
262+-------------------
263+
264+`juju add-relation` and `juju remove-relation` must trigger the
265+deployment and removal of subordinate units. This is done using the
266+watch machinery outlined above. Each principal service unit will
267+deploy a new unit agent for its subordinate when the appropriate new
268+relationship is added and remove it when the relationship is departed.
269+
270+The subordinate service will maintain a watch of its relationship to
271+the principal and should this relationship be removed the subordinate
272+will transition its state to stopped and then remove its own state
273+from the container and terminate. This will require follow up work to
274+handle the proper triggering of stop hooks on the subordinate units
275+which isn't handled
276+
277+Status
278+------
279+
280+The changes to status are outlined in the user oriented documentation.
281+
282+
283+Roadmap
284+-------
285+
286+This serves as a guide to the planned merge tree. ::
287+
288+ subordinate-spec
289+ subordinate-charms # (charm metadata support)
290+ subordinate-implicit-interfaces # (juju-info)
291+ subordinate-relation-type
292+
293+ subordinate-control-deploy
294+ subordinate-control-units # (add/remove)
295+ subordinate-control-status
296+
297+ subordinate-control-relations # (add/remove)
298+ subordinate-unit-agent-deploy
299
300=== added file 'docs/source/drafts/subordinate-services.rst'
301--- docs/source/drafts/subordinate-services.rst 1970-01-01 00:00:00 +0000
302+++ docs/source/drafts/subordinate-services.rst 2012-02-09 03:51:19 +0000
303@@ -0,0 +1,170 @@
304+ Subordinate services
305+=====================
306+
307+Services are composed of one or more service units. A service unit
308+runs the service's software and is the smallest entity managed by
309+juju. Service units are typically run in an isolated container on a
310+machine with no knowledge or access to other services deployed onto
311+the same machine. Subordinate services allows for units of different
312+services to be deployed into the same container and to have knowledge
313+of each other.
314+
315+
316+Motivations
317+-----------
318+
319+Services such as logging, monitoring, backups and some types of
320+storage often require some access to the runtime of the service they
321+wish to operate on. Under the current modeling of services it is only
322+possible to relate services to other services with an explicit
323+interface pairing. Requiring a specified relation implies that every
324+charm author need be aware of any and all services a deployment might
325+wish to depend on, even if the other service can operate without any
326+explicit cooperation. For example a logging service may only require
327+access to the container level logging directory to function.
328+
329+The following changes are designed to address these issues and allow a
330+class of charm that can execute in the context of an existing
331+container while still taking advantage of the existing relationship
332+machinery.
333+
334+
335+Terms
336+-----
337+
338+Principal service
339+ A traditional service or charm in whose container subordinate
340+ services will execute.
341+
342+Subordinate service/charm
343+ A service designed for and deployed to the running container of
344+ another service unit.
345+
346+Subordinate relation
347+ A qualified relation type between principal services and their
348+ subordinate service units. While modeled identically to
349+ traditional relationships, juju only implements the relationship
350+ between the unit of the principal and the subordinate service or
351+ charm in the same container.
352+
353+
354+Relations
355+---------
356+
357+When a traditional relation is added between two services, all the
358+service units for the first service will receive relation events about
359+all service units for the second service. Subordinate services have a
360+very tight relationship with their principal service, so it makes
361+sense to be able to restrict that communication in some cases so that
362+they only receive events about each other. That's precisely what
363+happens when a relation is tagged as being a scoped to the
364+container. See :doc:`scoped relations<charm>`.
365+
366+Container relations exist because they simplify responsibilities for
367+the subordinate service charm author who would otherwise always have
368+to filter units of their relation before finding the unit they can
369+operate on.
370+
371+If a subordinate service needs to communicate with all units of the
372+principal service, it can still establish a traditional
373+(non-container) relationship to it.
374+
375+In order to deploy a subordinate service a `scope: container`
376+relationship is required. Even when the principal services' charm
377+author doesn't provide an explicit relationship for the subordinate to
378+join, using an :doc:`implicit relation<implicit-relations>` with
379+`scope: container` will satisfy this constraint.
380+
381+
382+Addressability
383+--------------
384+
385+No special changes are made for the purpose of naming or addressing
386+subordinate units. If a subordinate logging service is deployed with a
387+single unit of wordpress we would expect the logging unit to be
388+addressable as logging/0, if this service were then related to a mysql
389+service with a single unit we'd expect logging/1 to be deployed in its
390+container. Subordinate units inherit the public/private address of the
391+principal service. The container of the principal defines the network
392+setup.
393+
394+
395+Declaring subordinate charms
396+----------------------------
397+
398+When a charm author wishes to indicate their charm should operate as a
399+subordinate service only a small changes to the subordinate charms
400+metadata is required. Declaring a required interface with `scope:
401+container` in the interface definition of the charms metadata will
402+result in a subordinate deployment. Subordinate services may still
403+declare traditional relations to any service. The deployment is
404+delayed until a container relation is added.
405+
406+The example below shows adding a container relation to a charm. ::
407+
408+ requires:
409+ logging-directory:
410+ interface: logging
411+ scope: container
412+
413+
414+
415+Status of subordinates
416+----------------------
417+
418+The status output contains details about subordinate units under the
419+status of the principal service unit that it is sharing the container
420+with. The subordinate unit's output matches the formatting of existing
421+unit entries but omits `machine`, `public-address` and `subordinates`
422+(which are all the same as the principal unit).
423+
424+The subordinate service is listed in the top level `services`
425+dictionary in an abbreviated form. The `subordinate-to: []` list is
426+added to the service which contains the names of all services this
427+service is subordinate to. `units` is displayed as a list of principal
428+unit names under which instances of this service are found. ::
429+
430+ services:
431+ logging:
432+ charm: local:series/logging-1
433+ subordinate-to: [wordpress]
434+ relations:
435+ logging-directory: wordpress
436+ wordpress:
437+ machine: 0
438+ public-address: wordpress-0.example.com
439+ charm: local:series/wordpress-3
440+ relations: {loggin: logging}
441+ units:
442+ wordpress/0:
443+ relations:
444+ logging: {state: up}
445+ state: started
446+ subordinates:
447+ logging/0:
448+ relations:
449+ logging: {state: up}
450+
451+
452+
453+Usage
454+-----
455+
456+Assume the following deployment::
457+
458+ juju deploy mysql
459+ juju deploy wordpress
460+ juju add-relation mysql wordpress
461+
462+Now we'll create a subordinate logging service::
463+
464+ juju deploy logging
465+ juju add-relation logging mysql
466+ juju add-relation logging wordpress
467+
468+This will create a logging service unit inside each of the containers
469+holding the mysql and wordpress units. The logging service has a
470+standard client-server relation to both wordpress and mysql but these
471+new relationships are implemented only between the principal unit and
472+the subordinate unit . A subordinate unit may still have standard
473+relations established with any unit in its environment as usual.
474
475=== modified file 'juju/charm/metadata.py'
476--- juju/charm/metadata.py 2011-11-14 15:58:01 +0000
477+++ juju/charm/metadata.py 2012-02-09 03:51:19 +0000
478@@ -15,10 +15,16 @@
479
480 UTF8_SCHEMA = UnicodeOrString("utf-8")
481
482+SCOPE_GLOBAL = "global"
483+SCOPE_CONTAINER = "container"
484+
485+
486 INTERFACE_SCHEMA = KeyDict({
487- "interface": UTF8_SCHEMA,
488- "limit": OneOf(Constant(None), Int()),
489- "optional": Bool()})
490+ "interface": UTF8_SCHEMA,
491+ "limit": OneOf(Constant(None), Int()),
492+ "scope": OneOf(Constant(SCOPE_GLOBAL), Constant(SCOPE_CONTAINER)),
493+ "optional": Bool()},
494+ optional=["scope"])
495
496
497 class InterfaceExpander(object):
498@@ -66,6 +72,7 @@
499 return {
500 "interface": UTF8_SCHEMA.coerce(value, path),
501 "limit": self.limit,
502+ "scope": "global",
503 "optional": False}
504 else:
505 # Optional values are context-sensitive and/or have
506@@ -76,6 +83,7 @@
507 value["limit"] = self.limit
508 if "optional" not in value:
509 value["optional"] = False
510+ value["scope"] = value.get("scope", "global")
511 return INTERFACE_SCHEMA.coerce(value, path)
512
513
514@@ -87,7 +95,8 @@
515 "peers": Dict(UTF8_SCHEMA, InterfaceExpander(limit=1)),
516 "provides": Dict(UTF8_SCHEMA, InterfaceExpander(limit=None)),
517 "requires": Dict(UTF8_SCHEMA, InterfaceExpander(limit=1)),
518- }, optional=set(["provides", "requires", "peers", "revision"]))
519+ "subordinate": Bool(),
520+ }, optional=set(["provides", "requires", "peers", "revision", "subordinate"]))
521
522
523 class MetaData(object):
524@@ -144,6 +153,26 @@
525 """The charm peers relations."""
526 return self._data.get("peers")
527
528+ @property
529+ def is_subordinate(self):
530+ """Indicates the charm requires a contained relationship.
531+
532+ This property will effect the deployment options of its
533+ charm. When a charm is_subordinate it can only be deployed
534+ when its contained relationship is satisfied. See the
535+ subordinates specification.
536+ """
537+ if self._data.get("subordinate", False) is False:
538+ return False
539+
540+ if not self.requires:
541+ return False
542+
543+ for relation_data in self.requires.values():
544+ if relation_data.get("scope") == "container":
545+ return True
546+ return False
547+
548 def get_serialization_data(self):
549 """Get internal dictionary representing the state of this instance.
550
551@@ -187,6 +216,17 @@
552 "%s: revision field is obsolete. Move it to the 'revision' "
553 "file." % path)
554
555+ if self._data.get("subordinate", False) is True:
556+ proper_subordinate = False
557+ if self.requires:
558+ for relation_data in self.requires.values():
559+ if relation_data.get("scope") == "container":
560+ proper_subordinate = True
561+ if not proper_subordinate:
562+ log.warning(
563+ "%s labeled subordinate but lacking scope:container `requires` relation",
564+ path)
565+
566 def parse_serialization_data(self, serialization_data, path=None):
567 """Parse the unprocessed serialization data and load in this instance.
568
569
570=== added directory 'juju/charm/tests/repository/series/logging'
571=== added file 'juju/charm/tests/repository/series/logging/.ignored'
572--- juju/charm/tests/repository/series/logging/.ignored 1970-01-01 00:00:00 +0000
573+++ juju/charm/tests/repository/series/logging/.ignored 2012-02-09 03:51:19 +0000
574@@ -0,0 +1,1 @@
575+#
576\ No newline at end of file
577
578=== added directory 'juju/charm/tests/repository/series/logging/hooks'
579=== added file 'juju/charm/tests/repository/series/logging/hooks/install'
580--- juju/charm/tests/repository/series/logging/hooks/install 1970-01-01 00:00:00 +0000
581+++ juju/charm/tests/repository/series/logging/hooks/install 2012-02-09 03:51:19 +0000
582@@ -0,0 +1,2 @@
583+#!/bin/bash
584+echo "Done!"
585
586=== added file 'juju/charm/tests/repository/series/logging/metadata.yaml'
587--- juju/charm/tests/repository/series/logging/metadata.yaml 1970-01-01 00:00:00 +0000
588+++ juju/charm/tests/repository/series/logging/metadata.yaml 2012-02-09 03:51:19 +0000
589@@ -0,0 +1,13 @@
590+name: logging
591+summary: "Subordinate logging test charm"
592+description: |
593+ This is a longer description which
594+ potentially contains multiple lines.
595+subordinate: true
596+provides:
597+ logging-client:
598+ interface: logging
599+requires:
600+ logging-directory:
601+ interface: logging
602+ scope: container
603
604=== added file 'juju/charm/tests/repository/series/logging/revision'
605--- juju/charm/tests/repository/series/logging/revision 1970-01-01 00:00:00 +0000
606+++ juju/charm/tests/repository/series/logging/revision 2012-02-09 03:51:19 +0000
607@@ -0,0 +1,1 @@
608+1
609\ No newline at end of file
610
611=== modified file 'juju/charm/tests/test_metadata.py'
612--- juju/charm/tests/test_metadata.py 2011-11-08 14:30:44 +0000
613+++ juju/charm/tests/test_metadata.py 2012-02-09 03:51:19 +0000
614@@ -59,6 +59,7 @@
615 self.assertEquals(self.metadata.obsolete_revision, None)
616 self.assertEquals(self.metadata.summary, None)
617 self.assertEquals(self.metadata.description, None)
618+ self.assertEquals(self.metadata.is_subordinate, False)
619
620 def test_parse_and_check_basic_info(self):
621 """
622@@ -72,6 +73,41 @@
623 self.assertEquals(self.metadata.description,
624 u"This is a longer description which\n"
625 u"potentially contains multiple lines.\n")
626+ self.assertEquals(self.metadata.is_subordinate, False)
627+
628+ def test_is_subordinate(self):
629+ """Validate rules for detecting proper subordinate charms are working"""
630+ logging_path = os.path.join(
631+ test_repository_path, "series", "logging", "metadata.yaml")
632+ logging_configuration = open(logging_path).read()
633+ self.metadata.parse(logging_configuration)
634+ self.assertTrue(self.metadata.is_subordinate)
635+
636+ def test_subordinate_without_container_relation(self):
637+ """Validate rules for detecting proper subordinate charms are working
638+
639+ Case where no container relation is specified.
640+ """
641+ with self.change_sample() as data:
642+ data["subordinate"] = True
643+ log = self.capture_logging("juju.charm")
644+
645+ self.metadata.parse(self.sample, "some/path")
646+ self.assertIn("some/path labeled subordinate but lacking scope:container `requires` relation",
647+ log.getvalue())
648+
649+ def test_scope_constraint(self):
650+ """Verify the scope constrain is parsed properly."""
651+ logging_path = os.path.join(
652+ test_repository_path, "series", "logging", "metadata.yaml")
653+ logging_configuration = open(logging_path).read()
654+ self.metadata.parse(logging_configuration)
655+ # Verify the scope settings
656+ self.assertEqual(self.metadata.provides[u"logging-client"]["scope"],
657+ "global")
658+ self.assertEqual(self.metadata.requires[u"logging-directory"]["scope"],
659+ "container")
660+ self.assertTrue(self.metadata.is_subordinate)
661
662 def assert_parse_with_revision(self, with_path):
663 """
664@@ -204,7 +240,7 @@
665 self.assertEqual(metadata.peers, None)
666 self.assertEqual(
667 metadata.provides["server"],
668- {"interface": "mysql", "limit": None, "optional": False})
669+ {"interface": "mysql", "limit": None, "optional": False, "scope": "global"})
670 self.assertEqual(metadata.requires, None)
671
672 def test_riak_sample(self):
673@@ -212,13 +248,13 @@
674 metadata = self.get_metadata("riak")
675 self.assertEqual(
676 metadata.peers["ring"],
677- {"interface": "riak", "limit": 1, "optional": False})
678+ {"interface": "riak", "limit": 1, "optional": False, "scope": "global"})
679 self.assertEqual(
680 metadata.provides["endpoint"],
681- {"interface": "http", "limit": None, "optional": False})
682+ {"interface": "http", "limit": None, "optional": False, "scope": "global"})
683 self.assertEqual(
684 metadata.provides["admin"],
685- {"interface": "http", "limit": None, "optional": False})
686+ {"interface": "http", "limit": None, "optional": False, "scope": "global"})
687 self.assertEqual(metadata.requires, None)
688
689 def test_wordpress_sample(self):
690@@ -227,13 +263,13 @@
691 self.assertEqual(metadata.peers, None)
692 self.assertEqual(
693 metadata.provides["url"],
694- {"interface": "http", "limit": None, "optional": False})
695+ {"interface": "http", "limit": None, "optional": False, "scope": "global"})
696 self.assertEqual(
697 metadata.requires["db"],
698- {"interface": "mysql", "limit": 1, "optional": False})
699+ {"interface": "mysql", "limit": 1, "optional": False, "scope": "global"})
700 self.assertEqual(
701 metadata.requires["cache"],
702- {"interface": "varnish", "limit": 2, "optional": True})
703+ {"interface": "varnish", "limit": 2, "optional": True, "scope": "global"})
704
705 def test_interface_expander(self):
706 """Test rewriting of a given interface specification into long form.
707@@ -252,22 +288,22 @@
708 # shorthand is properly rewritten
709 self.assertEqual(
710 expander.coerce("http", ["provides"]),
711- {"interface": "http", "limit": None, "optional": False})
712+ {"interface": "http", "limit": None, "optional": False, "scope": "global"})
713
714 # defaults are properly applied
715 self.assertEqual(
716 expander.coerce(
717 {"interface": "http"}, ["provides"]),
718- {"interface": "http", "limit": None, "optional": False})
719+ {"interface": "http", "limit": None, "optional": False, "scope": "global"})
720 self.assertEqual(
721 expander.coerce(
722 {"interface": "http", "limit": 2}, ["provides"]),
723- {"interface": "http", "limit": 2, "optional": False})
724+ {"interface": "http", "limit": 2, "optional": False, "scope": "global"})
725 self.assertEqual(
726 expander.coerce(
727- {"interface": "http", "optional": True},
728+ {"interface": "http", "optional": True, "scope": "global"},
729 ["provides"]),
730- {"interface": "http", "limit": None, "optional": True})
731+ {"interface": "http", "limit": None, "optional": True, "scope": "global"})
732
733 # invalid data raises SchemaError
734 self.assertRaises(
735@@ -276,7 +312,7 @@
736 self.assertRaises(
737 SchemaError,
738 expander.coerce,
739- {"interface": "http", "optional": None}, ["provides"])
740+ {"interface": "http", "optional": None, "scope": "global"}, ["provides"])
741 self.assertRaises(
742 SchemaError,
743 expander.coerce,
744@@ -286,4 +322,4 @@
745 expander = InterfaceExpander(limit=1)
746 self.assertEqual(
747 expander.coerce("http", ["consumes"]),
748- {"interface": "http", "limit": 1, "optional": False})
749+ {"interface": "http", "limit": 1, "optional": False, "scope": "global"})

Subscribers

People subscribed via source and target branches

to status/vote changes: