def test_geoloc_interface(self, geoloc_mock):
        """Test geolocation-related interface and implementation of Timezone"""

        # without any actions, we should get empty GeolocationData
        new = GeolocationData.from_structure(
            self.timezone_interface.GeolocationResult)
        assert new.is_empty()

        # let's "run" geolocation
        self.timezone_interface.StartGeolocation()

        # the task should have been instantiated, signal connected, and started
        geoloc_mock.assert_called_once_with()
        mock_task = geoloc_mock.return_value
        mock_task.start.assert_called_once_with()
        mock_task.succeeded_signal.connect.assert_called_once_with(
            self.timezone_module._set_geolocation_result)

        # running the callback for finishing should also save result
        self.timezone_module._set_geolocation_result()
        mock_task.get_result.assert_called_once_with()
        assert self.timezone_module.geolocation_result == mock_task.get_result.return_value

        # try re-running, same as earlier
        geoloc_mock.reset_mock()
        self.timezone_interface.StartGeolocation()
        geoloc_mock.assert_called_once_with()
    def GeolocationResult(self) -> Structure:
        """Get geolocation result, if any.

        :return DBusData: geolocation result data
        """
        return GeolocationData.to_structure(
            self.implementation.geolocation_result)
Example #3
0
    def _apply_geolocation_results(self):
        """Apply the geolocation results if any."""
        # Without timezone module, we have neither geolocation results nor way to apply them.
        if not self._tz_module:
            return

        geoloc_result = GeolocationData.from_structure(
            self._tz_module.GeolocationResult)

        # Skip timezone and keyboard default setting for kickstart installs.
        # The user may have provided these values via kickstart and if not, we
        # need to prompt for them. But do continue if geolocation-with-kickstart
        # is enabled, which can be inferred from a non-empty result.
        if flags.flags.automatedInstall and geoloc_result.is_empty():
            return

        if geoloc_result.timezone:
            # (the geolocation module makes sure that the returned timezone is
            # either a valid timezone or empty string)
            log.info("using timezone determined by geolocation")
            self._tz_module.Timezone = geoloc_result.timezone
            # Either this is an interactive install and timezone.seen propagates
            # from the interactive default kickstart, or this is a kickstart
            # install where the user explicitly requested geolocation to be used.
            # So set timezone.seen to True, so that the user isn't forced to
            # enter the Date & Time spoke to acknowledge the timezone detected
            # by geolocation before continuing the installation.
            self._tz_module.Kickstarted = True
Example #4
0
    def __init__(self):
        super().__init__()
        self.timezone_changed = Signal()
        self._timezone = "America/New_York"

        self._geoloc_task = None
        self.geolocation_result_changed = Signal()
        self._geoloc_result = GeolocationData()

        self.is_utc_changed = Signal()
        self._is_utc = False

        self.ntp_enabled_changed = Signal()
        self._ntp_enabled = True

        self.time_sources_changed = Signal()
        self._time_sources = []
Example #5
0
    def _get_starting_locales(self):
        """Get the starting locale(s) - kickstart, geoloc, or default"""
        # boot options and kickstart have priority over geoip
        language = self._l12_module.Language
        if language and self._l12_module.LanguageKickstarted:
            return [language]

        # the lookup should be done by now, so just get the territory
        geo_result = GeolocationData.from_structure(
            self._tz_module.GeolocationResult)
        territory = geo_result.territory
        return localization.get_territory_locales(territory) or [DEFAULT_LANG]
    def _locate(self, url):
        """Geolocate the computer using the service at given URL

        :param str url: URL to query
        :return GeolocationData: data structure describing geolocation results
        """
        try:
            log.info("Geoloc: querying the API")
            reply = requests.get(url,
                                 timeout=NETWORK_CONNECTION_TIMEOUT,
                                 verify=True)
            if reply.status_code == requests.codes.ok:  # pylint: disable=no-member
                json_reply = reply.json()
                territory = json_reply.get("country_code", "")
                timezone = json_reply.get("time_zone", "")

                # check if the timezone returned by the API is valid
                if not is_valid_timezone(timezone):
                    # try to get a timezone from the territory code
                    timezone = get_preferred_timezone(territory)

                if territory or timezone:
                    return GeolocationData.from_values(
                        territory=territory,
                        timezone=timezone,
                    )
            else:
                log.error("Geoloc: API lookup failed with status code: %s",
                          reply.status_code)

        except requests.exceptions.RequestException as exc:
            log.debug("Geoloc: RequestException for API lookup:\n%s", exc)
        except ValueError as exc:
            log.debug("Geoloc: Unable to decode JSON:\n%s", exc)

        return GeolocationData()
    def run(self):
        url = conf.timezone.geolocation_provider

        log.info("Geoloc: starting lookup using provider: %s", url)
        start_time = time.time()

        if not self._wait_for_network():
            log.error("Geoloc: no network connection")
            return GeolocationData()

        result = self._locate(url)
        log.info(
            "Geoloc: lookup finished in %1.1f seconds, result is valid: %s",
            time.time() - start_time, not result.is_empty())
        return result
Example #8
0
    def test_no_result(self, conf_mock):
        """Test GeolocationTask with no viable result"""
        conf_mock.timezone.geolocation_provider = GEOLOC_URL_FEDORA_GEOIP
        retval = GeolocationData()  # empty by default

        with patch.object(GeolocationTask,
                          "_wait_for_network",
                          return_value=True) as wfn_mock:
            with patch.object(GeolocationTask, "_locate",
                              return_value=retval) as loc_mock:
                task = GeolocationTask()
                result = task.run()

        assert isinstance(result, GeolocationData)
        assert result.is_empty()
        wfn_mock.assert_called_once_with()
        loc_mock.assert_called_once()
Example #9
0
    def test_success(self, conf_mock):
        """Test success case for GeolocationTask"""
        conf_mock.timezone.geolocation_provider = GEOLOC_URL_FEDORA_GEOIP
        retval = GeolocationData.from_values(territory="territory",
                                             timezone="timezone")

        with patch.object(GeolocationTask,
                          "_wait_for_network",
                          return_value=True) as wfn_mock:
            with patch.object(GeolocationTask, "_locate",
                              return_value=retval) as loc_mock:
                task = GeolocationTask()
                result = task.run()

        assert isinstance(result, GeolocationData)
        assert result.timezone == retval.timezone
        assert result.territory == retval.territory
        wfn_mock.assert_called_once_with()
        loc_mock.assert_called_once()
Example #10
0
    def test_no_network(self, conf_mock):
        """Test GeolocationTask with no network access"""
        conf_mock.timezone.geolocation_provider = GEOLOC_URL_FEDORA_GEOIP
        retval = GeolocationData.from_values(territory="territory",
                                             timezone="timezone")

        with self.assertLogs(level="DEBUG") as logs:
            with patch.object(GeolocationTask,
                              "_wait_for_network",
                              return_value=False) as wfn_mock:
                with patch.object(GeolocationTask,
                                  "_locate",
                                  return_value=retval) as loc_mock:
                    task = GeolocationTask()
                    result = task.run()

        assert isinstance(result, GeolocationData)
        assert result.is_empty()
        wfn_mock.assert_called_once_with()
        loc_mock.assert_not_called()
        assert "no network connection" in "\n".join(logs.output)
Example #11
0
class TimezoneService(KickstartService):
    """The Timezone service."""
    def __init__(self):
        super().__init__()
        self.timezone_changed = Signal()
        self._timezone = "America/New_York"

        self._geoloc_task = None
        self.geolocation_result_changed = Signal()
        self._geoloc_result = GeolocationData()

        self.is_utc_changed = Signal()
        self._is_utc = False

        self.ntp_enabled_changed = Signal()
        self._ntp_enabled = True

        self.time_sources_changed = Signal()
        self._time_sources = []

    def publish(self):
        """Publish the module."""
        TaskContainer.set_namespace(TIMEZONE.namespace)
        DBus.publish_object(TIMEZONE.object_path, TimezoneInterface(self))
        DBus.register_service(TIMEZONE.service_name)

    @property
    def kickstart_specification(self):
        """Return the kickstart specification."""
        return TimezoneKickstartSpecification

    def process_kickstart(self, data):
        """Process the kickstart data."""
        self.set_timezone(data.timezone.timezone)
        self.set_is_utc(data.timezone.isUtc)
        self.set_ntp_enabled(not data.timezone.nontp)

        sources = []

        for hostname in data.timezone.ntpservers:
            source = TimeSourceData()
            source.type = TIME_SOURCE_SERVER
            source.hostname = hostname
            source.options = ["iburst"]
            sources.append(source)

        for source_data in data.timesource.dataList():
            if source_data.ntp_disable:
                self.set_ntp_enabled(False)
                continue

            source = TimeSourceData()
            source.options = ["iburst"]

            if source_data.ntp_server:
                source.type = TIME_SOURCE_SERVER
                source.hostname = source_data.ntp_server
            elif source_data.ntp_pool:
                source.type = TIME_SOURCE_POOL
                source.hostname = source_data.ntp_pool
            else:
                KickstartParseError(_("Invalid time source."),
                                    lineno=source_data.lineno)

            if source_data.nts:
                source.options.append("nts")

            sources.append(source)

        self.set_time_sources(sources)

    def setup_kickstart(self, data):
        """Set up the kickstart data."""
        data.timezone.timezone = self.timezone
        data.timezone.isUtc = self.is_utc
        source_data_list = data.timesource.dataList()

        if not self.ntp_enabled:
            source_data = data.TimesourceData()
            source_data.ntp_disable = True
            source_data_list.append(source_data)
            return

        for source in self.time_sources:
            source_data = data.TimesourceData()

            if source.type == TIME_SOURCE_SERVER:
                source_data.ntp_server = source.hostname
            elif source.type == TIME_SOURCE_POOL:
                source_data.ntp_pool = source.hostname
            else:
                log.warning("Skipping %s.", source)
                continue

            if "nts" in source.options:
                source_data.nts = True

            source_data_list.append(source_data)

    @property
    def timezone(self):
        """Return the timezone."""
        return self._timezone

    def set_timezone(self, timezone):
        """Set the timezone."""
        self._timezone = timezone
        self.timezone_changed.emit()
        log.debug("Timezone is set to %s.", timezone)

    @property
    def is_utc(self):
        """Is the hardware clock set to UTC?"""
        return self._is_utc

    def set_is_utc(self, is_utc):
        """Set if the hardware clock is set to UTC."""
        self._is_utc = is_utc
        self.is_utc_changed.emit()
        log.debug("UTC is set to %s.", is_utc)

    @property
    def ntp_enabled(self):
        """Enable automatic starting of NTP service."""
        return self._ntp_enabled

    def set_ntp_enabled(self, ntp_enabled):
        """Enable or disable automatic starting of NTP service."""
        self._ntp_enabled = ntp_enabled
        self.ntp_enabled_changed.emit()
        log.debug("NTP is set to %s.", ntp_enabled)

    @property
    def time_sources(self):
        """Return a list of time sources."""
        return self._time_sources

    def set_time_sources(self, servers):
        """Set time sources."""
        self._time_sources = list(servers)
        self.time_sources_changed.emit()
        log.debug("Time sources are set to: %s", servers)

    def collect_requirements(self):
        """Return installation requirements for this module.

        :return: a list of requirements
        """
        requirements = []

        # Add ntp service requirements.
        if self._ntp_enabled:
            requirements.append(
                Requirement.for_package(NTP_PACKAGE,
                                        reason="Needed to run NTP service."))

        return requirements

    def install_with_tasks(self):
        """Return the installation tasks of this module.

        :return: list of installation tasks
        """
        return [
            ConfigureHardwareClockTask(is_utc=self.is_utc),
            ConfigureTimezoneTask(sysroot=conf.target.system_root,
                                  timezone=self.timezone,
                                  is_utc=self.is_utc),
            ConfigureNTPTask(sysroot=conf.target.system_root,
                             ntp_enabled=self.ntp_enabled,
                             ntp_servers=self.time_sources)
        ]

    def _set_geolocation_result(self):
        """Set geolocation result when the task finished."""
        self._geoloc_result = self._geoloc_task.get_result()
        self.geolocation_result_changed.emit()
        self._geoloc_task = None
        log.debug("Geolocation result is set, valid=%s",
                  not self._geoloc_result.is_empty())

    def start_geolocation(self):
        """Start geolocation, if not already started."""
        if self._geoloc_task is not None:
            log.info("Geoloc: already started")
            return

        self._geoloc_task = GeolocationTask()
        self._geoloc_task.succeeded_signal.connect(
            self._set_geolocation_result)
        log.info("Geoloc: starting lookup")
        self._geoloc_task.start()

    @property
    def geolocation_result(self):
        """Get geolocation result.

        :return GeolocationData: result of the lookup, empty if not ready yet
        """
        return self._geoloc_result