From 7bb174214c46f81da52307faefe8114e2a5a53d9 Mon Sep 17 00:00:00 2001 From: Hannes Ljungberg Date: Mon, 11 Mar 2019 09:55:41 +0100 Subject: [PATCH] docker_swarm_service: Add option rollback_config (#53594) * Add rollback_config * Add change log fragment * Fix broken test * Actually fix broken tests * Add rollback_config example * Default rollback_config as None * Abort early if rollback_config does not exist --- ...-docker_swarm_service-rollback_config.yaml | 2 + .../cloud/docker/docker_swarm_service.py | 180 +++++++++- .../tasks/tests/rollback_config.yml | 339 ++++++++++++++++++ .../tasks/tests/update_config.yml | 33 ++ .../docker_swarm_service/vars/main.yml | 1 + 5 files changed, 553 insertions(+), 2 deletions(-) create mode 100644 changelogs/fragments/53594-docker_swarm_service-rollback_config.yaml create mode 100644 test/integration/targets/docker_swarm_service/tasks/tests/rollback_config.yml diff --git a/changelogs/fragments/53594-docker_swarm_service-rollback_config.yaml b/changelogs/fragments/53594-docker_swarm_service-rollback_config.yaml new file mode 100644 index 0000000000..50c1b725c1 --- /dev/null +++ b/changelogs/fragments/53594-docker_swarm_service-rollback_config.yaml @@ -0,0 +1,2 @@ +minor_changes: + - "docker_swarm_service - Added support for ``rollback_config`` parameter." diff --git a/lib/ansible/modules/cloud/docker/docker_swarm_service.py b/lib/ansible/modules/cloud/docker/docker_swarm_service.py index 689f54490a..9ef9d002e0 100644 --- a/lib/ansible/modules/cloud/docker/docker_swarm_service.py +++ b/lib/ansible/modules/cloud/docker/docker_swarm_service.py @@ -537,6 +537,58 @@ options: - Corresponds to the C(--restart-window) option of C(docker service create). - Deprecated in 2.8, will be removed in 2.12. Use parameter C(restart_config.window) instead. type: raw + rollback_config: + description: + - Configures how the service should be rolled back in case of a failing update. + suboptions: + parallelism: + description: + - The number of containers to rollback at a time. If set to 0, all containers rollback simultaneously. + - Corresponds to the C(--rollback-parallelism) option of C(docker service create). + - Requires API version >= 1.28. + type: int + delay: + description: + - Delay between task rollbacks. + - "Accepts a string in a format that look like: + C(5h34m56s), C(1m30s) etc. The supported units are C(us), C(ms), C(s), C(m) and C(h)." + - Corresponds to the C(--rollback-delay) option of C(docker service create). + - Requires API version >= 1.28. + type: str + failure_action: + description: + - Action to take in case of rollback failure. + - Corresponds to the C(--rollback-failure-action) option of C(docker service create). + - Requires API version >= 1.28. + type: str + choices: + - continue + - pause + monitor: + description: + - Duration after each task rollback to monitor for failure. + - "Accepts a string in a format that look like: + C(5h34m56s), C(1m30s) etc. The supported units are C(us), C(ms), C(s), C(m) and C(h)." + - Corresponds to the C(--rollback-monitor) option of C(docker service create). + - Requires API version >= 1.28. + type: str + max_failure_ratio: + description: + - Fraction of tasks that may fail during a rollback. + - Corresponds to the C(--rollback-max-failure-ratio) option of C(docker service create). + - Requires API version >= 1.28. + type: float + order: + description: + - Specifies the order of operations during rollbacks. + - Corresponds to the C(--rollback-order) option of C(docker service create). + - Requires API version >= 1.29. + type: str + choices: + - stop-first + - start-first + type: dict + version_added: "2.8" secrets: description: - List of dictionaries describing the service secrets. @@ -622,10 +674,12 @@ options: description: - Action to take in case of container failure. - Corresponds to the C(--update-failure-action) option of C(docker service create). + - Usage of I(rollback) requires API version >= 1.29. type: str choices: - continue - pause + - rollback monitor: description: - Time to monitor updated tasks for failures. @@ -671,11 +725,13 @@ options: description: - Action to take in case of container failure. - Corresponds to the C(--update-failure-action) option of C(docker service create). + - Usage of I(rollback) requires API version >= 1.29. - Deprecated in 2.8, will be removed in 2.12. Use parameter C(update_config.failure_action) instead. type: str choices: - continue - pause + - rollback update_monitor: description: - Time to monitor updated tasks for failures. @@ -913,6 +969,17 @@ EXAMPLES = ''' delay: 10s order: stop-first +- name: Set rollback config + docker_swarm_service: + name: myservice + image: alpine + update_config: + failure_action: rollback + rollback_config: + parallelism: 2 + delay: 10s + order: stop-first + - name: Set placement preferences docker_swarm_service: name: myservice @@ -1125,6 +1192,7 @@ class DockerService(DockerBaseClass): self.restart_policy_attempts = None self.restart_policy_delay = None self.restart_policy_window = None + self.rollback_config = None self.update_delay = None self.update_parallelism = None self.update_failure_action = None @@ -1175,6 +1243,7 @@ class DockerService(DockerBaseClass): 'restart_policy_delay': self.restart_policy_delay, 'restart_policy_attempts': self.restart_policy_attempts, 'restart_policy_window': self.restart_policy_window, + 'rollback_config': self.rollback_config, 'update_delay': self.update_delay, 'update_parallelism': self.update_parallelism, 'update_failure_action': self.update_failure_action, @@ -1273,6 +1342,29 @@ class DockerService(DockerBaseClass): 'update_order': order } + @staticmethod + def get_rollback_config_from_ansible_params(params): + if params['rollback_config'] is None: + return None + rollback_config = params['rollback_config'] or {} + delay = get_nanoseconds_from_raw_option( + 'rollback_config.delay', + rollback_config.get('delay') + ) + monitor = get_nanoseconds_from_raw_option( + 'rollback_config.monitor', + rollback_config.get('monitor') + ) + return { + 'parallelism': rollback_config.get('parallelism'), + 'delay': delay, + 'failure_action': rollback_config.get('failure_action'), + 'monitor': monitor, + 'max_failure_ratio': rollback_config.get('max_failure_ratio'), + 'order': rollback_config.get('order'), + + } + @staticmethod def get_logging_from_ansible_params(params): logging_config = params['logging'] or {} @@ -1406,6 +1498,7 @@ class DockerService(DockerBaseClass): ) s.env = get_docker_environment(ap['env'], ap['env_files']) + s.rollback_config = cls.get_rollback_config_from_ansible_params(ap) update_config = cls.get_update_config_from_ansible_params(ap) for key, value in update_config.items(): @@ -1575,6 +1668,8 @@ class DockerService(DockerBaseClass): differences.add('restart_policy_delay', parameter=self.restart_policy_delay, active=os.restart_policy_delay) if self.restart_policy_window is not None and self.restart_policy_window != os.restart_policy_window: differences.add('restart_policy_window', parameter=self.restart_policy_window, active=os.restart_policy_window) + if self.rollback_config is not None and self.has_rollback_config_changed(os.rollback_config): + differences.add('rollback_config', parameter=self.rollback_config, active=os.rollback_config) if self.update_delay is not None and self.update_delay != os.update_delay: differences.add('update_delay', parameter=self.update_delay, active=os.update_delay) if self.update_parallelism is not None and self.update_parallelism != os.update_parallelism: @@ -1648,6 +1743,18 @@ class DockerService(DockerBaseClass): old_image = old_image.split('@')[0] return self.image != old_image, old_image + def has_rollback_config_changed(self, old_rollback_config): + if old_rollback_config is None and self.rollback_config: + return True + defined_options = dict( + (option, value) for option, value in self.rollback_config.items() + if value is not None + ) + for option, value in defined_options.items(): + if value != old_rollback_config.get(option): + return True + return False + def __str__(self): return str({ 'mode': self.mode, @@ -1812,6 +1919,24 @@ class DockerService(DockerBaseClass): restart_policy_args['window'] = self.restart_policy_window return types.RestartPolicy(**restart_policy_args) if restart_policy_args else None + def build_rollback_config(self): + if self.rollback_config is None: + return None + rollback_config_options = [ + 'parallelism', + 'delay', + 'failure_action', + 'monitor', + 'max_failure_ratio', + 'order', + ] + rollback_config_args = {} + for option in rollback_config_options: + value = self.rollback_config.get(option) + if value is not None: + rollback_config_args[option] = value + return types.RollbackConfig(**rollback_config_args) if rollback_config_args else None + def build_resources(self): resources_args = {} if self.limit_cpu is not None: @@ -1892,6 +2017,7 @@ class DockerService(DockerBaseClass): task_template = self.build_task_template(container_spec, placement) update_config = self.build_update_config() + rollback_config = self.build_rollback_config() service_mode = self.build_service_mode() networks = self.build_networks(docker_networks) endpoint_spec = self.build_endpoint_spec() @@ -1899,6 +2025,8 @@ class DockerService(DockerBaseClass): service = {'task_template': task_template, 'mode': service_mode} if update_config: service['update_config'] = update_config + if rollback_config: + service['rollback_config'] = rollback_config if networks: service['networks'] = networks if endpoint_spec: @@ -1955,6 +2083,17 @@ class DockerServiceManager(object): ds.update_max_failure_ratio = update_config_data.get('MaxFailureRatio') ds.update_order = update_config_data.get('Order') + rollback_config_data = raw_data['Spec'].get('RollbackConfig') + if rollback_config_data: + ds.rollback_config = { + 'parallelism': rollback_config_data.get('Parallelism'), + 'delay': rollback_config_data.get('Delay'), + 'failure_action': rollback_config_data.get('FailureAction'), + 'monitor': rollback_config_data.get('Monitor'), + 'max_failure_ratio': rollback_config_data.get('MaxFailureRatio'), + 'order': rollback_config_data.get('Order'), + } + dns_config = task_template_data['ContainerSpec'].get('DNSConfig') if dns_config: ds.dns = dns_config.get('Nameservers') @@ -2281,6 +2420,15 @@ def _detect_mount_tmpfs_usage(client): return False +def _detect_update_config_failure_action_rollback(client): + rollback_config_failure_action = ( + (client.module.params['update_config'] or {}).get('failure_action') + ) + update_failure_action = client.module.params['update_failure_action'] + failure_action = rollback_config_failure_action or update_failure_action + return failure_action == 'rollback' + + def main(): argument_spec = dict( name=dict(type='str', required=True), @@ -2407,10 +2555,24 @@ def main(): restart_policy_delay=dict(type='raw', removed_in_version='2.12'), restart_policy_attempts=dict(type='int', removed_in_version='2.12'), restart_policy_window=dict(type='raw', removed_in_version='2.12'), + rollback_config=dict(type='dict', options=dict( + parallelism=dict(type='int'), + delay=dict(type='str'), + failure_action=dict( + type='str', + choices=['continue', 'pause'] + ), + monitor=dict(type='str'), + max_failure_ratio=dict(type='float'), + order=dict(type='str'), + )), update_config=dict(type='dict', options=dict( parallelism=dict(type='int'), delay=dict(type='str'), - failure_action=dict(type='str', choices=['continue', 'pause']), + failure_action=dict( + type='str', + choices=['continue', 'pause', 'rollback'] + ), monitor=dict(type='str'), max_failure_ratio=dict(type='float'), order=dict(type='str'), @@ -2419,7 +2581,7 @@ def main(): update_parallelism=dict(type='int', removed_in_version='2.12'), update_failure_action=dict( type='str', - choices=['continue', 'pause'], + choices=['continue', 'pause', 'rollback'], removed_in_version='2.12' ), update_monitor=dict(type='raw', removed_in_version='2.12'), @@ -2453,6 +2615,7 @@ def main(): stop_signal=dict(docker_py_version='2.6.0', docker_api_version='1.28'), publish=dict(docker_py_version='3.0.0', docker_api_version='1.25'), read_only=dict(docker_py_version='2.6.0', docker_api_version='1.28'), + rollback_config=dict(docker_py_version='3.5.0', docker_api_version='1.28'), # specials publish_mode=dict( docker_py_version='3.0.0', @@ -2474,6 +2637,12 @@ def main(): ) is not None, usage_msg='set update_config.max_failure_ratio' ), + update_config_failure_action=dict( + docker_py_version='3.5.0', + docker_api_version='1.28', + detect_usage=_detect_update_config_failure_action_rollback, + usage_msg='set update_config.failure_action.rollback' + ), update_config_monitor=dict( docker_py_version='2.1.0', docker_api_version='1.25', @@ -2510,6 +2679,13 @@ def main(): detect_usage=_detect_mount_tmpfs_usage, usage_msg='set mounts.tmpfs' ), + rollback_config_order=dict( + docker_api_version='1.29', + detect_usage=lambda c: (c.module.params['rollback_config'] or {}).get( + 'order' + ) is not None, + usage_msg='set rollback_config.order' + ), ) required_if = [ diff --git a/test/integration/targets/docker_swarm_service/tasks/tests/rollback_config.yml b/test/integration/targets/docker_swarm_service/tasks/tests/rollback_config.yml new file mode 100644 index 0000000000..33f437bab0 --- /dev/null +++ b/test/integration/targets/docker_swarm_service/tasks/tests/rollback_config.yml @@ -0,0 +1,339 @@ +--- + +- name: Registering service name + set_fact: + service_name: "{{ name_prefix ~ '-rollback_config' }}" + +- name: Registering service name + set_fact: + service_names: "{{ service_names }} + [service_name]" + +################################################################### +## rollback_config.delay ############################################ +################################################################### + +- name: rollback_config.delay + docker_swarm_service: + name: "{{ service_name }}" + image: alpine:3.8 + resolve_image: no + command: '/bin/sh -v -c "sleep 10m"' + rollback_config: + delay: 5s + register: rollback_config_delay_1 + ignore_errors: yes + +- name: rollback_config.delay (idempotency) + docker_swarm_service: + name: "{{ service_name }}" + image: alpine:3.8 + resolve_image: no + command: '/bin/sh -v -c "sleep 10m"' + rollback_config: + delay: 5s + register: rollback_config_delay_2 + ignore_errors: yes + +- name: rollback_config.delay (change) + docker_swarm_service: + name: "{{ service_name }}" + image: alpine:3.8 + resolve_image: no + command: '/bin/sh -v -c "sleep 10m"' + rollback_config: + delay: 12s + register: rollback_config_delay_3 + ignore_errors: yes + +- name: cleanup + docker_swarm_service: + name: "{{ service_name }}" + state: absent + diff: no + +- assert: + that: + - rollback_config_delay_1 is changed + - rollback_config_delay_2 is not changed + - rollback_config_delay_3 is changed + when: docker_api_version is version('1.28', '>=') and docker_py_version is version('3.5.0', '>=') +- assert: + that: + - rollback_config_delay_1 is failed + - "'Minimum version required' in rollback_config_delay_1.msg" + when: docker_api_version is version('1.28', '<') or docker_py_version is version('3.5.0', '<') + +################################################################### +## rollback_config.failure_action ################################### +################################################################### + +- name: rollback_config.failure_action + docker_swarm_service: + name: "{{ service_name }}" + image: alpine:3.8 + resolve_image: no + command: '/bin/sh -v -c "sleep 10m"' + rollback_config: + failure_action: "pause" + register: rollback_config_failure_action_1 + ignore_errors: yes + +- name: rollback_config.failure_action (idempotency) + docker_swarm_service: + name: "{{ service_name }}" + image: alpine:3.8 + resolve_image: no + command: '/bin/sh -v -c "sleep 10m"' + rollback_config: + failure_action: "pause" + register: rollback_config_failure_action_2 + ignore_errors: yes + +- name: rollback_config.failure_action (change) + docker_swarm_service: + name: "{{ service_name }}" + image: alpine:3.8 + resolve_image: no + command: '/bin/sh -v -c "sleep 10m"' + rollback_config: + failure_action: "continue" + register: rollback_config_failure_action_3 + ignore_errors: yes + +- name: cleanup + docker_swarm_service: + name: "{{ service_name }}" + state: absent + diff: no + +- assert: + that: + - rollback_config_failure_action_1 is changed + - rollback_config_failure_action_2 is not changed + - rollback_config_failure_action_3 is changed + when: docker_api_version is version('1.28', '>=') and docker_py_version is version('3.5.0', '>=') +- assert: + that: + - rollback_config_failure_action_1 is failed + - "'Minimum version required' in rollback_config_failure_action_1.msg" + when: docker_api_version is version('1.28', '<') or docker_py_version is version('3.5.0', '<') + +################################################################### +## rollback_config.max_failure_ratio ################################ +################################################################### + +- name: rollback_config.max_failure_ratio + docker_swarm_service: + name: "{{ service_name }}" + image: alpine:3.8 + resolve_image: no + command: '/bin/sh -v -c "sleep 10m"' + rollback_config: + max_failure_ratio: 0.25 + register: rollback_config_max_failure_ratio_1 + ignore_errors: yes + +- name: rollback_config.max_failure_ratio (idempotency) + docker_swarm_service: + name: "{{ service_name }}" + image: alpine:3.8 + resolve_image: no + command: '/bin/sh -v -c "sleep 10m"' + rollback_config: + max_failure_ratio: 0.25 + register: rollback_config_max_failure_ratio_2 + ignore_errors: yes + +- name: rollback_config.max_failure_ratio (change) + docker_swarm_service: + name: "{{ service_name }}" + image: alpine:3.8 + resolve_image: no + command: '/bin/sh -v -c "sleep 10m"' + rollback_config: + max_failure_ratio: 0.50 + register: rollback_config_max_failure_ratio_3 + ignore_errors: yes + +- name: cleanup + docker_swarm_service: + name: "{{ service_name }}" + state: absent + diff: no + +- assert: + that: + - rollback_config_max_failure_ratio_1 is changed + - rollback_config_max_failure_ratio_2 is not changed + - rollback_config_max_failure_ratio_3 is changed + when: docker_api_version is version('1.28', '>=') and docker_py_version is version('3.5.0', '>=') +- assert: + that: + - rollback_config_max_failure_ratio_1 is failed + - "'Minimum version required' in rollback_config_max_failure_ratio_1.msg" + when: docker_api_version is version('1.28', '<') or docker_py_version is version('3.5.0', '<') + +################################################################### +# rollback_config.monitor ########################################### +################################################################### + +- name: rollback_config.monitor + docker_swarm_service: + name: "{{ service_name }}" + image: alpine:3.8 + resolve_image: no + command: '/bin/sh -v -c "sleep 10m"' + rollback_config: + monitor: 10s + register: rollback_config_monitor_1 + ignore_errors: yes + +- name: rollback_config.monitor (idempotency) + docker_swarm_service: + name: "{{ service_name }}" + image: alpine:3.8 + resolve_image: no + command: '/bin/sh -v -c "sleep 10m"' + rollback_config: + monitor: 10s + register: rollback_config_monitor_2 + ignore_errors: yes + +- name: rollback_config.monitor (change) + docker_swarm_service: + name: "{{ service_name }}" + image: alpine:3.8 + resolve_image: no + command: '/bin/sh -v -c "sleep 10m"' + rollback_config: + monitor: 60s + register: rollback_config_monitor_3 + ignore_errors: yes + +- name: cleanup + docker_swarm_service: + name: "{{ service_name }}" + state: absent + diff: no + +- assert: + that: + - rollback_config_monitor_1 is changed + - rollback_config_monitor_2 is not changed + - rollback_config_monitor_3 is changed + when: docker_api_version is version('1.28', '>=') and docker_py_version is version('3.5.0', '>=') +- assert: + that: + - rollback_config_monitor_1 is failed + - "'Minimum version required' in rollback_config_monitor_1.msg" + when: docker_api_version is version('1.28', '<') or docker_py_version is version('3.5.0', '<') + +################################################################### +# rollback_config.order ############################################# +################################################################### + +- name: rollback_config.order + docker_swarm_service: + name: "{{ service_name }}" + image: alpine:3.8 + resolve_image: no + command: '/bin/sh -v -c "sleep 10m"' + rollback_config: + order: "start-first" + register: rollback_config_order_1 + ignore_errors: yes + +- name: rollback_config.order (idempotency) + docker_swarm_service: + name: "{{ service_name }}" + image: alpine:3.8 + resolve_image: no + command: '/bin/sh -v -c "sleep 10m"' + rollback_config: + order: "start-first" + register: rollback_config_order_2 + ignore_errors: yes + +- name: rollback_config.order (change) + docker_swarm_service: + name: "{{ service_name }}" + image: alpine:3.8 + resolve_image: no + command: '/bin/sh -v -c "sleep 10m"' + rollback_config: + order: "stop-first" + register: rollback_config_order_3 + ignore_errors: yes + +- name: cleanup + docker_swarm_service: + name: "{{ service_name }}" + state: absent + diff: no + +- assert: + that: + - rollback_config_order_1 is changed + - rollback_config_order_2 is not changed + - rollback_config_order_3 is changed + when: docker_api_version is version('1.29', '>=') and docker_py_version is version('2.7.0', '>=') +- assert: + that: + - rollback_config_order_1 is failed + - "'Minimum version required' in rollback_config_order_1.msg" + when: docker_api_version is version('1.29', '<') or docker_py_version is version('2.7.0', '<') + +################################################################### +## rollback_config.parallelism ###################################### +################################################################### + +- name: rollback_config.parallelism + docker_swarm_service: + name: "{{ service_name }}" + image: alpine:3.8 + resolve_image: no + command: '/bin/sh -v -c "sleep 10m"' + rollback_config: + parallelism: 2 + register: rollback_config_parallelism_1 + ignore_errors: yes + +- name: rollback_config.parallelism (idempotency) + docker_swarm_service: + name: "{{ service_name }}" + image: alpine:3.8 + resolve_image: no + command: '/bin/sh -v -c "sleep 10m"' + rollback_config: + parallelism: 2 + register: rollback_config_parallelism_2 + ignore_errors: yes + +- name: rollback_config.parallelism (change) + docker_swarm_service: + name: "{{ service_name }}" + image: alpine:3.8 + resolve_image: no + command: '/bin/sh -v -c "sleep 10m"' + rollback_config: + parallelism: 1 + register: rollback_config_parallelism_3 + ignore_errors: yes + +- name: cleanup + docker_swarm_service: + name: "{{ service_name }}" + state: absent + diff: no + +- assert: + that: + - rollback_config_parallelism_1 is changed + - rollback_config_parallelism_2 is not changed + - rollback_config_parallelism_3 is changed + when: docker_api_version is version('1.28', '>=') and docker_py_version is version('2.7.0', '>=') +- assert: + that: + - rollback_config_parallelism_1 is failed + - "'Minimum version required' in rollback_config_parallelism_1.msg" + when: docker_api_version is version('1.28', '<') or docker_py_version is version('2.7.0', '<') diff --git a/test/integration/targets/docker_swarm_service/tasks/tests/update_config.yml b/test/integration/targets/docker_swarm_service/tasks/tests/update_config.yml index eeb725e1b7..c91d83da84 100644 --- a/test/integration/targets/docker_swarm_service/tasks/tests/update_config.yml +++ b/test/integration/targets/docker_swarm_service/tasks/tests/update_config.yml @@ -107,6 +107,27 @@ failure_action: "continue" register: update_failure_action_3 +- name: update_config.failure_action (rollback) + docker_swarm_service: + name: "{{ service_name }}" + image: alpine:3.8 + resolve_image: no + command: '/bin/sh -v -c "sleep 10m"' + update_config: + failure_action: "rollback" + register: update_failure_action_4 + ignore_errors: yes + +- name: update_config.failure_action (rollback idempotency) + docker_swarm_service: + name: "{{ service_name }}" + image: alpine:3.8 + resolve_image: no + command: '/bin/sh -v -c "sleep 10m"' + update_failure_action: "rollback" + register: update_failure_action_5 + ignore_errors: yes + - name: cleanup docker_swarm_service: name: "{{ service_name }}" @@ -120,6 +141,18 @@ - update_failure_action_2b is not changed - update_failure_action_3 is changed +- assert: + that: + - update_failure_action_4 is changed + - update_failure_action_5 is not changed + when: docker_api_version is version('1.28', '>=') and docker_py_version is version('3.5.0', '>=') + +- assert: + that: + - update_failure_action_4 is failed + - "'Minimum version required' in update_failure_action_4.msg" + when: docker_api_version is version('1.28', '<') or docker_py_version is version('3.5.0', '<') + ################################################################### ## update_config.max_failure_ratio ################################ ################################################################### diff --git a/test/integration/targets/docker_swarm_service/vars/main.yml b/test/integration/targets/docker_swarm_service/vars/main.yml index d190864892..45bef6c4ff 100644 --- a/test/integration/targets/docker_swarm_service/vars/main.yml +++ b/test/integration/targets/docker_swarm_service/vars/main.yml @@ -41,6 +41,7 @@ service_expected_output: restart_policy_attempts: null restart_policy_delay: null restart_policy_window: null + rollback_config: null tty: null update_delay: null update_failure_action: null