return "Scheduled %s update..." % spec.service_name()
@handle_orch_error
- def apply(self, specs: Sequence[GenericSpec], no_overwrite: bool = False) -> List[str]:
+ def apply(
+ self,
+ specs: Sequence[GenericSpec],
+ no_overwrite: bool = False,
+ continue_on_error: bool = True
+ ) -> List[str]:
results = []
for spec in specs:
if no_overwrite:
results.append('Skipped %s service spec. To change %s spec omit --no-overwrite flag'
% (cast(ServiceSpec, spec).service_name(), cast(ServiceSpec, spec).service_name()))
continue
- results.append(self._apply(spec))
+ try:
+ res = self._apply(spec)
+ results.append(res)
+ except Exception as e:
+ if continue_on_error:
+ results.append(f'Failed to apply spec for {spec}: {str(e)}')
+ else:
+ raise e
return results
@handle_orch_error
format: Format = Format.plain,
unmanaged: bool = False,
no_overwrite: bool = False,
+ continue_on_error: bool = False,
inbuf: Optional[str] = None) -> HandleCommandResult:
"""Update the size or placement for a service or apply a large yaml spec"""
usage = """Usage:
ceph orch apply -i <yaml spec> [--dry-run]
ceph orch apply <service_type> [--placement=<placement_string>] [--unmanaged]
"""
+ errs: List[str] = []
if inbuf:
if service_type or placement or unmanaged:
raise OrchestratorValidationError(usage)
# None entries in the output. Let's skip them silently.
content = [o for o in yaml_objs if o is not None]
for s in content:
- spec = json_to_generic_spec(s)
+ try:
+ spec = json_to_generic_spec(s)
+ except Exception as e:
+ if continue_on_error:
+ errs.append(f'Failed to convert {s} from json object: {str(e)}')
+ continue
+ else:
+ raise e
# validate the config (we need MgrModule for that)
if isinstance(spec, ServiceSpec) and spec.config:
try:
self.get_foreign_ceph_option('mon', k)
except KeyError:
- raise SpecValidationError(f'Invalid config option {k} in spec')
+ err = SpecValidationError(f'Invalid config option {k} in spec')
+ if continue_on_error:
+ errs.append(str(err))
+ continue
+ else:
+ raise err
# There is a general "osd" service with no service id, but we use
# that to dump osds created individually with "ceph orch daemon add osd"
and spec.service_type == 'osd'
and not spec.service_id
):
- raise SpecValidationError('Please provide the service_id field in your OSD spec')
+ err = SpecValidationError('Please provide the service_id field in your OSD spec')
+ if continue_on_error:
+ errs.append(str(err))
+ continue
+ else:
+ raise err
if dry_run and not isinstance(spec, HostSpec):
spec.preview_only = dry_run
continue
specs.append(spec)
else:
+ # Note in this case there is only ever one spec
+ # being applied so there is no need to worry about
+ # handling of continue_on_error
placementspec = PlacementSpec.from_string(placement)
if not service_type:
raise OrchestratorValidationError(usage)
specs = [ServiceSpec(service_type.value, placement=placementspec,
unmanaged=unmanaged, preview_only=dry_run)]
- return self._apply_misc(specs, dry_run, format, no_overwrite)
-
- def _apply_misc(self, specs: Sequence[GenericSpec], dry_run: bool, format: Format, no_overwrite: bool = False) -> HandleCommandResult:
- completion = self.apply(specs, no_overwrite)
+ cmd_result = self._apply_misc(specs, dry_run, format, no_overwrite, continue_on_error)
+ if errs:
+ # HandleCommandResult is a named tuple, so use
+ # _replace to modify it.
+ cmd_result = cmd_result._replace(stdout=cmd_result.stdout + '\n' + '\n'.join(errs))
+ return cmd_result
+
+ def _apply_misc(
+ self,
+ specs: Sequence[GenericSpec],
+ dry_run: bool,
+ format: Format,
+ no_overwrite: bool = False,
+ continue_on_error: bool = False
+ ) -> HandleCommandResult:
+ completion = self.apply(specs, no_overwrite, continue_on_error)
raise_if_exception(completion)
out = completion.result_str()
if dry_run: