Merge lp:~free.ekanayaka/landscape-client/drop-cloud-registration into lp:~free.ekanayaka/landscape-client/drop-old-juju-info
- drop-cloud-registration
- Merge into drop-old-juju-info
Proposed by
Free Ekanayaka
Status: | Superseded | ||||
---|---|---|---|---|---|
Proposed branch: | lp:~free.ekanayaka/landscape-client/drop-cloud-registration | ||||
Merge into: | lp:~free.ekanayaka/landscape-client/drop-old-juju-info | ||||
Diff against target: |
1241 lines (+40/-942) 11 files modified
debian/cloud-default.conf (+0/-7) debian/landscape-client.init (+2/-18) debian/landscape-client.install (+0/-1) landscape/broker/config.py (+0/-6) landscape/broker/registration.py (+6/-210) landscape/broker/tests/test_registration.py (+20/-669) landscape/configuration.py (+1/-1) landscape/message_schemas.py (+11/-1) landscape/tests/test_configuration.py (+0/-16) scripts/landscape-is-cloud-managed (+0/-12) setup.py (+0/-1) |
||||
To merge this branch: | bzr merge lp:~free.ekanayaka/landscape-client/drop-cloud-registration | ||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Landscape | Pending | ||
Landscape | Pending | ||
Review via email: mp+226992@code.launchpad.net |
Commit message
Description of the change
Drop the cloud registration code and associated tests.
To post a comment you must log in.
- 783. By Free Ekanayaka
-
Address review comments
Unmerged revisions
- 783. By Free Ekanayaka
-
Address review comments
- 782. By Free Ekanayaka
-
Add comment
- 781. By Free Ekanayaka
-
Revert drop-juju-info
- 780. By Free Ekanayaka
-
Merge from drop-old-juju-info
- 779. By Free Ekanayaka
-
Merge from drop-old-juju-info
- 778. By Free Ekanayaka
-
Drop cloud registration
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === removed file 'debian/cloud-default.conf' | |||
2 | --- debian/cloud-default.conf 2009-03-28 19:04:34 +0000 | |||
3 | +++ debian/cloud-default.conf 1970-01-01 00:00:00 +0000 | |||
4 | @@ -1,7 +0,0 @@ | |||
5 | 1 | [client] | ||
6 | 2 | cloud = True | ||
7 | 3 | url = https://landscape.canonical.com/message-system | ||
8 | 4 | data_path = /var/lib/landscape/client | ||
9 | 5 | ping_url = http://landscape.canonical.com/ping | ||
10 | 6 | include_manager_plugins = ScriptExecution | ||
11 | 7 | script_users = ALL | ||
12 | 8 | 0 | ||
13 | === modified file 'debian/landscape-client.init' | |||
14 | --- debian/landscape-client.init 2012-09-05 15:37:17 +0000 | |||
15 | +++ debian/landscape-client.init 2014-07-16 10:26:46 +0000 | |||
16 | @@ -31,24 +31,8 @@ | |||
17 | 31 | # This $RUN check should match the semantics of | 31 | # This $RUN check should match the semantics of |
18 | 32 | # l.sysvconfig.SysVConfig.is_configured_to_run. | 32 | # l.sysvconfig.SysVConfig.is_configured_to_run. |
19 | 33 | if [ $RUN -eq 0 ]; then | 33 | if [ $RUN -eq 0 ]; then |
38 | 34 | if [ $CLOUD -eq 1 ]; then | 34 | echo "$NAME is not configured, please run landscape-config." |
39 | 35 | if landscape-is-cloud-managed; then | 35 | exit 0 |
22 | 36 | # Install the cloud default configuration file | ||
23 | 37 | cp /usr/share/landscape/cloud-default.conf /etc/landscape/client.conf | ||
24 | 38 | # Override default file for not going in this conditional again at | ||
25 | 39 | # next startup | ||
26 | 40 | sed -i "s/^RUN=.*/RUN=1/" $LANDSCAPE_DEFAULTS | ||
27 | 41 | if ! grep -q "^RUN=" $LANDSCAPE_DEFAULTS; then | ||
28 | 42 | echo "RUN=1" >> $LANDSCAPE_DEFAULTS | ||
29 | 43 | fi | ||
30 | 44 | else | ||
31 | 45 | echo "$NAME is not configured, please run landscape-config." | ||
32 | 46 | exit 0 | ||
33 | 47 | fi | ||
34 | 48 | else | ||
35 | 49 | echo "$NAME is not configured, please run landscape-config." | ||
36 | 50 | exit 0 | ||
37 | 51 | fi | ||
40 | 52 | fi | 36 | fi |
41 | 53 | } | 37 | } |
42 | 54 | 38 | ||
43 | 55 | 39 | ||
44 | === modified file 'debian/landscape-client.install' | |||
45 | --- debian/landscape-client.install 2013-05-16 09:15:13 +0000 | |||
46 | +++ debian/landscape-client.install 2014-07-16 10:26:46 +0000 | |||
47 | @@ -7,7 +7,6 @@ | |||
48 | 7 | usr/bin/landscape-package-changer | 7 | usr/bin/landscape-package-changer |
49 | 8 | usr/bin/landscape-package-reporter | 8 | usr/bin/landscape-package-reporter |
50 | 9 | usr/bin/landscape-release-upgrader | 9 | usr/bin/landscape-release-upgrader |
51 | 10 | usr/bin/landscape-is-cloud-managed | ||
52 | 11 | usr/bin/landscape-dbus-proxy | 10 | usr/bin/landscape-dbus-proxy |
53 | 12 | usr/share/landscape/cloud-default.conf | 11 | usr/share/landscape/cloud-default.conf |
54 | 13 | usr/lib/landscape | 12 | usr/lib/landscape |
55 | 14 | 13 | ||
56 | === modified file 'landscape/broker/config.py' | |||
57 | --- landscape/broker/config.py 2014-03-25 15:21:52 +0000 | |||
58 | +++ landscape/broker/config.py 2014-07-16 10:26:46 +0000 | |||
59 | @@ -32,8 +32,6 @@ | |||
60 | 32 | - C{urgent_exchange_interval} (C{1*60}) | 32 | - C{urgent_exchange_interval} (C{1*60}) |
61 | 33 | - C{http_proxy} | 33 | - C{http_proxy} |
62 | 34 | - C{https_proxy} | 34 | - C{https_proxy} |
63 | 35 | - C{cloud} | ||
64 | 36 | - C{otp} | ||
65 | 37 | - C{provisioning_otp} | 35 | - C{provisioning_otp} |
66 | 38 | """ | 36 | """ |
67 | 39 | parser = super(BrokerConfiguration, self).make_parser() | 37 | parser = super(BrokerConfiguration, self).make_parser() |
68 | @@ -60,10 +58,6 @@ | |||
69 | 60 | help="The URL of the HTTP proxy, if one is needed.") | 58 | help="The URL of the HTTP proxy, if one is needed.") |
70 | 61 | parser.add_option("--https-proxy", metavar="URL", | 59 | parser.add_option("--https-proxy", metavar="URL", |
71 | 62 | help="The URL of the HTTPS proxy, if one is needed.") | 60 | help="The URL of the HTTPS proxy, if one is needed.") |
72 | 63 | parser.add_option("--cloud", action="store_true", | ||
73 | 64 | help="Set this if your computer is in an EC2 cloud.") | ||
74 | 65 | parser.add_option("--otp", default="", | ||
75 | 66 | help="The OTP to use in cloud configuration.") | ||
76 | 67 | parser.add_option("--access-group", default="", | 61 | parser.add_option("--access-group", default="", |
77 | 68 | help="Suggested access group for this computer.") | 62 | help="Suggested access group for this computer.") |
78 | 69 | parser.add_option("--tags", | 63 | parser.add_option("--tags", |
79 | 70 | 64 | ||
80 | === modified file 'landscape/broker/registration.py' | |||
81 | --- landscape/broker/registration.py 2014-07-13 07:19:38 +0000 | |||
82 | +++ landscape/broker/registration.py 2014-07-16 10:26:46 +0000 | |||
83 | @@ -8,25 +8,16 @@ | |||
84 | 8 | credentials yet and that the server accepts registration messages, so it | 8 | credentials yet and that the server accepts registration messages, so it |
85 | 9 | will craft an appropriate one and send it out. | 9 | will craft an appropriate one and send it out. |
86 | 10 | """ | 10 | """ |
87 | 11 | import time | ||
88 | 12 | import logging | 11 | import logging |
89 | 13 | import socket | ||
90 | 14 | 12 | ||
91 | 15 | from twisted.internet.defer import Deferred | 13 | from twisted.internet.defer import Deferred |
92 | 16 | 14 | ||
93 | 17 | from landscape.lib.bpickle import loads | ||
94 | 18 | from landscape.lib.log import log_failure | ||
95 | 19 | from landscape.lib.juju import get_juju_info | 15 | from landscape.lib.juju import get_juju_info |
96 | 20 | from landscape.lib.fetch import fetch, FetchError | ||
97 | 21 | from landscape.lib.tag import is_valid_tag_list | 16 | from landscape.lib.tag import is_valid_tag_list |
98 | 22 | from landscape.lib.network import get_fqdn | 17 | from landscape.lib.network import get_fqdn |
99 | 23 | from landscape.lib.vm_info import get_vm_info, get_container_info | 18 | from landscape.lib.vm_info import get_vm_info, get_container_info |
100 | 24 | 19 | ||
101 | 25 | 20 | ||
102 | 26 | EC2_HOST = "169.254.169.254" | ||
103 | 27 | EC2_API = "http://%s/latest" % (EC2_HOST,) | ||
104 | 28 | |||
105 | 29 | |||
106 | 30 | class InvalidCredentialsError(Exception): | 21 | class InvalidCredentialsError(Exception): |
107 | 31 | """ | 22 | """ |
108 | 32 | Raised when an invalid account title and/or registration key | 23 | Raised when an invalid account title and/or registration key |
109 | @@ -99,7 +90,6 @@ | |||
110 | 99 | self._exchange = exchange | 90 | self._exchange = exchange |
111 | 100 | self._pinger = pinger | 91 | self._pinger = pinger |
112 | 101 | self._message_store = message_store | 92 | self._message_store = message_store |
113 | 102 | self._reactor.call_on("run", self._fetch_ec2_data) | ||
114 | 103 | self._reactor.call_on("run", self._get_juju_data) | 93 | self._reactor.call_on("run", self._get_juju_data) |
115 | 104 | self._reactor.call_on("pre-exchange", self._handle_pre_exchange) | 94 | self._reactor.call_on("pre-exchange", self._handle_pre_exchange) |
116 | 105 | self._reactor.call_on("exchange-done", self._handle_exchange_done) | 95 | self._reactor.call_on("exchange-done", self._handle_exchange_done) |
117 | @@ -109,21 +99,15 @@ | |||
118 | 109 | self._handle_registration) | 99 | self._handle_registration) |
119 | 110 | self._should_register = None | 100 | self._should_register = None |
120 | 111 | self._fetch_async = fetch_async | 101 | self._fetch_async = fetch_async |
121 | 112 | self._otp = None | ||
122 | 113 | self._ec2_data = None | 102 | self._ec2_data = None |
123 | 114 | self._juju_data = None | 103 | self._juju_data = None |
124 | 115 | 104 | ||
125 | 116 | def should_register(self): | 105 | def should_register(self): |
126 | 117 | id = self._identity | 106 | id = self._identity |
127 | 118 | if id.secure_id: | 107 | if id.secure_id: |
128 | 119 | # We already have a secure ID, no need to register | ||
129 | 120 | logging.info("Machine already has a secure-id. Skipping " | ||
130 | 121 | "registration.") | ||
131 | 122 | return False | 108 | return False |
132 | 123 | 109 | ||
136 | 124 | if self._config.cloud: | 110 | if self._config.provisioning_otp: |
134 | 125 | return self._message_store.accepts("register-cloud-vm") | ||
135 | 126 | elif self._config.provisioning_otp: | ||
137 | 127 | return self._message_store.accepts("register-provisioned-machine") | 111 | return self._message_store.accepts("register-provisioned-machine") |
138 | 128 | 112 | ||
139 | 129 | return bool(id.computer_title and id.account_name | 113 | return bool(id.computer_title and id.account_name |
140 | @@ -157,94 +141,6 @@ | |||
141 | 157 | """ | 141 | """ |
142 | 158 | return self._fetch_async(EC2_API + path).addCallback(accumulate.append) | 142 | return self._fetch_async(EC2_API + path).addCallback(accumulate.append) |
143 | 159 | 143 | ||
144 | 160 | def _fetch_ec2_data(self): | ||
145 | 161 | """Retrieve available EC2 information, if in a EC2 compatible cloud.""" | ||
146 | 162 | id = self._identity | ||
147 | 163 | if self._config.cloud and not id.secure_id: | ||
148 | 164 | # Fetch data from the EC2 API, to be used later in the registration | ||
149 | 165 | # process | ||
150 | 166 | # We ignore errors from user-data because it's common for the | ||
151 | 167 | # URL to return a 404 when the data is unavailable. | ||
152 | 168 | ec2_data = [] | ||
153 | 169 | deferred = self._fetch_async(EC2_API + "/user-data").addErrback( | ||
154 | 170 | log_failure).addCallback(ec2_data.append) | ||
155 | 171 | paths = [ | ||
156 | 172 | "/meta-data/instance-id", | ||
157 | 173 | "/meta-data/reservation-id", | ||
158 | 174 | "/meta-data/local-hostname", | ||
159 | 175 | "/meta-data/public-hostname", | ||
160 | 176 | "/meta-data/ami-launch-index", | ||
161 | 177 | "/meta-data/ami-id", | ||
162 | 178 | "/meta-data/local-ipv4", | ||
163 | 179 | "/meta-data/public-ipv4"] | ||
164 | 180 | # We're not using a DeferredList here because we want to keep the | ||
165 | 181 | # number of connections to the backend minimal. See lp:567515. | ||
166 | 182 | for path in paths: | ||
167 | 183 | deferred.addCallback( | ||
168 | 184 | lambda ignore, path=path: self._get_data(path, ec2_data)) | ||
169 | 185 | # Special case the ramdisk retrieval, because it may not be present | ||
170 | 186 | deferred.addCallback( | ||
171 | 187 | lambda ignore: self._fetch_async( | ||
172 | 188 | EC2_API + "/meta-data/ramdisk-id").addErrback(log_failure)) | ||
173 | 189 | deferred.addCallback(ec2_data.append) | ||
174 | 190 | # And same for kernel | ||
175 | 191 | deferred.addCallback( | ||
176 | 192 | lambda ignore: self._fetch_async( | ||
177 | 193 | EC2_API + "/meta-data/kernel-id").addErrback(log_failure)) | ||
178 | 194 | deferred.addCallback(ec2_data.append) | ||
179 | 195 | |||
180 | 196 | def record_data(ignore): | ||
181 | 197 | """Record the instance data returned by the EC2 API.""" | ||
182 | 198 | (raw_user_data, instance_key, reservation_key, | ||
183 | 199 | local_hostname, public_hostname, launch_index, | ||
184 | 200 | ami_key, local_ip, public_ip, ramdisk_key, | ||
185 | 201 | kernel_key) = ec2_data | ||
186 | 202 | self._ec2_data = { | ||
187 | 203 | "instance_key": instance_key, | ||
188 | 204 | "reservation_key": reservation_key, | ||
189 | 205 | "local_hostname": local_hostname, | ||
190 | 206 | "public_hostname": public_hostname, | ||
191 | 207 | "launch_index": launch_index, | ||
192 | 208 | "kernel_key": kernel_key, | ||
193 | 209 | "ramdisk_key": ramdisk_key, | ||
194 | 210 | "image_key": ami_key, | ||
195 | 211 | "public_ipv4": public_ip, | ||
196 | 212 | "local_ipv4": local_ip} | ||
197 | 213 | for k, v in self._ec2_data.items(): | ||
198 | 214 | if v is None and k in ("ramdisk_key", "kernel_key"): | ||
199 | 215 | continue | ||
200 | 216 | self._ec2_data[k] = v.decode("utf-8") | ||
201 | 217 | self._ec2_data["launch_index"] = int( | ||
202 | 218 | self._ec2_data["launch_index"]) | ||
203 | 219 | |||
204 | 220 | if self._config.otp: | ||
205 | 221 | self._otp = self._config.otp | ||
206 | 222 | return | ||
207 | 223 | instance_data = _extract_ec2_instance_data( | ||
208 | 224 | raw_user_data, int(launch_index)) | ||
209 | 225 | if instance_data is not None: | ||
210 | 226 | self._otp = instance_data["otp"] | ||
211 | 227 | exchange_url = instance_data["exchange-url"] | ||
212 | 228 | ping_url = instance_data["ping-url"] | ||
213 | 229 | self._exchange._transport.set_url(exchange_url) | ||
214 | 230 | self._config.url = exchange_url | ||
215 | 231 | self._config.ping_url = ping_url | ||
216 | 232 | if "ssl-ca-certificate" in instance_data: | ||
217 | 233 | from landscape.configuration import \ | ||
218 | 234 | store_public_key_data | ||
219 | 235 | public_key_file = store_public_key_data( | ||
220 | 236 | self._config, instance_data["ssl-ca-certificate"]) | ||
221 | 237 | self._config.ssl_public_key = public_key_file | ||
222 | 238 | self._exchange._transport._pubkey = public_key_file | ||
223 | 239 | self._config.write() | ||
224 | 240 | |||
225 | 241 | def log_error(error): | ||
226 | 242 | log_failure(error, msg="Got error while fetching meta-data: %r" | ||
227 | 243 | % (error.value,)) | ||
228 | 244 | |||
229 | 245 | deferred.addCallback(record_data) | ||
230 | 246 | deferred.addErrback(log_error) | ||
231 | 247 | |||
232 | 248 | def _handle_exchange_done(self): | 144 | def _handle_exchange_done(self): |
233 | 249 | """Registered handler for the C{"exchange-done"} event. | 145 | """Registered handler for the C{"exchange-done"} event. |
234 | 250 | 146 | ||
235 | @@ -264,12 +160,8 @@ | |||
236 | 264 | message with the server. | 160 | message with the server. |
237 | 265 | 161 | ||
238 | 266 | A computer can fall into several categories: | 162 | A computer can fall into several categories: |
239 | 267 | - a "cloud VM" | ||
240 | 268 | - a "normal" computer | 163 | - a "normal" computer |
241 | 269 | - a "provisionned machine". | 164 | - a "provisionned machine". |
242 | 270 | |||
243 | 271 | Furthermore, Cloud VMs can be registered with either a One Time | ||
244 | 272 | Password (OTP), or with a normal registration password. | ||
245 | 273 | """ | 165 | """ |
246 | 274 | registration_failed = False | 166 | registration_failed = False |
247 | 275 | 167 | ||
248 | @@ -293,10 +185,9 @@ | |||
249 | 293 | 185 | ||
250 | 294 | if not is_valid_tag_list(tags): | 186 | if not is_valid_tag_list(tags): |
251 | 295 | tags = None | 187 | tags = None |
253 | 296 | logging.error("Invalid tags provided for cloud registration.") | 188 | logging.error("Invalid tags provided for registration.") |
254 | 297 | 189 | ||
255 | 298 | message = {"type": None, # either "register" or "register-cloud-vm" | 190 | message = {"type": None, # either "register" or "register-cloud-vm" |
256 | 299 | "otp": None, | ||
257 | 300 | "hostname": get_fqdn(), | 191 | "hostname": get_fqdn(), |
258 | 301 | "account_name": identity.account_name, | 192 | "account_name": identity.account_name, |
259 | 302 | "registration_password": None, | 193 | "registration_password": None, |
260 | @@ -306,28 +197,7 @@ | |||
261 | 306 | if group: | 197 | if group: |
262 | 307 | message["access_group"] = group | 198 | message["access_group"] = group |
263 | 308 | 199 | ||
286 | 309 | if self._config.cloud and self._ec2_data is not None: | 200 | if account_name: |
265 | 310 | # This is the "cloud VM" case. | ||
266 | 311 | message["type"] = "register-cloud-vm" | ||
267 | 312 | |||
268 | 313 | message.update(self._ec2_data) | ||
269 | 314 | if self._otp: | ||
270 | 315 | logging.info("Queueing message to register with OTP") | ||
271 | 316 | message["otp"] = self._otp | ||
272 | 317 | |||
273 | 318 | elif account_name: | ||
274 | 319 | with_tags = "and tags %s " % tags if tags else "" | ||
275 | 320 | with_group = "in access group '%s' " % group if group else "" | ||
276 | 321 | logging.info( | ||
277 | 322 | u"Queueing message to register with account %r %s%s" | ||
278 | 323 | u"as an EC2 instance." % ( | ||
279 | 324 | account_name, with_group, with_tags)) | ||
280 | 325 | message["registration_password"] = registration_key | ||
281 | 326 | |||
282 | 327 | else: | ||
283 | 328 | registration_failed = True | ||
284 | 329 | |||
285 | 330 | elif account_name: | ||
287 | 331 | # The computer is a normal computer, possibly a container. | 201 | # The computer is a normal computer, possibly a container. |
288 | 332 | with_word = "with" if bool(registration_key) else "without" | 202 | with_word = "with" if bool(registration_key) else "without" |
289 | 333 | with_tags = "and tags %s " % tags if tags else "" | 203 | with_tags = "and tags %s " % tags if tags else "" |
290 | @@ -342,6 +212,9 @@ | |||
291 | 342 | message["container-info"] = get_container_info() | 212 | message["container-info"] = get_container_info() |
292 | 343 | 213 | ||
293 | 344 | if self._juju_data is not None: | 214 | if self._juju_data is not None: |
294 | 215 | # For backwards compatibility, set the juju-info to be one of | ||
295 | 216 | # the juju infos (it used not to be a list). | ||
296 | 217 | message["juju-info"] = self._juju_data[0] | ||
297 | 345 | message["juju-info-list"] = self._juju_data | 218 | message["juju-info-list"] = self._juju_data |
298 | 346 | 219 | ||
299 | 347 | elif self._config.provisioning_otp: | 220 | elif self._config.provisioning_otp: |
300 | @@ -430,80 +303,3 @@ | |||
301 | 430 | def _failed(self): | 303 | def _failed(self): |
302 | 431 | self.deferred.errback(InvalidCredentialsError()) | 304 | self.deferred.errback(InvalidCredentialsError()) |
303 | 432 | self._cancel_calls() | 305 | self._cancel_calls() |
304 | 433 | |||
305 | 434 | |||
306 | 435 | def _extract_ec2_instance_data(raw_user_data, launch_index): | ||
307 | 436 | """ | ||
308 | 437 | Given the raw string of EC2 User Data, parse it and return the dict of | ||
309 | 438 | instance data for this particular instance. | ||
310 | 439 | |||
311 | 440 | If the data can't be parsed, a debug message will be logged and None | ||
312 | 441 | will be returned. | ||
313 | 442 | """ | ||
314 | 443 | try: | ||
315 | 444 | user_data = loads(raw_user_data) | ||
316 | 445 | except ValueError: | ||
317 | 446 | logging.debug("Got invalid user-data %r" % (raw_user_data,)) | ||
318 | 447 | return | ||
319 | 448 | |||
320 | 449 | if not isinstance(user_data, dict): | ||
321 | 450 | logging.debug("user-data %r is not a dict" % (user_data,)) | ||
322 | 451 | return | ||
323 | 452 | for key in "otps", "exchange-url", "ping-url": | ||
324 | 453 | if key not in user_data: | ||
325 | 454 | logging.debug("user-data %r doesn't have key %r." | ||
326 | 455 | % (user_data, key)) | ||
327 | 456 | return | ||
328 | 457 | if len(user_data["otps"]) <= launch_index: | ||
329 | 458 | logging.debug("user-data %r doesn't have OTP for launch index %d" | ||
330 | 459 | % (user_data, launch_index)) | ||
331 | 460 | return | ||
332 | 461 | instance_data = {"otp": user_data["otps"][launch_index], | ||
333 | 462 | "exchange-url": user_data["exchange-url"], | ||
334 | 463 | "ping-url": user_data["ping-url"]} | ||
335 | 464 | if "ssl-ca-certificate" in user_data: | ||
336 | 465 | instance_data["ssl-ca-certificate"] = user_data["ssl-ca-certificate"] | ||
337 | 466 | return instance_data | ||
338 | 467 | |||
339 | 468 | |||
340 | 469 | def _wait_for_network(): | ||
341 | 470 | """ | ||
342 | 471 | Keep trying to connect to the EC2 metadata server until it becomes | ||
343 | 472 | accessible or until five minutes pass. | ||
344 | 473 | |||
345 | 474 | This is necessary because the networking init script on Ubuntu is | ||
346 | 475 | asynchronous; the network may not actually be up by the time the | ||
347 | 476 | landscape-client init script is invoked. | ||
348 | 477 | """ | ||
349 | 478 | timeout = 5 * 60 | ||
350 | 479 | port = 80 | ||
351 | 480 | |||
352 | 481 | start = time.time() | ||
353 | 482 | while True: | ||
354 | 483 | s = socket.socket() | ||
355 | 484 | try: | ||
356 | 485 | s.connect((EC2_HOST, port)) | ||
357 | 486 | s.close() | ||
358 | 487 | return | ||
359 | 488 | except socket.error: | ||
360 | 489 | time.sleep(1) | ||
361 | 490 | if time.time() - start > timeout: | ||
362 | 491 | break | ||
363 | 492 | |||
364 | 493 | |||
365 | 494 | def is_cloud_managed(fetch=fetch): | ||
366 | 495 | """ | ||
367 | 496 | Return C{True} if the machine has been started by Landscape, i.e. if we can | ||
368 | 497 | find the expected data inside the EC2 user-data field. | ||
369 | 498 | """ | ||
370 | 499 | _wait_for_network() | ||
371 | 500 | try: | ||
372 | 501 | raw_user_data = fetch(EC2_API + "/user-data", | ||
373 | 502 | connect_timeout=5) | ||
374 | 503 | launch_index = fetch(EC2_API + "/meta-data/ami-launch-index", | ||
375 | 504 | connect_timeout=5) | ||
376 | 505 | except FetchError: | ||
377 | 506 | return False | ||
378 | 507 | instance_data = _extract_ec2_instance_data( | ||
379 | 508 | raw_user_data, int(launch_index)) | ||
380 | 509 | return instance_data is not None | ||
381 | 510 | 306 | ||
382 | === modified file 'landscape/broker/tests/test_registration.py' | |||
383 | --- landscape/broker/tests/test_registration.py 2014-07-13 07:01:19 +0000 | |||
384 | +++ landscape/broker/tests/test_registration.py 2014-07-16 10:26:46 +0000 | |||
385 | @@ -1,24 +1,15 @@ | |||
386 | 1 | import json | 1 | import json |
387 | 2 | import os | ||
388 | 3 | import logging | 2 | import logging |
389 | 4 | import pycurl | ||
390 | 5 | import socket | 3 | import socket |
391 | 6 | 4 | ||
392 | 7 | from twisted.internet.defer import succeed, fail | ||
393 | 8 | |||
394 | 9 | from landscape.broker.registration import ( | 5 | from landscape.broker.registration import ( |
397 | 10 | InvalidCredentialsError, RegistrationHandler, is_cloud_managed, EC2_HOST, | 6 | InvalidCredentialsError, Identity) |
396 | 11 | EC2_API, Identity) | ||
398 | 12 | 7 | ||
399 | 13 | from landscape.broker.config import BrokerConfiguration | ||
400 | 14 | from landscape.tests.helpers import LandscapeTest | 8 | from landscape.tests.helpers import LandscapeTest |
401 | 15 | from landscape.broker.tests.helpers import ( | 9 | from landscape.broker.tests.helpers import ( |
402 | 16 | BrokerConfigurationHelper, RegistrationHelper) | 10 | BrokerConfigurationHelper, RegistrationHelper) |
403 | 17 | from landscape.lib.bpickle import dumps | ||
404 | 18 | from landscape.lib.fetch import HTTPCodeError, FetchError | ||
405 | 19 | from landscape.lib.persist import Persist | 11 | from landscape.lib.persist import Persist |
406 | 20 | from landscape.lib.vm_info import get_vm_info | 12 | from landscape.lib.vm_info import get_vm_info |
407 | 21 | from landscape.configuration import print_text | ||
408 | 22 | 13 | ||
409 | 23 | 14 | ||
410 | 24 | class IdentityTest(LandscapeTest): | 15 | class IdentityTest(LandscapeTest): |
411 | @@ -250,8 +241,7 @@ | |||
412 | 250 | If the admin has defined tags for this computer, but they are not | 241 | If the admin has defined tags for this computer, but they are not |
413 | 251 | valid, we drop them, and report an error. | 242 | valid, we drop them, and report an error. |
414 | 252 | """ | 243 | """ |
417 | 253 | self.log_helper.ignore_errors("Invalid tags provided for cloud " | 244 | self.log_helper.ignore_errors("Invalid tags provided for registration") |
416 | 254 | "registration") | ||
418 | 255 | self.mstore.set_accepted_types(["register"]) | 245 | self.mstore.set_accepted_types(["register"]) |
419 | 256 | self.config.computer_title = "Computer Title" | 246 | self.config.computer_title = "Computer Title" |
420 | 257 | self.config.account_name = "account_name" | 247 | self.config.account_name = "account_name" |
421 | @@ -261,8 +251,7 @@ | |||
422 | 261 | messages = self.mstore.get_pending_messages() | 251 | messages = self.mstore.get_pending_messages() |
423 | 262 | self.assertIs(None, messages[0]["tags"]) | 252 | self.assertIs(None, messages[0]["tags"]) |
424 | 263 | self.assertEqual(self.logfile.getvalue().strip(), | 253 | self.assertEqual(self.logfile.getvalue().strip(), |
427 | 264 | "ERROR: Invalid tags provided for cloud " | 254 | "ERROR: Invalid tags provided for registration.\n " |
426 | 265 | "registration.\n " | ||
428 | 266 | "INFO: Queueing message to register with account " | 255 | "INFO: Queueing message to register with account " |
429 | 267 | "'account_name' with a password.\n " | 256 | "'account_name' with a password.\n " |
430 | 268 | "INFO: Sending registration message to exchange.") | 257 | "INFO: Sending registration message to exchange.") |
431 | @@ -536,6 +525,23 @@ | |||
432 | 536 | "unit-name": "service/0"} | 525 | "unit-name": "service/0"} |
433 | 537 | self.assertEqual(expected, messages[0]["juju-info-list"][0]) | 526 | self.assertEqual(expected, messages[0]["juju-info-list"][0]) |
434 | 538 | 527 | ||
435 | 528 | def test_juju_info_compatibility_present(self): | ||
436 | 529 | """ | ||
437 | 530 | When Juju information is found in $data_dir/juju-info.d/*.json, | ||
438 | 531 | the registration message also contains a "juju-info" key for | ||
439 | 532 | backwards compatibility with older servers. | ||
440 | 533 | """ | ||
441 | 534 | self.mstore.set_accepted_types(["register"]) | ||
442 | 535 | self.config.account_name = "account_name" | ||
443 | 536 | self.reactor.fire("run") | ||
444 | 537 | self.reactor.fire("pre-exchange") | ||
445 | 538 | |||
446 | 539 | messages = self.mstore.get_pending_messages() | ||
447 | 540 | expected = {"environment-uuid": "DEAD-BEEF", | ||
448 | 541 | "api-addresses": ["10.0.3.1:17070"], | ||
449 | 542 | "unit-name": "service/0"} | ||
450 | 543 | self.assertEqual(expected, messages[0]["juju-info"]) | ||
451 | 544 | |||
452 | 539 | def test_multiple_juju_information_added_when_present(self): | 545 | def test_multiple_juju_information_added_when_present(self): |
453 | 540 | """ | 546 | """ |
454 | 541 | When Juju information is found in $data_dir/juju-info.json, | 547 | When Juju information is found in $data_dir/juju-info.json, |
455 | @@ -571,661 +577,6 @@ | |||
456 | 571 | self.assertIn(expected2, juju_info) | 577 | self.assertIn(expected2, juju_info) |
457 | 572 | 578 | ||
458 | 573 | 579 | ||
459 | 574 | class CloudRegistrationHandlerTest(RegistrationHandlerTestBase): | ||
460 | 575 | |||
461 | 576 | cloud = True | ||
462 | 577 | |||
463 | 578 | def setUp(self): | ||
464 | 579 | super(CloudRegistrationHandlerTest, self).setUp() | ||
465 | 580 | self.query_results = {} | ||
466 | 581 | |||
467 | 582 | def fetch_stub(url): | ||
468 | 583 | value = self.query_results[url] | ||
469 | 584 | if isinstance(value, Exception): | ||
470 | 585 | return fail(value) | ||
471 | 586 | else: | ||
472 | 587 | return succeed(value) | ||
473 | 588 | |||
474 | 589 | self.fetch_func = fetch_stub | ||
475 | 590 | |||
476 | 591 | def get_user_data(self, otps=None, | ||
477 | 592 | exchange_url="https://example.com/message-system", | ||
478 | 593 | ping_url="http://example.com/ping", | ||
479 | 594 | ssl_ca_certificate=None): | ||
480 | 595 | if otps is None: | ||
481 | 596 | otps = ["otp1"] | ||
482 | 597 | user_data = {"otps": otps, "exchange-url": exchange_url, | ||
483 | 598 | "ping-url": ping_url} | ||
484 | 599 | if ssl_ca_certificate is not None: | ||
485 | 600 | user_data["ssl-ca-certificate"] = ssl_ca_certificate | ||
486 | 601 | return user_data | ||
487 | 602 | |||
488 | 603 | def prepare_query_results( | ||
489 | 604 | self, user_data=None, instance_key="key1", launch_index=0, | ||
490 | 605 | local_hostname="ooga.local", public_hostname="ooga.amazon.com", | ||
491 | 606 | reservation_key=u"res1", ramdisk_key=u"ram1", kernel_key=u"kernel1", | ||
492 | 607 | image_key=u"image1", local_ip="10.0.0.1", public_ip="10.0.0.2", | ||
493 | 608 | ssl_ca_certificate=None): | ||
494 | 609 | if user_data is None: | ||
495 | 610 | user_data = self.get_user_data( | ||
496 | 611 | ssl_ca_certificate=ssl_ca_certificate) | ||
497 | 612 | if not isinstance(user_data, Exception): | ||
498 | 613 | user_data = dumps(user_data) | ||
499 | 614 | api_base = "http://169.254.169.254/latest" | ||
500 | 615 | self.query_results.clear() | ||
501 | 616 | for url_suffix, value in [ | ||
502 | 617 | ("/user-data", user_data), | ||
503 | 618 | ("/meta-data/instance-id", instance_key), | ||
504 | 619 | ("/meta-data/reservation-id", reservation_key), | ||
505 | 620 | ("/meta-data/local-hostname", local_hostname), | ||
506 | 621 | ("/meta-data/public-hostname", public_hostname), | ||
507 | 622 | ("/meta-data/ami-launch-index", str(launch_index)), | ||
508 | 623 | ("/meta-data/kernel-id", kernel_key), | ||
509 | 624 | ("/meta-data/ramdisk-id", ramdisk_key), | ||
510 | 625 | ("/meta-data/ami-id", image_key), | ||
511 | 626 | ("/meta-data/local-ipv4", local_ip), | ||
512 | 627 | ("/meta-data/public-ipv4", public_ip), | ||
513 | 628 | ]: | ||
514 | 629 | self.query_results[api_base + url_suffix] = value | ||
515 | 630 | |||
516 | 631 | def prepare_cloud_registration(self, account_name=None, | ||
517 | 632 | registration_key=None, tags=None, | ||
518 | 633 | access_group=None): | ||
519 | 634 | # Set things up so that the client thinks it should register | ||
520 | 635 | self.mstore.set_accepted_types(list(self.mstore.get_accepted_types()) | ||
521 | 636 | + ["register-cloud-vm"]) | ||
522 | 637 | self.config.account_name = account_name | ||
523 | 638 | self.config.registration_key = registration_key | ||
524 | 639 | self.config.computer_title = None | ||
525 | 640 | self.config.tags = tags | ||
526 | 641 | self.config.access_group = access_group | ||
527 | 642 | self.identity.secure_id = None | ||
528 | 643 | self.assertTrue(self.handler.should_register()) | ||
529 | 644 | |||
530 | 645 | def get_expected_cloud_message(self, **kwargs): | ||
531 | 646 | """ | ||
532 | 647 | Return the message which is expected from a similar call to | ||
533 | 648 | L{get_registration_handler_for_cloud}. | ||
534 | 649 | """ | ||
535 | 650 | message = dict(type="register-cloud-vm", | ||
536 | 651 | otp="otp1", | ||
537 | 652 | hostname="ooga.local", | ||
538 | 653 | local_hostname="ooga.local", | ||
539 | 654 | public_hostname="ooga.amazon.com", | ||
540 | 655 | instance_key=u"key1", | ||
541 | 656 | reservation_key=u"res1", | ||
542 | 657 | ramdisk_key=u"ram1", | ||
543 | 658 | kernel_key=u"kernel1", | ||
544 | 659 | launch_index=0, | ||
545 | 660 | image_key=u"image1", | ||
546 | 661 | account_name=None, | ||
547 | 662 | registration_password=None, | ||
548 | 663 | local_ipv4=u"10.0.0.1", | ||
549 | 664 | public_ipv4=u"10.0.0.2", | ||
550 | 665 | tags=None) | ||
551 | 666 | # The get_vm_info() needs to be deferred to the else. If vm-info is | ||
552 | 667 | # not specified in kwargs, get_vm_info() will typically be mocked. | ||
553 | 668 | if "vm_info" in kwargs: | ||
554 | 669 | message["vm-info"] = kwargs.pop("vm_info") | ||
555 | 670 | else: | ||
556 | 671 | message["vm-info"] = get_vm_info() | ||
557 | 672 | message.update(kwargs) | ||
558 | 673 | return message | ||
559 | 674 | |||
560 | 675 | def test_cloud_registration(self): | ||
561 | 676 | """ | ||
562 | 677 | When the 'cloud' configuration variable is set, cloud registration is | ||
563 | 678 | done instead of normal password-based registration. This means: | ||
564 | 679 | |||
565 | 680 | - "Launch Data" is fetched from the EC2 Launch Data URL. This contains | ||
566 | 681 | a one-time password that is used during registration. | ||
567 | 682 | - A different "register-cloud-vm" message is sent to the server instead | ||
568 | 683 | of "register", containing the OTP. This message is handled by | ||
569 | 684 | immediately accepting the computer, instead of going through the | ||
570 | 685 | pending computer stage. | ||
571 | 686 | """ | ||
572 | 687 | get_vm_info_mock = self.mocker.replace(get_vm_info) | ||
573 | 688 | get_vm_info_mock() | ||
574 | 689 | self.mocker.result("xen") | ||
575 | 690 | self.mocker.replay() | ||
576 | 691 | self.prepare_query_results() | ||
577 | 692 | self.prepare_cloud_registration(tags=u"server,london") | ||
578 | 693 | |||
579 | 694 | # metadata is fetched and stored at reactor startup: | ||
580 | 695 | self.reactor.fire("run") | ||
581 | 696 | |||
582 | 697 | # And the metadata returned determines the URLs that are used | ||
583 | 698 | self.assertEqual(self.transport.get_url(), | ||
584 | 699 | "https://example.com/message-system") | ||
585 | 700 | self.assertEqual(self.pinger.get_url(), | ||
586 | 701 | "http://example.com/ping") | ||
587 | 702 | # Lets make sure those values were written back to the config file | ||
588 | 703 | new_config = BrokerConfiguration() | ||
589 | 704 | new_config.load_configuration_file(self.config_filename) | ||
590 | 705 | self.assertEqual(new_config.url, "https://example.com/message-system") | ||
591 | 706 | self.assertEqual(new_config.ping_url, "http://example.com/ping") | ||
592 | 707 | |||
593 | 708 | # Okay! Exchange should cause the registration to happen. | ||
594 | 709 | self.exchanger.exchange() | ||
595 | 710 | # This *should* be asynchronous, but I think a billion tests are | ||
596 | 711 | # written like this | ||
597 | 712 | self.assertEqual(len(self.transport.payloads), 1) | ||
598 | 713 | self.assertMessages( | ||
599 | 714 | self.transport.payloads[0]["messages"], | ||
600 | 715 | [self.get_expected_cloud_message(tags=u"server,london", | ||
601 | 716 | vm_info="xen")]) | ||
602 | 717 | |||
603 | 718 | def test_cloud_registration_with_access_group(self): | ||
604 | 719 | """ | ||
605 | 720 | If the access_group field is presnet in the configuration, the | ||
606 | 721 | access_group field is present in the outgoing message for a VM | ||
607 | 722 | registration, and a notice appears in the logs. | ||
608 | 723 | """ | ||
609 | 724 | self.prepare_query_results() | ||
610 | 725 | self.prepare_cloud_registration(access_group=u"dinosaurs", | ||
611 | 726 | tags=u"server,london") | ||
612 | 727 | |||
613 | 728 | self.reactor.fire("run") | ||
614 | 729 | self.exchanger.exchange() | ||
615 | 730 | self.assertEqual(len(self.transport.payloads), 1) | ||
616 | 731 | self.assertMessages( | ||
617 | 732 | self.transport.payloads[0]["messages"], | ||
618 | 733 | [self.get_expected_cloud_message( | ||
619 | 734 | access_group=u"dinosaurs", tags=u"server,london")]) | ||
620 | 735 | |||
621 | 736 | def test_cloud_registration_with_otp(self): | ||
622 | 737 | """ | ||
623 | 738 | If the OTP is present in the configuration, it's used to trigger the | ||
624 | 739 | registration instead of using the user data. | ||
625 | 740 | """ | ||
626 | 741 | self.config.otp = "otp1" | ||
627 | 742 | self.prepare_query_results(user_data=None) | ||
628 | 743 | |||
629 | 744 | self.prepare_cloud_registration() | ||
630 | 745 | |||
631 | 746 | # metadata is fetched and stored at reactor startup: | ||
632 | 747 | self.reactor.fire("run") | ||
633 | 748 | |||
634 | 749 | # Okay! Exchange should cause the registration to happen. | ||
635 | 750 | self.exchanger.exchange() | ||
636 | 751 | # This *should* be asynchronous, but I think a billion tests are | ||
637 | 752 | # written like this | ||
638 | 753 | self.assertEqual(len(self.transport.payloads), 1) | ||
639 | 754 | self.assertMessages( | ||
640 | 755 | self.transport.payloads[0]["messages"], | ||
641 | 756 | [self.get_expected_cloud_message()]) | ||
642 | 757 | |||
643 | 758 | def test_cloud_registration_with_invalid_tags(self): | ||
644 | 759 | """ | ||
645 | 760 | Invalid tags in the configuration should result in the tags not being | ||
646 | 761 | sent to the server, and this fact logged. | ||
647 | 762 | """ | ||
648 | 763 | self.log_helper.ignore_errors("Invalid tags provided for cloud " | ||
649 | 764 | "registration") | ||
650 | 765 | self.prepare_query_results() | ||
651 | 766 | self.prepare_cloud_registration(tags=u"<script>alert()</script>,hardy") | ||
652 | 767 | |||
653 | 768 | # metadata is fetched and stored at reactor startup: | ||
654 | 769 | self.reactor.fire("run") | ||
655 | 770 | self.exchanger.exchange() | ||
656 | 771 | self.assertEqual(len(self.transport.payloads), 1) | ||
657 | 772 | self.assertMessages(self.transport.payloads[0]["messages"], | ||
658 | 773 | [self.get_expected_cloud_message(tags=None)]) | ||
659 | 774 | self.assertEqual(self.logfile.getvalue().strip()[:-7], | ||
660 | 775 | "ERROR: Invalid tags provided for cloud " | ||
661 | 776 | "registration.\n " | ||
662 | 777 | "INFO: Queueing message to register with OTP\n " | ||
663 | 778 | "INFO: Sending registration message to exchange.\n " | ||
664 | 779 | " INFO: Starting message exchange with " | ||
665 | 780 | "https://example.com/message-system.\n " | ||
666 | 781 | "INFO: Message exchange completed in") | ||
667 | 782 | |||
668 | 783 | def test_cloud_registration_with_ssl_ca_certificate(self): | ||
669 | 784 | """ | ||
670 | 785 | If we have an SSL certificate CA included in the user-data, this should | ||
671 | 786 | be written out, and the configuration updated to reflect this. | ||
672 | 787 | """ | ||
673 | 788 | key_filename = os.path.join(self.config.data_path, | ||
674 | 789 | "%s.ssl_public_key" % os.path.basename(self.config_filename)) | ||
675 | 790 | |||
676 | 791 | print_text_mock = self.mocker.replace(print_text) | ||
677 | 792 | print_text_mock("Writing SSL CA certificate to %s..." % | ||
678 | 793 | key_filename) | ||
679 | 794 | self.mocker.replay() | ||
680 | 795 | self.prepare_query_results(ssl_ca_certificate=u"1234567890") | ||
681 | 796 | self.prepare_cloud_registration(tags=u"server,london") | ||
682 | 797 | # metadata is fetched and stored at reactor startup: | ||
683 | 798 | self.reactor.fire("run") | ||
684 | 799 | # And the metadata returned determines the URLs that are used | ||
685 | 800 | self.assertEqual("https://example.com/message-system", | ||
686 | 801 | self.transport.get_url()) | ||
687 | 802 | self.assertEqual(key_filename, self.transport._pubkey) | ||
688 | 803 | self.assertEqual("http://example.com/ping", | ||
689 | 804 | self.pinger.get_url()) | ||
690 | 805 | # Let's make sure those values were written back to the config file | ||
691 | 806 | new_config = BrokerConfiguration() | ||
692 | 807 | new_config.load_configuration_file(self.config_filename) | ||
693 | 808 | self.assertEqual("https://example.com/message-system", new_config.url) | ||
694 | 809 | self.assertEqual("http://example.com/ping", new_config.ping_url) | ||
695 | 810 | self.assertEqual(key_filename, new_config.ssl_public_key) | ||
696 | 811 | self.assertEqual("1234567890", open(key_filename, "r").read()) | ||
697 | 812 | |||
698 | 813 | def test_wrong_user_data(self): | ||
699 | 814 | self.prepare_query_results(user_data="other stuff, not a bpickle") | ||
700 | 815 | self.prepare_cloud_registration() | ||
701 | 816 | |||
702 | 817 | # Mock registration-failed call | ||
703 | 818 | reactor_mock = self.mocker.patch(self.reactor) | ||
704 | 819 | reactor_mock.fire("registration-failed") | ||
705 | 820 | self.mocker.replay() | ||
706 | 821 | |||
707 | 822 | self.reactor.fire("run") | ||
708 | 823 | self.exchanger.exchange() | ||
709 | 824 | |||
710 | 825 | def test_wrong_object_type_in_user_data(self): | ||
711 | 826 | self.prepare_query_results(user_data=True) | ||
712 | 827 | self.prepare_cloud_registration() | ||
713 | 828 | |||
714 | 829 | # Mock registration-failed call | ||
715 | 830 | reactor_mock = self.mocker.patch(self.reactor) | ||
716 | 831 | reactor_mock.fire("registration-failed") | ||
717 | 832 | self.mocker.replay() | ||
718 | 833 | |||
719 | 834 | self.reactor.fire("run") | ||
720 | 835 | self.exchanger.exchange() | ||
721 | 836 | |||
722 | 837 | def test_user_data_with_not_enough_elements(self): | ||
723 | 838 | """ | ||
724 | 839 | If the AMI launch index isn't represented in the list of OTPs in the | ||
725 | 840 | user data then BOOM. | ||
726 | 841 | """ | ||
727 | 842 | self.prepare_query_results(launch_index=1) | ||
728 | 843 | self.prepare_cloud_registration() | ||
729 | 844 | |||
730 | 845 | # Mock registration-failed call | ||
731 | 846 | reactor_mock = self.mocker.patch(self.reactor) | ||
732 | 847 | reactor_mock.fire("registration-failed") | ||
733 | 848 | self.mocker.replay() | ||
734 | 849 | |||
735 | 850 | self.reactor.fire("run") | ||
736 | 851 | self.exchanger.exchange() | ||
737 | 852 | |||
738 | 853 | def test_user_data_bpickle_without_otp(self): | ||
739 | 854 | self.prepare_query_results(user_data={"foo": "bar"}) | ||
740 | 855 | self.prepare_cloud_registration() | ||
741 | 856 | |||
742 | 857 | # Mock registration-failed call | ||
743 | 858 | reactor_mock = self.mocker.patch(self.reactor) | ||
744 | 859 | reactor_mock.fire("registration-failed") | ||
745 | 860 | self.mocker.replay() | ||
746 | 861 | |||
747 | 862 | self.reactor.fire("run") | ||
748 | 863 | self.exchanger.exchange() | ||
749 | 864 | |||
750 | 865 | def test_no_otp_fallback_to_account(self): | ||
751 | 866 | self.prepare_query_results(user_data="other stuff, not a bpickle", | ||
752 | 867 | instance_key=u"key1") | ||
753 | 868 | self.prepare_cloud_registration(account_name=u"onward", | ||
754 | 869 | registration_key=u"password", | ||
755 | 870 | tags=u"london,server") | ||
756 | 871 | |||
757 | 872 | self.reactor.fire("run") | ||
758 | 873 | self.exchanger.exchange() | ||
759 | 874 | |||
760 | 875 | self.assertEqual(len(self.transport.payloads), 1) | ||
761 | 876 | self.assertMessages(self.transport.payloads[0]["messages"], | ||
762 | 877 | [self.get_expected_cloud_message( | ||
763 | 878 | otp=None, | ||
764 | 879 | account_name=u"onward", | ||
765 | 880 | registration_password=u"password", | ||
766 | 881 | tags=u"london,server")]) | ||
767 | 882 | self.assertEqual(self.logfile.getvalue().strip()[:-7], | ||
768 | 883 | "INFO: Queueing message to register with account u'onward' and " | ||
769 | 884 | "tags london,server as an EC2 instance.\n " | ||
770 | 885 | "INFO: Sending registration message to exchange.\n " | ||
771 | 886 | "INFO: Starting message exchange with http://localhost:91919.\n " | ||
772 | 887 | " INFO: Message exchange completed in") | ||
773 | 888 | |||
774 | 889 | def test_queueing_cloud_registration_message_resets_message_store(self): | ||
775 | 890 | """ | ||
776 | 891 | When a registration from a cloud is about to happen, the message store | ||
777 | 892 | is reset, because all previous messages are now meaningless. | ||
778 | 893 | """ | ||
779 | 894 | self.mstore.set_accepted_types(list(self.mstore.get_accepted_types()) | ||
780 | 895 | + ["test"]) | ||
781 | 896 | |||
782 | 897 | self.mstore.add({"type": "test"}) | ||
783 | 898 | |||
784 | 899 | self.prepare_query_results() | ||
785 | 900 | |||
786 | 901 | self.prepare_cloud_registration() | ||
787 | 902 | |||
788 | 903 | self.reactor.fire("run") | ||
789 | 904 | self.reactor.fire("pre-exchange") | ||
790 | 905 | |||
791 | 906 | messages = self.mstore.get_pending_messages() | ||
792 | 907 | self.assertEqual(len(messages), 1) | ||
793 | 908 | self.assertEqual(messages[0]["type"], "register-cloud-vm") | ||
794 | 909 | |||
795 | 910 | def test_cloud_registration_fetch_errors(self): | ||
796 | 911 | """ | ||
797 | 912 | If fetching metadata fails, and we have no account details to fall | ||
798 | 913 | back to, we fire 'registration-failed'. | ||
799 | 914 | """ | ||
800 | 915 | self.log_helper.ignore_errors(pycurl.error) | ||
801 | 916 | |||
802 | 917 | def fetch_stub(url): | ||
803 | 918 | return fail(pycurl.error(7, "couldn't connect to host")) | ||
804 | 919 | |||
805 | 920 | self.handler = RegistrationHandler( | ||
806 | 921 | self.config, self.identity, self.reactor, self.exchanger, | ||
807 | 922 | self.pinger, self.mstore, fetch_async=fetch_stub) | ||
808 | 923 | |||
809 | 924 | self.fetch_stub = fetch_stub | ||
810 | 925 | self.prepare_query_results() | ||
811 | 926 | self.fetch_stub = fetch_stub | ||
812 | 927 | |||
813 | 928 | self.prepare_cloud_registration() | ||
814 | 929 | |||
815 | 930 | failed = [] | ||
816 | 931 | self.reactor.call_on( | ||
817 | 932 | "registration-failed", lambda: failed.append(True)) | ||
818 | 933 | |||
819 | 934 | self.log_helper.ignore_errors("Got error while fetching meta-data") | ||
820 | 935 | self.reactor.fire("run") | ||
821 | 936 | self.exchanger.exchange() | ||
822 | 937 | self.assertEqual(failed, [True]) | ||
823 | 938 | self.assertIn('error: (7, "couldn\'t connect to host")', | ||
824 | 939 | self.logfile.getvalue()) | ||
825 | 940 | |||
826 | 941 | def test_cloud_registration_continues_without_user_data(self): | ||
827 | 942 | """ | ||
828 | 943 | If no user-data exists (i.e., the user-data URL returns a 404), then | ||
829 | 944 | register-cloud-vm still occurs. | ||
830 | 945 | """ | ||
831 | 946 | self.log_helper.ignore_errors(HTTPCodeError) | ||
832 | 947 | self.prepare_query_results(user_data=HTTPCodeError(404, "ohno")) | ||
833 | 948 | self.prepare_cloud_registration(account_name="onward", | ||
834 | 949 | registration_key="password") | ||
835 | 950 | |||
836 | 951 | self.reactor.fire("run") | ||
837 | 952 | self.exchanger.exchange() | ||
838 | 953 | self.assertIn("HTTPCodeError: Server returned HTTP code 404", | ||
839 | 954 | self.logfile.getvalue()) | ||
840 | 955 | self.assertEqual(len(self.transport.payloads), 1) | ||
841 | 956 | self.assertMessages(self.transport.payloads[0]["messages"], | ||
842 | 957 | [self.get_expected_cloud_message( | ||
843 | 958 | otp=None, | ||
844 | 959 | account_name=u"onward", | ||
845 | 960 | registration_password=u"password")]) | ||
846 | 961 | |||
847 | 962 | def test_cloud_registration_continues_without_ramdisk(self): | ||
848 | 963 | """ | ||
849 | 964 | If the instance doesn't have a ramdisk (ie, the query for ramdisk | ||
850 | 965 | returns a 404), then register-cloud-vm still occurs. | ||
851 | 966 | """ | ||
852 | 967 | self.log_helper.ignore_errors(HTTPCodeError) | ||
853 | 968 | self.prepare_query_results(ramdisk_key=HTTPCodeError(404, "ohno")) | ||
854 | 969 | self.prepare_cloud_registration() | ||
855 | 970 | |||
856 | 971 | self.reactor.fire("run") | ||
857 | 972 | self.exchanger.exchange() | ||
858 | 973 | self.assertIn("HTTPCodeError: Server returned HTTP code 404", | ||
859 | 974 | self.logfile.getvalue()) | ||
860 | 975 | self.assertEqual(len(self.transport.payloads), 1) | ||
861 | 976 | self.assertMessages(self.transport.payloads[0]["messages"], | ||
862 | 977 | [self.get_expected_cloud_message( | ||
863 | 978 | ramdisk_key=None)]) | ||
864 | 979 | |||
865 | 980 | def test_cloud_registration_continues_without_kernel(self): | ||
866 | 981 | """ | ||
867 | 982 | If the instance doesn't have a kernel (ie, the query for kernel | ||
868 | 983 | returns a 404), then register-cloud-vm still occurs. | ||
869 | 984 | """ | ||
870 | 985 | self.log_helper.ignore_errors(HTTPCodeError) | ||
871 | 986 | self.prepare_query_results(kernel_key=HTTPCodeError(404, "ohno")) | ||
872 | 987 | self.prepare_cloud_registration() | ||
873 | 988 | |||
874 | 989 | self.reactor.fire("run") | ||
875 | 990 | self.exchanger.exchange() | ||
876 | 991 | self.assertIn("HTTPCodeError: Server returned HTTP code 404", | ||
877 | 992 | self.logfile.getvalue()) | ||
878 | 993 | self.assertEqual(len(self.transport.payloads), 1) | ||
879 | 994 | self.assertMessages(self.transport.payloads[0]["messages"], | ||
880 | 995 | [self.get_expected_cloud_message( | ||
881 | 996 | kernel_key=None)]) | ||
882 | 997 | |||
883 | 998 | def test_fall_back_to_normal_registration_when_metadata_fetch_fails(self): | ||
884 | 999 | """ | ||
885 | 1000 | If fetching metadata fails, but we do have an account name, then we | ||
886 | 1001 | fall back to normal 'register' registration. | ||
887 | 1002 | """ | ||
888 | 1003 | self.mstore.set_accepted_types(["register"]) | ||
889 | 1004 | self.log_helper.ignore_errors(HTTPCodeError) | ||
890 | 1005 | self.prepare_query_results( | ||
891 | 1006 | public_hostname=HTTPCodeError(404, "ohnoes")) | ||
892 | 1007 | self.prepare_cloud_registration(account_name="onward", | ||
893 | 1008 | registration_key="password") | ||
894 | 1009 | self.config.computer_title = "whatever" | ||
895 | 1010 | self.reactor.fire("run") | ||
896 | 1011 | self.exchanger.exchange() | ||
897 | 1012 | self.assertIn("HTTPCodeError: Server returned HTTP code 404", | ||
898 | 1013 | self.logfile.getvalue()) | ||
899 | 1014 | self.assertEqual(len(self.transport.payloads), 1) | ||
900 | 1015 | messages = self.transport.payloads[0]["messages"] | ||
901 | 1016 | self.assertEqual("register", messages[0]["type"]) | ||
902 | 1017 | |||
903 | 1018 | def test_should_register_in_cloud(self): | ||
904 | 1019 | """ | ||
905 | 1020 | The client should register when it's in the cloud even though | ||
906 | 1021 | it doesn't have the normal account details. | ||
907 | 1022 | """ | ||
908 | 1023 | self.mstore.set_accepted_types(self.mstore.get_accepted_types() | ||
909 | 1024 | + ("register-cloud-vm",)) | ||
910 | 1025 | self.config.account_name = None | ||
911 | 1026 | self.config.registration_key = None | ||
912 | 1027 | self.config.computer_title = None | ||
913 | 1028 | self.identity.secure_id = None | ||
914 | 1029 | self.assertTrue(self.handler.should_register()) | ||
915 | 1030 | |||
916 | 1031 | def test_launch_index(self): | ||
917 | 1032 | """ | ||
918 | 1033 | The client used the value in C{ami-launch-index} to choose the | ||
919 | 1034 | appropriate OTP in the user data. | ||
920 | 1035 | """ | ||
921 | 1036 | otp = "correct otp for launch index" | ||
922 | 1037 | self.prepare_query_results( | ||
923 | 1038 | user_data=self.get_user_data(otps=["wrong index", otp, | ||
924 | 1039 | "wrong again"],), | ||
925 | 1040 | instance_key="key1", | ||
926 | 1041 | launch_index=1) | ||
927 | 1042 | |||
928 | 1043 | self.prepare_cloud_registration() | ||
929 | 1044 | |||
930 | 1045 | self.reactor.fire("run") | ||
931 | 1046 | self.exchanger.exchange() | ||
932 | 1047 | self.assertEqual(len(self.transport.payloads), 1) | ||
933 | 1048 | self.assertMessages(self.transport.payloads[0]["messages"], | ||
934 | 1049 | [self.get_expected_cloud_message(otp=otp, | ||
935 | 1050 | launch_index=1)]) | ||
936 | 1051 | |||
937 | 1052 | def test_should_not_register_in_cloud(self): | ||
938 | 1053 | """ | ||
939 | 1054 | Having a secure ID means we shouldn't register, even in the cloud. | ||
940 | 1055 | """ | ||
941 | 1056 | self.mstore.set_accepted_types(self.mstore.get_accepted_types() | ||
942 | 1057 | + ("register-cloud-vm",)) | ||
943 | 1058 | self.config.account_name = None | ||
944 | 1059 | self.config.registration_key = None | ||
945 | 1060 | self.config.computer_title = None | ||
946 | 1061 | self.identity.secure_id = "hello" | ||
947 | 1062 | self.assertFalse(self.handler.should_register()) | ||
948 | 1063 | |||
949 | 1064 | def test_should_not_register_without_register_cloud_vm(self): | ||
950 | 1065 | """ | ||
951 | 1066 | If the server isn't accepting a 'register-cloud-vm' message, | ||
952 | 1067 | we shouldn't register. | ||
953 | 1068 | """ | ||
954 | 1069 | self.config.account_name = None | ||
955 | 1070 | self.config.registration_key = None | ||
956 | 1071 | self.config.computer_title = None | ||
957 | 1072 | self.identity.secure_id = None | ||
958 | 1073 | self.assertFalse(self.handler.should_register()) | ||
959 | 1074 | |||
960 | 1075 | |||
961 | 1076 | class IsCloudManagedTests(LandscapeTest): | ||
962 | 1077 | |||
963 | 1078 | def setUp(self): | ||
964 | 1079 | super(IsCloudManagedTests, self).setUp() | ||
965 | 1080 | self.urls = [] | ||
966 | 1081 | self.responses = [] | ||
967 | 1082 | |||
968 | 1083 | def fake_fetch(self, url, connect_timeout=None): | ||
969 | 1084 | self.urls.append((url, connect_timeout)) | ||
970 | 1085 | return self.responses.pop(0) | ||
971 | 1086 | |||
972 | 1087 | def mock_socket(self): | ||
973 | 1088 | """ | ||
974 | 1089 | Mock out socket usage by is_cloud_managed to wait for the network. | ||
975 | 1090 | """ | ||
976 | 1091 | # Mock the socket.connect call that it also does | ||
977 | 1092 | socket_class = self.mocker.replace("socket.socket", passthrough=False) | ||
978 | 1093 | socket = socket_class() | ||
979 | 1094 | socket.connect((EC2_HOST, 80)) | ||
980 | 1095 | socket.close() | ||
981 | 1096 | |||
982 | 1097 | def test_is_managed(self): | ||
983 | 1098 | """ | ||
984 | 1099 | L{is_cloud_managed} returns True if the EC2 user-data contains | ||
985 | 1100 | Landscape instance information. It fetches the EC2 data with low | ||
986 | 1101 | timeouts. | ||
987 | 1102 | """ | ||
988 | 1103 | user_data = {"otps": ["otp1"], "exchange-url": "http://exchange", | ||
989 | 1104 | "ping-url": "http://ping"} | ||
990 | 1105 | self.responses = [dumps(user_data), "0"] | ||
991 | 1106 | |||
992 | 1107 | self.mock_socket() | ||
993 | 1108 | self.mocker.replay() | ||
994 | 1109 | |||
995 | 1110 | self.assertTrue(is_cloud_managed(self.fake_fetch)) | ||
996 | 1111 | self.assertEqual( | ||
997 | 1112 | self.urls, | ||
998 | 1113 | [(EC2_API + "/user-data", 5), | ||
999 | 1114 | (EC2_API + "/meta-data/ami-launch-index", 5)]) | ||
1000 | 1115 | |||
1001 | 1116 | def test_is_managed_index(self): | ||
1002 | 1117 | user_data = {"otps": ["otp1", "otp2"], | ||
1003 | 1118 | "exchange-url": "http://exchange", | ||
1004 | 1119 | "ping-url": "http://ping"} | ||
1005 | 1120 | self.responses = [dumps(user_data), "1"] | ||
1006 | 1121 | self.mock_socket() | ||
1007 | 1122 | self.mocker.replay() | ||
1008 | 1123 | self.assertTrue(is_cloud_managed(self.fake_fetch)) | ||
1009 | 1124 | |||
1010 | 1125 | def test_is_managed_wrong_index(self): | ||
1011 | 1126 | user_data = {"otps": ["otp1"], "exchange-url": "http://exchange", | ||
1012 | 1127 | "ping-url": "http://ping"} | ||
1013 | 1128 | self.responses = [dumps(user_data), "1"] | ||
1014 | 1129 | self.mock_socket() | ||
1015 | 1130 | self.mocker.replay() | ||
1016 | 1131 | self.assertFalse(is_cloud_managed(self.fake_fetch)) | ||
1017 | 1132 | |||
1018 | 1133 | def test_is_managed_exchange_url(self): | ||
1019 | 1134 | user_data = {"otps": ["otp1"], "ping-url": "http://ping"} | ||
1020 | 1135 | self.responses = [dumps(user_data), "0"] | ||
1021 | 1136 | self.mock_socket() | ||
1022 | 1137 | self.mocker.replay() | ||
1023 | 1138 | self.assertFalse(is_cloud_managed(self.fake_fetch)) | ||
1024 | 1139 | |||
1025 | 1140 | def test_is_managed_ping_url(self): | ||
1026 | 1141 | user_data = {"otps": ["otp1"], "exchange-url": "http://exchange"} | ||
1027 | 1142 | self.responses = [dumps(user_data), "0"] | ||
1028 | 1143 | self.mock_socket() | ||
1029 | 1144 | self.mocker.replay() | ||
1030 | 1145 | self.assertFalse(is_cloud_managed(self.fake_fetch)) | ||
1031 | 1146 | |||
1032 | 1147 | def test_is_managed_bpickle(self): | ||
1033 | 1148 | self.responses = ["some other user data", "0"] | ||
1034 | 1149 | self.mock_socket() | ||
1035 | 1150 | self.mocker.replay() | ||
1036 | 1151 | self.assertFalse(is_cloud_managed(self.fake_fetch)) | ||
1037 | 1152 | |||
1038 | 1153 | def test_is_managed_no_data(self): | ||
1039 | 1154 | self.responses = ["", "0"] | ||
1040 | 1155 | self.mock_socket() | ||
1041 | 1156 | self.mocker.replay() | ||
1042 | 1157 | self.assertFalse(is_cloud_managed(self.fake_fetch)) | ||
1043 | 1158 | |||
1044 | 1159 | def test_is_managed_fetch_not_found(self): | ||
1045 | 1160 | |||
1046 | 1161 | def fake_fetch(url, connect_timeout=None): | ||
1047 | 1162 | raise HTTPCodeError(404, "ohnoes") | ||
1048 | 1163 | |||
1049 | 1164 | self.mock_socket() | ||
1050 | 1165 | self.mocker.replay() | ||
1051 | 1166 | self.assertFalse(is_cloud_managed(fake_fetch)) | ||
1052 | 1167 | |||
1053 | 1168 | def test_is_managed_fetch_error(self): | ||
1054 | 1169 | |||
1055 | 1170 | def fake_fetch(url, connect_timeout=None): | ||
1056 | 1171 | raise FetchError(7, "couldn't connect to host") | ||
1057 | 1172 | |||
1058 | 1173 | self.mock_socket() | ||
1059 | 1174 | self.mocker.replay() | ||
1060 | 1175 | self.assertFalse(is_cloud_managed(fake_fetch)) | ||
1061 | 1176 | |||
1062 | 1177 | def test_waits_for_network(self): | ||
1063 | 1178 | """ | ||
1064 | 1179 | is_cloud_managed will wait until the network before trying to fetch | ||
1065 | 1180 | the EC2 user data. | ||
1066 | 1181 | """ | ||
1067 | 1182 | user_data = {"otps": ["otp1"], "exchange-url": "http://exchange", | ||
1068 | 1183 | "ping-url": "http://ping"} | ||
1069 | 1184 | self.responses = [dumps(user_data), "0"] | ||
1070 | 1185 | |||
1071 | 1186 | self.mocker.order() | ||
1072 | 1187 | time_sleep = self.mocker.replace("time.sleep", passthrough=False) | ||
1073 | 1188 | socket_class = self.mocker.replace("socket.socket", passthrough=False) | ||
1074 | 1189 | socket_obj = socket_class() | ||
1075 | 1190 | socket_obj.connect((EC2_HOST, 80)) | ||
1076 | 1191 | self.mocker.throw(socket.error("woops")) | ||
1077 | 1192 | time_sleep(1) | ||
1078 | 1193 | socket_obj = socket_class() | ||
1079 | 1194 | socket_obj.connect((EC2_HOST, 80)) | ||
1080 | 1195 | self.mocker.result(None) | ||
1081 | 1196 | socket_obj.close() | ||
1082 | 1197 | self.mocker.replay() | ||
1083 | 1198 | self.assertTrue(is_cloud_managed(self.fake_fetch)) | ||
1084 | 1199 | |||
1085 | 1200 | def test_waiting_times_out(self): | ||
1086 | 1201 | """ | ||
1087 | 1202 | We'll only wait five minutes for the network to come up. | ||
1088 | 1203 | """ | ||
1089 | 1204 | |||
1090 | 1205 | def fake_fetch(url, connect_timeout=None): | ||
1091 | 1206 | raise FetchError(7, "couldn't connect to host") | ||
1092 | 1207 | |||
1093 | 1208 | self.mocker.order() | ||
1094 | 1209 | time_sleep = self.mocker.replace("time.sleep", passthrough=False) | ||
1095 | 1210 | time_time = self.mocker.replace("time.time", passthrough=False) | ||
1096 | 1211 | time_time() | ||
1097 | 1212 | self.mocker.result(100) | ||
1098 | 1213 | socket_class = self.mocker.replace("socket.socket", passthrough=False) | ||
1099 | 1214 | socket_obj = socket_class() | ||
1100 | 1215 | socket_obj.connect((EC2_HOST, 80)) | ||
1101 | 1216 | self.mocker.throw(socket.error("woops")) | ||
1102 | 1217 | time_sleep(1) | ||
1103 | 1218 | time_time() | ||
1104 | 1219 | self.mocker.result(401) | ||
1105 | 1220 | self.mocker.replay() | ||
1106 | 1221 | # Mocking time.time is dangerous, because the test harness calls it. So | ||
1107 | 1222 | # we explicitly reset mocker before returning from the test. | ||
1108 | 1223 | try: | ||
1109 | 1224 | self.assertFalse(is_cloud_managed(fake_fetch)) | ||
1110 | 1225 | finally: | ||
1111 | 1226 | self.mocker.reset() | ||
1112 | 1227 | |||
1113 | 1228 | |||
1114 | 1229 | class ProvisioningRegistrationTest(RegistrationHandlerTestBase): | 580 | class ProvisioningRegistrationTest(RegistrationHandlerTestBase): |
1115 | 1230 | 581 | ||
1116 | 1231 | def test_provisioned_machine_registration_with_otp(self): | 582 | def test_provisioned_machine_registration_with_otp(self): |
1117 | 1232 | 583 | ||
1118 | === modified file 'landscape/configuration.py' | |||
1119 | --- landscape/configuration.py 2014-07-01 14:52:02 +0000 | |||
1120 | +++ landscape/configuration.py 2014-07-16 10:26:46 +0000 | |||
1121 | @@ -581,7 +581,7 @@ | |||
1122 | 581 | decode_base64_ssl_public_certificate(config) | 581 | decode_base64_ssl_public_certificate(config) |
1123 | 582 | config.write() | 582 | config.write() |
1124 | 583 | # Restart the client to ensure that it's using the new configuration. | 583 | # Restart the client to ensure that it's using the new configuration. |
1126 | 584 | if not config.no_start and not config.otp: | 584 | if not config.no_start: |
1127 | 585 | try: | 585 | try: |
1128 | 586 | sysvconfig.restart_landscape() | 586 | sysvconfig.restart_landscape() |
1129 | 587 | except ProcessError: | 587 | except ProcessError: |
1130 | 588 | 588 | ||
1131 | === modified file 'landscape/message_schemas.py' | |||
1132 | --- landscape/message_schemas.py 2014-07-13 07:01:19 +0000 | |||
1133 | +++ landscape/message_schemas.py 2014-07-16 10:26:46 +0000 | |||
1134 | @@ -112,6 +112,11 @@ | |||
1135 | 112 | "unit-name": Unicode(), | 112 | "unit-name": Unicode(), |
1136 | 113 | "private-address": Unicode()} | 113 | "private-address": Unicode()} |
1137 | 114 | 114 | ||
1138 | 115 | # The copy is needed because Message mutates the dictionary | ||
1139 | 116 | JUJU_INFO = Message("juju-info", | ||
1140 | 117 | juju_data.copy(), | ||
1141 | 118 | optional=["private-address"]) | ||
1142 | 119 | |||
1143 | 115 | JUJU_UNITS_INFO = Message("juju-units-info", { | 120 | JUJU_UNITS_INFO = Message("juju-units-info", { |
1144 | 116 | "juju-info-list": List(KeyDict(juju_data.copy(), | 121 | "juju-info-list": List(KeyDict(juju_data.copy(), |
1145 | 117 | optional=["private-address"])) | 122 | optional=["private-address"])) |
1146 | @@ -191,6 +196,9 @@ | |||
1147 | 191 | "tags": Any(Unicode(), Constant(None)), | 196 | "tags": Any(Unicode(), Constant(None)), |
1148 | 192 | "vm-info": Bytes(), | 197 | "vm-info": Bytes(), |
1149 | 193 | "container-info": Unicode(), | 198 | "container-info": Unicode(), |
1150 | 199 | "juju-info": KeyDict(juju_data, optional=["private-address"]), | ||
1151 | 200 | # Because of backwards compatibility we need another member with the list | ||
1152 | 201 | # of juju-info, so it can safely be ignored by old servers. | ||
1153 | 194 | "juju-info-list": List(KeyDict(juju_data, optional=["private-address"])), | 202 | "juju-info-list": List(KeyDict(juju_data, optional=["private-address"])), |
1154 | 195 | "access_group": Unicode()}, | 203 | "access_group": Unicode()}, |
1155 | 196 | optional=["registration_password", "hostname", "tags", "vm-info", | 204 | optional=["registration_password", "hostname", "tags", "vm-info", |
1156 | @@ -203,6 +211,8 @@ | |||
1157 | 203 | {"otp": Bytes()}) | 211 | {"otp": Bytes()}) |
1158 | 204 | 212 | ||
1159 | 205 | 213 | ||
1160 | 214 | # XXX The register-cloud-vm message is obsolete, it's kept around just to not | ||
1161 | 215 | # break older LDS releases that import it. Eventually it shall be dropped. | ||
1162 | 206 | REGISTER_CLOUD_VM = Message( | 216 | REGISTER_CLOUD_VM = Message( |
1163 | 207 | "register-cloud-vm", | 217 | "register-cloud-vm", |
1164 | 208 | {"hostname": Unicode(), | 218 | {"hostname": Unicode(), |
1165 | @@ -476,5 +486,5 @@ | |||
1166 | 476 | EUCALYPTUS_INFO_ERROR, NETWORK_DEVICE, NETWORK_ACTIVITY, | 486 | EUCALYPTUS_INFO_ERROR, NETWORK_DEVICE, NETWORK_ACTIVITY, |
1167 | 477 | REBOOT_REQUIRED_INFO, UPDATE_MANAGER_INFO, CPU_USAGE, | 487 | REBOOT_REQUIRED_INFO, UPDATE_MANAGER_INFO, CPU_USAGE, |
1168 | 478 | CEPH_USAGE, SWIFT_USAGE, SWIFT_DEVICE_INFO, KEYSTONE_TOKEN, | 488 | CEPH_USAGE, SWIFT_USAGE, SWIFT_DEVICE_INFO, KEYSTONE_TOKEN, |
1170 | 479 | CHANGE_HA_SERVICE, JUJU_UNITS_INFO, CLOUD_METADATA]: | 489 | CHANGE_HA_SERVICE, JUJU_INFO, JUJU_UNITS_INFO, CLOUD_METADATA]: |
1171 | 480 | message_schemas[schema.type] = schema | 490 | message_schemas[schema.type] = schema |
1172 | 481 | 491 | ||
1173 | === modified file 'landscape/tests/test_configuration.py' | |||
1174 | --- landscape/tests/test_configuration.py 2014-02-25 18:06:48 +0000 | |||
1175 | +++ landscape/tests/test_configuration.py 2014-07-16 10:26:46 +0000 | |||
1176 | @@ -820,7 +820,6 @@ | |||
1177 | 820 | "--ping-interval", "30", | 820 | "--ping-interval", "30", |
1178 | 821 | "--http-proxy", "", | 821 | "--http-proxy", "", |
1179 | 822 | "--https-proxy", "", | 822 | "--https-proxy", "", |
1180 | 823 | "--otp", "", | ||
1181 | 824 | "--tags", "", | 823 | "--tags", "", |
1182 | 825 | "--provisioning-otp", ""] | 824 | "--provisioning-otp", ""] |
1183 | 826 | config = self.get_config(args) | 825 | config = self.get_config(args) |
1184 | @@ -837,7 +836,6 @@ | |||
1185 | 837 | "https_proxy = \n" | 836 | "https_proxy = \n" |
1186 | 838 | "url = https://landscape.canonical.com/message-system\n" | 837 | "url = https://landscape.canonical.com/message-system\n" |
1187 | 839 | "exchange_interval = 900\n" | 838 | "exchange_interval = 900\n" |
1188 | 840 | "otp = \n" | ||
1189 | 841 | "ping_interval = 30\n" | 839 | "ping_interval = 30\n" |
1190 | 842 | "ping_url = http://landscape.canonical.com/ping\n" | 840 | "ping_url = http://landscape.canonical.com/ping\n" |
1191 | 843 | "provisioning_otp = \n" | 841 | "provisioning_otp = \n" |
1192 | @@ -862,20 +860,6 @@ | |||
1193 | 862 | config = self.get_config(["--silent", "-t", "rex"]) | 860 | config = self.get_config(["--silent", "-t", "rex"]) |
1194 | 863 | self.assertRaises(ConfigurationError, setup, config) | 861 | self.assertRaises(ConfigurationError, setup, config) |
1195 | 864 | 862 | ||
1196 | 865 | def test_silent_setup_with_otp(self): | ||
1197 | 866 | """ | ||
1198 | 867 | If the OTP is specified, there is no need to pass the account name and | ||
1199 | 868 | the computer title. | ||
1200 | 869 | """ | ||
1201 | 870 | sysvconfig_mock = self.mocker.patch(SysVConfig) | ||
1202 | 871 | sysvconfig_mock.set_start_on_boot(True) | ||
1203 | 872 | self.mocker.replay() | ||
1204 | 873 | |||
1205 | 874 | config = self.get_config(["--silent", "--otp", "otp1"]) | ||
1206 | 875 | setup(config) | ||
1207 | 876 | |||
1208 | 877 | self.assertEqual("otp1", config.otp) | ||
1209 | 878 | |||
1210 | 879 | def test_silent_setup_with_provisioning_otp(self): | 863 | def test_silent_setup_with_provisioning_otp(self): |
1211 | 880 | """ | 864 | """ |
1212 | 881 | If the provisioning OTP is specified, there is no need to pass the | 865 | If the provisioning OTP is specified, there is no need to pass the |
1213 | 882 | 866 | ||
1214 | === removed file 'scripts/landscape-is-cloud-managed' | |||
1215 | --- scripts/landscape-is-cloud-managed 2009-04-09 14:32:45 +0000 | |||
1216 | +++ scripts/landscape-is-cloud-managed 1970-01-01 00:00:00 +0000 | |||
1217 | @@ -1,12 +0,0 @@ | |||
1218 | 1 | #!/usr/bin/python | ||
1219 | 2 | import sys, os | ||
1220 | 3 | if os.path.dirname(os.path.abspath(sys.argv[0])) == os.path.abspath("scripts"): | ||
1221 | 4 | sys.path.insert(0, "./") | ||
1222 | 5 | else: | ||
1223 | 6 | from landscape.lib.warning import hide_warnings | ||
1224 | 7 | hide_warnings() | ||
1225 | 8 | |||
1226 | 9 | from landscape.broker.registration import is_cloud_managed | ||
1227 | 10 | |||
1228 | 11 | # We return 0 if it succeeds | ||
1229 | 12 | sys.exit(not is_cloud_managed()) | ||
1230 | 13 | 0 | ||
1231 | === modified file 'setup.py' | |||
1232 | --- setup.py 2013-06-03 12:26:30 +0000 | |||
1233 | +++ setup.py 2014-07-16 10:26:46 +0000 | |||
1234 | @@ -56,7 +56,6 @@ | |||
1235 | 56 | "scripts/landscape-package-reporter", | 56 | "scripts/landscape-package-reporter", |
1236 | 57 | "scripts/landscape-release-upgrader", | 57 | "scripts/landscape-release-upgrader", |
1237 | 58 | "scripts/landscape-sysinfo", | 58 | "scripts/landscape-sysinfo", |
1238 | 59 | "scripts/landscape-is-cloud-managed", | ||
1239 | 60 | "scripts/landscape-dbus-proxy", | 59 | "scripts/landscape-dbus-proxy", |
1240 | 61 | "scripts/landscape-client-settings-mechanism", | 60 | "scripts/landscape-client-settings-mechanism", |
1241 | 62 | "scripts/landscape-client-registration-mechanism", | 61 | "scripts/landscape-client-registration-mechanism", |