Skip to content

Commit c62e5ac

Browse files
authored
Merge pull request xapi-project#2810 from huizh/CA-223030
CA-223030: Introduce update API error code and refine error message accordingly.
2 parents 08b0d1e + b40798d commit c62e5ac

File tree

5 files changed

+115
-36
lines changed

5 files changed

+115
-36
lines changed

ocaml/idl/api_errors.ml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -350,12 +350,18 @@ let patch_already_applied = "PATCH_ALREADY_APPLIED"
350350
let update_already_applied = "UPDATE_ALREADY_APPLIED"
351351
let update_already_applied_in_pool = "UPDATE_ALREADY_APPLIED_IN_POOL"
352352
let update_pool_apply_failed = "UPDATE_POOL_APPLY_FAILED"
353+
let update_apply_failed = "UPDATE_APPLY_FAILED"
354+
let update_precheck_failed_unknown_error = "UPDATE_PRECHECK_FAILED_UNKNOWN_ERROR"
355+
let update_precheck_failed_prerequisite_missing = "UPDATE_PRECHECK_FAILED_PREREQUISITE_MISSING"
356+
let update_precheck_failed_conflict_present = "UPDATE_PRECHECK_FAILED_CONFLICT_PRESENT"
357+
let update_precheck_failed_wrong_server_version = "UPDATE_PRECHECK_FAILED_WRONG_SERVER_VERSION"
353358
let patch_precheck_failed_unknown_error = "PATCH_PRECHECK_FAILED_UNKNOWN_ERROR"
354359
let patch_precheck_failed_prerequisite_missing = "PATCH_PRECHECK_FAILED_PREREQUISITE_MISSING"
355360
let patch_precheck_failed_wrong_server_version = "PATCH_PRECHECK_FAILED_WRONG_SERVER_VERSION"
356361
let patch_precheck_failed_wrong_server_build = "PATCH_PRECHECK_FAILED_WRONG_SERVER_BUILD"
357362
let patch_precheck_failed_vm_running = "PATCH_PRECHECK_FAILED_VM_RUNNING"
358363
let patch_precheck_failed_out_of_space = "PATCH_PRECHECK_FAILED_OUT_OF_SPACE"
364+
let update_precheck_failed_out_of_space = "UPDATE_PRECHECK_FAILED_OUT_OF_SPACE"
359365
let patch_precheck_tools_iso_mounted = "PATCH_PRECHECK_FAILED_ISO_MOUNTED"
360366
let patch_apply_failed = "PATCH_APPLY_FAILED"
361367
let patch_apply_failed_backup_files_exist = "PATCH_APPLY_FAILED_BACKUP_FILES_EXIST"

ocaml/idl/datamodel.ml

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1192,19 +1192,30 @@ let _ =
11921192
~doc:"This command is not allowed on the OEM edition." ();
11931193

11941194
(* Update errors *)
1195-
error Api_errors.invalid_update []
1195+
error Api_errors.invalid_update [ "info" ]
11961196
~doc:"The uploaded update package is invalid." ();
11971197
error Api_errors.update_is_applied [ ]
11981198
~doc:"The specified update has been applied and cannot be destroyed." ();
11991199
error Api_errors.cannot_find_update []
12001200
~doc:"The requested update could not be found. Please upload the update again. This can occur when you run xe update-pool-clean before xe update-apply. " ();
12011201
error Api_errors.update_pool_apply_failed [ "hosts" ]
12021202
~doc:"The update cannot be applied for the following host(s)." ();
1203+
error Api_errors.update_apply_failed [ "output" ]
1204+
~doc:"The update failed to apply. Please see attached output." ();
12031205
error Api_errors.update_already_applied [ "update" ]
12041206
~doc:"This update has already been applied." ();
12051207
error Api_errors.update_already_applied_in_pool [ "update" ]
12061208
~doc:"This update has already been applied to all hosts in the pool." ();
1207-
1209+
error Api_errors.update_precheck_failed_unknown_error [ "update"; "info" ]
1210+
~doc:"The update precheck stage failed with an unknown error." ();
1211+
error Api_errors.update_precheck_failed_prerequisite_missing [ "update"; "info" ]
1212+
~doc:"The update precheck stage failed: prerequisite update(s) are missing." ();
1213+
error Api_errors.update_precheck_failed_conflict_present ["update"; "info"]
1214+
~doc:"The update precheck stage failed: conflicting updates are present." ();
1215+
error Api_errors.update_precheck_failed_wrong_server_version ["update"; "info"]
1216+
~doc:"The update precheck stage failed: the server is of an incorrect version." ();
1217+
error Api_errors.update_precheck_failed_out_of_space ["update"; "available_space"; "required_space "]
1218+
~doc:"The update precheck stage failed: the server does not have enough space." ();
12081219

12091220
(* Pool errors *)
12101221

ocaml/xapi/xapi_pool_update.ml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -377,7 +377,7 @@ let pool_apply ~__context ~self =
377377
if List.length unapplied_hosts = 0
378378
then begin
379379
debug "pool_update.pool_apply, %s has already been applied on all hosts." pool_update_name;
380-
raise (Api_errors.Server_error(Api_errors.update_already_applied_in_pool, []))
380+
raise (Api_errors.Server_error(Api_errors.update_already_applied_in_pool, [Ref.string_of self]))
381381
end
382382
else
383383
let failed_hosts = unapplied_hosts |>

scripts/extensions/pool_update.apply

Lines changed: 26 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import json
77
import traceback
88
import subprocess
99
import os
10+
import re
1011
import fasteners
1112
import errno
1213
import shutil
@@ -17,13 +18,19 @@ TMP_DIR = '/tmp/'
1718
UPDATE_ALREADY_APPLIED = 'UPDATE_ALREADY_APPLIED'
1819
UPDATE_APPLY_FAILED = 'UPDATE_APPLY_FAILED'
1920
OTHER_OPERATION_IN_PROGRESS = 'OTHER_OPERATION_IN_PROGRESS'
20-
PATCH_PRECHECK_FAILED_UNKNOWN_ERROR = 'PATCH_PRECHECK_FAILED_UNKNOWN_ERROR'
21+
UPDATE_PRECHECK_FAILED_UNKNOWN_ERROR = 'UPDATE_PRECHECK_FAILED_UNKNOWN_ERROR'
2122
CANNOT_FIND_UPDATE = 'CANNOT_FIND_UPDATE'
23+
INVALID_UPDATE = 'INVALID_UPDATE'
24+
ERROR_MESSAGE_DOWNLOAD_PACKAGE = 'Error downloading packages:\n'
25+
ERROR_MESSAGE_START = 'Error: '
26+
ERROR_MESSAGE_END = 'You could try '
2227

2328

2429
class ApplyFailure(Exception):
2530
pass
2631

32+
class InvalidUpdate(Exception):
33+
pass
2734

2835
def success_message():
2936
rpcparams = {'Status': 'Success', 'Value': ''}
@@ -42,14 +49,17 @@ def execute_apply(session, update_package, yum_conf_file):
4249
output, _ = p.communicate()
4350
xcp.logger.info('pool_update.apply %r returncode=%r output=%r', cmd, p.returncode, output)
4451
if p.returncode != 0:
45-
lines = output.splitlines()
46-
errorlines = [l for l in lines if l.startswith('Error: ')]
47-
if errorlines:
48-
errmsg = errorlines[-1].split(':', 1)[-1].strip()
52+
if ERROR_MESSAGE_DOWNLOAD_PACKAGE in output:
53+
raise InvalidUpdate('Missing package(s) in the update.')
54+
55+
m = re.search('(?<=' + ERROR_MESSAGE_START + ').+$', output, flags=re.DOTALL)
56+
if m:
57+
errmsg = m.group()
58+
errmsg = re.sub(ERROR_MESSAGE_END + '.+', '', errmsg, flags=re.DOTALL)
59+
errmsg = re.sub('\n +', ' ', errmsg, flags=re.DOTALL)
60+
raise ApplyFailure(errmsg)
4961
else:
50-
errmsg = lines[-1]
51-
raise ApplyFailure(
52-
'Install %s error: %s' % (update_package, errmsg))
62+
raise ApplyFailure()
5363

5464

5565
if __name__ == '__main__':
@@ -68,7 +78,7 @@ if __name__ == '__main__':
6878
# Check if the update has been applied.
6979
if update in session.xenapi.host.get_updates(host):
7080
print(failure_message(
71-
UPDATE_ALREADY_APPLIED, ['This update has already been applied.']))
81+
UPDATE_ALREADY_APPLIED, [update]))
7282
sys.exit(0)
7383

7484
update_uuid = session.xenapi.pool_update.get_uuid(update)
@@ -82,26 +92,24 @@ if __name__ == '__main__':
8292

8393
if not lock_acquired:
8494
print(failure_message(
85-
OTHER_OPERATION_IN_PROGRESS, ['Another apply operation is in progress.']))
95+
OTHER_OPERATION_IN_PROGRESS, ['Applying the update', update]))
8696
sys.exit(0)
8797

8898
# Run precheck
8999
try:
90100
session.xenapi.pool_update.precheck(update, host)
91101
except Exception as e:
92102
try:
93-
errors = json.loads(str(e))
94-
print(failure_message(errors[0], errors[1:]))
103+
print(failure_message(e.details[0], e.details[1:]))
95104
except:
96-
print(failure_message(PATCH_PRECHECK_FAILED_UNKNOWN_ERROR, [str(e)]))
105+
print(failure_message(UPDATE_PRECHECK_FAILED_UNKNOWN_ERROR, [str(e)]))
97106
sys.exit(0)
98107

99108
update_vdi = session.xenapi.pool_update.get_vdi(update)
100109
try:
101110
update_vdi_uuid = session.xenapi.VDI.get_uuid(update_vdi)
102111
except Exception as e:
103-
print(failure_message(CANNOT_FIND_UPDATE,
104-
['Missing update package [uuid=%s]' % update_uuid]))
112+
print(failure_message(CANNOT_FIND_UPDATE, []))
105113
sys.exit(0)
106114

107115
# Apply the update.
@@ -121,9 +129,10 @@ if __name__ == '__main__':
121129

122130
session.xenapi.pool_update.resync_host(host)
123131
print(success_message())
132+
except InvalidUpdate as e:
133+
print(failure_message(INVALID_UPDATE, [str(e)]))
124134
except Exception as e:
125-
print(failure_message(UPDATE_APPLY_FAILED,
126-
['Failed to apply the update: %s' % e]))
135+
print(failure_message(UPDATE_APPLY_FAILED, [str(e)]))
127136
finally:
128137
session.xenapi.pool_update.detach(update)
129138
try:

scripts/extensions/pool_update.precheck

Lines changed: 69 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import traceback
1010
import subprocess
1111
import os
1212
import errno
13+
import re
1314
import shutil
1415
import StringIO
1516
import ConfigParser
@@ -18,12 +19,35 @@ import xcp.logger
1819

1920

2021
TMP_DIR = '/tmp/'
21-
PATCH_PRECHECK_FAILED_UNKNOWN_ERROR = 'PATCH_PRECHECK_FAILED_UNKNOWN_ERROR'
22+
UPDATE_PRECHECK_FAILED_UNKNOWN_ERROR = 'UPDATE_PRECHECK_FAILED_UNKNOWN_ERROR'
23+
UPDATE_PRECHECK_FAILED_PREREQUISITE_MISSING = 'UPDATE_PRECHECK_FAILED_PREREQUISITE_MISSING'
24+
UPDATE_PRECHECK_FAILED_CONFLICT_PRESENT = 'UPDATE_PRECHECK_FAILED_CONFLICT_PRESENT'
25+
UPDATE_PRECHECK_FAILED_WRONG_SERVER_VERSION = 'UPDATE_PRECHECK_FAILED_WRONG_SERVER_VERSION'
26+
UPDATE_PRECHECK_FAILED_OUT_OF_SPACE = 'UPDATE_PRECHECK_FAILED_OUT_OF_SPACE'
27+
INVALID_UPDATE = 'INVALID_UPDATE'
2228
CANNOT_FIND_UPDATE = 'CANNOT_FIND_UPDATE'
29+
ERROR_MESSAGE_START = 'Error: '
30+
ERROR_MESSAGE_END = 'You could try '
31+
ERROR_MESSAGE_CONFLICT = ' conflicts with '
32+
ERROR_MESSAGE_PREREQUISITE = 'Requires: '
33+
ERROR_MESSAGE_VERSION_REQUIRED = 'Requires: '
34+
ERROR_MESSAGE_VERSION_INSTALLED = 'Installed: '
35+
ERROR_MESSAGE_DOWNLOAD_PACKAGE = 'Error downloading packages:\n'
2336

2437
class PrecheckFailure(Exception):
2538
pass
2639

40+
class PrerequisiteMissing(Exception):
41+
pass
42+
43+
class ConflictPresent(Exception):
44+
pass
45+
46+
class WrongServerVersion(Exception):
47+
pass
48+
49+
class InvalidUpdate(Exception):
50+
pass
2751

2852
def success_message(result):
2953
rpcparams = {'Status': 'Success', 'Value': result}
@@ -41,10 +65,10 @@ def parse_control_package(session, yum_url):
4165
update_xml = urllib2.urlopen(yum_url + '/update.xml').read()
4266
xmldoc = xml.dom.minidom.parse(StringIO.StringIO(update_xml))
4367
else:
44-
raise PrecheckFailure('Incorrect yum repo (%r)' % yum_url)
68+
raise PrecheckFailure('Incorrect yum repo')
4569
items = xmldoc.getElementsByTagName('update')
4670
if not items:
47-
raise PrecheckFailure('Parse Control Package from yum (%r)' % yum_url)
71+
raise PrecheckFailure('Missing <update> in update.xml')
4872
return items[0].getAttribute('control')
4973

5074

@@ -54,19 +78,29 @@ def execute_precheck(session, control_package, yum_conf_file):
5478
livepatch_dic = {'PATCH_PRECHECK_LIVEPATCH_COMPLETE': 'ok_livepatch_complete', 'PATCH_PRECHECK_LIVEPATCH_INCOMPLETE': 'ok_livepatch_incomplete', 'PATCH_PRECHECK_LIVEPATCH_NOT_APPLICABLE': 'ok'}
5579
cmd = ['yum', 'install', '-y', '--noplugins', '-c', yum_conf_file, control_package]
5680
p = subprocess.Popen(cmd, shell=False, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, close_fds=True)
57-
p.wait()
58-
output = p.stdout.read()
81+
output, _ = p.communicate()
5982
xcp.logger.info('pool_update.precheck %r returncode=%r output=%r', cmd, p.returncode, output)
60-
lines = output.splitlines()
6183
if p.returncode != 0:
62-
errorlines = [l for l in lines if l.startswith('Error: ')]
63-
if errorlines:
64-
errmsg = errorlines[-1].split(':', 1)[-1].strip()
84+
if ERROR_MESSAGE_DOWNLOAD_PACKAGE in output:
85+
raise InvalidUpdate('Missing package(s) in the update')
86+
87+
m = re.search('(?<=' + ERROR_MESSAGE_START + ').+$', output, flags=re.DOTALL)
88+
if m:
89+
errmsg = m.group()
90+
errmsg = re.sub(ERROR_MESSAGE_END + '.+', '', errmsg, flags=re.DOTALL)
91+
errmsg = re.sub('\n +', ' ', errmsg, flags=re.DOTALL)
92+
if ERROR_MESSAGE_CONFLICT in errmsg:
93+
raise ConflictPresent(errmsg)
94+
elif ERROR_MESSAGE_VERSION_REQUIRED in errmsg and ERROR_MESSAGE_VERSION_INSTALLED in errmsg:
95+
raise WrongServerVersion(errmsg)
96+
elif ERROR_MESSAGE_PREREQUISITE in errmsg:
97+
raise PrerequisiteMissing(errmsg)
98+
else:
99+
raise PrecheckFailure(errmsg)
65100
else:
66-
errmsg = lines[-1]
67-
raise PrecheckFailure(
68-
'Install %s error: %s' % (control_package, errmsg))
101+
raise PrecheckFailure()
69102
else:
103+
lines = output.splitlines()
70104
resultlines = [l for l in lines if l.startswith('<livepatch ')]
71105
if resultlines:
72106
result = resultlines[-1].split('\"')[1]
@@ -102,8 +136,20 @@ if __name__ == '__main__':
102136
update_vdi_uuid = session.xenapi.VDI.get_uuid(update_vdi)
103137
update_vdi_valid = True
104138
except Exception as e:
105-
print(failure_message(CANNOT_FIND_UPDATE,
106-
['Missing update package [uuid=%s]' % update_uuid]))
139+
print(failure_message(CANNOT_FIND_UPDATE, []))
140+
sys.exit(0)
141+
142+
# verify that dom0 has enough space to install the package
143+
statvfs = os.statvfs('/')
144+
available_dom0_disk_size = statvfs.f_frsize * statvfs.f_bavail
145+
try:
146+
required_size = int(int(session.xenapi.pool_update.get_installation_size(update)) * 1.5)
147+
if required_size > available_dom0_disk_size:
148+
print(failure_message(UPDATE_PRECHECK_FAILED_OUT_OF_SPACE,
149+
[update_package, str(available_dom0_disk_size), str(required_size)]))
150+
sys.exit(0)
151+
except:
152+
print(failure_message(INVALID_UPDATE, ["Issue with <installation-size> in update.xml"]))
107153
sys.exit(0)
108154

109155
# attach and get the yum configuration
@@ -126,9 +172,16 @@ if __name__ == '__main__':
126172

127173
control_package = parse_control_package(session, yum_url)
128174
print(success_message(execute_precheck(session, control_package, yum_conf_file)))
175+
except PrerequisiteMissing as e:
176+
print(failure_message(UPDATE_PRECHECK_FAILED_PREREQUISITE_MISSING, [update_package, str(e)]))
177+
except ConflictPresent as e:
178+
print(failure_message(UPDATE_PRECHECK_FAILED_CONFLICT_PRESENT, [update_package, str(e)]))
179+
except WrongServerVersion as e:
180+
print(failure_message(UPDATE_PRECHECK_FAILED_WRONG_SERVER_VERSION, [update_package, str(e)]))
181+
except InvalidUpdate as e:
182+
print(failure_message(INVALID_UPDATE, [update_package, str(e)]))
129183
except Exception as e:
130-
print(failure_message(PATCH_PRECHECK_FAILED_UNKNOWN_ERROR,
131-
['Precheck failed on host %s [uuid=%s]: %s' % (host_name_label ,host_uuid, e)]))
184+
print(failure_message(UPDATE_PRECHECK_FAILED_UNKNOWN_ERROR, [update_package, str(e)]))
132185
finally:
133186
if session is not None and update_vdi_valid is True:
134187
session.xenapi.pool_update.detach(update)

0 commit comments

Comments
 (0)