From de57fa71c17157e0df124db477786d2c7d217544 Mon Sep 17 00:00:00 2001 From: Strahinja Kustudic Date: Fri, 25 May 2018 19:01:03 +0200 Subject: [PATCH] Improve timezone module for none systemd Linux distributions (#38032) * The module now correctly sets the timezone in both the config file and in /etc/localtime; while hwclock is set in both the config and /etc/adjtime. * Module checks if the timezone is actually set by checking /etc/localtime. Before it only checked if it was set in the config file. * Fixed module not setting the timezone on RedHat systems if /etc/localtime was a symbolic link. * Fixed module failures in case of missing config files or incorrect data in them. * Added a lot of integrations tests to cover most of these situations. --- lib/ansible/modules/system/timezone.py | 172 ++++- .../targets/timezone/tasks/main.yml | 612 +++++++++++++++++- test/sanity/validate-modules/ignore.txt | 1 - 3 files changed, 736 insertions(+), 49 deletions(-) diff --git a/lib/ansible/modules/system/timezone.py b/lib/ansible/modules/system/timezone.py index d1fc2abb105..b7e8b4acab3 100644 --- a/lib/ansible/modules/system/timezone.py +++ b/lib/ansible/modules/system/timezone.py @@ -40,6 +40,7 @@ options: B(At least one of name and hwclock are required.) I(Only used on Linux.) aliases: [ rtc ] + choices: [ "UTC", "local" ] notes: - On SmartOS the C(sm-set-timezone) utility (part of the smtools package) is required to set the zone timezone author: @@ -75,8 +76,9 @@ import platform import random import re import string +import filecmp -from ansible.module_utils.basic import AnsibleModule, get_platform +from ansible.module_utils.basic import AnsibleModule, get_platform, get_distribution from ansible.module_utils.six import iteritems @@ -318,7 +320,12 @@ class NosystemdTimezone(Timezone): adjtime='/etc/adjtime' ) - allow_no_file = dict() + # It's fine if all tree config files don't exist + allow_no_file = dict( + name=True, + hwclock=True, + adjtime=True + ) regexps = dict( name=None, # To be set in __init__ @@ -326,47 +333,77 @@ class NosystemdTimezone(Timezone): adjtime=re.compile(r'^(UTC|LOCAL)$', re.MULTILINE) ) + dist_regexps = dict( + SuSE=re.compile(r'^TIMEZONE\s*=\s*"?([^"\s]+)"?', re.MULTILINE), + redhat=re.compile(r'^ZONE\s*=\s*"?([^"\s]+)"?', re.MULTILINE) + ) + + dist_tzline_format = dict( + SuSE='TIMEZONE="%s"\n', + redhat='ZONE="%s"\n' + ) + def __init__(self, module): super(NosystemdTimezone, self).__init__(module) # Validate given timezone if 'name' in self.value: tzfile = self._verify_timezone() - self.update_timezone = ['%s %s /etc/localtime' % (self.module.get_bin_path('cp', required=True), tzfile)] + # `--remove-destination` is needed if /etc/localtime is a symlink so + # that it overwrites it instead of following it. + self.update_timezone = ['%s --remove-destination %s /etc/localtime' % (self.module.get_bin_path('cp', required=True), tzfile)] self.update_hwclock = self.module.get_bin_path('hwclock', required=True) - self.allow_no_file['hwclock'] = True # Since this is only used for get values, file absense does not metter # Distribution-specific configurations if self.module.get_bin_path('dpkg-reconfigure') is not None: # Debian/Ubuntu - self.update_timezone = ['%s -sf %s /etc/localtime' % (self.module.get_bin_path('ln', required=True), tzfile), - '%s --frontend noninteractive tzdata' % self.module.get_bin_path('dpkg-reconfigure', required=True)] + if 'name' in self.value: + self.update_timezone = ['%s -sf %s /etc/localtime' % (self.module.get_bin_path('ln', required=True), tzfile), + '%s --frontend noninteractive tzdata' % self.module.get_bin_path('dpkg-reconfigure', required=True)] self.conf_files['name'] = '/etc/timezone' - self.allow_no_file['name'] = True self.conf_files['hwclock'] = '/etc/default/rcS' self.regexps['name'] = re.compile(r'^([^\s]+)', re.MULTILINE) self.tzline_format = '%s\n' else: # RHEL/CentOS/SUSE if self.module.get_bin_path('tzdata-update') is not None: - self.update_timezone = [self.module.get_bin_path('tzdata-update', required=True)] - self.allow_no_file['name'] = True - # else: - # self.update_timezone = 'cp ...' <- configured above - # self.allow_no_file['name'] = False <- this is default behavior + # tzdata-update cannot update the timezone if /etc/localtime is + # a symlink so we have to use cp to update the time zone which + # was set above. + if not os.path.islink('/etc/localtime'): + self.update_timezone = [self.module.get_bin_path('tzdata-update', required=True)] + # else: + # self.update_timezone = 'cp --remove-destination ...' <- configured above self.conf_files['name'] = '/etc/sysconfig/clock' self.conf_files['hwclock'] = '/etc/sysconfig/clock' - # The key for timezone might be `ZONE` or `TIMEZONE` - # (the former is used in RHEL/CentOS and the latter is used in SUSE linux). - # So check the content of /etc/sysconfig/clock and decide which key to use. - with open(self.conf_files['name'], mode='r') as f: - sysconfig_clock = f.read() - if re.search(r'^TIMEZONE\s*=', sysconfig_clock, re.MULTILINE): - # For SUSE - self.regexps['name'] = re.compile(r'^TIMEZONE\s*=\s*"?([^"\s]+)"?', re.MULTILINE) - self.tzline_format = 'TIMEZONE="%s"\n' + try: + f = open(self.conf_files['name'], 'r') + except IOError as err: + if self._allow_ioerror(err, 'name'): + # If the config file doesn't exist detect the distribution and set regexps. + distribution = get_distribution() + if distribution == 'SuSE': + # For SUSE + self.regexps['name'] = self.dist_regexps['SuSE'] + self.tzline_format = self.dist_tzline_format['SuSE'] + else: + # For RHEL/CentOS + self.regexps['name'] = self.dist_regexps['redhat'] + self.tzline_format = self.dist_tzline_format['redhat'] + else: + self.abort('could not read configuration file "%s"' % self.conf_files['name']) else: - # For RHEL/CentOS - self.regexps['name'] = re.compile(r'^ZONE\s*=\s*"?([^"\s]+)"?', re.MULTILINE) - self.tzline_format = 'ZONE="%s"\n' + # The key for timezone might be `ZONE` or `TIMEZONE` + # (the former is used in RHEL/CentOS and the latter is used in SUSE linux). + # So check the content of /etc/sysconfig/clock and decide which key to use. + sysconfig_clock = f.read() + f.close() + if re.search(r'^TIMEZONE\s*=', sysconfig_clock, re.MULTILINE): + # For SUSE + self.regexps['name'] = self.dist_regexps['SuSE'] + self.tzline_format = self.dist_tzline_format['SuSE'] + else: + # For RHEL/CentOS + self.regexps['name'] = self.dist_regexps['redhat'] + self.tzline_format = self.dist_tzline_format['redhat'] def _allow_ioerror(self, err, key): # In some cases, even if the target file does not exist, @@ -424,18 +461,18 @@ class NosystemdTimezone(Timezone): file.close() self.msg.append('Added 1 line and deleted %s line(s) on %s' % (len(matched_indices), filename)) - def get(self, key, phase): - if key == 'hwclock' and os.path.isfile('/etc/adjtime'): - # If /etc/adjtime exists, use that file. - key = 'adjtime' - + def _get_value_from_config(self, key, phase): filename = self.conf_files[key] - try: file = open(filename, mode='r') except IOError as err: if self._allow_ioerror(err, key): - return None + if key == 'hwclock': + return 'n/a' + elif key == 'adjtime': + return 'UTC' + elif key == 'name': + return 'n/a' else: self.abort('tried to configure %s using a file "%s", but could not read it' % (key, filename)) else: @@ -444,19 +481,77 @@ class NosystemdTimezone(Timezone): try: value = self.regexps[key].search(status).group(1) except AttributeError: - self.abort('tried to configure %s using a file "%s", but could not find a valid value in it' % (key, filename)) + if key == 'hwclock': + # If we cannot find UTC in the config that's fine. + return 'n/a' + elif key == 'adjtime': + # If we cannot find UTC/LOCAL in /etc/cannot that means UTC + # will be used by default. + return 'UTC' + elif key == 'name': + if phase == 'before': + # In 'before' phase UTC/LOCAL doesn't need to be set in + # the timezone config file, so we ignore this error. + return 'n/a' + else: + self.abort('tried to configure %s using a file "%s", but could not find a valid value in it' % (key, filename)) else: if key == 'hwclock': - # For key='hwclock'; convert yes/no -> UTC/local + # convert yes/no -> UTC/local if self.module.boolean(value): value = 'UTC' else: value = 'local' elif key == 'adjtime': - # For key='adjtime'; convert LOCAL -> local + # convert LOCAL -> local if value != 'UTC': value = value.lower() - return value + return value + + def get(self, key, phase): + planned = self.value[key]['planned'] + if key == 'hwclock': + value = self._get_value_from_config(key, phase) + if value == planned: + # If the value in the config file is the same as the 'planned' + # value, we need to check /etc/adjtime. + value = self._get_value_from_config('adjtime', phase) + elif key == 'name': + value = self._get_value_from_config(key, phase) + if value == planned: + # If the planned values is the same as the one in the config file + # we need to check if /etc/localtime is also set to the 'planned' zone. + if os.path.islink('/etc/localtime'): + # If /etc/localtime is a symlink and is not set to the TZ we 'planned' + # to set, we need to return the TZ which the symlink points to. + if os.path.exists('/etc/localtime'): + # We use readlink() because on some distros zone files are symlinks + # to other zone files, so it's hard to get which TZ is actually set + # if we follow the symlink. + path = os.readlink('/etc/localtime') + linktz = re.search(r'/usr/share/zoneinfo/(.*)', path, re.MULTILINE) + if linktz: + valuelink = linktz.group(1) + if valuelink != planned: + value = valuelink + else: + # Set current TZ to 'n/a' if the symlink points to a path + # which isn't a zone file. + value = 'n/a' + else: + # Set current TZ to 'n/a' if the symlink to the zone file is broken. + value = 'n/a' + else: + # If /etc/localtime is not a symlink best we can do is compare it with + # the 'planned' zone info file and return 'n/a' if they are different. + try: + if not filecmp.cmp('/etc/localtime', '/usr/share/zoneinfo/' + planned): + return 'n/a' + except: + return 'n/a' + else: + self.abort('unknown parameter "%s"' % key) + return value def set_timezone(self, value): self._edit_file(filename=self.conf_files['name'], @@ -469,8 +564,15 @@ class NosystemdTimezone(Timezone): def set_hwclock(self, value): if value == 'local': option = '--localtime' + utc = 'no' else: option = '--utc' + utc = 'yes' + if self.conf_files['hwclock'] is not None: + self._edit_file(filename=self.conf_files['hwclock'], + regexp=self.regexps['hwclock'], + value='UTC=%s\n' % utc, + key='hwclock') self.execute(self.update_hwclock, '--systohc', option, log=True) def set(self, key, value): diff --git a/test/integration/targets/timezone/tasks/main.yml b/test/integration/targets/timezone/tasks/main.yml index d0b396d72cf..7bd351f9ac1 100644 --- a/test/integration/targets/timezone/tasks/main.yml +++ b/test/integration/targets/timezone/tasks/main.yml @@ -1,21 +1,607 @@ -- name: Set timezone to Etc/UTC +# Because hwclock usually isn't available inside Docker containers in Shippable +# these tasks will detect if hwclock works and only run hwclock tests if it is +# supported. That is why it is recommended to run these tests locally with +# `--docker-privileged` on centos6, centos7 and ubuntu1404 images. Example +# command to run on centos6: +# +# ansible-test integration --docker centos6 --docker-privileged -v timezone + +#### +#### timezone tests +#### + + +## +## set path to timezone config files +## + +- name: set config file path on Debian + set_fact: + timezone_config_file: '/etc/timezone' + when: ansible_os_family == 'Debian' + +- name: set config file path on RedHat + set_fact: + timezone_config_file: '/etc/sysconfig/clock' + when: ansible_os_family == 'RedHat' + + +## +## set path to hwclock config files +## + +- name: set config file path on Debian + set_fact: + hwclock_config_file: '/etc/default/rcS' + when: ansible_os_family == 'Debian' + +- name: set config file path on RedHat + set_fact: + hwclock_config_file: '/etc/sysconfig/clock' + when: ansible_os_family == 'RedHat' + + +## +## test setting timezone, idempotency and checkmode +## + +- name: set timezone to Etc/UTC timezone: name: Etc/UTC register: original_timezone -- block: - - name: Set timezone to Australia/Brisbane - timezone: - name: Australia/Brisbane - register: timezone_set +- name: set timezone to Australia/Brisbane (checkmode) + timezone: + name: Australia/Brisbane + check_mode: yes + register: timezone_set_checkmode - - name: Ensure timezone changed +- name: ensure timezone reported as changed in checkmode + assert: + that: + - timezone_set_checkmode.changed + - timezone_set_checkmode.diff.after.name == 'Australia/Brisbane' + - timezone_set_checkmode.diff.before.name == 'Etc/UTC' + +- name: ensure checkmode didn't change the timezone + command: cmp /etc/localtime /usr/share/zoneinfo/Australia/Brisbane + register: result + failed_when: result is not failed + changed_when: no + +- name: ensure that checkmode didn't update the timezone in the config file + command: egrep '^(TIME)?ZONE="Etc/UTC"' {{ timezone_config_file }} + when: + - ansible_service_mgr != 'systemd' + - ansible_os_family == 'RedHat' + +- name: ensure that checkmode didn't update the timezone in the config file + command: egrep '^Etc/UTC' {{ timezone_config_file }} + when: + - ansible_service_mgr != 'systemd' + - ansible_os_family == 'Debian' + +- name: set timezone to Australia/Brisbane + timezone: + name: Australia/Brisbane + register: timezone_set + +- name: ensure timezone changed + assert: + that: + - timezone_set.changed + - timezone_set.diff.after.name == 'Australia/Brisbane' + - timezone_set.diff.before.name == 'Etc/UTC' + +- name: ensure that the timezone is actually set + command: cmp /etc/localtime /usr/share/zoneinfo/Australia/Brisbane + changed_when: no + +- name: ensure that the timezone is updated in the config file + command: egrep '^(TIME)?ZONE="Australia/Brisbane"' {{ timezone_config_file }} + when: + - ansible_service_mgr != 'systemd' + - ansible_os_family == 'RedHat' + +- name: ensure that the timezone is updated in the config file + command: egrep '^Australia/Brisbane' {{ timezone_config_file }} + when: + - ansible_service_mgr != 'systemd' + - ansible_os_family == 'Debian' + +- name: set timezone to Australia/Brisbane again + timezone: + name: Australia/Brisbane + register: timezone_again + +- name: ensure timezone idempotency + assert: + that: + - not timezone_again.changed + +- name: set timezone to Australia/Brisbane again in checkmode + timezone: + name: Australia/Brisbane + register: timezone_again_checkmode + +- name: set timezone idempotency (checkmode) + assert: + that: + - not timezone_again_checkmode.changed + + +## +## no systemd tests for timezone +## + +- block: + ## + ## test with empty config file + ## + + - name: empty config file + command: cp /dev/null {{ timezone_config_file }} + + - name: set timezone to Europe/Belgrade (empty config file) + timezone: + name: Europe/Belgrade + register: timezone_empty_conf + + - name: check if timezone set (empty config file) assert: that: - - timezone_set is changed - - timezone_set.diff.after.name == 'Australia/Brisbane' - - timezone_set.diff.before.name == 'Etc/UTC' - always: - - name: Restore original system timezone - {{ original_timezone.diff.before.name }} + - timezone_empty_conf.changed + - timezone_empty_conf.diff.after.name == 'Europe/Belgrade' + - timezone_empty_conf.diff.before.name == 'n/a' + + - name: check if the timezone is actually set (empty config file) + command: cmp /etc/localtime /usr/share/zoneinfo/Europe/Belgrade + changed_when: no + + + ## + ## test with deleted config file + ## + + - name: remove config file + file: + path: '{{ timezone_config_file }}' + state: absent + + - name: set timezone to Europe/Belgrade (no config file) timezone: - name: "{{ original_timezone.diff.before.name }}" + name: Europe/Belgrade + register: timezone_missing_conf + + - name: check if timezone set (no config file) + assert: + that: + - timezone_missing_conf.changed + - timezone_missing_conf.diff.after.name == 'Europe/Belgrade' + - timezone_missing_conf.diff.before.name == 'n/a' + + - name: check if the timezone is actually set (no config file) + command: cmp /etc/localtime /usr/share/zoneinfo/Europe/Belgrade + changed_when: no + + + ## + ## test with /etc/localtime as symbolic link to a zoneinfo file + ## + + - name: create symlink /etc/locatime -> /usr/share/zoneinfo/Etc/UTC + file: + src: /usr/share/zoneinfo/Etc/UTC + dest: /etc/localtime + state: link + force: yes + + - name: set timezone to Europe/Belgrade (over symlink) + timezone: + name: Europe/Belgrade + register: timezone_symllink + + - name: check if timezone set (over symlink) + assert: + that: + - timezone_symllink.changed + - timezone_symllink.diff.after.name == 'Europe/Belgrade' + - timezone_symllink.diff.before.name == 'Etc/UTC' + + - name: check if the timezone is actually set (over symlink) + command: cmp /etc/localtime /usr/share/zoneinfo/Europe/Belgrade + changed_when: no + + + ## + ## test with /etc/localtime as broken symbolic link + ## + + - name: set timezone to a broken symlink + file: + src: /tmp/foo + dest: /etc/localtime + state: link + force: yes + + - name: set timezone to Europe/Belgrade (over broken symlink) + timezone: + name: Europe/Belgrade + register: timezone_symllink_broken + + - name: check if timezone set (over broken symlink) + assert: + that: + - timezone_symllink_broken.changed + - timezone_symllink_broken.diff.after.name == 'Europe/Belgrade' + - timezone_symllink_broken.diff.before.name == 'n/a' + + - name: check if the timezone is actually set (over broken symlink) + command: cmp /etc/localtime /usr/share/zoneinfo/Europe/Belgrade + changed_when: no + + + ## + ## test with /etc/localtime set manually using copy + ## + + - name: set timezone manually by coping zone info file to /etc/localtime + copy: + src: /usr/share/zoneinfo/Etc/UTC + dest: /etc/localtime + remote_src: yes + + - name: set timezone to Europe/Belgrade (over copied file) + timezone: + name: Europe/Belgrade + register: timezone_copied + + - name: check if timezone set (over copied file) + assert: + that: + - timezone_copied.changed + - timezone_copied.diff.after.name == 'Europe/Belgrade' + - timezone_copied.diff.before.name == 'n/a' + + - name: check if the timezone is actually set (over copied file) + command: cmp /etc/localtime /usr/share/zoneinfo/Europe/Belgrade + changed_when: no + when: + - ansible_service_mgr != 'systemd' + - timezone_config_file is defined + + +#### +#### hwclock tests +#### + +- name: check if hwclock is supported in the environment + command: hwclock --test + register: hwclock_test + ignore_errors: yes + +- name: check if timedatectl works in the environment + command: timedatectl + register: timedatectl_test + ignore_errors: yes + +- name: + set_fact: + hwclock_supported: '{{ hwclock_test is successful or timedatectl_test is successful }}' +## +## test set hwclock, idempotency and checkmode +## + +- block: + - name: set hwclock to local + timezone: + hwclock: local + + - name: set hwclock to UTC (checkmode) + timezone: + hwclock: UTC + check_mode: yes + register: hwclock_set_checkmode + + - name: ensure hwclock reported as changed (checkmode) + assert: + that: + - hwclock_set_checkmode.changed + - hwclock_set_checkmode.diff.after.hwclock == 'UTC' + - hwclock_set_checkmode.diff.before.hwclock == 'local' + + - block: + - name: ensure that checkmode didn't update hwclock in /etc/adjtime + command: grep ^UTC /etc/adjtime + register: result + failed_when: result is not failed + + - name: ensure that checkmode didn't update hwclock the config file + command: grep ^UTC=no {{ hwclock_config_file }} + when: ansible_service_mgr != 'systemd' + + - name: set hwclock to UTC + timezone: + hwclock: UTC + register: hwclock_set + + - name: ensure hwclock changed + assert: + that: + - hwclock_set.changed + - hwclock_set.diff.after.hwclock == 'UTC' + - hwclock_set.diff.before.hwclock == 'local' + + - block: + - name: ensure that hwclock is updated in /etc/adjtime + command: grep ^UTC /etc/adjtime + + - name: ensure that hwclock is updated in the config file + command: grep ^UTC=yes {{ hwclock_config_file }} + when: ansible_service_mgr != 'systemd' + + - name: set hwclock to RTC again + timezone: + hwclock: UTC + register: hwclock_again + + - name: set hwclock idempotency + assert: + that: + - not hwclock_again.changed + + - name: set hwclock to RTC again (checkmode) + timezone: + hwclock: UTC + check_mode: yes + register: hwclock_again_checkmode + + - name: set hwclock idempotency (checkmode) + assert: + that: + - not hwclock_again_checkmode.changed + + + ## + ## no systemd tests for hwclock + ## + + - block: + ## + ## test set hwclock with both /etc/adjtime and conf file deleted + ## + + - name: remove /etc/adjtime and conf file + file: + path: '{{ item }}' + state: absent + with_items: + - /etc/adjtime + - '{{ hwclock_config_file }}' + + - name: set hwclock to UTC with deleted /etc/adjtime and conf file + timezone: + hwclock: UTC + register: hwclock_set_utc_deleted_adjtime_and_conf + + - name: ensure hwclock changed with deleted /etc/adjtime and conf + assert: + that: + - hwclock_set_utc_deleted_adjtime_and_conf.changed + - hwclock_set_utc_deleted_adjtime_and_conf.diff.after.hwclock == 'UTC' + - hwclock_set_utc_deleted_adjtime_and_conf.diff.before.hwclock == 'n/a' + + + ## + ## test set hwclock with /etc/adjtime deleted + ## + + - name: remove /etc/adjtime + file: + path: '{{ item }}' + state: absent + with_items: + - /etc/adjtime + + - name: set hwclock to UTC with deleted /etc/adjtime + timezone: + hwclock: UTC + register: hwclock_set_utc_deleted_adjtime_utc + + - name: ensure hwclock changed with deleted /etc/adjtime + assert: + that: + - not hwclock_set_utc_deleted_adjtime_utc.changed + - hwclock_set_utc_deleted_adjtime_utc.diff.after.hwclock == 'UTC' + - hwclock_set_utc_deleted_adjtime_utc.diff.before.hwclock == 'UTC' + + - name: set hwclock to LOCAL with deleted /etc/adjtime + timezone: + hwclock: local + register: hwclock_set_local_deleted_adjtime_local + + - name: ensure hwclock changed to LOCAL with deleted /etc/adjtime + assert: + that: + - hwclock_set_local_deleted_adjtime_local.changed + - hwclock_set_local_deleted_adjtime_local.diff.after.hwclock == 'local' + - hwclock_set_local_deleted_adjtime_local.diff.before.hwclock == 'UTC' + + + ## + ## test set hwclock with conf file deleted + ## + + - name: remove conf file + file: + path: '{{ item }}' + state: absent + with_items: + - '{{ hwclock_config_file }}' + + - name: set hwclock to UTC with deleted conf + timezone: + hwclock: UTC + register: hwclock_set_utc_deleted_conf + + - name: ensure hwclock changed with deleted /etc/adjtime + assert: + that: + - hwclock_set_utc_deleted_conf.changed + - hwclock_set_utc_deleted_conf.diff.after.hwclock == 'UTC' + - hwclock_set_utc_deleted_conf.diff.before.hwclock == 'n/a' + + + ## + ## test set hwclock with /etc/adjtime missing UTC/LOCAL strings + ## + + - name: create /etc/adjtime without UTC/LOCAL + copy: + content: '0.0 0 0\n0' + dest: /etc/adjtime + + - name: set hwclock to UTC with broken /etc/adjtime + timezone: + hwclock: UTC + register: hwclock_set_utc_broken_adjtime + + - name: ensure hwclock doesn't report changed with broken /etc/adjtime + assert: + that: + - not hwclock_set_utc_broken_adjtime.changed + - hwclock_set_utc_broken_adjtime.diff.after.hwclock == 'UTC' + - hwclock_set_utc_broken_adjtime.diff.before.hwclock == 'UTC' + + - name: set hwclock to LOCAL with broken /etc/adjtime + timezone: + hwclock: local + register: hwclock_set_local_broken_adjtime + + - name: ensure hwclock changed to LOCAL with broken /etc/adjtime + assert: + that: + - hwclock_set_local_broken_adjtime.changed + - hwclock_set_local_broken_adjtime.diff.after.hwclock == 'local' + - hwclock_set_local_broken_adjtime.diff.before.hwclock == 'UTC' + when: + - ansible_service_mgr != 'systemd' + - hwclock_config_file is defined + + #### + #### timezone + hwclock tests + #### + + ## + ## test set timezone and hwclock, idempotency and checkmode + ## + + - name: set timezone to Etc/UTC and hwclock to local + timezone: + name: Etc/UTC + hwclock: local + + - name: set timezone to Europe/Belgrade and hwclock to UTC (checkmode) + timezone: + name: Europe/Belgrade + hwclock: UTC + check_mode: yes + register: tzclock_set_checkmode + + - name: ensure timezone and hwclock reported as changed in checkmode + assert: + that: + - tzclock_set_checkmode.changed + - tzclock_set_checkmode.diff.after.name == 'Europe/Belgrade' + - tzclock_set_checkmode.diff.before.name == 'Etc/UTC' + - tzclock_set_checkmode.diff.after.hwclock == 'UTC' + - tzclock_set_checkmode.diff.before.hwclock == 'local' + + - name: ensure checkmode didn't change the timezone + command: cmp /etc/localtime /usr/share/zoneinfo/Australia/Brisbane + register: result + failed_when: result is not failed + changed_when: no + + - block: + - name: ensure that checkmode didn't update the timezone in the config file + command: egrep '^(TIME)?ZONE="Etc/UTC"' {{ timezone_config_file }} + when: + - ansible_os_family == 'RedHat' + + - name: ensure that checkmode didn't update the timezone in the config file + command: egrep '^Etc/UTC' {{ timezone_config_file }} + when: + - ansible_os_family == 'Debian' + + - name: ensure that checkmode didn't update hwclock in /etc/adjtime + command: grep ^UTC /etc/adjtime + register: result + failed_when: result is not failed + + - name: ensure that checkmode didn't update hwclock the config file + command: grep ^UTC=no {{ hwclock_config_file }} + when: ansible_service_mgr != 'systemd' + + - name: set timezone to Europe/Belgrade and hwclock to UTC + timezone: + name: Europe/Belgrade + hwclock: UTC + register: tzclock_set + + - name: ensure timezone and hwclock changed + assert: + that: + - tzclock_set.changed + - tzclock_set.diff.after.name == 'Europe/Belgrade' + - tzclock_set.diff.before.name == 'Etc/UTC' + - tzclock_set.diff.after.hwclock == 'UTC' + - tzclock_set.diff.before.hwclock == 'local' + + - name: ensure that the timezone is actually set + command: cmp /etc/localtime /usr/share/zoneinfo/Europe/Belgrade + changed_when: no + + - block: + - name: ensure that the timezone is updated in the config file + command: egrep '^(TIME)?ZONE="Europe/Belgrade"' {{ timezone_config_file }} + when: + - ansible_os_family == 'RedHat' + + - name: ensure that the timezone is updated in the config file + command: egrep 'Europe/Belgrade' {{ timezone_config_file }} + when: + - ansible_os_family == 'Debian' + + - name: ensure that hwclock is updated in /etc/adjtime + command: grep ^UTC /etc/adjtime + + - name: ensure that hwclock is updated in the config file + command: grep ^UTC=yes {{ hwclock_config_file }} + when: ansible_service_mgr != 'systemd' + + - name: set timezone to Europe/Belgrade and hwclock to UTC again + timezone: + name: Europe/Belgrade + hwclock: UTC + register: tzclock_set_again + + - name: set timezone and hwclock idempotency + assert: + that: + - not tzclock_set_again.changed + + - name: set timezone to Europe/Belgrade and hwclock to UTC again (checkmode) + timezone: + name: Europe/Belgrade + hwclock: UTC + register: tzclock_set_again_checkmode + + - name: set timezone and hwclock idempotency in checkmode + assert: + that: + - not tzclock_set_again_checkmode.changed + + when: + - ansible_system == 'Linux' + - hwclock_supported diff --git a/test/sanity/validate-modules/ignore.txt b/test/sanity/validate-modules/ignore.txt index 1f686c3b657..4718a245187 100644 --- a/test/sanity/validate-modules/ignore.txt +++ b/test/sanity/validate-modules/ignore.txt @@ -1228,7 +1228,6 @@ lib/ansible/modules/system/service.py E323 lib/ansible/modules/system/solaris_zone.py E324 lib/ansible/modules/system/svc.py E322 lib/ansible/modules/system/svc.py E324 -lib/ansible/modules/system/timezone.py E326 lib/ansible/modules/system/ufw.py E322 lib/ansible/modules/system/ufw.py E326 lib/ansible/modules/system/user.py E324