Merge lp:~johnsca/charms/trusty/cf-nats/port-conflicts into lp:~cf-charmers/charms/trusty/cf-nats/trunk
- Trusty Tahr (14.04)
- port-conflicts
- Merge into trunk
Proposed by
Cory Johns
Status: | Merged |
---|---|
Merged at revision: | 33 |
Proposed branch: | lp:~johnsca/charms/trusty/cf-nats/port-conflicts |
Merge into: | lp:~cf-charmers/charms/trusty/cf-nats/trunk |
Diff against target: |
263 lines (+104/-67) 4 files modified
hooks/charmhelpers/contrib/cloudfoundry/contexts.py (+17/-6) hooks/charmhelpers/core/services.py (+81/-55) hooks/config.py (+4/-4) templates/nats.yml (+2/-2) |
To merge this branch: | bzr merge lp:~johnsca/charms/trusty/cf-nats/port-conflicts |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Cloud Foundry Charmers | Pending | ||
Review via email: mp+222677@code.launchpad.net |
Commit message
Description of the change
Resolve port conflicts for cf-nats
To post a comment you must log in.
Revision history for this message
Cory Johns (johnsca) wrote : | # |
Revision history for this message
Benjamin Saller (bcsaller) wrote : | # |
LGTM, thanks for fixing the prefixes
Revision history for this message
Cory Johns (johnsca) wrote : | # |
*** Submitted:
Resolve port conflicts for cf-nats
R=benjamin.saller
CC=
https:/
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'hooks/charmhelpers/contrib/cloudfoundry/contexts.py' | |||
2 | --- hooks/charmhelpers/contrib/cloudfoundry/contexts.py 2014-05-27 22:33:13 +0000 | |||
3 | +++ hooks/charmhelpers/contrib/cloudfoundry/contexts.py 2014-06-10 16:00:32 +0000 | |||
4 | @@ -33,7 +33,7 @@ | |||
5 | 33 | 33 | ||
6 | 34 | class NatsRelation(RelationContext): | 34 | class NatsRelation(RelationContext): |
7 | 35 | interface = 'nats' | 35 | interface = 'nats' |
9 | 36 | required_keys = ['nats_port', 'nats_address', 'nats_user', 'nats_password'] | 36 | required_keys = ['address', 'port', 'user', 'password'] |
10 | 37 | 37 | ||
11 | 38 | 38 | ||
12 | 39 | class MysqlRelation(RelationContext): | 39 | class MysqlRelation(RelationContext): |
13 | @@ -44,9 +44,10 @@ | |||
14 | 44 | def get_data(self): | 44 | def get_data(self): |
15 | 45 | RelationContext.get_data(self) | 45 | RelationContext.get_data(self) |
16 | 46 | if self.is_ready(): | 46 | if self.is_ready(): |
20 | 47 | if 'port' not in self['db']: | 47 | for unit in self['db']: |
21 | 48 | self['db']['port'] = '3306' | 48 | if 'port' not in unit: |
22 | 49 | self['db']['dsn'] = self.dsn_template.format(**self['db']) | 49 | unit['port'] = '3306' |
23 | 50 | unit['dsn'] = self.dsn_template.format(**unit) | ||
24 | 50 | 51 | ||
25 | 51 | 52 | ||
26 | 52 | class RouterRelation(RelationContext): | 53 | class RouterRelation(RelationContext): |
27 | @@ -56,9 +57,19 @@ | |||
28 | 56 | 57 | ||
29 | 57 | class LogRouterRelation(RelationContext): | 58 | class LogRouterRelation(RelationContext): |
30 | 58 | interface = 'logrouter' | 59 | interface = 'logrouter' |
32 | 59 | required_keys = ['shared-secret', 'logrouter-address'] | 60 | required_keys = ['shared_secret', 'address', 'incoming_port', 'outgoing_port'] |
33 | 60 | 61 | ||
34 | 61 | 62 | ||
35 | 62 | class LoggregatorRelation(RelationContext): | 63 | class LoggregatorRelation(RelationContext): |
36 | 63 | interface = 'loggregator' | 64 | interface = 'loggregator' |
38 | 64 | required_keys = ['shared_secret', 'loggregator_address'] | 65 | required_keys = ['address', 'incoming_port', 'outgoing_port'] |
39 | 66 | |||
40 | 67 | |||
41 | 68 | class EtcdRelation(RelationContext): | ||
42 | 69 | interface = 'etcd' | ||
43 | 70 | required_keys = ['hostname', 'port'] | ||
44 | 71 | |||
45 | 72 | |||
46 | 73 | class CloudControllerRelation(RelationContext): | ||
47 | 74 | interface = 'cc' | ||
48 | 75 | required_keys = ['hostname', 'port', 'user', 'password'] | ||
49 | 65 | 76 | ||
50 | === modified file 'hooks/charmhelpers/core/services.py' | |||
51 | --- hooks/charmhelpers/core/services.py 2014-05-29 17:33:12 +0000 | |||
52 | +++ hooks/charmhelpers/core/services.py 2014-06-10 16:00:32 +0000 | |||
53 | @@ -11,6 +11,14 @@ | |||
54 | 11 | """ | 11 | """ |
55 | 12 | Register a list of services, given their definitions. | 12 | Register a list of services, given their definitions. |
56 | 13 | 13 | ||
57 | 14 | Traditional charm authoring is focused on implementing hooks. That is, | ||
58 | 15 | the charm author is thinking in terms of "What hook am I handling; what | ||
59 | 16 | does this hook need to do?" However, in most cases, the real question | ||
60 | 17 | should be "Do I have the information I need to configure and start this | ||
61 | 18 | piece of software and, if so, what are the steps for doing so." The | ||
62 | 19 | ServiceManager framework tries to bring the focus to the data and the | ||
63 | 20 | setup tasks, in the most declarative way possible. | ||
64 | 21 | |||
65 | 14 | Service definitions are dicts in the following formats (all keys except | 22 | Service definitions are dicts in the following formats (all keys except |
66 | 15 | 'service' are optional): | 23 | 'service' are optional): |
67 | 16 | 24 | ||
68 | @@ -67,28 +75,28 @@ | |||
69 | 67 | a mongodb relation and which runs a custom `db_migrate` function prior to | 75 | a mongodb relation and which runs a custom `db_migrate` function prior to |
70 | 68 | restarting the service, and a Runit serivce called spadesd. | 76 | restarting the service, and a Runit serivce called spadesd. |
71 | 69 | 77 | ||
94 | 70 | >>> manager = services.ServiceManager([ | 78 | manager = services.ServiceManager([ |
95 | 71 | ... { | 79 | { |
96 | 72 | ... 'service': 'bingod', | 80 | 'service': 'bingod', |
97 | 73 | ... 'ports': [80, 443], | 81 | 'ports': [80, 443], |
98 | 74 | ... 'required_data': [MongoRelation(), config()], | 82 | 'required_data': [MongoRelation(), config(), {'my': 'data'}], |
99 | 75 | ... 'data_ready': [ | 83 | 'data_ready': [ |
100 | 76 | ... services.template(source='bingod.conf'), | 84 | services.template(source='bingod.conf'), |
101 | 77 | ... services.template(source='bingod.ini', | 85 | services.template(source='bingod.ini', |
102 | 78 | ... target='/etc/bingod.ini', | 86 | target='/etc/bingod.ini', |
103 | 79 | ... owner='bingo', perms=0400), | 87 | owner='bingo', perms=0400), |
104 | 80 | ... ], | 88 | ], |
105 | 81 | ... }, | 89 | }, |
106 | 82 | ... { | 90 | { |
107 | 83 | ... 'service': 'spadesd', | 91 | 'service': 'spadesd', |
108 | 84 | ... 'data_ready': services.template(source='spadesd_run.j2', | 92 | 'data_ready': services.template(source='spadesd_run.j2', |
109 | 85 | ... target='/etc/sv/spadesd/run', | 93 | target='/etc/sv/spadesd/run', |
110 | 86 | ... perms=0555), | 94 | perms=0555), |
111 | 87 | ... 'start': runit_start, | 95 | 'start': runit_start, |
112 | 88 | ... 'stop': runit_stop, | 96 | 'stop': runit_stop, |
113 | 89 | ... }, | 97 | }, |
114 | 90 | ... ]) | 98 | ]) |
115 | 91 | ... manager.manage() | 99 | manager.manage() |
116 | 92 | """ | 100 | """ |
117 | 93 | self.services = {} | 101 | self.services = {} |
118 | 94 | for service in services or []: | 102 | for service in services or []: |
119 | @@ -213,55 +221,73 @@ | |||
120 | 213 | interface = None | 221 | interface = None |
121 | 214 | required_keys = [] | 222 | required_keys = [] |
122 | 215 | 223 | ||
123 | 224 | def __init__(self, *args, **kwargs): | ||
124 | 225 | super(RelationContext, self).__init__(*args, **kwargs) | ||
125 | 226 | self.get_data() | ||
126 | 227 | |||
127 | 216 | def __bool__(self): | 228 | def __bool__(self): |
128 | 217 | """ | 229 | """ |
130 | 218 | Updates the data and returns True if all of the required_keys are available. | 230 | Returns True if all of the required_keys are available. |
131 | 219 | """ | 231 | """ |
132 | 220 | self.get_data() | ||
133 | 221 | return self.is_ready() | 232 | return self.is_ready() |
134 | 222 | 233 | ||
135 | 223 | __nonzero__ = __bool__ | 234 | __nonzero__ = __bool__ |
136 | 224 | 235 | ||
137 | 236 | def __repr__(self): | ||
138 | 237 | return super(RelationContext, self).__repr__() | ||
139 | 238 | |||
140 | 225 | def is_ready(self): | 239 | def is_ready(self): |
141 | 226 | """ | 240 | """ |
145 | 227 | Returns True if all of the required_keys are available. | 241 | Returns True if all of the `required_keys` are available from any units. |
146 | 228 | """ | 242 | """ |
147 | 229 | return set(self.get(self.interface, {}).keys()).issuperset(set(self.required_keys)) | 243 | ready = len(self.get(self.interface, [])) > 0 |
148 | 244 | if not ready: | ||
149 | 245 | hookenv.log('Incomplete relation: {}'.format(self.__class__.__name__), hookenv.DEBUG) | ||
150 | 246 | return ready | ||
151 | 247 | |||
152 | 248 | def _is_ready(self, unit_data): | ||
153 | 249 | """ | ||
154 | 250 | Helper method that tests a set of relation data and returns True if | ||
155 | 251 | all of the `required_keys` are present. | ||
156 | 252 | """ | ||
157 | 253 | return set(unit_data.keys()).issuperset(set(self.required_keys)) | ||
158 | 230 | 254 | ||
159 | 231 | def get_data(self): | 255 | def get_data(self): |
160 | 232 | """ | 256 | """ |
180 | 233 | Retrieve the relation data and store it under `self[self.interface]`. | 257 | Retrieve the relation data for each unit involved in a realtion and, |
181 | 234 | 258 | if complete, store it in a list under `self[self.interface]`. This | |
182 | 235 | If there are more than one units related on the desired interface, | 259 | is automatically called when the RelationContext is instantiated. |
183 | 236 | then each unit will have its data stored under `self[self.interface][unit_id]` | 260 | |
184 | 237 | and one of the units with complete information will chosen at random | 261 | The units are sorted lexographically first by the service ID, then by |
185 | 238 | to fill the values at `self[self.interface]`. | 262 | the unit ID. Thus, if an interface has two other services, 'db:1' |
186 | 239 | 263 | and 'db:2', with 'db:1' having two units, 'wordpress/0' and 'wordpress/1', | |
187 | 240 | 264 | and 'db:2' having one unit, 'mediawiki/0', all of which have a complete | |
188 | 241 | For example: | 265 | set of data, the relation data for the units will be stored in the |
189 | 242 | 266 | order: 'wordpress/0', 'wordpress/1', 'mediawiki/0'. | |
190 | 243 | { | 267 | |
191 | 244 | 'foo': 'bar', | 268 | If you only care about a single unit on the relation, you can just |
192 | 245 | 'unit/0': { | 269 | access it as `{{ interface[0]['key'] }}`. However, if you can at all |
193 | 246 | 'foo': 'bar', | 270 | support multiple units on a relation, you should iterate over the list, |
194 | 247 | }, | 271 | like: |
195 | 248 | 'unit/1': { | 272 | |
196 | 249 | 'foo': 'baz', | 273 | {% for unit in interface -%} |
197 | 250 | }, | 274 | {{ unit['key'] }}{% if not loop.last %},{% endif %} |
198 | 251 | } | 275 | {%- endfor %} |
199 | 276 | |||
200 | 277 | Note that since all sets of relation data from all related services and | ||
201 | 278 | units are in a single list, if you need to know which service or unit a | ||
202 | 279 | set of data came from, you'll need to extend this class to preserve | ||
203 | 280 | that information. | ||
204 | 252 | """ | 281 | """ |
205 | 253 | if not hookenv.relation_ids(self.interface): | 282 | if not hookenv.relation_ids(self.interface): |
206 | 254 | return | 283 | return |
207 | 255 | 284 | ||
212 | 256 | ns = self.setdefault(self.interface, {}) | 285 | ns = self.setdefault(self.interface, []) |
213 | 257 | required = set(self.required_keys) | 286 | for rid in sorted(hookenv.relation_ids(self.interface)): |
214 | 258 | for rid in hookenv.relation_ids(self.interface): | 287 | for unit in sorted(hookenv.related_units(rid)): |
211 | 259 | for unit in hookenv.related_units(rid): | ||
215 | 260 | reldata = hookenv.relation_get(rid=rid, unit=unit) | 288 | reldata = hookenv.relation_get(rid=rid, unit=unit) |
220 | 261 | unit_ns = ns.setdefault(unit, {}) | 289 | if self._is_ready(reldata): |
221 | 262 | unit_ns.update(reldata) | 290 | ns.append(reldata) |
218 | 263 | if set(reldata.keys()).issuperset(required): | ||
219 | 264 | ns.update(reldata) | ||
222 | 265 | 291 | ||
223 | 266 | 292 | ||
224 | 267 | class ManagerCallback(object): | 293 | class ManagerCallback(object): |
225 | @@ -316,6 +342,6 @@ | |||
226 | 316 | 342 | ||
227 | 317 | 343 | ||
228 | 318 | # Convenience aliases | 344 | # Convenience aliases |
230 | 319 | template = TemplateCallback | 345 | render_template = template = TemplateCallback |
231 | 320 | open_ports = PortManagerCallback() | 346 | open_ports = PortManagerCallback() |
232 | 321 | close_ports = PortManagerCallback() | 347 | close_ports = PortManagerCallback() |
233 | 322 | 348 | ||
234 | === modified file 'hooks/config.py' | |||
235 | --- hooks/config.py 2014-05-29 17:33:12 +0000 | |||
236 | +++ hooks/config.py 2014-06-10 16:00:32 +0000 | |||
237 | @@ -17,10 +17,10 @@ | |||
238 | 17 | 17 | ||
239 | 18 | NATS_CONTEXT = contexts.StoredContext( | 18 | NATS_CONTEXT = contexts.StoredContext( |
240 | 19 | 'nats_credentials.yml', { | 19 | 'nats_credentials.yml', { |
245 | 20 | 'nats_user': host.pwgen(7), | 20 | 'user': host.pwgen(7), |
246 | 21 | 'nats_password': host.pwgen(7), | 21 | 'password': host.pwgen(7), |
247 | 22 | 'nats_port': 4222, | 22 | 'port': 4222, |
248 | 23 | 'nats_address': hookenv.unit_get('private-address').encode('utf-8')}) | 23 | 'address': hookenv.unit_get('private-address').encode('utf-8')}) |
249 | 24 | 24 | ||
250 | 25 | SERVICES = [ | 25 | SERVICES = [ |
251 | 26 | { | 26 | { |
252 | 27 | 27 | ||
253 | === modified file 'templates/nats.yml' | |||
254 | --- templates/nats.yml 2014-04-01 08:34:31 +0000 | |||
255 | +++ templates/nats.yml 2014-06-10 16:00:32 +0000 | |||
256 | @@ -3,5 +3,5 @@ | |||
257 | 3 | logtime: true | 3 | logtime: true |
258 | 4 | authentication: | 4 | authentication: |
259 | 5 | timeout: 15 | 5 | timeout: 15 |
262 | 6 | user: {{ nats_user }} | 6 | user: {{ user }} |
263 | 7 | password: {{ nats_password }} | 7 | password: {{ password }} |
Reviewers: mp+222677_ code.launchpad. net,
Message:
Please take a look.
Description:
Resolve port conflicts for cf-nats
https:/ /code.launchpad .net/~johnsca/ charms/ trusty/ cf-nats/ port-conflicts/ +merge/ 222677
(do not edit description out of merge proposal)
Please review this at https:/ /codereview. appspot. com/102270043/
Affected files (+106, -67 lines): ers/contrib/ cloudfoundry/ contexts. py ers/core/ services. py
A [revision details]
M hooks/charmhelp
M hooks/charmhelp
M hooks/config.py
M templates/nats.yml