Merge lp:~free.ekanayaka/landscape-client/drop-cloud-registration into lp:~landscape/landscape-client/trunk
- drop-cloud-registration
- Merge into trunk
Status: | Merged | ||||
---|---|---|---|---|---|
Approved by: | Free Ekanayaka | ||||
Approved revision: | 783 | ||||
Merged at revision: | 778 | ||||
Proposed branch: | lp:~free.ekanayaka/landscape-client/drop-cloud-registration | ||||
Merge into: | lp:~landscape/landscape-client/trunk | ||||
Diff against target: |
1179 lines (+12/-941) 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 (+3/-210) landscape/broker/tests/test_registration.py (+3/-669) landscape/configuration.py (+1/-1) landscape/message_schemas.py (+3/-0) 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 |
---|---|---|---|
Benji York (community) | Approve | ||
Geoff Teale (community) | Approve | ||
Review via email: mp+226993@code.launchpad.net |
Commit message
Drop the cloud registration code and associated tests.
Description of the change
Drop the cloud registration code and associated tests.
Benji York (benji) wrote : | # |
So much deleteed code! It's a thing of beauty.
=== modified file 'landscape/
from landscape.
- InvalidCredenti
- EC2_API, Identity)
+ InvalidCredenti
I can't remember if we're uptight about alphebetical import order, but
if we are, look above.
=== modified file 'landscape/
--- landscape/
+++ landscape/
@@ -211,6 +211,8 @@
{"otp": Bytes()})
+# XXX The register-cloud-vm message is obsolete, it's kept around just to not
+# break older LDS releases that import it. Eventually it shall be dropped.
REGISTER_CLOUD_VM = Message(
"register-
{"hostname": Unicode(),
I guess we can bzr-blame the above, but a date would make it obvious
when we can drop the noop.
Free Ekanayaka (free.ekanayaka) wrote : | # |
Benji: we don't maintain imports in alphabetical order for now, but it'd be probably a good thing to do, together to having them one per line instead of smashed together. That takes increases a bit the lines count but it easier to read and maintain (e.g. conflicts). Might be a good topic for the sprint if you care to add it.
About the date, I've added the last LDS version that has this imports: 14.07.
Preview Diff
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-17 06:52:14 +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-17 06:52:14 +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-17 06:52:14 +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-06-19 10:51:57 +0000 | |||
82 | +++ landscape/broker/registration.py 2014-07-17 06:52:14 +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 | @@ -433,80 +303,3 @@ | |||
291 | 433 | def _failed(self): | 303 | def _failed(self): |
292 | 434 | self.deferred.errback(InvalidCredentialsError()) | 304 | self.deferred.errback(InvalidCredentialsError()) |
293 | 435 | self._cancel_calls() | 305 | self._cancel_calls() |
294 | 436 | |||
295 | 437 | |||
296 | 438 | def _extract_ec2_instance_data(raw_user_data, launch_index): | ||
297 | 439 | """ | ||
298 | 440 | Given the raw string of EC2 User Data, parse it and return the dict of | ||
299 | 441 | instance data for this particular instance. | ||
300 | 442 | |||
301 | 443 | If the data can't be parsed, a debug message will be logged and None | ||
302 | 444 | will be returned. | ||
303 | 445 | """ | ||
304 | 446 | try: | ||
305 | 447 | user_data = loads(raw_user_data) | ||
306 | 448 | except ValueError: | ||
307 | 449 | logging.debug("Got invalid user-data %r" % (raw_user_data,)) | ||
308 | 450 | return | ||
309 | 451 | |||
310 | 452 | if not isinstance(user_data, dict): | ||
311 | 453 | logging.debug("user-data %r is not a dict" % (user_data,)) | ||
312 | 454 | return | ||
313 | 455 | for key in "otps", "exchange-url", "ping-url": | ||
314 | 456 | if key not in user_data: | ||
315 | 457 | logging.debug("user-data %r doesn't have key %r." | ||
316 | 458 | % (user_data, key)) | ||
317 | 459 | return | ||
318 | 460 | if len(user_data["otps"]) <= launch_index: | ||
319 | 461 | logging.debug("user-data %r doesn't have OTP for launch index %d" | ||
320 | 462 | % (user_data, launch_index)) | ||
321 | 463 | return | ||
322 | 464 | instance_data = {"otp": user_data["otps"][launch_index], | ||
323 | 465 | "exchange-url": user_data["exchange-url"], | ||
324 | 466 | "ping-url": user_data["ping-url"]} | ||
325 | 467 | if "ssl-ca-certificate" in user_data: | ||
326 | 468 | instance_data["ssl-ca-certificate"] = user_data["ssl-ca-certificate"] | ||
327 | 469 | return instance_data | ||
328 | 470 | |||
329 | 471 | |||
330 | 472 | def _wait_for_network(): | ||
331 | 473 | """ | ||
332 | 474 | Keep trying to connect to the EC2 metadata server until it becomes | ||
333 | 475 | accessible or until five minutes pass. | ||
334 | 476 | |||
335 | 477 | This is necessary because the networking init script on Ubuntu is | ||
336 | 478 | asynchronous; the network may not actually be up by the time the | ||
337 | 479 | landscape-client init script is invoked. | ||
338 | 480 | """ | ||
339 | 481 | timeout = 5 * 60 | ||
340 | 482 | port = 80 | ||
341 | 483 | |||
342 | 484 | start = time.time() | ||
343 | 485 | while True: | ||
344 | 486 | s = socket.socket() | ||
345 | 487 | try: | ||
346 | 488 | s.connect((EC2_HOST, port)) | ||
347 | 489 | s.close() | ||
348 | 490 | return | ||
349 | 491 | except socket.error: | ||
350 | 492 | time.sleep(1) | ||
351 | 493 | if time.time() - start > timeout: | ||
352 | 494 | break | ||
353 | 495 | |||
354 | 496 | |||
355 | 497 | def is_cloud_managed(fetch=fetch): | ||
356 | 498 | """ | ||
357 | 499 | Return C{True} if the machine has been started by Landscape, i.e. if we can | ||
358 | 500 | find the expected data inside the EC2 user-data field. | ||
359 | 501 | """ | ||
360 | 502 | _wait_for_network() | ||
361 | 503 | try: | ||
362 | 504 | raw_user_data = fetch(EC2_API + "/user-data", | ||
363 | 505 | connect_timeout=5) | ||
364 | 506 | launch_index = fetch(EC2_API + "/meta-data/ami-launch-index", | ||
365 | 507 | connect_timeout=5) | ||
366 | 508 | except FetchError: | ||
367 | 509 | return False | ||
368 | 510 | instance_data = _extract_ec2_instance_data( | ||
369 | 511 | raw_user_data, int(launch_index)) | ||
370 | 512 | return instance_data is not None | ||
371 | 513 | 306 | ||
372 | === modified file 'landscape/broker/tests/test_registration.py' | |||
373 | --- landscape/broker/tests/test_registration.py 2014-06-20 06:06:51 +0000 | |||
374 | +++ landscape/broker/tests/test_registration.py 2014-07-17 06:52:14 +0000 | |||
375 | @@ -1,24 +1,15 @@ | |||
376 | 1 | import json | 1 | import json |
377 | 2 | import os | ||
378 | 3 | import logging | 2 | import logging |
379 | 4 | import pycurl | ||
380 | 5 | import socket | 3 | import socket |
381 | 6 | 4 | ||
382 | 7 | from twisted.internet.defer import succeed, fail | ||
383 | 8 | |||
384 | 9 | from landscape.broker.registration import ( | 5 | from landscape.broker.registration import ( |
387 | 10 | InvalidCredentialsError, RegistrationHandler, is_cloud_managed, EC2_HOST, | 6 | InvalidCredentialsError, Identity) |
386 | 11 | EC2_API, Identity) | ||
388 | 12 | 7 | ||
389 | 13 | from landscape.broker.config import BrokerConfiguration | ||
390 | 14 | from landscape.tests.helpers import LandscapeTest | 8 | from landscape.tests.helpers import LandscapeTest |
391 | 15 | from landscape.broker.tests.helpers import ( | 9 | from landscape.broker.tests.helpers import ( |
392 | 16 | BrokerConfigurationHelper, RegistrationHelper) | 10 | BrokerConfigurationHelper, RegistrationHelper) |
393 | 17 | from landscape.lib.bpickle import dumps | ||
394 | 18 | from landscape.lib.fetch import HTTPCodeError, FetchError | ||
395 | 19 | from landscape.lib.persist import Persist | 11 | from landscape.lib.persist import Persist |
396 | 20 | from landscape.lib.vm_info import get_vm_info | 12 | from landscape.lib.vm_info import get_vm_info |
397 | 21 | from landscape.configuration import print_text | ||
398 | 22 | 13 | ||
399 | 23 | 14 | ||
400 | 24 | class IdentityTest(LandscapeTest): | 15 | class IdentityTest(LandscapeTest): |
401 | @@ -250,8 +241,7 @@ | |||
402 | 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 |
403 | 251 | valid, we drop them, and report an error. | 242 | valid, we drop them, and report an error. |
404 | 252 | """ | 243 | """ |
407 | 253 | self.log_helper.ignore_errors("Invalid tags provided for cloud " | 244 | self.log_helper.ignore_errors("Invalid tags provided for registration") |
406 | 254 | "registration") | ||
408 | 255 | self.mstore.set_accepted_types(["register"]) | 245 | self.mstore.set_accepted_types(["register"]) |
409 | 256 | self.config.computer_title = "Computer Title" | 246 | self.config.computer_title = "Computer Title" |
410 | 257 | self.config.account_name = "account_name" | 247 | self.config.account_name = "account_name" |
411 | @@ -261,8 +251,7 @@ | |||
412 | 261 | messages = self.mstore.get_pending_messages() | 251 | messages = self.mstore.get_pending_messages() |
413 | 262 | self.assertIs(None, messages[0]["tags"]) | 252 | self.assertIs(None, messages[0]["tags"]) |
414 | 263 | self.assertEqual(self.logfile.getvalue().strip(), | 253 | self.assertEqual(self.logfile.getvalue().strip(), |
417 | 264 | "ERROR: Invalid tags provided for cloud " | 254 | "ERROR: Invalid tags provided for registration.\n " |
416 | 265 | "registration.\n " | ||
418 | 266 | "INFO: Queueing message to register with account " | 255 | "INFO: Queueing message to register with account " |
419 | 267 | "'account_name' with a password.\n " | 256 | "'account_name' with a password.\n " |
420 | 268 | "INFO: Sending registration message to exchange.") | 257 | "INFO: Sending registration message to exchange.") |
421 | @@ -588,661 +577,6 @@ | |||
422 | 588 | self.assertIn(expected2, juju_info) | 577 | self.assertIn(expected2, juju_info) |
423 | 589 | 578 | ||
424 | 590 | 579 | ||
425 | 591 | class CloudRegistrationHandlerTest(RegistrationHandlerTestBase): | ||
426 | 592 | |||
427 | 593 | cloud = True | ||
428 | 594 | |||
429 | 595 | def setUp(self): | ||
430 | 596 | super(CloudRegistrationHandlerTest, self).setUp() | ||
431 | 597 | self.query_results = {} | ||
432 | 598 | |||
433 | 599 | def fetch_stub(url): | ||
434 | 600 | value = self.query_results[url] | ||
435 | 601 | if isinstance(value, Exception): | ||
436 | 602 | return fail(value) | ||
437 | 603 | else: | ||
438 | 604 | return succeed(value) | ||
439 | 605 | |||
440 | 606 | self.fetch_func = fetch_stub | ||
441 | 607 | |||
442 | 608 | def get_user_data(self, otps=None, | ||
443 | 609 | exchange_url="https://example.com/message-system", | ||
444 | 610 | ping_url="http://example.com/ping", | ||
445 | 611 | ssl_ca_certificate=None): | ||
446 | 612 | if otps is None: | ||
447 | 613 | otps = ["otp1"] | ||
448 | 614 | user_data = {"otps": otps, "exchange-url": exchange_url, | ||
449 | 615 | "ping-url": ping_url} | ||
450 | 616 | if ssl_ca_certificate is not None: | ||
451 | 617 | user_data["ssl-ca-certificate"] = ssl_ca_certificate | ||
452 | 618 | return user_data | ||
453 | 619 | |||
454 | 620 | def prepare_query_results( | ||
455 | 621 | self, user_data=None, instance_key="key1", launch_index=0, | ||
456 | 622 | local_hostname="ooga.local", public_hostname="ooga.amazon.com", | ||
457 | 623 | reservation_key=u"res1", ramdisk_key=u"ram1", kernel_key=u"kernel1", | ||
458 | 624 | image_key=u"image1", local_ip="10.0.0.1", public_ip="10.0.0.2", | ||
459 | 625 | ssl_ca_certificate=None): | ||
460 | 626 | if user_data is None: | ||
461 | 627 | user_data = self.get_user_data( | ||
462 | 628 | ssl_ca_certificate=ssl_ca_certificate) | ||
463 | 629 | if not isinstance(user_data, Exception): | ||
464 | 630 | user_data = dumps(user_data) | ||
465 | 631 | api_base = "http://169.254.169.254/latest" | ||
466 | 632 | self.query_results.clear() | ||
467 | 633 | for url_suffix, value in [ | ||
468 | 634 | ("/user-data", user_data), | ||
469 | 635 | ("/meta-data/instance-id", instance_key), | ||
470 | 636 | ("/meta-data/reservation-id", reservation_key), | ||
471 | 637 | ("/meta-data/local-hostname", local_hostname), | ||
472 | 638 | ("/meta-data/public-hostname", public_hostname), | ||
473 | 639 | ("/meta-data/ami-launch-index", str(launch_index)), | ||
474 | 640 | ("/meta-data/kernel-id", kernel_key), | ||
475 | 641 | ("/meta-data/ramdisk-id", ramdisk_key), | ||
476 | 642 | ("/meta-data/ami-id", image_key), | ||
477 | 643 | ("/meta-data/local-ipv4", local_ip), | ||
478 | 644 | ("/meta-data/public-ipv4", public_ip), | ||
479 | 645 | ]: | ||
480 | 646 | self.query_results[api_base + url_suffix] = value | ||
481 | 647 | |||
482 | 648 | def prepare_cloud_registration(self, account_name=None, | ||
483 | 649 | registration_key=None, tags=None, | ||
484 | 650 | access_group=None): | ||
485 | 651 | # Set things up so that the client thinks it should register | ||
486 | 652 | self.mstore.set_accepted_types(list(self.mstore.get_accepted_types()) | ||
487 | 653 | + ["register-cloud-vm"]) | ||
488 | 654 | self.config.account_name = account_name | ||
489 | 655 | self.config.registration_key = registration_key | ||
490 | 656 | self.config.computer_title = None | ||
491 | 657 | self.config.tags = tags | ||
492 | 658 | self.config.access_group = access_group | ||
493 | 659 | self.identity.secure_id = None | ||
494 | 660 | self.assertTrue(self.handler.should_register()) | ||
495 | 661 | |||
496 | 662 | def get_expected_cloud_message(self, **kwargs): | ||
497 | 663 | """ | ||
498 | 664 | Return the message which is expected from a similar call to | ||
499 | 665 | L{get_registration_handler_for_cloud}. | ||
500 | 666 | """ | ||
501 | 667 | message = dict(type="register-cloud-vm", | ||
502 | 668 | otp="otp1", | ||
503 | 669 | hostname="ooga.local", | ||
504 | 670 | local_hostname="ooga.local", | ||
505 | 671 | public_hostname="ooga.amazon.com", | ||
506 | 672 | instance_key=u"key1", | ||
507 | 673 | reservation_key=u"res1", | ||
508 | 674 | ramdisk_key=u"ram1", | ||
509 | 675 | kernel_key=u"kernel1", | ||
510 | 676 | launch_index=0, | ||
511 | 677 | image_key=u"image1", | ||
512 | 678 | account_name=None, | ||
513 | 679 | registration_password=None, | ||
514 | 680 | local_ipv4=u"10.0.0.1", | ||
515 | 681 | public_ipv4=u"10.0.0.2", | ||
516 | 682 | tags=None) | ||
517 | 683 | # The get_vm_info() needs to be deferred to the else. If vm-info is | ||
518 | 684 | # not specified in kwargs, get_vm_info() will typically be mocked. | ||
519 | 685 | if "vm_info" in kwargs: | ||
520 | 686 | message["vm-info"] = kwargs.pop("vm_info") | ||
521 | 687 | else: | ||
522 | 688 | message["vm-info"] = get_vm_info() | ||
523 | 689 | message.update(kwargs) | ||
524 | 690 | return message | ||
525 | 691 | |||
526 | 692 | def test_cloud_registration(self): | ||
527 | 693 | """ | ||
528 | 694 | When the 'cloud' configuration variable is set, cloud registration is | ||
529 | 695 | done instead of normal password-based registration. This means: | ||
530 | 696 | |||
531 | 697 | - "Launch Data" is fetched from the EC2 Launch Data URL. This contains | ||
532 | 698 | a one-time password that is used during registration. | ||
533 | 699 | - A different "register-cloud-vm" message is sent to the server instead | ||
534 | 700 | of "register", containing the OTP. This message is handled by | ||
535 | 701 | immediately accepting the computer, instead of going through the | ||
536 | 702 | pending computer stage. | ||
537 | 703 | """ | ||
538 | 704 | get_vm_info_mock = self.mocker.replace(get_vm_info) | ||
539 | 705 | get_vm_info_mock() | ||
540 | 706 | self.mocker.result("xen") | ||
541 | 707 | self.mocker.replay() | ||
542 | 708 | self.prepare_query_results() | ||
543 | 709 | self.prepare_cloud_registration(tags=u"server,london") | ||
544 | 710 | |||
545 | 711 | # metadata is fetched and stored at reactor startup: | ||
546 | 712 | self.reactor.fire("run") | ||
547 | 713 | |||
548 | 714 | # And the metadata returned determines the URLs that are used | ||
549 | 715 | self.assertEqual(self.transport.get_url(), | ||
550 | 716 | "https://example.com/message-system") | ||
551 | 717 | self.assertEqual(self.pinger.get_url(), | ||
552 | 718 | "http://example.com/ping") | ||
553 | 719 | # Lets make sure those values were written back to the config file | ||
554 | 720 | new_config = BrokerConfiguration() | ||
555 | 721 | new_config.load_configuration_file(self.config_filename) | ||
556 | 722 | self.assertEqual(new_config.url, "https://example.com/message-system") | ||
557 | 723 | self.assertEqual(new_config.ping_url, "http://example.com/ping") | ||
558 | 724 | |||
559 | 725 | # Okay! Exchange should cause the registration to happen. | ||
560 | 726 | self.exchanger.exchange() | ||
561 | 727 | # This *should* be asynchronous, but I think a billion tests are | ||
562 | 728 | # written like this | ||
563 | 729 | self.assertEqual(len(self.transport.payloads), 1) | ||
564 | 730 | self.assertMessages( | ||
565 | 731 | self.transport.payloads[0]["messages"], | ||
566 | 732 | [self.get_expected_cloud_message(tags=u"server,london", | ||
567 | 733 | vm_info="xen")]) | ||
568 | 734 | |||
569 | 735 | def test_cloud_registration_with_access_group(self): | ||
570 | 736 | """ | ||
571 | 737 | If the access_group field is presnet in the configuration, the | ||
572 | 738 | access_group field is present in the outgoing message for a VM | ||
573 | 739 | registration, and a notice appears in the logs. | ||
574 | 740 | """ | ||
575 | 741 | self.prepare_query_results() | ||
576 | 742 | self.prepare_cloud_registration(access_group=u"dinosaurs", | ||
577 | 743 | tags=u"server,london") | ||
578 | 744 | |||
579 | 745 | self.reactor.fire("run") | ||
580 | 746 | self.exchanger.exchange() | ||
581 | 747 | self.assertEqual(len(self.transport.payloads), 1) | ||
582 | 748 | self.assertMessages( | ||
583 | 749 | self.transport.payloads[0]["messages"], | ||
584 | 750 | [self.get_expected_cloud_message( | ||
585 | 751 | access_group=u"dinosaurs", tags=u"server,london")]) | ||
586 | 752 | |||
587 | 753 | def test_cloud_registration_with_otp(self): | ||
588 | 754 | """ | ||
589 | 755 | If the OTP is present in the configuration, it's used to trigger the | ||
590 | 756 | registration instead of using the user data. | ||
591 | 757 | """ | ||
592 | 758 | self.config.otp = "otp1" | ||
593 | 759 | self.prepare_query_results(user_data=None) | ||
594 | 760 | |||
595 | 761 | self.prepare_cloud_registration() | ||
596 | 762 | |||
597 | 763 | # metadata is fetched and stored at reactor startup: | ||
598 | 764 | self.reactor.fire("run") | ||
599 | 765 | |||
600 | 766 | # Okay! Exchange should cause the registration to happen. | ||
601 | 767 | self.exchanger.exchange() | ||
602 | 768 | # This *should* be asynchronous, but I think a billion tests are | ||
603 | 769 | # written like this | ||
604 | 770 | self.assertEqual(len(self.transport.payloads), 1) | ||
605 | 771 | self.assertMessages( | ||
606 | 772 | self.transport.payloads[0]["messages"], | ||
607 | 773 | [self.get_expected_cloud_message()]) | ||
608 | 774 | |||
609 | 775 | def test_cloud_registration_with_invalid_tags(self): | ||
610 | 776 | """ | ||
611 | 777 | Invalid tags in the configuration should result in the tags not being | ||
612 | 778 | sent to the server, and this fact logged. | ||
613 | 779 | """ | ||
614 | 780 | self.log_helper.ignore_errors("Invalid tags provided for cloud " | ||
615 | 781 | "registration") | ||
616 | 782 | self.prepare_query_results() | ||
617 | 783 | self.prepare_cloud_registration(tags=u"<script>alert()</script>,hardy") | ||
618 | 784 | |||
619 | 785 | # metadata is fetched and stored at reactor startup: | ||
620 | 786 | self.reactor.fire("run") | ||
621 | 787 | self.exchanger.exchange() | ||
622 | 788 | self.assertEqual(len(self.transport.payloads), 1) | ||
623 | 789 | self.assertMessages(self.transport.payloads[0]["messages"], | ||
624 | 790 | [self.get_expected_cloud_message(tags=None)]) | ||
625 | 791 | self.assertEqual(self.logfile.getvalue().strip()[:-7], | ||
626 | 792 | "ERROR: Invalid tags provided for cloud " | ||
627 | 793 | "registration.\n " | ||
628 | 794 | "INFO: Queueing message to register with OTP\n " | ||
629 | 795 | "INFO: Sending registration message to exchange.\n " | ||
630 | 796 | " INFO: Starting message exchange with " | ||
631 | 797 | "https://example.com/message-system.\n " | ||
632 | 798 | "INFO: Message exchange completed in") | ||
633 | 799 | |||
634 | 800 | def test_cloud_registration_with_ssl_ca_certificate(self): | ||
635 | 801 | """ | ||
636 | 802 | If we have an SSL certificate CA included in the user-data, this should | ||
637 | 803 | be written out, and the configuration updated to reflect this. | ||
638 | 804 | """ | ||
639 | 805 | key_filename = os.path.join(self.config.data_path, | ||
640 | 806 | "%s.ssl_public_key" % os.path.basename(self.config_filename)) | ||
641 | 807 | |||
642 | 808 | print_text_mock = self.mocker.replace(print_text) | ||
643 | 809 | print_text_mock("Writing SSL CA certificate to %s..." % | ||
644 | 810 | key_filename) | ||
645 | 811 | self.mocker.replay() | ||
646 | 812 | self.prepare_query_results(ssl_ca_certificate=u"1234567890") | ||
647 | 813 | self.prepare_cloud_registration(tags=u"server,london") | ||
648 | 814 | # metadata is fetched and stored at reactor startup: | ||
649 | 815 | self.reactor.fire("run") | ||
650 | 816 | # And the metadata returned determines the URLs that are used | ||
651 | 817 | self.assertEqual("https://example.com/message-system", | ||
652 | 818 | self.transport.get_url()) | ||
653 | 819 | self.assertEqual(key_filename, self.transport._pubkey) | ||
654 | 820 | self.assertEqual("http://example.com/ping", | ||
655 | 821 | self.pinger.get_url()) | ||
656 | 822 | # Let's make sure those values were written back to the config file | ||
657 | 823 | new_config = BrokerConfiguration() | ||
658 | 824 | new_config.load_configuration_file(self.config_filename) | ||
659 | 825 | self.assertEqual("https://example.com/message-system", new_config.url) | ||
660 | 826 | self.assertEqual("http://example.com/ping", new_config.ping_url) | ||
661 | 827 | self.assertEqual(key_filename, new_config.ssl_public_key) | ||
662 | 828 | self.assertEqual("1234567890", open(key_filename, "r").read()) | ||
663 | 829 | |||
664 | 830 | def test_wrong_user_data(self): | ||
665 | 831 | self.prepare_query_results(user_data="other stuff, not a bpickle") | ||
666 | 832 | self.prepare_cloud_registration() | ||
667 | 833 | |||
668 | 834 | # Mock registration-failed call | ||
669 | 835 | reactor_mock = self.mocker.patch(self.reactor) | ||
670 | 836 | reactor_mock.fire("registration-failed") | ||
671 | 837 | self.mocker.replay() | ||
672 | 838 | |||
673 | 839 | self.reactor.fire("run") | ||
674 | 840 | self.exchanger.exchange() | ||
675 | 841 | |||
676 | 842 | def test_wrong_object_type_in_user_data(self): | ||
677 | 843 | self.prepare_query_results(user_data=True) | ||
678 | 844 | self.prepare_cloud_registration() | ||
679 | 845 | |||
680 | 846 | # Mock registration-failed call | ||
681 | 847 | reactor_mock = self.mocker.patch(self.reactor) | ||
682 | 848 | reactor_mock.fire("registration-failed") | ||
683 | 849 | self.mocker.replay() | ||
684 | 850 | |||
685 | 851 | self.reactor.fire("run") | ||
686 | 852 | self.exchanger.exchange() | ||
687 | 853 | |||
688 | 854 | def test_user_data_with_not_enough_elements(self): | ||
689 | 855 | """ | ||
690 | 856 | If the AMI launch index isn't represented in the list of OTPs in the | ||
691 | 857 | user data then BOOM. | ||
692 | 858 | """ | ||
693 | 859 | self.prepare_query_results(launch_index=1) | ||
694 | 860 | self.prepare_cloud_registration() | ||
695 | 861 | |||
696 | 862 | # Mock registration-failed call | ||
697 | 863 | reactor_mock = self.mocker.patch(self.reactor) | ||
698 | 864 | reactor_mock.fire("registration-failed") | ||
699 | 865 | self.mocker.replay() | ||
700 | 866 | |||
701 | 867 | self.reactor.fire("run") | ||
702 | 868 | self.exchanger.exchange() | ||
703 | 869 | |||
704 | 870 | def test_user_data_bpickle_without_otp(self): | ||
705 | 871 | self.prepare_query_results(user_data={"foo": "bar"}) | ||
706 | 872 | self.prepare_cloud_registration() | ||
707 | 873 | |||
708 | 874 | # Mock registration-failed call | ||
709 | 875 | reactor_mock = self.mocker.patch(self.reactor) | ||
710 | 876 | reactor_mock.fire("registration-failed") | ||
711 | 877 | self.mocker.replay() | ||
712 | 878 | |||
713 | 879 | self.reactor.fire("run") | ||
714 | 880 | self.exchanger.exchange() | ||
715 | 881 | |||
716 | 882 | def test_no_otp_fallback_to_account(self): | ||
717 | 883 | self.prepare_query_results(user_data="other stuff, not a bpickle", | ||
718 | 884 | instance_key=u"key1") | ||
719 | 885 | self.prepare_cloud_registration(account_name=u"onward", | ||
720 | 886 | registration_key=u"password", | ||
721 | 887 | tags=u"london,server") | ||
722 | 888 | |||
723 | 889 | self.reactor.fire("run") | ||
724 | 890 | self.exchanger.exchange() | ||
725 | 891 | |||
726 | 892 | self.assertEqual(len(self.transport.payloads), 1) | ||
727 | 893 | self.assertMessages(self.transport.payloads[0]["messages"], | ||
728 | 894 | [self.get_expected_cloud_message( | ||
729 | 895 | otp=None, | ||
730 | 896 | account_name=u"onward", | ||
731 | 897 | registration_password=u"password", | ||
732 | 898 | tags=u"london,server")]) | ||
733 | 899 | self.assertEqual(self.logfile.getvalue().strip()[:-7], | ||
734 | 900 | "INFO: Queueing message to register with account u'onward' and " | ||
735 | 901 | "tags london,server as an EC2 instance.\n " | ||
736 | 902 | "INFO: Sending registration message to exchange.\n " | ||
737 | 903 | "INFO: Starting message exchange with http://localhost:91919.\n " | ||
738 | 904 | " INFO: Message exchange completed in") | ||
739 | 905 | |||
740 | 906 | def test_queueing_cloud_registration_message_resets_message_store(self): | ||
741 | 907 | """ | ||
742 | 908 | When a registration from a cloud is about to happen, the message store | ||
743 | 909 | is reset, because all previous messages are now meaningless. | ||
744 | 910 | """ | ||
745 | 911 | self.mstore.set_accepted_types(list(self.mstore.get_accepted_types()) | ||
746 | 912 | + ["test"]) | ||
747 | 913 | |||
748 | 914 | self.mstore.add({"type": "test"}) | ||
749 | 915 | |||
750 | 916 | self.prepare_query_results() | ||
751 | 917 | |||
752 | 918 | self.prepare_cloud_registration() | ||
753 | 919 | |||
754 | 920 | self.reactor.fire("run") | ||
755 | 921 | self.reactor.fire("pre-exchange") | ||
756 | 922 | |||
757 | 923 | messages = self.mstore.get_pending_messages() | ||
758 | 924 | self.assertEqual(len(messages), 1) | ||
759 | 925 | self.assertEqual(messages[0]["type"], "register-cloud-vm") | ||
760 | 926 | |||
761 | 927 | def test_cloud_registration_fetch_errors(self): | ||
762 | 928 | """ | ||
763 | 929 | If fetching metadata fails, and we have no account details to fall | ||
764 | 930 | back to, we fire 'registration-failed'. | ||
765 | 931 | """ | ||
766 | 932 | self.log_helper.ignore_errors(pycurl.error) | ||
767 | 933 | |||
768 | 934 | def fetch_stub(url): | ||
769 | 935 | return fail(pycurl.error(7, "couldn't connect to host")) | ||
770 | 936 | |||
771 | 937 | self.handler = RegistrationHandler( | ||
772 | 938 | self.config, self.identity, self.reactor, self.exchanger, | ||
773 | 939 | self.pinger, self.mstore, fetch_async=fetch_stub) | ||
774 | 940 | |||
775 | 941 | self.fetch_stub = fetch_stub | ||
776 | 942 | self.prepare_query_results() | ||
777 | 943 | self.fetch_stub = fetch_stub | ||
778 | 944 | |||
779 | 945 | self.prepare_cloud_registration() | ||
780 | 946 | |||
781 | 947 | failed = [] | ||
782 | 948 | self.reactor.call_on( | ||
783 | 949 | "registration-failed", lambda: failed.append(True)) | ||
784 | 950 | |||
785 | 951 | self.log_helper.ignore_errors("Got error while fetching meta-data") | ||
786 | 952 | self.reactor.fire("run") | ||
787 | 953 | self.exchanger.exchange() | ||
788 | 954 | self.assertEqual(failed, [True]) | ||
789 | 955 | self.assertIn('error: (7, "couldn\'t connect to host")', | ||
790 | 956 | self.logfile.getvalue()) | ||
791 | 957 | |||
792 | 958 | def test_cloud_registration_continues_without_user_data(self): | ||
793 | 959 | """ | ||
794 | 960 | If no user-data exists (i.e., the user-data URL returns a 404), then | ||
795 | 961 | register-cloud-vm still occurs. | ||
796 | 962 | """ | ||
797 | 963 | self.log_helper.ignore_errors(HTTPCodeError) | ||
798 | 964 | self.prepare_query_results(user_data=HTTPCodeError(404, "ohno")) | ||
799 | 965 | self.prepare_cloud_registration(account_name="onward", | ||
800 | 966 | registration_key="password") | ||
801 | 967 | |||
802 | 968 | self.reactor.fire("run") | ||
803 | 969 | self.exchanger.exchange() | ||
804 | 970 | self.assertIn("HTTPCodeError: Server returned HTTP code 404", | ||
805 | 971 | self.logfile.getvalue()) | ||
806 | 972 | self.assertEqual(len(self.transport.payloads), 1) | ||
807 | 973 | self.assertMessages(self.transport.payloads[0]["messages"], | ||
808 | 974 | [self.get_expected_cloud_message( | ||
809 | 975 | otp=None, | ||
810 | 976 | account_name=u"onward", | ||
811 | 977 | registration_password=u"password")]) | ||
812 | 978 | |||
813 | 979 | def test_cloud_registration_continues_without_ramdisk(self): | ||
814 | 980 | """ | ||
815 | 981 | If the instance doesn't have a ramdisk (ie, the query for ramdisk | ||
816 | 982 | returns a 404), then register-cloud-vm still occurs. | ||
817 | 983 | """ | ||
818 | 984 | self.log_helper.ignore_errors(HTTPCodeError) | ||
819 | 985 | self.prepare_query_results(ramdisk_key=HTTPCodeError(404, "ohno")) | ||
820 | 986 | self.prepare_cloud_registration() | ||
821 | 987 | |||
822 | 988 | self.reactor.fire("run") | ||
823 | 989 | self.exchanger.exchange() | ||
824 | 990 | self.assertIn("HTTPCodeError: Server returned HTTP code 404", | ||
825 | 991 | self.logfile.getvalue()) | ||
826 | 992 | self.assertEqual(len(self.transport.payloads), 1) | ||
827 | 993 | self.assertMessages(self.transport.payloads[0]["messages"], | ||
828 | 994 | [self.get_expected_cloud_message( | ||
829 | 995 | ramdisk_key=None)]) | ||
830 | 996 | |||
831 | 997 | def test_cloud_registration_continues_without_kernel(self): | ||
832 | 998 | """ | ||
833 | 999 | If the instance doesn't have a kernel (ie, the query for kernel | ||
834 | 1000 | returns a 404), then register-cloud-vm still occurs. | ||
835 | 1001 | """ | ||
836 | 1002 | self.log_helper.ignore_errors(HTTPCodeError) | ||
837 | 1003 | self.prepare_query_results(kernel_key=HTTPCodeError(404, "ohno")) | ||
838 | 1004 | self.prepare_cloud_registration() | ||
839 | 1005 | |||
840 | 1006 | self.reactor.fire("run") | ||
841 | 1007 | self.exchanger.exchange() | ||
842 | 1008 | self.assertIn("HTTPCodeError: Server returned HTTP code 404", | ||
843 | 1009 | self.logfile.getvalue()) | ||
844 | 1010 | self.assertEqual(len(self.transport.payloads), 1) | ||
845 | 1011 | self.assertMessages(self.transport.payloads[0]["messages"], | ||
846 | 1012 | [self.get_expected_cloud_message( | ||
847 | 1013 | kernel_key=None)]) | ||
848 | 1014 | |||
849 | 1015 | def test_fall_back_to_normal_registration_when_metadata_fetch_fails(self): | ||
850 | 1016 | """ | ||
851 | 1017 | If fetching metadata fails, but we do have an account name, then we | ||
852 | 1018 | fall back to normal 'register' registration. | ||
853 | 1019 | """ | ||
854 | 1020 | self.mstore.set_accepted_types(["register"]) | ||
855 | 1021 | self.log_helper.ignore_errors(HTTPCodeError) | ||
856 | 1022 | self.prepare_query_results( | ||
857 | 1023 | public_hostname=HTTPCodeError(404, "ohnoes")) | ||
858 | 1024 | self.prepare_cloud_registration(account_name="onward", | ||
859 | 1025 | registration_key="password") | ||
860 | 1026 | self.config.computer_title = "whatever" | ||
861 | 1027 | self.reactor.fire("run") | ||
862 | 1028 | self.exchanger.exchange() | ||
863 | 1029 | self.assertIn("HTTPCodeError: Server returned HTTP code 404", | ||
864 | 1030 | self.logfile.getvalue()) | ||
865 | 1031 | self.assertEqual(len(self.transport.payloads), 1) | ||
866 | 1032 | messages = self.transport.payloads[0]["messages"] | ||
867 | 1033 | self.assertEqual("register", messages[0]["type"]) | ||
868 | 1034 | |||
869 | 1035 | def test_should_register_in_cloud(self): | ||
870 | 1036 | """ | ||
871 | 1037 | The client should register when it's in the cloud even though | ||
872 | 1038 | it doesn't have the normal account details. | ||
873 | 1039 | """ | ||
874 | 1040 | self.mstore.set_accepted_types(self.mstore.get_accepted_types() | ||
875 | 1041 | + ("register-cloud-vm",)) | ||
876 | 1042 | self.config.account_name = None | ||
877 | 1043 | self.config.registration_key = None | ||
878 | 1044 | self.config.computer_title = None | ||
879 | 1045 | self.identity.secure_id = None | ||
880 | 1046 | self.assertTrue(self.handler.should_register()) | ||
881 | 1047 | |||
882 | 1048 | def test_launch_index(self): | ||
883 | 1049 | """ | ||
884 | 1050 | The client used the value in C{ami-launch-index} to choose the | ||
885 | 1051 | appropriate OTP in the user data. | ||
886 | 1052 | """ | ||
887 | 1053 | otp = "correct otp for launch index" | ||
888 | 1054 | self.prepare_query_results( | ||
889 | 1055 | user_data=self.get_user_data(otps=["wrong index", otp, | ||
890 | 1056 | "wrong again"],), | ||
891 | 1057 | instance_key="key1", | ||
892 | 1058 | launch_index=1) | ||
893 | 1059 | |||
894 | 1060 | self.prepare_cloud_registration() | ||
895 | 1061 | |||
896 | 1062 | self.reactor.fire("run") | ||
897 | 1063 | self.exchanger.exchange() | ||
898 | 1064 | self.assertEqual(len(self.transport.payloads), 1) | ||
899 | 1065 | self.assertMessages(self.transport.payloads[0]["messages"], | ||
900 | 1066 | [self.get_expected_cloud_message(otp=otp, | ||
901 | 1067 | launch_index=1)]) | ||
902 | 1068 | |||
903 | 1069 | def test_should_not_register_in_cloud(self): | ||
904 | 1070 | """ | ||
905 | 1071 | Having a secure ID means we shouldn't register, even in the cloud. | ||
906 | 1072 | """ | ||
907 | 1073 | self.mstore.set_accepted_types(self.mstore.get_accepted_types() | ||
908 | 1074 | + ("register-cloud-vm",)) | ||
909 | 1075 | self.config.account_name = None | ||
910 | 1076 | self.config.registration_key = None | ||
911 | 1077 | self.config.computer_title = None | ||
912 | 1078 | self.identity.secure_id = "hello" | ||
913 | 1079 | self.assertFalse(self.handler.should_register()) | ||
914 | 1080 | |||
915 | 1081 | def test_should_not_register_without_register_cloud_vm(self): | ||
916 | 1082 | """ | ||
917 | 1083 | If the server isn't accepting a 'register-cloud-vm' message, | ||
918 | 1084 | we shouldn't register. | ||
919 | 1085 | """ | ||
920 | 1086 | self.config.account_name = None | ||
921 | 1087 | self.config.registration_key = None | ||
922 | 1088 | self.config.computer_title = None | ||
923 | 1089 | self.identity.secure_id = None | ||
924 | 1090 | self.assertFalse(self.handler.should_register()) | ||
925 | 1091 | |||
926 | 1092 | |||
927 | 1093 | class IsCloudManagedTests(LandscapeTest): | ||
928 | 1094 | |||
929 | 1095 | def setUp(self): | ||
930 | 1096 | super(IsCloudManagedTests, self).setUp() | ||
931 | 1097 | self.urls = [] | ||
932 | 1098 | self.responses = [] | ||
933 | 1099 | |||
934 | 1100 | def fake_fetch(self, url, connect_timeout=None): | ||
935 | 1101 | self.urls.append((url, connect_timeout)) | ||
936 | 1102 | return self.responses.pop(0) | ||
937 | 1103 | |||
938 | 1104 | def mock_socket(self): | ||
939 | 1105 | """ | ||
940 | 1106 | Mock out socket usage by is_cloud_managed to wait for the network. | ||
941 | 1107 | """ | ||
942 | 1108 | # Mock the socket.connect call that it also does | ||
943 | 1109 | socket_class = self.mocker.replace("socket.socket", passthrough=False) | ||
944 | 1110 | socket = socket_class() | ||
945 | 1111 | socket.connect((EC2_HOST, 80)) | ||
946 | 1112 | socket.close() | ||
947 | 1113 | |||
948 | 1114 | def test_is_managed(self): | ||
949 | 1115 | """ | ||
950 | 1116 | L{is_cloud_managed} returns True if the EC2 user-data contains | ||
951 | 1117 | Landscape instance information. It fetches the EC2 data with low | ||
952 | 1118 | timeouts. | ||
953 | 1119 | """ | ||
954 | 1120 | user_data = {"otps": ["otp1"], "exchange-url": "http://exchange", | ||
955 | 1121 | "ping-url": "http://ping"} | ||
956 | 1122 | self.responses = [dumps(user_data), "0"] | ||
957 | 1123 | |||
958 | 1124 | self.mock_socket() | ||
959 | 1125 | self.mocker.replay() | ||
960 | 1126 | |||
961 | 1127 | self.assertTrue(is_cloud_managed(self.fake_fetch)) | ||
962 | 1128 | self.assertEqual( | ||
963 | 1129 | self.urls, | ||
964 | 1130 | [(EC2_API + "/user-data", 5), | ||
965 | 1131 | (EC2_API + "/meta-data/ami-launch-index", 5)]) | ||
966 | 1132 | |||
967 | 1133 | def test_is_managed_index(self): | ||
968 | 1134 | user_data = {"otps": ["otp1", "otp2"], | ||
969 | 1135 | "exchange-url": "http://exchange", | ||
970 | 1136 | "ping-url": "http://ping"} | ||
971 | 1137 | self.responses = [dumps(user_data), "1"] | ||
972 | 1138 | self.mock_socket() | ||
973 | 1139 | self.mocker.replay() | ||
974 | 1140 | self.assertTrue(is_cloud_managed(self.fake_fetch)) | ||
975 | 1141 | |||
976 | 1142 | def test_is_managed_wrong_index(self): | ||
977 | 1143 | user_data = {"otps": ["otp1"], "exchange-url": "http://exchange", | ||
978 | 1144 | "ping-url": "http://ping"} | ||
979 | 1145 | self.responses = [dumps(user_data), "1"] | ||
980 | 1146 | self.mock_socket() | ||
981 | 1147 | self.mocker.replay() | ||
982 | 1148 | self.assertFalse(is_cloud_managed(self.fake_fetch)) | ||
983 | 1149 | |||
984 | 1150 | def test_is_managed_exchange_url(self): | ||
985 | 1151 | user_data = {"otps": ["otp1"], "ping-url": "http://ping"} | ||
986 | 1152 | self.responses = [dumps(user_data), "0"] | ||
987 | 1153 | self.mock_socket() | ||
988 | 1154 | self.mocker.replay() | ||
989 | 1155 | self.assertFalse(is_cloud_managed(self.fake_fetch)) | ||
990 | 1156 | |||
991 | 1157 | def test_is_managed_ping_url(self): | ||
992 | 1158 | user_data = {"otps": ["otp1"], "exchange-url": "http://exchange"} | ||
993 | 1159 | self.responses = [dumps(user_data), "0"] | ||
994 | 1160 | self.mock_socket() | ||
995 | 1161 | self.mocker.replay() | ||
996 | 1162 | self.assertFalse(is_cloud_managed(self.fake_fetch)) | ||
997 | 1163 | |||
998 | 1164 | def test_is_managed_bpickle(self): | ||
999 | 1165 | self.responses = ["some other user data", "0"] | ||
1000 | 1166 | self.mock_socket() | ||
1001 | 1167 | self.mocker.replay() | ||
1002 | 1168 | self.assertFalse(is_cloud_managed(self.fake_fetch)) | ||
1003 | 1169 | |||
1004 | 1170 | def test_is_managed_no_data(self): | ||
1005 | 1171 | self.responses = ["", "0"] | ||
1006 | 1172 | self.mock_socket() | ||
1007 | 1173 | self.mocker.replay() | ||
1008 | 1174 | self.assertFalse(is_cloud_managed(self.fake_fetch)) | ||
1009 | 1175 | |||
1010 | 1176 | def test_is_managed_fetch_not_found(self): | ||
1011 | 1177 | |||
1012 | 1178 | def fake_fetch(url, connect_timeout=None): | ||
1013 | 1179 | raise HTTPCodeError(404, "ohnoes") | ||
1014 | 1180 | |||
1015 | 1181 | self.mock_socket() | ||
1016 | 1182 | self.mocker.replay() | ||
1017 | 1183 | self.assertFalse(is_cloud_managed(fake_fetch)) | ||
1018 | 1184 | |||
1019 | 1185 | def test_is_managed_fetch_error(self): | ||
1020 | 1186 | |||
1021 | 1187 | def fake_fetch(url, connect_timeout=None): | ||
1022 | 1188 | raise FetchError(7, "couldn't connect to host") | ||
1023 | 1189 | |||
1024 | 1190 | self.mock_socket() | ||
1025 | 1191 | self.mocker.replay() | ||
1026 | 1192 | self.assertFalse(is_cloud_managed(fake_fetch)) | ||
1027 | 1193 | |||
1028 | 1194 | def test_waits_for_network(self): | ||
1029 | 1195 | """ | ||
1030 | 1196 | is_cloud_managed will wait until the network before trying to fetch | ||
1031 | 1197 | the EC2 user data. | ||
1032 | 1198 | """ | ||
1033 | 1199 | user_data = {"otps": ["otp1"], "exchange-url": "http://exchange", | ||
1034 | 1200 | "ping-url": "http://ping"} | ||
1035 | 1201 | self.responses = [dumps(user_data), "0"] | ||
1036 | 1202 | |||
1037 | 1203 | self.mocker.order() | ||
1038 | 1204 | time_sleep = self.mocker.replace("time.sleep", passthrough=False) | ||
1039 | 1205 | socket_class = self.mocker.replace("socket.socket", passthrough=False) | ||
1040 | 1206 | socket_obj = socket_class() | ||
1041 | 1207 | socket_obj.connect((EC2_HOST, 80)) | ||
1042 | 1208 | self.mocker.throw(socket.error("woops")) | ||
1043 | 1209 | time_sleep(1) | ||
1044 | 1210 | socket_obj = socket_class() | ||
1045 | 1211 | socket_obj.connect((EC2_HOST, 80)) | ||
1046 | 1212 | self.mocker.result(None) | ||
1047 | 1213 | socket_obj.close() | ||
1048 | 1214 | self.mocker.replay() | ||
1049 | 1215 | self.assertTrue(is_cloud_managed(self.fake_fetch)) | ||
1050 | 1216 | |||
1051 | 1217 | def test_waiting_times_out(self): | ||
1052 | 1218 | """ | ||
1053 | 1219 | We'll only wait five minutes for the network to come up. | ||
1054 | 1220 | """ | ||
1055 | 1221 | |||
1056 | 1222 | def fake_fetch(url, connect_timeout=None): | ||
1057 | 1223 | raise FetchError(7, "couldn't connect to host") | ||
1058 | 1224 | |||
1059 | 1225 | self.mocker.order() | ||
1060 | 1226 | time_sleep = self.mocker.replace("time.sleep", passthrough=False) | ||
1061 | 1227 | time_time = self.mocker.replace("time.time", passthrough=False) | ||
1062 | 1228 | time_time() | ||
1063 | 1229 | self.mocker.result(100) | ||
1064 | 1230 | socket_class = self.mocker.replace("socket.socket", passthrough=False) | ||
1065 | 1231 | socket_obj = socket_class() | ||
1066 | 1232 | socket_obj.connect((EC2_HOST, 80)) | ||
1067 | 1233 | self.mocker.throw(socket.error("woops")) | ||
1068 | 1234 | time_sleep(1) | ||
1069 | 1235 | time_time() | ||
1070 | 1236 | self.mocker.result(401) | ||
1071 | 1237 | self.mocker.replay() | ||
1072 | 1238 | # Mocking time.time is dangerous, because the test harness calls it. So | ||
1073 | 1239 | # we explicitly reset mocker before returning from the test. | ||
1074 | 1240 | try: | ||
1075 | 1241 | self.assertFalse(is_cloud_managed(fake_fetch)) | ||
1076 | 1242 | finally: | ||
1077 | 1243 | self.mocker.reset() | ||
1078 | 1244 | |||
1079 | 1245 | |||
1080 | 1246 | class ProvisioningRegistrationTest(RegistrationHandlerTestBase): | 580 | class ProvisioningRegistrationTest(RegistrationHandlerTestBase): |
1081 | 1247 | 581 | ||
1082 | 1248 | def test_provisioned_machine_registration_with_otp(self): | 582 | def test_provisioned_machine_registration_with_otp(self): |
1083 | 1249 | 583 | ||
1084 | === modified file 'landscape/configuration.py' | |||
1085 | --- landscape/configuration.py 2014-07-01 14:52:02 +0000 | |||
1086 | +++ landscape/configuration.py 2014-07-17 06:52:14 +0000 | |||
1087 | @@ -581,7 +581,7 @@ | |||
1088 | 581 | decode_base64_ssl_public_certificate(config) | 581 | decode_base64_ssl_public_certificate(config) |
1089 | 582 | config.write() | 582 | config.write() |
1090 | 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. |
1092 | 584 | if not config.no_start and not config.otp: | 584 | if not config.no_start: |
1093 | 585 | try: | 585 | try: |
1094 | 586 | sysvconfig.restart_landscape() | 586 | sysvconfig.restart_landscape() |
1095 | 587 | except ProcessError: | 587 | except ProcessError: |
1096 | 588 | 588 | ||
1097 | === modified file 'landscape/message_schemas.py' | |||
1098 | --- landscape/message_schemas.py 2014-06-09 08:18:05 +0000 | |||
1099 | +++ landscape/message_schemas.py 2014-07-17 06:52:14 +0000 | |||
1100 | @@ -211,6 +211,9 @@ | |||
1101 | 211 | {"otp": Bytes()}) | 211 | {"otp": Bytes()}) |
1102 | 212 | 212 | ||
1103 | 213 | 213 | ||
1104 | 214 | # XXX The register-cloud-vm message is obsolete, it's kept around just to not | ||
1105 | 215 | # break older LDS releases that import it (the last LDS release to have it | ||
1106 | 216 | # is 14.07). Eventually it shall be dropped. | ||
1107 | 214 | REGISTER_CLOUD_VM = Message( | 217 | REGISTER_CLOUD_VM = Message( |
1108 | 215 | "register-cloud-vm", | 218 | "register-cloud-vm", |
1109 | 216 | {"hostname": Unicode(), | 219 | {"hostname": Unicode(), |
1110 | 217 | 220 | ||
1111 | === modified file 'landscape/tests/test_configuration.py' | |||
1112 | --- landscape/tests/test_configuration.py 2014-02-25 18:06:48 +0000 | |||
1113 | +++ landscape/tests/test_configuration.py 2014-07-17 06:52:14 +0000 | |||
1114 | @@ -820,7 +820,6 @@ | |||
1115 | 820 | "--ping-interval", "30", | 820 | "--ping-interval", "30", |
1116 | 821 | "--http-proxy", "", | 821 | "--http-proxy", "", |
1117 | 822 | "--https-proxy", "", | 822 | "--https-proxy", "", |
1118 | 823 | "--otp", "", | ||
1119 | 824 | "--tags", "", | 823 | "--tags", "", |
1120 | 825 | "--provisioning-otp", ""] | 824 | "--provisioning-otp", ""] |
1121 | 826 | config = self.get_config(args) | 825 | config = self.get_config(args) |
1122 | @@ -837,7 +836,6 @@ | |||
1123 | 837 | "https_proxy = \n" | 836 | "https_proxy = \n" |
1124 | 838 | "url = https://landscape.canonical.com/message-system\n" | 837 | "url = https://landscape.canonical.com/message-system\n" |
1125 | 839 | "exchange_interval = 900\n" | 838 | "exchange_interval = 900\n" |
1126 | 840 | "otp = \n" | ||
1127 | 841 | "ping_interval = 30\n" | 839 | "ping_interval = 30\n" |
1128 | 842 | "ping_url = http://landscape.canonical.com/ping\n" | 840 | "ping_url = http://landscape.canonical.com/ping\n" |
1129 | 843 | "provisioning_otp = \n" | 841 | "provisioning_otp = \n" |
1130 | @@ -862,20 +860,6 @@ | |||
1131 | 862 | config = self.get_config(["--silent", "-t", "rex"]) | 860 | config = self.get_config(["--silent", "-t", "rex"]) |
1132 | 863 | self.assertRaises(ConfigurationError, setup, config) | 861 | self.assertRaises(ConfigurationError, setup, config) |
1133 | 864 | 862 | ||
1134 | 865 | def test_silent_setup_with_otp(self): | ||
1135 | 866 | """ | ||
1136 | 867 | If the OTP is specified, there is no need to pass the account name and | ||
1137 | 868 | the computer title. | ||
1138 | 869 | """ | ||
1139 | 870 | sysvconfig_mock = self.mocker.patch(SysVConfig) | ||
1140 | 871 | sysvconfig_mock.set_start_on_boot(True) | ||
1141 | 872 | self.mocker.replay() | ||
1142 | 873 | |||
1143 | 874 | config = self.get_config(["--silent", "--otp", "otp1"]) | ||
1144 | 875 | setup(config) | ||
1145 | 876 | |||
1146 | 877 | self.assertEqual("otp1", config.otp) | ||
1147 | 878 | |||
1148 | 879 | def test_silent_setup_with_provisioning_otp(self): | 863 | def test_silent_setup_with_provisioning_otp(self): |
1149 | 880 | """ | 864 | """ |
1150 | 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 |
1151 | 882 | 866 | ||
1152 | === removed file 'scripts/landscape-is-cloud-managed' | |||
1153 | --- scripts/landscape-is-cloud-managed 2009-04-09 14:32:45 +0000 | |||
1154 | +++ scripts/landscape-is-cloud-managed 1970-01-01 00:00:00 +0000 | |||
1155 | @@ -1,12 +0,0 @@ | |||
1156 | 1 | #!/usr/bin/python | ||
1157 | 2 | import sys, os | ||
1158 | 3 | if os.path.dirname(os.path.abspath(sys.argv[0])) == os.path.abspath("scripts"): | ||
1159 | 4 | sys.path.insert(0, "./") | ||
1160 | 5 | else: | ||
1161 | 6 | from landscape.lib.warning import hide_warnings | ||
1162 | 7 | hide_warnings() | ||
1163 | 8 | |||
1164 | 9 | from landscape.broker.registration import is_cloud_managed | ||
1165 | 10 | |||
1166 | 11 | # We return 0 if it succeeds | ||
1167 | 12 | sys.exit(not is_cloud_managed()) | ||
1168 | 13 | 0 | ||
1169 | === modified file 'setup.py' | |||
1170 | --- setup.py 2013-06-03 12:26:30 +0000 | |||
1171 | +++ setup.py 2014-07-17 06:52:14 +0000 | |||
1172 | @@ -56,7 +56,6 @@ | |||
1173 | 56 | "scripts/landscape-package-reporter", | 56 | "scripts/landscape-package-reporter", |
1174 | 57 | "scripts/landscape-release-upgrader", | 57 | "scripts/landscape-release-upgrader", |
1175 | 58 | "scripts/landscape-sysinfo", | 58 | "scripts/landscape-sysinfo", |
1176 | 59 | "scripts/landscape-is-cloud-managed", | ||
1177 | 60 | "scripts/landscape-dbus-proxy", | 59 | "scripts/landscape-dbus-proxy", |
1178 | 61 | "scripts/landscape-client-settings-mechanism", | 60 | "scripts/landscape-client-settings-mechanism", |
1179 | 62 | "scripts/landscape-client-registration-mechanism", | 61 | "scripts/landscape-client-registration-mechanism", |
+1 - I see a lot of lint output, might want to take a look.