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
=== modified file '.bzrignore'
--- .bzrignore 2011-09-05 10:53:18 +0000
+++ .bzrignore 2012-02-09 03:51:19 +0000
@@ -1,6 +1,6 @@
1/debian/files
2/docs/build1/docs/build
3/docs/source/generated2/docs/source/generated
4/_trial_temp3/_trial_temp
5/tags4/tags
6zookeeper.log5/zookeeper.log
6/TAGS
77
=== modified file 'docs/source/charm-upgrades.rst'
--- docs/source/charm-upgrades.rst 2011-09-15 17:56:23 +0000
+++ docs/source/charm-upgrades.rst 2012-02-09 03:51:19 +0000
@@ -5,7 +5,7 @@
5Upgrading a charm5Upgrading a charm
6-------------------6-------------------
77
8A charm_ can be upgraded via the command line using the following8:doc:`charm` can be upgraded via the command line using the following
9syntax::9syntax::
1010
11 $ juju upgrade-charm <service-name>11 $ juju upgrade-charm <service-name>
@@ -21,7 +21,6 @@
21The unit agent will switch over to executing hooks from the new charm,21The unit agent will switch over to executing hooks from the new charm,
22after executing the `upgrade-charm` hook.22after executing the `upgrade-charm` hook.
2323
24.. _charm: ../charm.html
2524
2625
27Charm upgrade support26Charm upgrade support
2827
=== modified file 'docs/source/charm.rst'
--- docs/source/charm.rst 2011-10-06 21:04:13 +0000
+++ docs/source/charm.rst 2012-02-09 03:51:19 +0000
@@ -74,6 +74,20 @@
74 means the relation is required. While you may define it, this74 means the relation is required. While you may define it, this
75 field is not yet enforced by juju.75 field is not yet enforced by juju.
7676
77 * **scope:** - Controls which units of related-to services can
78 be communicated with through this relationship. Juju supports
79 the following scopes:
80
81 * **global:** `default` When a traditional relation is added
82 between two services, all the service units for the first service will
83 receive relation events about all service units for the second
84 service.
85
86 * **container**: Communication is restricted to units deployed
87 in the same container. It is not possible to establish a
88 container scoped relation between principal services. See
89 :doc:`subordinate-services`.
90
77 As a shortcut, if these properties are not defined, and instead91 As a shortcut, if these properties are not defined, and instead
78 a single string value is provided next to the relation name, the92 a single string value is provided next to the relation name, the
79 string is taken as the interface value, as seen in this93 string is taken as the interface value, as seen in this
8094
=== added file 'docs/source/drafts/implicit-relations.rst'
--- docs/source/drafts/implicit-relations.rst 1970-01-01 00:00:00 +0000
+++ docs/source/drafts/implicit-relations.rst 2012-02-09 03:51:19 +0000
@@ -0,0 +1,62 @@
1 Implicit relations
2===================
3
4Implicit relations allow for interested services to gather
5lifecycle-oriented events and data about other services without
6expecting or requiring any modifications on the part of the author of
7the other service's charm.
8
9Implicit relationships are named in the reserved the juju-*
10namespace. Both the relation name and interface names provided by juju
11are prefixed with `juju-`. Charms attempting to provide new
12relationships in this namespace will trigger an error.
13
14Juju currently provides one implicit relationship to all deployed
15services.
16
17`juju-info`, if specified would look like::
18
19 provides:
20 juju-info:
21 interface: juju-info
22
23The charm author should not declare the `juju-info` relation and is
24provided here only as an example. The `juju-info` relation is
25implicitly provided by all charms, and enables the requiring unit to
26obtain basic details about the related-to unit. The following settings
27will be implicitly provided by the remote unit in a relation through its
28`juju-info` relation ::
29
30 private-address
31 public-address
32
33
34Relationship resolution
35-----------------------
36
37If **rsyslog** is a :doc:`subordinate charm<subordinate-services>` and
38requires a valid `scope: container` relationship in order to
39deploy. It can take advantage of optional support from the principal
40charm but in the event that the principal charm doesn't provide this
41support it will still require a `scope: container` relationship. In
42this event the logging charm author can take advantage of the implicit
43relationship offered by all charms, `juju-info`. ::
44
45 requires:
46 logging:
47 interface: logging-directory
48 scope: container
49 juju-info:
50 interface: juju-info
51 scope: container
52
53The admin then issues the following ::
54
55 juju add-relation wordpress rsyslog
56
57If the wordpress author doesn't provide the `logging-directory`
58interface juju will use the less-specific (in the sense that it likely
59provides less information) `juju-info` interface.
60
61juju always attempts to match user provided interfaces before looking
62for possible relationship matches in the `juju-*` namespace.
063
=== added file 'docs/source/drafts/subordinate-internals.rst'
--- docs/source/drafts/subordinate-internals.rst 1970-01-01 00:00:00 +0000
+++ docs/source/drafts/subordinate-internals.rst 2012-02-09 03:51:19 +0000
@@ -0,0 +1,168 @@
1 Subordinate service implementation details
2===========================================
3
4
5This document explains the implementation of subordinate services. For
6a higher level understanding please refer to the primary :doc:`subordinates
7document <subordinate-services>`.
8
9
10Overview
11-------
12
13Principal services can have relationships with subordinates. This is
14modeled using extensions to the `client-server` relationship
15type. This new relation type is used exclusively between subordinates
16and principals and is used to limit communication to only service
17units in the same container.
18
19
20ZooKeeper state
21---------------
22
23Subordinate relations use the normal `client-server` relationship type
24but store additional information in the relation nodes to maintain a
251-1 mapping from the unit-id of the principal to the unit id of the
26subordinate. ::
27
28 relations/
29 relation-000000001/
30 container_relations/
31 | subordinate_id-00000002: principal_id-00000001
32 | subordinate_id-00000004: principal_id-00000003
33 ---------------------------------------------------
34 settings/
35 principal_id-00000001/
36 | private-address: 10.2.2.2
37 -------------------------------
38 principal_id-00000003/
39 | private-address: 10.2.2.3
40 ---------------------------------------------------
41
42Elements ending with **/** refer to ZK nodes with its children
43indented under it.. Elements preceded by **|** represent a YAML
44encoded structure under the preceding ZK node. `container_relations`
45is a new structure under the each relations ZK node. This contains a
46mapping from the subordinate unit's id to the id of its principal
47services' unit agent. This representation takes advantage of the fact
48that while a principal can have many subordinates a subordinate can
49only have one principal. In the context of a given relationship this
50mapping is unique.
51
52Additionally there are changes related to the storage of the relation
53in the topology. A `relation_scope` is added to the ordered list of
54properties such that ::
55
56 relations
57 relation-00000001:
58 - relation_type
59 - relation_scope
60 - service-00000001: {name: name, role: role}
61 service-00000002: {name: name, role: role}
62
63
64Watch related changes
65---------------------
66
67The unit agent will use watch_relation_states to see when new
68relations are added. When a subordinate relation is present deployment
69actions will be taken in the unit agents lifecycle.
70
71UnitRelationStates.watch_related_units will dispatch on the relation
72and only establish watches between the principal and subordinate units
73in the same container.
74
75Deployment
76----------
77
78`juju deploy` needs to enforce that services which require `scope:
79container` relations are not deployed until one of those relationships
80have been satisfied.
81
82The UnitMachineDeployment/UnitContainerDeployment in machine/unit will
83undergo minor refactoring to make it more easily useable by the
84UnitAgent to do its deployment of subordinate services. The Unit Agent
85is the only entity with direct a relationship to its subordinate unit
86and so the UnitAgent does the deployment of its subordinate units
87rather than the MachineAgent. This model will continue to work in
88the expected LXCEverywhere future.
89
90When a subordinate unit is deployed it is assigned the public and private
91addresses of its principal service (even though it may expose its own
92ports). This is because networking is dependent on the container its
93running it, i.e, that of the principal's service unit.
94
95One interesting caveat is that we don't assign the subordinate unit a
96machine id. `juju-status` exposes this information in a way that is
97clear and is outlined below. Machine assignment is currently used to
98trigger a class of deployment activities which subordinate services do
99not take advantage of. It is an error to assign a machine directly to
100a subordinate unit (as this indicates a usage error).
101
102`juju.unit.lifecycle._process_service_changes` is currently
103responsible for adding unit state to relations. This code will change
104so that when relationships are added or removed we have access to the
105unit_name of the subordinate. This information is used to annotate the
106UnitRelationState with the mapping from principal unit_name to
107subordinate unit name and is stored in ZooKeeper as outlined
108above. When a relationship is removed we detect this here as well and
109update the mapping using the unit_name of the principal.
110
111`juju.state.relation.ServiceRelationState.add_unit_state` will be
112augmented to support tracking of the 1-1 mapping between principal and
113subordinate unit names. It will take an optional `principal_unit`
114argument. This will take the `unit_name` the of principal unit the
115service is contained with. Error checking will validate that the
116service unit should be subordinate to the principal service in
117question.
118
119
120Unit management
121---------------
122
123`juju add-unit` must raise an error informing the admin they can not
124add units of a subordinate service. These scale automatically with the
125principal service.
126
127`juju remove-unit` produces an error on subordinate services for the
128same reason.
129
130
131Relation management
132-------------------
133
134`juju add-relation` and `juju remove-relation` must trigger the
135deployment and removal of subordinate units. This is done using the
136watch machinery outlined above. Each principal service unit will
137deploy a new unit agent for its subordinate when the appropriate new
138relationship is added and remove it when the relationship is departed.
139
140The subordinate service will maintain a watch of its relationship to
141the principal and should this relationship be removed the subordinate
142will transition its state to stopped and then remove its own state
143from the container and terminate. This will require follow up work to
144handle the proper triggering of stop hooks on the subordinate units
145which isn't handled
146
147Status
148------
149
150The changes to status are outlined in the user oriented documentation.
151
152
153Roadmap
154-------
155
156This serves as a guide to the planned merge tree. ::
157
158 subordinate-spec
159 subordinate-charms # (charm metadata support)
160 subordinate-implicit-interfaces # (juju-info)
161 subordinate-relation-type
162
163 subordinate-control-deploy
164 subordinate-control-units # (add/remove)
165 subordinate-control-status
166
167 subordinate-control-relations # (add/remove)
168 subordinate-unit-agent-deploy
0169
=== added file 'docs/source/drafts/subordinate-services.rst'
--- docs/source/drafts/subordinate-services.rst 1970-01-01 00:00:00 +0000
+++ docs/source/drafts/subordinate-services.rst 2012-02-09 03:51:19 +0000
@@ -0,0 +1,170 @@
1 Subordinate services
2=====================
3
4Services are composed of one or more service units. A service unit
5runs the service's software and is the smallest entity managed by
6juju. Service units are typically run in an isolated container on a
7machine with no knowledge or access to other services deployed onto
8the same machine. Subordinate services allows for units of different
9services to be deployed into the same container and to have knowledge
10of each other.
11
12
13Motivations
14-----------
15
16Services such as logging, monitoring, backups and some types of
17storage often require some access to the runtime of the service they
18wish to operate on. Under the current modeling of services it is only
19possible to relate services to other services with an explicit
20interface pairing. Requiring a specified relation implies that every
21charm author need be aware of any and all services a deployment might
22wish to depend on, even if the other service can operate without any
23explicit cooperation. For example a logging service may only require
24access to the container level logging directory to function.
25
26The following changes are designed to address these issues and allow a
27class of charm that can execute in the context of an existing
28container while still taking advantage of the existing relationship
29machinery.
30
31
32Terms
33-----
34
35Principal service
36 A traditional service or charm in whose container subordinate
37 services will execute.
38
39Subordinate service/charm
40 A service designed for and deployed to the running container of
41 another service unit.
42
43Subordinate relation
44 A qualified relation type between principal services and their
45 subordinate service units. While modeled identically to
46 traditional relationships, juju only implements the relationship
47 between the unit of the principal and the subordinate service or
48 charm in the same container.
49
50
51Relations
52---------
53
54When a traditional relation is added between two services, all the
55service units for the first service will receive relation events about
56all service units for the second service. Subordinate services have a
57very tight relationship with their principal service, so it makes
58sense to be able to restrict that communication in some cases so that
59they only receive events about each other. That's precisely what
60happens when a relation is tagged as being a scoped to the
61container. See :doc:`scoped relations<charm>`.
62
63Container relations exist because they simplify responsibilities for
64the subordinate service charm author who would otherwise always have
65to filter units of their relation before finding the unit they can
66operate on.
67
68If a subordinate service needs to communicate with all units of the
69principal service, it can still establish a traditional
70(non-container) relationship to it.
71
72In order to deploy a subordinate service a `scope: container`
73relationship is required. Even when the principal services' charm
74author doesn't provide an explicit relationship for the subordinate to
75join, using an :doc:`implicit relation<implicit-relations>` with
76`scope: container` will satisfy this constraint.
77
78
79Addressability
80--------------
81
82No special changes are made for the purpose of naming or addressing
83subordinate units. If a subordinate logging service is deployed with a
84single unit of wordpress we would expect the logging unit to be
85addressable as logging/0, if this service were then related to a mysql
86service with a single unit we'd expect logging/1 to be deployed in its
87container. Subordinate units inherit the public/private address of the
88principal service. The container of the principal defines the network
89setup.
90
91
92Declaring subordinate charms
93----------------------------
94
95When a charm author wishes to indicate their charm should operate as a
96subordinate service only a small changes to the subordinate charms
97metadata is required. Declaring a required interface with `scope:
98container` in the interface definition of the charms metadata will
99result in a subordinate deployment. Subordinate services may still
100declare traditional relations to any service. The deployment is
101delayed until a container relation is added.
102
103The example below shows adding a container relation to a charm. ::
104
105 requires:
106 logging-directory:
107 interface: logging
108 scope: container
109
110
111
112Status of subordinates
113----------------------
114
115The status output contains details about subordinate units under the
116status of the principal service unit that it is sharing the container
117with. The subordinate unit's output matches the formatting of existing
118unit entries but omits `machine`, `public-address` and `subordinates`
119(which are all the same as the principal unit).
120
121The subordinate service is listed in the top level `services`
122dictionary in an abbreviated form. The `subordinate-to: []` list is
123added to the service which contains the names of all services this
124service is subordinate to. `units` is displayed as a list of principal
125unit names under which instances of this service are found. ::
126
127 services:
128 logging:
129 charm: local:series/logging-1
130 subordinate-to: [wordpress]
131 relations:
132 logging-directory: wordpress
133 wordpress:
134 machine: 0
135 public-address: wordpress-0.example.com
136 charm: local:series/wordpress-3
137 relations: {loggin: logging}
138 units:
139 wordpress/0:
140 relations:
141 logging: {state: up}
142 state: started
143 subordinates:
144 logging/0:
145 relations:
146 logging: {state: up}
147
148
149
150Usage
151-----
152
153Assume the following deployment::
154
155 juju deploy mysql
156 juju deploy wordpress
157 juju add-relation mysql wordpress
158
159Now we'll create a subordinate logging service::
160
161 juju deploy logging
162 juju add-relation logging mysql
163 juju add-relation logging wordpress
164
165This will create a logging service unit inside each of the containers
166holding the mysql and wordpress units. The logging service has a
167standard client-server relation to both wordpress and mysql but these
168new relationships are implemented only between the principal unit and
169the subordinate unit . A subordinate unit may still have standard
170relations established with any unit in its environment as usual.
0171
=== modified file 'juju/charm/metadata.py'
--- juju/charm/metadata.py 2011-11-14 15:58:01 +0000
+++ juju/charm/metadata.py 2012-02-09 03:51:19 +0000
@@ -15,10 +15,16 @@
1515
16UTF8_SCHEMA = UnicodeOrString("utf-8")16UTF8_SCHEMA = UnicodeOrString("utf-8")
1717
18SCOPE_GLOBAL = "global"
19SCOPE_CONTAINER = "container"
20
21
18INTERFACE_SCHEMA = KeyDict({22INTERFACE_SCHEMA = KeyDict({
19 "interface": UTF8_SCHEMA,23 "interface": UTF8_SCHEMA,
20 "limit": OneOf(Constant(None), Int()),24 "limit": OneOf(Constant(None), Int()),
21 "optional": Bool()})25 "scope": OneOf(Constant(SCOPE_GLOBAL), Constant(SCOPE_CONTAINER)),
26 "optional": Bool()},
27 optional=["scope"])
2228
2329
24class InterfaceExpander(object):30class InterfaceExpander(object):
@@ -66,6 +72,7 @@
66 return {72 return {
67 "interface": UTF8_SCHEMA.coerce(value, path),73 "interface": UTF8_SCHEMA.coerce(value, path),
68 "limit": self.limit,74 "limit": self.limit,
75 "scope": "global",
69 "optional": False}76 "optional": False}
70 else:77 else:
71 # Optional values are context-sensitive and/or have78 # Optional values are context-sensitive and/or have
@@ -76,6 +83,7 @@
76 value["limit"] = self.limit83 value["limit"] = self.limit
77 if "optional" not in value:84 if "optional" not in value:
78 value["optional"] = False85 value["optional"] = False
86 value["scope"] = value.get("scope", "global")
79 return INTERFACE_SCHEMA.coerce(value, path)87 return INTERFACE_SCHEMA.coerce(value, path)
8088
8189
@@ -87,7 +95,8 @@
87 "peers": Dict(UTF8_SCHEMA, InterfaceExpander(limit=1)),95 "peers": Dict(UTF8_SCHEMA, InterfaceExpander(limit=1)),
88 "provides": Dict(UTF8_SCHEMA, InterfaceExpander(limit=None)),96 "provides": Dict(UTF8_SCHEMA, InterfaceExpander(limit=None)),
89 "requires": Dict(UTF8_SCHEMA, InterfaceExpander(limit=1)),97 "requires": Dict(UTF8_SCHEMA, InterfaceExpander(limit=1)),
90 }, optional=set(["provides", "requires", "peers", "revision"]))98 "subordinate": Bool(),
99 }, optional=set(["provides", "requires", "peers", "revision", "subordinate"]))
91100
92101
93class MetaData(object):102class MetaData(object):
@@ -144,6 +153,26 @@
144 """The charm peers relations."""153 """The charm peers relations."""
145 return self._data.get("peers")154 return self._data.get("peers")
146155
156 @property
157 def is_subordinate(self):
158 """Indicates the charm requires a contained relationship.
159
160 This property will effect the deployment options of its
161 charm. When a charm is_subordinate it can only be deployed
162 when its contained relationship is satisfied. See the
163 subordinates specification.
164 """
165 if self._data.get("subordinate", False) is False:
166 return False
167
168 if not self.requires:
169 return False
170
171 for relation_data in self.requires.values():
172 if relation_data.get("scope") == "container":
173 return True
174 return False
175
147 def get_serialization_data(self):176 def get_serialization_data(self):
148 """Get internal dictionary representing the state of this instance.177 """Get internal dictionary representing the state of this instance.
149178
@@ -187,6 +216,17 @@
187 "%s: revision field is obsolete. Move it to the 'revision' "216 "%s: revision field is obsolete. Move it to the 'revision' "
188 "file." % path)217 "file." % path)
189218
219 if self._data.get("subordinate", False) is True:
220 proper_subordinate = False
221 if self.requires:
222 for relation_data in self.requires.values():
223 if relation_data.get("scope") == "container":
224 proper_subordinate = True
225 if not proper_subordinate:
226 log.warning(
227 "%s labeled subordinate but lacking scope:container `requires` relation",
228 path)
229
190 def parse_serialization_data(self, serialization_data, path=None):230 def parse_serialization_data(self, serialization_data, path=None):
191 """Parse the unprocessed serialization data and load in this instance.231 """Parse the unprocessed serialization data and load in this instance.
192232
193233
=== added directory 'juju/charm/tests/repository/series/logging'
=== added file 'juju/charm/tests/repository/series/logging/.ignored'
--- juju/charm/tests/repository/series/logging/.ignored 1970-01-01 00:00:00 +0000
+++ juju/charm/tests/repository/series/logging/.ignored 2012-02-09 03:51:19 +0000
@@ -0,0 +1,1 @@
1#
0\ No newline at end of file2\ No newline at end of file
13
=== added directory 'juju/charm/tests/repository/series/logging/hooks'
=== added file 'juju/charm/tests/repository/series/logging/hooks/install'
--- juju/charm/tests/repository/series/logging/hooks/install 1970-01-01 00:00:00 +0000
+++ juju/charm/tests/repository/series/logging/hooks/install 2012-02-09 03:51:19 +0000
@@ -0,0 +1,2 @@
1#!/bin/bash
2echo "Done!"
03
=== added file 'juju/charm/tests/repository/series/logging/metadata.yaml'
--- juju/charm/tests/repository/series/logging/metadata.yaml 1970-01-01 00:00:00 +0000
+++ juju/charm/tests/repository/series/logging/metadata.yaml 2012-02-09 03:51:19 +0000
@@ -0,0 +1,13 @@
1name: logging
2summary: "Subordinate logging test charm"
3description: |
4 This is a longer description which
5 potentially contains multiple lines.
6subordinate: true
7provides:
8 logging-client:
9 interface: logging
10requires:
11 logging-directory:
12 interface: logging
13 scope: container
014
=== added file 'juju/charm/tests/repository/series/logging/revision'
--- juju/charm/tests/repository/series/logging/revision 1970-01-01 00:00:00 +0000
+++ juju/charm/tests/repository/series/logging/revision 2012-02-09 03:51:19 +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/test_metadata.py'
--- juju/charm/tests/test_metadata.py 2011-11-08 14:30:44 +0000
+++ juju/charm/tests/test_metadata.py 2012-02-09 03:51:19 +0000
@@ -59,6 +59,7 @@
59 self.assertEquals(self.metadata.obsolete_revision, None)59 self.assertEquals(self.metadata.obsolete_revision, None)
60 self.assertEquals(self.metadata.summary, None)60 self.assertEquals(self.metadata.summary, None)
61 self.assertEquals(self.metadata.description, None)61 self.assertEquals(self.metadata.description, None)
62 self.assertEquals(self.metadata.is_subordinate, False)
6263
63 def test_parse_and_check_basic_info(self):64 def test_parse_and_check_basic_info(self):
64 """65 """
@@ -72,6 +73,41 @@
72 self.assertEquals(self.metadata.description,73 self.assertEquals(self.metadata.description,
73 u"This is a longer description which\n"74 u"This is a longer description which\n"
74 u"potentially contains multiple lines.\n")75 u"potentially contains multiple lines.\n")
76 self.assertEquals(self.metadata.is_subordinate, False)
77
78 def test_is_subordinate(self):
79 """Validate rules for detecting proper subordinate charms are working"""
80 logging_path = os.path.join(
81 test_repository_path, "series", "logging", "metadata.yaml")
82 logging_configuration = open(logging_path).read()
83 self.metadata.parse(logging_configuration)
84 self.assertTrue(self.metadata.is_subordinate)
85
86 def test_subordinate_without_container_relation(self):
87 """Validate rules for detecting proper subordinate charms are working
88
89 Case where no container relation is specified.
90 """
91 with self.change_sample() as data:
92 data["subordinate"] = True
93 log = self.capture_logging("juju.charm")
94
95 self.metadata.parse(self.sample, "some/path")
96 self.assertIn("some/path labeled subordinate but lacking scope:container `requires` relation",
97 log.getvalue())
98
99 def test_scope_constraint(self):
100 """Verify the scope constrain is parsed properly."""
101 logging_path = os.path.join(
102 test_repository_path, "series", "logging", "metadata.yaml")
103 logging_configuration = open(logging_path).read()
104 self.metadata.parse(logging_configuration)
105 # Verify the scope settings
106 self.assertEqual(self.metadata.provides[u"logging-client"]["scope"],
107 "global")
108 self.assertEqual(self.metadata.requires[u"logging-directory"]["scope"],
109 "container")
110 self.assertTrue(self.metadata.is_subordinate)
75111
76 def assert_parse_with_revision(self, with_path):112 def assert_parse_with_revision(self, with_path):
77 """113 """
@@ -204,7 +240,7 @@
204 self.assertEqual(metadata.peers, None)240 self.assertEqual(metadata.peers, None)
205 self.assertEqual(241 self.assertEqual(
206 metadata.provides["server"],242 metadata.provides["server"],
207 {"interface": "mysql", "limit": None, "optional": False})243 {"interface": "mysql", "limit": None, "optional": False, "scope": "global"})
208 self.assertEqual(metadata.requires, None)244 self.assertEqual(metadata.requires, None)
209245
210 def test_riak_sample(self):246 def test_riak_sample(self):
@@ -212,13 +248,13 @@
212 metadata = self.get_metadata("riak")248 metadata = self.get_metadata("riak")
213 self.assertEqual(249 self.assertEqual(
214 metadata.peers["ring"],250 metadata.peers["ring"],
215 {"interface": "riak", "limit": 1, "optional": False})251 {"interface": "riak", "limit": 1, "optional": False, "scope": "global"})
216 self.assertEqual(252 self.assertEqual(
217 metadata.provides["endpoint"],253 metadata.provides["endpoint"],
218 {"interface": "http", "limit": None, "optional": False})254 {"interface": "http", "limit": None, "optional": False, "scope": "global"})
219 self.assertEqual(255 self.assertEqual(
220 metadata.provides["admin"],256 metadata.provides["admin"],
221 {"interface": "http", "limit": None, "optional": False})257 {"interface": "http", "limit": None, "optional": False, "scope": "global"})
222 self.assertEqual(metadata.requires, None)258 self.assertEqual(metadata.requires, None)
223259
224 def test_wordpress_sample(self):260 def test_wordpress_sample(self):
@@ -227,13 +263,13 @@
227 self.assertEqual(metadata.peers, None)263 self.assertEqual(metadata.peers, None)
228 self.assertEqual(264 self.assertEqual(
229 metadata.provides["url"],265 metadata.provides["url"],
230 {"interface": "http", "limit": None, "optional": False})266 {"interface": "http", "limit": None, "optional": False, "scope": "global"})
231 self.assertEqual(267 self.assertEqual(
232 metadata.requires["db"],268 metadata.requires["db"],
233 {"interface": "mysql", "limit": 1, "optional": False})269 {"interface": "mysql", "limit": 1, "optional": False, "scope": "global"})
234 self.assertEqual(270 self.assertEqual(
235 metadata.requires["cache"],271 metadata.requires["cache"],
236 {"interface": "varnish", "limit": 2, "optional": True})272 {"interface": "varnish", "limit": 2, "optional": True, "scope": "global"})
237273
238 def test_interface_expander(self):274 def test_interface_expander(self):
239 """Test rewriting of a given interface specification into long form.275 """Test rewriting of a given interface specification into long form.
@@ -252,22 +288,22 @@
252 # shorthand is properly rewritten288 # shorthand is properly rewritten
253 self.assertEqual(289 self.assertEqual(
254 expander.coerce("http", ["provides"]),290 expander.coerce("http", ["provides"]),
255 {"interface": "http", "limit": None, "optional": False})291 {"interface": "http", "limit": None, "optional": False, "scope": "global"})
256292
257 # defaults are properly applied293 # defaults are properly applied
258 self.assertEqual(294 self.assertEqual(
259 expander.coerce(295 expander.coerce(
260 {"interface": "http"}, ["provides"]),296 {"interface": "http"}, ["provides"]),
261 {"interface": "http", "limit": None, "optional": False})297 {"interface": "http", "limit": None, "optional": False, "scope": "global"})
262 self.assertEqual(298 self.assertEqual(
263 expander.coerce(299 expander.coerce(
264 {"interface": "http", "limit": 2}, ["provides"]),300 {"interface": "http", "limit": 2}, ["provides"]),
265 {"interface": "http", "limit": 2, "optional": False})301 {"interface": "http", "limit": 2, "optional": False, "scope": "global"})
266 self.assertEqual(302 self.assertEqual(
267 expander.coerce(303 expander.coerce(
268 {"interface": "http", "optional": True},304 {"interface": "http", "optional": True, "scope": "global"},
269 ["provides"]),305 ["provides"]),
270 {"interface": "http", "limit": None, "optional": True})306 {"interface": "http", "limit": None, "optional": True, "scope": "global"})
271307
272 # invalid data raises SchemaError308 # invalid data raises SchemaError
273 self.assertRaises(309 self.assertRaises(
@@ -276,7 +312,7 @@
276 self.assertRaises(312 self.assertRaises(
277 SchemaError,313 SchemaError,
278 expander.coerce,314 expander.coerce,
279 {"interface": "http", "optional": None}, ["provides"])315 {"interface": "http", "optional": None, "scope": "global"}, ["provides"])
280 self.assertRaises(316 self.assertRaises(
281 SchemaError,317 SchemaError,
282 expander.coerce,318 expander.coerce,
@@ -286,4 +322,4 @@
286 expander = InterfaceExpander(limit=1)322 expander = InterfaceExpander(limit=1)
287 self.assertEqual(323 self.assertEqual(
288 expander.coerce("http", ["consumes"]),324 expander.coerce("http", ["consumes"]),
289 {"interface": "http", "limit": 1, "optional": False})325 {"interface": "http", "limit": 1, "optional": False, "scope": "global"})

Subscribers

People subscribed via source and target branches

to status/vote changes: