blob: 32012d912e673cebeb35715fa0f137ce0e5f9c06 [file] [log] [blame]
Alex Klein2966e302019-01-17 13:29:38 -07001# Copyright 2018 The Chromium OS Authors. All rights reserved.
2# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
5"""Image API Service.
6
7The image related API endpoints should generally be found here.
8"""
9
Alex Klein2966e302019-01-17 13:29:38 -070010import os
11
Alex Klein8cb365a2019-05-15 16:24:53 -060012from chromite.api import controller
Alex Klein076841b2019-08-29 15:19:39 -060013from chromite.api import faux
Alex Klein2b236722019-06-19 15:44:26 -060014from chromite.api import validate
Alex Kleine9a7dbf2020-10-06 18:12:12 -060015from chromite.api.controller import controller_util
David Burgerb171d652019-05-13 16:07:00 -060016from chromite.api.gen.chromiumos import common_pb2
Will Bradley9bc85452019-10-10 10:48:21 -060017from chromite.api.metrics import deserialize_metrics_log
George Engelbrechtc9a8e812021-06-16 18:14:17 -060018from chromite.lib import build_target_lib
19from chromite.lib import chroot_lib
Alex Klein4f0eb432019-05-02 13:56:04 -060020from chromite.lib import cros_build_lib
Alex Klein56355682019-02-07 10:36:54 -070021from chromite.lib import constants
22from chromite.lib import image_lib
Jack Neus761e1842020-12-01 18:20:11 +000023from chromite.lib import cros_logging as logging
George Engelbrechtc9a8e812021-06-16 18:14:17 -060024from chromite.lib import sysroot_lib
Jack Neus761e1842020-12-01 18:20:11 +000025from chromite.scripts import pushimage
Alex Kleinb7cdbe62019-02-22 11:41:32 -070026from chromite.service import image
Will Bradley9bc85452019-10-10 10:48:21 -060027from chromite.utils import metrics
Alex Klein2966e302019-01-17 13:29:38 -070028
Alex Klein56355682019-02-07 10:36:54 -070029# The image.proto ImageType enum ids.
George Engelbrechtc55d6312021-05-05 12:11:13 -060030_BASE_ID = common_pb2.IMAGE_TYPE_BASE
31_DEV_ID = common_pb2.IMAGE_TYPE_DEV
32_TEST_ID = common_pb2.IMAGE_TYPE_TEST
33_BASE_VM_ID = common_pb2.IMAGE_TYPE_BASE_VM
34_TEST_VM_ID = common_pb2.IMAGE_TYPE_TEST_VM
35_RECOVERY_ID = common_pb2.IMAGE_TYPE_RECOVERY
36_FACTORY_ID = common_pb2.IMAGE_TYPE_FACTORY
37_FIRMWARE_ID = common_pb2.IMAGE_TYPE_FIRMWARE
38_BASE_GUEST_VM_ID = common_pb2.IMAGE_TYPE_BASE_GUEST_VM
39_TEST_GUEST_VM_ID = common_pb2.IMAGE_TYPE_TEST_GUEST_VM
Alex Klein56355682019-02-07 10:36:54 -070040
41# Dict to allow easily translating names to enum ids and vice versa.
42_IMAGE_MAPPING = {
43 _BASE_ID: constants.IMAGE_TYPE_BASE,
44 constants.IMAGE_TYPE_BASE: _BASE_ID,
45 _DEV_ID: constants.IMAGE_TYPE_DEV,
46 constants.IMAGE_TYPE_DEV: _DEV_ID,
47 _TEST_ID: constants.IMAGE_TYPE_TEST,
48 constants.IMAGE_TYPE_TEST: _TEST_ID,
Michael Mortenseneefe8952019-08-12 15:37:15 -060049 _RECOVERY_ID: constants.IMAGE_TYPE_RECOVERY,
50 constants.IMAGE_TYPE_RECOVERY: _RECOVERY_ID,
51 _FACTORY_ID: constants.IMAGE_TYPE_FACTORY,
52 constants.IMAGE_TYPE_FACTORY: _FACTORY_ID,
53 _FIRMWARE_ID: constants.IMAGE_TYPE_FIRMWARE,
54 constants.IMAGE_TYPE_FIRMWARE: _FIRMWARE_ID,
Alex Klein56355682019-02-07 10:36:54 -070055}
56
George Engelbrecht9f4f8322021-03-08 12:04:17 -070057# Dict to describe the prerequisite built images for each VM image type.
Alex Klein21b95022019-05-09 14:14:46 -060058_VM_IMAGE_MAPPING = {
59 _BASE_VM_ID: _IMAGE_MAPPING[_BASE_ID],
60 _TEST_VM_ID: _IMAGE_MAPPING[_TEST_ID],
Trent Begin008cade2019-10-31 13:40:59 -060061 _BASE_GUEST_VM_ID: _IMAGE_MAPPING[_BASE_ID],
62 _TEST_GUEST_VM_ID: _IMAGE_MAPPING[_TEST_ID],
Alex Klein21b95022019-05-09 14:14:46 -060063}
64
George Engelbrecht9f4f8322021-03-08 12:04:17 -070065# Dict to describe the prerequisite built images for each mod image type.
66_MOD_IMAGE_MAPPING = {
67 _RECOVERY_ID: _IMAGE_MAPPING[_BASE_ID],
68}
69
Jack Neus761e1842020-12-01 18:20:11 +000070# Supported image types for PushImage.
71SUPPORTED_IMAGE_TYPES = {
72 common_pb2.IMAGE_TYPE_RECOVERY: constants.IMAGE_TYPE_RECOVERY,
73 common_pb2.IMAGE_TYPE_FACTORY: constants.IMAGE_TYPE_FACTORY,
74 common_pb2.IMAGE_TYPE_FIRMWARE: constants.IMAGE_TYPE_FIRMWARE,
75 common_pb2.IMAGE_TYPE_ACCESSORY_USBPD: constants.IMAGE_TYPE_ACCESSORY_USBPD,
76 common_pb2.IMAGE_TYPE_ACCESSORY_RWSIG: constants.IMAGE_TYPE_ACCESSORY_RWSIG,
77 common_pb2.IMAGE_TYPE_BASE: constants.IMAGE_TYPE_BASE,
George Engelbrecht9f4f8322021-03-08 12:04:17 -070078 common_pb2.IMAGE_TYPE_GSC_FIRMWARE: constants.IMAGE_TYPE_GSC_FIRMWARE,
Jack Neus761e1842020-12-01 18:20:11 +000079}
80
Alex Klein56355682019-02-07 10:36:54 -070081
George Engelbrecht9f4f8322021-03-08 12:04:17 -070082def _add_image_to_proto(output_proto, path, image_type, board):
83 """Quick helper function to add a new image to the output proto."""
84 new_image = output_proto.images.add()
85 new_image.path = path
86 new_image.type = image_type
87 new_image.build_target.name = board
88
89
George Engelbrechtc9a8e812021-06-16 18:14:17 -060090def ExampleGetResponse():
91 """Give an example response to assemble upstream in caller artifacts."""
92 uabs = common_pb2.UploadedArtifactsByService
93 cabs = common_pb2.ArtifactsByService
94 return uabs.Sysroot(artifacts=[
95 uabs.Image.ArtifactPaths(
96 artifact_type=cabs.Image.ArtifactType.DLC_IMAGE,
97 paths=[
98 common_pb2.Path(
99 path='/tmp/dlc/dlc.img', location=common_pb2.Path.OUTSIDE)
100 ])
101 ])
102
103
104def GetArtifacts(in_proto: common_pb2.ArtifactsByService.Image,
105 chroot: chroot_lib.Chroot, sysroot_class: sysroot_lib.Sysroot,
Pi-Hsun Shih8d9c8e42021-06-30 03:27:28 +0000106 _build_target: build_target_lib.BuildTarget, output_dir) -> list:
George Engelbrechtc9a8e812021-06-16 18:14:17 -0600107 """Builds and copies images to specified output_dir.
108
109 Copies (after optionally bundling) all required images into the output_dir,
110 returning a mapping of image type to a list of (output_dir) paths to
111 the desired files. Note that currently it is only processing one image (DLC),
112 but the future direction is to process all required images. Required images
113 are located within output_artifact.artifact_type.
114
115 Args:
116 in_proto: Proto request defining reqs.
117 chroot: The chroot proto used for these artifacts.
118 sysroot_class: The sysroot proto used for these artifacts.
119 build_target: The build target used for these artifacts.
120 output_dir: The path to write artifacts to.
121
122 Returns:
123 A list of dictionary mappings of ArtifactType to list of paths.
124 """
Jack Neus2ffee8a2021-06-18 16:57:28 +0000125 generated = []
Pi-Hsun Shih8d9c8e42021-06-30 03:27:28 +0000126 base_path = chroot.full_path(sysroot_class.path)
George Engelbrechtc9a8e812021-06-16 18:14:17 -0600127
128 for output_artifact in in_proto.output_artifacts:
Pi-Hsun Shih8d9c8e42021-06-30 03:27:28 +0000129 if in_proto.ArtifactType.DLC_IMAGE in output_artifact.artifact_types:
130 # Handling DLC copying.
131 result_paths = image.copy_dlc_image(base_path, output_dir)
132 if result_paths:
133 generated.append({
134 'paths': result_paths,
135 'type': in_proto.ArtifactType.DLC_IMAGE,
136 })
Jack Neus2ffee8a2021-06-18 16:57:28 +0000137 return generated
George Engelbrechtc9a8e812021-06-16 18:14:17 -0600138
Pi-Hsun Shih8d9c8e42021-06-30 03:27:28 +0000139
Michael Mortensen10146cf2019-11-19 19:59:22 -0700140def _CreateResponse(_input_proto, output_proto, _config):
141 """Set output_proto success field on a successful Create response."""
142 output_proto.success = True
143
144
145@faux.success(_CreateResponse)
Michael Mortensen85d38402019-12-12 09:50:29 -0700146@faux.empty_completed_unsuccessfully_error
Alex Klein2b236722019-06-19 15:44:26 -0600147@validate.require('build_target.name')
Alex Klein231d2da2019-07-22 16:44:45 -0600148@validate.validation_complete
Will Bradley9bc85452019-10-10 10:48:21 -0600149@metrics.collect_metrics
Alex Klein231d2da2019-07-22 16:44:45 -0600150def Create(input_proto, output_proto, _config):
George Engelbrecht9f4f8322021-03-08 12:04:17 -0700151 """Build images.
Alex Klein56355682019-02-07 10:36:54 -0700152
153 Args:
154 input_proto (image_pb2.CreateImageRequest): The input message.
155 output_proto (image_pb2.CreateImageResult): The output message.
Alex Klein231d2da2019-07-22 16:44:45 -0600156 _config (api_config.ApiConfig): The API call config.
Alex Klein56355682019-02-07 10:36:54 -0700157 """
158 board = input_proto.build_target.name
Alex Klein56355682019-02-07 10:36:54 -0700159
Alex Klein56355682019-02-07 10:36:54 -0700160 # Build the base image if no images provided.
161 to_build = input_proto.image_types or [_BASE_ID]
Alex Klein56355682019-02-07 10:36:54 -0700162
George Engelbrecht9f4f8322021-03-08 12:04:17 -0700163 image_types, vm_types, mod_image_types = _ParseImagesToCreate(to_build)
Alex Klein21b95022019-05-09 14:14:46 -0600164 build_config = _ParseCreateBuildConfig(input_proto)
Alex Klein56355682019-02-07 10:36:54 -0700165
166 # Sorted isn't really necessary here, but it's much easier to test.
Jack Neus761e1842020-12-01 18:20:11 +0000167 result = image.Build(
168 board=board, images=sorted(list(image_types)), config=build_config)
Alex Klein56355682019-02-07 10:36:54 -0700169
Alex Klein1bcd9882019-03-19 13:25:24 -0600170 output_proto.success = result.success
Will Bradley29a49c22019-10-21 11:50:08 -0600171
Alex Klein1bcd9882019-03-19 13:25:24 -0600172 if result.success:
173 # Success -- we need to list out the images we built in the output.
174 _PopulateBuiltImages(board, image_types, output_proto)
Will Bradley29a49c22019-10-21 11:50:08 -0600175
George Engelbrecht9f4f8322021-03-08 12:04:17 -0700176 for vm_type in vm_types:
177 is_test = vm_type in [_TEST_VM_ID, _TEST_GUEST_VM_ID]
178 try:
179 if vm_type in [_BASE_GUEST_VM_ID, _TEST_GUEST_VM_ID]:
180 vm_path = image.CreateGuestVm(board, is_test=is_test)
181 else:
182 vm_path = image.CreateVm(
183 board, disk_layout=build_config.disk_layout, is_test=is_test)
184 except image.ImageToVmError as e:
185 cros_build_lib.Die(e)
Will Bradley29a49c22019-10-21 11:50:08 -0600186
George Engelbrecht9f4f8322021-03-08 12:04:17 -0700187 _add_image_to_proto(output_proto, vm_path, vm_type, board)
188
189 for mod_type in mod_image_types:
190 if mod_type == _RECOVERY_ID:
191 base_image_path = _GetBaseImagePath(output_proto)
192 result = image.BuildRecoveryImage(board=board,
193 image_path=base_image_path)
194 if result.success:
195 _PopulateBuiltImages(board, [_IMAGE_MAPPING[mod_type]], output_proto)
196 else:
197 cros_build_lib.Die('Failed to create recovery image.')
198 else:
199 cros_build_lib.Die('_RECOVERY_ID is the only mod_image_type.')
Will Bradley29a49c22019-10-21 11:50:08 -0600200
201 # Read metric events log and pipe them into output_proto.events.
202 deserialize_metrics_log(output_proto.events, prefix=board)
203 return controller.RETURN_CODE_SUCCESS
204
Alex Klein1bcd9882019-03-19 13:25:24 -0600205 else:
Alex Klein2557b4f2019-07-11 14:34:00 -0600206 # Failure, include all of the failed packages in the output when available.
207 if not result.failed_packages:
208 return controller.RETURN_CODE_COMPLETED_UNSUCCESSFULLY
209
Alex Klein1bcd9882019-03-19 13:25:24 -0600210 for package in result.failed_packages:
211 current = output_proto.failed_packages.add()
Alex Kleine9a7dbf2020-10-06 18:12:12 -0600212 controller_util.serialize_package_info(package, current)
Alex Klein1bcd9882019-03-19 13:25:24 -0600213
Alex Klein8cb365a2019-05-15 16:24:53 -0600214 return controller.RETURN_CODE_UNSUCCESSFUL_RESPONSE_AVAILABLE
Alex Klein1bcd9882019-03-19 13:25:24 -0600215
George Engelbrecht9f4f8322021-03-08 12:04:17 -0700216def _GetBaseImagePath(output_proto):
217 """From image_pb2.CreateImageResult, return base image path or None."""
218 ret = None
219 for i in output_proto.images:
220 if i.type == _BASE_ID:
221 ret = i.path
222 return ret
223
Alex Klein21b95022019-05-09 14:14:46 -0600224
225def _ParseImagesToCreate(to_build):
226 """Helper function to parse the image types to build.
227
George Engelbrecht9f4f8322021-03-08 12:04:17 -0700228 This function expresses the dependencies of each image type and adds
229 the requisite image types if they're not explicitly defined.
Alex Klein21b95022019-05-09 14:14:46 -0600230
231 Args:
232 to_build (list[int]): The image type list.
233
234 Returns:
George Engelbrecht9f4f8322021-03-08 12:04:17 -0700235 (set, set, set): The image, vm, and mod_image types that need to be built.
Alex Klein21b95022019-05-09 14:14:46 -0600236 """
237 image_types = set()
238 vm_types = set()
George Engelbrecht9f4f8322021-03-08 12:04:17 -0700239 mod_image_types = set()
Alex Klein21b95022019-05-09 14:14:46 -0600240 for current in to_build:
George Engelbrecht9f4f8322021-03-08 12:04:17 -0700241 # Find out if it's a special case (vm, img mod), or just any old image.
242 if current in _VM_IMAGE_MAPPING:
Alex Klein21b95022019-05-09 14:14:46 -0600243 vm_types.add(current)
244 # Make sure we build the image required to build the VM.
245 image_types.add(_VM_IMAGE_MAPPING[current])
George Engelbrecht9f4f8322021-03-08 12:04:17 -0700246 elif current in _MOD_IMAGE_MAPPING:
247 mod_image_types.add(current)
248 image_types.add(_MOD_IMAGE_MAPPING[current])
249 elif current in _IMAGE_MAPPING:
250 image_types.add(_IMAGE_MAPPING[current])
Alex Klein21b95022019-05-09 14:14:46 -0600251 else:
252 # Not expected, but at least it will be obvious if this comes up.
253 cros_build_lib.Die(
254 "The service's known image types do not match those in image.proto. "
255 'Unknown Enum ID: %s' % current)
256
Trent Begin008cade2019-10-31 13:40:59 -0600257 # We can only build one type of these images at a time since image_to_vm.sh
258 # uses the default path if a name is not provided.
259 if vm_types.issuperset({_BASE_VM_ID, _TEST_VM_ID}):
Alex Klein21b95022019-05-09 14:14:46 -0600260 cros_build_lib.Die('Cannot create more than one VM.')
261
George Engelbrecht9f4f8322021-03-08 12:04:17 -0700262 return image_types, vm_types, mod_image_types
Alex Klein21b95022019-05-09 14:14:46 -0600263
264
265def _ParseCreateBuildConfig(input_proto):
266 """Helper to parse the image build config for Create."""
267 enable_rootfs_verification = not input_proto.disable_rootfs_verification
268 version = input_proto.version or None
269 disk_layout = input_proto.disk_layout or None
270 builder_path = input_proto.builder_path or None
271 return image.BuildConfig(
Jack Neus761e1842020-12-01 18:20:11 +0000272 enable_rootfs_verification=enable_rootfs_verification,
273 replace=True,
274 version=version,
275 disk_layout=disk_layout,
276 builder_path=builder_path,
Alex Klein21b95022019-05-09 14:14:46 -0600277 )
278
Alex Klein1bcd9882019-03-19 13:25:24 -0600279
280def _PopulateBuiltImages(board, image_types, output_proto):
281 """Helper to list out built images for Create."""
Alex Klein56355682019-02-07 10:36:54 -0700282 # Build out the ImageType->ImagePath mapping in the output.
283 # We're using the default path, so just fetch that, but read the symlink so
284 # the path we're returning is somewhat more permanent.
285 latest_link = image_lib.GetLatestImageLink(board)
Alex Klein4f0eb432019-05-02 13:56:04 -0600286 base_path = os.path.realpath(latest_link)
Alex Klein56355682019-02-07 10:36:54 -0700287
288 for current in image_types:
289 type_id = _IMAGE_MAPPING[current]
290 path = os.path.join(base_path, constants.IMAGE_TYPE_TO_NAME[current])
George Engelbrecht9f4f8322021-03-08 12:04:17 -0700291 _add_image_to_proto(output_proto, path, type_id, board)
Alex Klein4f0eb432019-05-02 13:56:04 -0600292
293
Michael Mortensen10146cf2019-11-19 19:59:22 -0700294def _SignerTestResponse(_input_proto, output_proto, _config):
295 """Set output_proto success field on a successful SignerTest response."""
296 output_proto.success = True
297 return controller.RETURN_CODE_SUCCESS
298
299
300@faux.success(_SignerTestResponse)
Michael Mortensen85d38402019-12-12 09:50:29 -0700301@faux.empty_completed_unsuccessfully_error
Michael Mortensenc83c9952019-08-05 12:15:12 -0600302@validate.exists('image.path')
Alex Klein231d2da2019-07-22 16:44:45 -0600303@validate.validation_complete
304def SignerTest(input_proto, output_proto, _config):
Michael Mortensenc83c9952019-08-05 12:15:12 -0600305 """Run image tests.
306
307 Args:
308 input_proto (image_pb2.ImageTestRequest): The input message.
309 output_proto (image_pb2.ImageTestResult): The output message.
Alex Klein231d2da2019-07-22 16:44:45 -0600310 _config (api_config.ApiConfig): The API call config.
Michael Mortensenc83c9952019-08-05 12:15:12 -0600311 """
Michael Mortensenc83c9952019-08-05 12:15:12 -0600312 image_path = input_proto.image.path
313
Alex Klein231d2da2019-07-22 16:44:45 -0600314 result = image_lib.SecurityTest(image=image_path)
Michael Mortensenc83c9952019-08-05 12:15:12 -0600315 output_proto.success = result
316 if result:
317 return controller.RETURN_CODE_SUCCESS
318 else:
319 return controller.RETURN_CODE_COMPLETED_UNSUCCESSFULLY
320
Alex Klein076841b2019-08-29 15:19:39 -0600321
Michael Mortensen10146cf2019-11-19 19:59:22 -0700322def _TestResponse(_input_proto, output_proto, _config):
323 """Set output_proto success field on a successful Test response."""
324 output_proto.success = True
325 return controller.RETURN_CODE_SUCCESS
326
327
328@faux.success(_TestResponse)
Michael Mortensen85d38402019-12-12 09:50:29 -0700329@faux.empty_completed_unsuccessfully_error
Alex Klein2b236722019-06-19 15:44:26 -0600330@validate.require('build_target.name', 'result.directory')
331@validate.exists('image.path')
Alex Klein231d2da2019-07-22 16:44:45 -0600332def Test(input_proto, output_proto, config):
Alex Klein2966e302019-01-17 13:29:38 -0700333 """Run image tests.
334
335 Args:
336 input_proto (image_pb2.ImageTestRequest): The input message.
337 output_proto (image_pb2.ImageTestResult): The output message.
Alex Klein231d2da2019-07-22 16:44:45 -0600338 config (api_config.ApiConfig): The API call config.
Alex Klein2966e302019-01-17 13:29:38 -0700339 """
340 image_path = input_proto.image.path
341 board = input_proto.build_target.name
342 result_directory = input_proto.result.directory
343
Alex Klein2966e302019-01-17 13:29:38 -0700344 if not os.path.isfile(image_path) or not image_path.endswith('.bin'):
Alex Klein4f0eb432019-05-02 13:56:04 -0600345 cros_build_lib.Die(
Alex Klein2966e302019-01-17 13:29:38 -0700346 'The image.path must be an existing image file with a .bin extension.')
347
Alex Klein231d2da2019-07-22 16:44:45 -0600348 if config.validate_only:
349 return controller.RETURN_CODE_VALID_INPUT
350
Alex Klein8cb365a2019-05-15 16:24:53 -0600351 success = image.Test(board, result_directory, image_dir=image_path)
352 output_proto.success = success
353
354 if success:
355 return controller.RETURN_CODE_SUCCESS
356 else:
357 return controller.RETURN_CODE_COMPLETED_UNSUCCESSFULLY
Jack Neus761e1842020-12-01 18:20:11 +0000358
359
360@faux.empty_success
361@faux.empty_completed_unsuccessfully_error
362@validate.require('gs_image_dir', 'sysroot.build_target.name')
363def PushImage(input_proto, _output_proto, config):
364 """Push artifacts from the archive bucket to the release bucket.
365
366 Wraps chromite/scripts/pushimage.py.
367
368 Args:
369 input_proto (PushImageRequest): Input proto.
370 _output_proto (PushImageResponse): Output proto.
371 config (api.config.ApiConfig): The API call config.
372
373 Returns:
374 A controller return code (e.g. controller.RETURN_CODE_SUCCESS).
375 """
376 sign_types = []
377 if input_proto.sign_types:
378 for sign_type in input_proto.sign_types:
379 if sign_type not in SUPPORTED_IMAGE_TYPES:
380 logging.error('unsupported sign type %g', sign_type)
381 return controller.RETURN_CODE_INVALID_INPUT
382 sign_types.append(SUPPORTED_IMAGE_TYPES[sign_type])
383
384 # If configured for validation only we're done here.
385 if config.validate_only:
386 return controller.RETURN_CODE_VALID_INPUT
387
Jack Neus485a9d22020-12-21 03:15:15 +0000388 kwargs = {}
389 if input_proto.profile.name:
390 kwargs['profile'] = input_proto.profile.name
391 if input_proto.dest_bucket:
392 kwargs['dest_bucket'] = input_proto.dest_bucket
Jack Neus761e1842020-12-01 18:20:11 +0000393 try:
394 pushimage.PushImage(
395 input_proto.gs_image_dir,
396 input_proto.sysroot.build_target.name,
397 dry_run=input_proto.dryrun,
Jack Neus485a9d22020-12-21 03:15:15 +0000398 sign_types=sign_types,
399 **kwargs)
Jack Neus761e1842020-12-01 18:20:11 +0000400 return controller.RETURN_CODE_SUCCESS
401 except Exception:
Jack Neuse3150a42021-01-29 17:16:36 +0000402 logging.error('PushImage failed: ', exc_info=True)
Jack Neus761e1842020-12-01 18:20:11 +0000403 return controller.RETURN_CODE_COMPLETED_UNSUCCESSFULLY