Merge lp:~caio1982/ubuntu-system-image/sis-skip404 into lp:ubuntu-system-image/server

Proposed by Caio Begotti
Status: Work in progress
Proposed branch: lp:~caio1982/ubuntu-system-image/sis-skip404
Merge into: lp:ubuntu-system-image/server
Prerequisite: lp:~caio1982/ubuntu-system-image/sis-logging
Diff against target: 172 lines (+57/-61)
1 file modified
lib/systemimage/generators.py (+57/-61)
To merge this branch: bzr merge lp:~caio1982/ubuntu-system-image/sis-skip404
Reviewer Review Type Date Requested Status
Stéphane Graber (community) Needs Fixing
Review via email: mp+252580@code.launchpad.net

This proposal supersedes a proposal from 2015-03-11.

Description of the change

Image server goes crazy if the configured image is not there at the time of importing. What happens is that it fetches an Apache error page (HTML) and try to import it, which surprisingly works. Then when the image is finally there it tries to generate a delta, and that obviously fails because one of the files (the source one) is not an image.

The only way to recover from this state is to manually intervene and remove the first processed files, json, images etc. This is very problematic for us in PES.

Although this MR is kind of hackish it does the job for running image server on Prodstack. I understand a more robust solution would be needed to handle corner cases (perhaps using Requests instead of urllib, to start with).

To post a comment you must log in.
Revision history for this message
Stéphane Graber (stgraber) :
Revision history for this message
Stéphane Graber (stgraber) :
review: Needs Fixing
262. By Caio Begotti

catch all http errors above 400 instead and make the logging message more generic to reflect it

263. By Caio Begotti

transform all calls to urlopen and urlretrieve in wrapper calls so we catch all timeouts and >= 400 http errors better

264. By Caio Begotti

fix flake errors due to leftovers from previous commit

Revision history for this message
Caio Begotti (caio1982) wrote :

Stephane, can you tell me if this is more or less how you imaged it to be like? If it is I'll get to work on the tests as they are failing now because of these changes. If it's not what you asked for then I think I'll just stick to my hack in a private branch as I believe it would be more benefit to have more time to convert it all to use Requests.

265. By Caio Begotti

add more debug logging to the methods we are wrapping around

Revision history for this message
Stéphane Graber (stgraber) wrote :

This change causes clear behavioral changes on error, please fix.

review: Needs Fixing
Revision history for this message
Stéphane Graber (stgraber) wrote :

> Stephane, can you tell me if this is more or less how you imaged it to be
> like? If it is I'll get to work on the tests as they are failing now because
> of these changes. If it's not what you asked for then I think I'll just stick
> to my hack in a private branch as I believe it would be more benefit to have
> more time to convert it all to use Requests.

This is basically what I imagined except for the part where you no longer return None, which will cause a crash.

Unmerged revisions

265. By Caio Begotti

add more debug logging to the methods we are wrapping around

264. By Caio Begotti

fix flake errors due to leftovers from previous commit

263. By Caio Begotti

transform all calls to urlopen and urlretrieve in wrapper calls so we catch all timeouts and >= 400 http errors better

262. By Caio Begotti

catch all http errors above 400 instead and make the logging message more generic to reflect it

261. By Caio Begotti

make s-i-s a bit smarted by returning none if the actual file was not found, otherwise it will process a dummy html page with a 404 error message inside it... this is more like a hack because using urllib these days seems already wrong and i dont have the time to fully fix how all files are urlopened or urlretrieved in its code

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'lib/systemimage/generators.py'
2--- lib/systemimage/generators.py 2015-03-11 20:55:00 +0000
3+++ lib/systemimage/generators.py 2015-03-12 14:10:25 +0000
4@@ -35,6 +35,56 @@
5 CACHE = {}
6
7
8+def _urlopen(url):
9+ res = None
10+ old_timeout = socket.getdefaulttimeout()
11+ socket.setdefaulttimeout(5)
12+
13+ try:
14+ res = urlopen(url)
15+ except socket.timeout:
16+ return None
17+ except IOError:
18+ return None
19+
20+ socket.setdefaulttimeout(old_timeout)
21+
22+ # Make sure it's there before retrieval
23+ if res.getcode() >= 400:
24+ logger.debug("Server side error, skipping %s" % url)
25+ return None
26+
27+ logger.debug("Returning URL opening with %s" % res)
28+ return res
29+
30+
31+def _urlretrieve(url, save):
32+ res = None
33+ old_timeout = socket.getdefaulttimeout()
34+ socket.setdefaulttimeout(5)
35+
36+ try:
37+ res = urlretrieve(url, save)
38+ except socket.timeout:
39+ if os.path.isfile(save):
40+ os.remove(save)
41+ elif os.path.isdir(save):
42+ shutil.rmtree(save)
43+ return None
44+ except IOError:
45+ if os.path.isfile(save):
46+ os.remove(save)
47+ elif os.path.isdir(save):
48+ shutil.rmtree(save)
49+ return None
50+
51+ socket.setdefaulttimeout(old_timeout)
52+
53+ logger.debug("Save path requested was %s" % save)
54+ logger.debug("Returning retrieval with %s" % res)
55+ return res
56+
57+
58 def list_versions(cdimage_path):
59 return sorted([version for version in os.listdir(cdimage_path)
60 if version not in ("pending", "current")],
61@@ -832,15 +882,7 @@
62 if "monitor" in options or version:
63 if not version:
64 # Grab the current version number
65- old_timeout = socket.getdefaulttimeout()
66- socket.setdefaulttimeout(5)
67- try:
68- version = urlopen(options['monitor']).read().strip()
69- except socket.timeout:
70- return None
71- except IOError:
72- return None
73- socket.setdefaulttimeout(old_timeout)
74+ version = _urlopen(options['monitor']).read().strip()
75
76 # Validate the version number
77 if not version or len(version.split("\n")) > 1:
78@@ -892,17 +934,7 @@
79
80 # Grab the real thing
81 tempdir = tempfile.mkdtemp()
82- old_timeout = socket.getdefaulttimeout()
83- socket.setdefaulttimeout(5)
84- try:
85- urlretrieve(url, os.path.join(tempdir, "download"))
86- except socket.timeout:
87- shutil.rmtree(tempdir)
88- return None
89- except IOError:
90- shutil.rmtree(tempdir)
91- return None
92- socket.setdefaulttimeout(old_timeout)
93+ _urlretrieve(url, os.path.join(tempdir, "download"))
94
95 # Hash it if we don't have a version number
96 if not version:
97@@ -1067,16 +1099,8 @@
98 device_name = options['device']
99
100 # Fetch and validate the remote channels.json
101- old_timeout = socket.getdefaulttimeout()
102- socket.setdefaulttimeout(5)
103- try:
104- channel_json = json.loads(urlopen("%s/channels.json" %
105- base_url).read().decode().strip())
106- except socket.timeout:
107- return None
108- except IOError:
109- return None
110- socket.setdefaulttimeout(old_timeout)
111+ channel_json = json.loads(_urlopen("%s/channels.json" %
112+ base_url).read().decode().strip())
113
114 if channel_name not in channel_json:
115 return None
116@@ -1095,15 +1119,7 @@
117 [device_name]['index'])
118
119 # Fetch and validate the remote index.json
120- old_timeout = socket.getdefaulttimeout()
121- socket.setdefaulttimeout(5)
122- try:
123- index_json = json.loads(urlopen(index_url).read().decode())
124- except socket.timeout:
125- return None
126- except IOError:
127- return None
128- socket.setdefaulttimeout(old_timeout)
129+ index_json = json.loads(_urlopen(index_url).read().decode())
130
131 # Grab the list of full images
132 full_images = sorted([image for image in index_json['images']
133@@ -1130,18 +1146,7 @@
134
135 # Grab the file
136 file_url = "%s/%s" % (base_url, file_entry['path'])
137- socket.setdefaulttimeout(5)
138- try:
139- urlretrieve(file_url, path)
140- except socket.timeout:
141- if os.path.exists(path):
142- os.remove(path)
143- return None
144- except IOError:
145- if os.path.exists(path):
146- os.remove(path)
147- return None
148- socket.setdefaulttimeout(old_timeout)
149+ _urlretrieve(file_url, path)
150
151 if "keyring" in options:
152 if not tools.repack_recovery_keyring(conf, path,
153@@ -1153,18 +1158,9 @@
154 gpg.sign_file(conf, "image-signing", path)
155
156 # Attempt to grab an associated json
157- socket.setdefaulttimeout(5)
158 json_path = path.replace(".tar.xz", ".json")
159 json_url = file_url.replace(".tar.xz", ".json")
160- try:
161- urlretrieve(json_url, json_path),
162- except socket.timeout:
163- if os.path.exists(json_path):
164- os.remove(json_path)
165- except IOError:
166- if os.path.exists(json_path):
167- os.remove(json_path)
168- socket.setdefaulttimeout(old_timeout)
169+ _urlretrieve(json_url, json_path)
170
171 if os.path.exists(json_path):
172 gpg.sign_file(conf, "image-signing", json_path)

Subscribers

People subscribed via source and target branches

to all changes: