Rietveld Code Review Tool
Help | Bug tracker | Discussion group | Source code | Sign in
(155)

Side by Side Diff: juju/charm/metadata.py

Issue 5532098: charm metadata support for subordinates
Patch Set: charm metadata support for subordinates Created 13 years, 1 month ago
Left:
Right:
Use n/p to move between diff chunks; N/P to move between comments. Please Sign in to add in-line comments.
Jump to:
View unified diff | Download patch
OLDNEW
1 import logging 1 import logging
2 import os 2 import os
3 3
4 import yaml 4 import yaml
5 5
6 from juju.charm.errors import MetaDataError 6 from juju.charm.errors import MetaDataError
7 from juju.errors import FileNotFound 7 from juju.errors import FileNotFound
8 from juju.lib.schema import ( 8 from juju.lib.schema import (
9 SchemaError, Bool, Constant, Dict, Int, 9 SchemaError, Bool, Constant, Dict, Int,
10 KeyDict, OneOf, UnicodeOrString) 10 KeyDict, OneOf, UnicodeOrString)
11 11
12 12
13 log = logging.getLogger("juju.charm") 13 log = logging.getLogger("juju.charm")
14 14
15 15
16 UTF8_SCHEMA = UnicodeOrString("utf-8") 16 UTF8_SCHEMA = UnicodeOrString("utf-8")
17 17
18 SCOPE_GLOBAL = "global"
19 SCOPE_CONTAINER = "container"
20
21
18 INTERFACE_SCHEMA = KeyDict({ 22 INTERFACE_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"])
22 28
23 29
24 class InterfaceExpander(object): 30 class InterfaceExpander(object):
25 """Schema coercer that expands the interface shorthand notation. 31 """Schema coercer that expands the interface shorthand notation.
26 32
27 We need this class because our charm shorthand is difficult to 33 We need this class because our charm shorthand is difficult to
28 work with (unfortunately). So we coerce shorthand and then store 34 work with (unfortunately). So we coerce shorthand and then store
29 the desired format in ZK. 35 the desired format in ZK.
30 36
31 Supports the following variants:: 37 Supports the following variants::
(...skipping 27 matching lines...) Expand all
59 65
60 Helper method to support each of the variants, either the 66 Helper method to support each of the variants, either the
61 charm does not specify limit and optional, such as foobar in 67 charm does not specify limit and optional, such as foobar in
62 the above example; or the interface spec is just a string, 68 the above example; or the interface spec is just a string,
63 such as the ``server: riak`` example. 69 such as the ``server: riak`` example.
64 """ 70 """
65 if not isinstance(value, dict): 71 if not isinstance(value, dict):
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 have 78 # Optional values are context-sensitive and/or have
72 # defaults, which is different than what KeyDict can 79 # defaults, which is different than what KeyDict can
73 # readily support. So just do it here first, then 80 # readily support. So just do it here first, then
74 # coerce. 81 # coerce.
75 if "limit" not in value: 82 if "limit" not in value:
76 value["limit"] = self.limit 83 value["limit"] = self.limit
77 if "optional" not in value: 84 if "optional" not in value:
78 value["optional"] = False 85 value["optional"] = False
86 value["scope"] = value.get("scope", "global")
79 return INTERFACE_SCHEMA.coerce(value, path) 87 return INTERFACE_SCHEMA.coerce(value, path)
80 88
81 89
82 SCHEMA = KeyDict({ 90 SCHEMA = KeyDict({
83 "name": UTF8_SCHEMA, 91 "name": UTF8_SCHEMA,
84 "revision": Int(), 92 "revision": Int(),
85 "summary": UTF8_SCHEMA, 93 "summary": UTF8_SCHEMA,
86 "description": UTF8_SCHEMA, 94 "description": UTF8_SCHEMA,
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"] ))
91 100
92 101
93 class MetaData(object): 102 class MetaData(object):
94 """Represents the charm info file. 103 """Represents the charm info file.
95 104
96 The main metadata for a charm (name, revision, etc) is maintained 105 The main metadata for a charm (name, revision, etc) is maintained
97 in the charm's info file. This class is able to parse, 106 in the charm's info file. This class is able to parse,
98 validate, and provide access to data in the info file. 107 validate, and provide access to data in the info file.
99 """ 108 """
100 109
(...skipping 36 matching lines...) Expand 10 before | Expand all | Expand 10 after
137 @property 146 @property
138 def requires(self): 147 def requires(self):
139 """The charm requires relations.""" 148 """The charm requires relations."""
140 return self._data.get("requires") 149 return self._data.get("requires")
141 150
142 @property 151 @property
143 def peers(self): 152 def peers(self):
144 """The charm peers relations.""" 153 """The charm peers relations."""
145 return self._data.get("peers") 154 return self._data.get("peers")
146 155
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.
149 178
150 This is useful to embed this information inside other storage-related 179 This is useful to embed this information inside other storage-related
151 dictionaries. 180 dictionaries.
152 """ 181 """
153 return dict(self._data) 182 return dict(self._data)
154 183
155 def load(self, path): 184 def load(self, path):
156 """Load and parse the info file. 185 """Load and parse the info file.
(...skipping 23 matching lines...) Expand all
180 # Capture the path name on the error if present. 209 # Capture the path name on the error if present.
181 if path is not None: 210 if path is not None:
182 e.problem_mark.name = path 211 e.problem_mark.name = path
183 raise 212 raise
184 213
185 if "revision" in self._data and path: 214 if "revision" in self._data and path:
186 log.warning( 215 log.warning(
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)
189 218
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 `require s` relation",
228 path)
hazmat 2012/02/27 19:21:20 Feels like this should be an error that's raised.
bcsaller 2012/02/28 00:22:58 It isn't currently because the other checks in the
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.
192 232
193 @param serialization_data: Unprocessed data matching the 233 @param serialization_data: Unprocessed data matching the
194 metadata schema. 234 metadata schema.
195 @param path: Optional path of the loaded file. Used when 235 @param path: Optional path of the loaded file. Used when
196 raising errors. 236 raising errors.
197 237
198 @raise MetaDataError: When errors are found in the info data. 238 @raise MetaDataError: When errors are found in the info data.
199 """ 239 """
200 try: 240 try:
201 self._data = SCHEMA.coerce(serialization_data, []) 241 self._data = SCHEMA.coerce(serialization_data, [])
202 except SchemaError, error: 242 except SchemaError, error:
203 if path: 243 if path:
204 path_info = " %s:" % path 244 path_info = " %s:" % path
205 else: 245 else:
206 path_info = "" 246 path_info = ""
207 raise MetaDataError("Bad data in charm info:%s %s" % 247 raise MetaDataError("Bad data in charm info:%s %s" %
208 (path_info, error)) 248 (path_info, error))
OLDNEW
« no previous file with comments | « docs/source/drafts/subordinate-services.rst ('k') | juju/charm/tests/repository/series/logging/.ignored » ('j') | no next file with comments »

Powered by Google App Engine
RSS Feeds Recent Issues | This issue
This is Rietveld f62528b