#!/usr/bin/python3

# This file is part of Cockpit.
#
# Copyright (C) 2013 Red Hat, Inc.
#
# Cockpit is free software; you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation; either version 2.1 of the License, or
# (at your option) any later version.
#
# Cockpit is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with Cockpit; If not, see <http://www.gnu.org/licenses/>.

import parent
import testvm
from testlib import *


class TestServices(MachineCase):

    def setUp(self):
        MachineCase.setUp(self)

        # Make sure the system finishes "booting" so that
        # when we add additional services to this target below
        # we don't race with the boot process
        self.machine.execute("systemctl start default.target")

    def pick_tab(self, n):
        if self.machine.image in ["rhel-8-1-distropkg"]: # Changed in #12349
            self.browser.click('#services-filter :nth-child({0})'.format(n))
        else:
            self.browser.click('#services-filter li:nth-child({0}) a'.format(n))

    def wait_onoff(self, val):
        self.browser.wait_present(".service-top-panel .onoff-ct input" + (":checked" if val else ":not(:checked)"))

    def toggle_onoff(self):
        self.browser.click(".service-top-panel .onoff-ct input")

    def do_action(self, action):
        self.browser.click(".service-top-panel .dropdown-kebab-pf button")
        self.browser.click(".service-top-panel .dropdown-menu a:contains('{0}')".format(action))

    def check_service_details(self, statuses, actions, enabled, onoff=True, kebab=True):
        self.browser.wait_collected_text("#statuses .status", "".join(statuses))
        if onoff:
            self.wait_onoff(enabled)
        else:
            self.browser.wait_not_present(".service-top-panel .onoff-ct")
        if kebab:
            self.browser.click(".service-top-panel .dropdown-kebab-pf button")
            self.browser.wait_text(".service-top-panel .dropdown-menu", "".join(actions))
            # Click away to close the dropdown-menu
            self.browser.click(".service-top-panel .dropdown-kebab-pf button")
        else:
            self.browser.wait_not_present(".service-top-panel .dropdown-kebab-pf")

    def wait_service_present(self, service):
        if self.machine.image in ["rhel-8-1-distropkg"]: # Changed in #12381
            self.browser.wait_text('tr[data-goto-unit="{0}"] td:first-child'.format(service), service)
        else:
            service_name = service
            if service.endswith(".service"):
                service_name = service[:-8]
            self.browser.wait_text('tr[data-goto-unit="{0}"] td:first-child'.format(service), service_name)


    def testBasic(self):
        m = self.machine
        b = self.browser

        m.write("/etc/systemd/system/test.service",
                """
[Unit]
Description=Test Service

[Service]
ExecStart=/bin/sh /usr/local/bin/test-service
ExecReload=/bin/sh true

[Install]
WantedBy=default.target
""")
        m.write("/usr/local/bin/test-service",
                """
#! /bin/sh

trap "echo STOP" 0

echo START
while true; do
  sleep 5
  echo WORKING
done
""")
        m.write("/etc/systemd/system/test-fail.service",
                """
[Unit]
Description=Failing Test Service

[Service]
ExecStart=/usr/bin/false

[Install]
WantedBy=default.target
""")
        m.write("/etc/systemd/system/test-template@.service",
                """
[Unit]
Description=Test Template for %I

[Service]
ExecStart=/usr/local/bin/test-service %I
""")
        m.write("/etc/systemd/system/test.timer",
                """
[Unit]
Description=Test Timer

[Timer]
OnCalendar=*:1/2
""")
        m.write("/etc/systemd/system/test-onboot.timer",
                """
[Unit]
Description=Test OnBoot Timer

[Timer]
OnBootSec=10min
Unit=test.service
""")

        # After writing files out tell systemd about them
        m.execute("systemctl daemon-reload")
        self.machine.execute("systemctl start default.target")

        self.login_and_go("/system/services")

        def svc_sel(service):
            return 'tr[data-goto-unit="%s"]' % service

        def wait_service_state(service, state):
            b.wait_in_text(svc_sel(service), state)

        def wait_service_in_panel(service, title):
            wait_service_state(service, title)

        self.wait_service_present("test.service")
        wait_service_in_panel("test.service", "Disabled")

        m.execute("systemctl start test.service")
        wait_service_state("test.service", "running")
        m.execute("systemctl stop test.service")
        wait_service_in_panel("test.service", "Disabled")
        m.execute("systemctl enable test.service")
        wait_service_state("test.service", "inactive")
        wait_service_state("test.service", "inactive")

        # Selects Timer tab
        self.pick_tab(4)
        b.wait_present(svc_sel('test.timer'))
        b.wait_text(svc_sel('test.timer') + ' td:nth-child(3)', '')  # next run
        b.wait_text(svc_sel('test.timer') + ' td:nth-child(4)', '')  # last trigger
        m.execute("systemctl start test.timer")
        b.wait_in_text(svc_sel('test.timer'), "Today")  # next run
        b.wait_in_text(svc_sel('test.timer'), "unknown")  # last trigger
        m.execute("systemctl stop test.timer")

        b.wait_present(svc_sel('test-onboot.timer'))
        b.wait_text(svc_sel('test-onboot.timer') + ' td:nth-child(3)', '')  # next run
        b.wait_text(svc_sel('test-onboot.timer') + ' td:nth-child(4)', '')  # last trigger
        m.execute("systemctl start test-onboot.timer")
        b.wait_in_text(svc_sel('test-onboot.timer'), "Today")  # next run
        b.wait_in_text(svc_sel('test-onboot.timer'), "unknown")  # last trigger
        m.execute("systemctl stop test-onboot.timer")

        if m.image in ["rhel-8-1-distropkg"]: # Changed in #12265
            return

        # Selects Services tab
        self.pick_tab(1)

        # Check test.service and then start it
        b.click(svc_sel("test.service"))
        self.check_service_details(["Not running", "Automatically starts"], ["Start", "Disallow running (mask)"], True)
        self.do_action("Start")
        self.check_service_details(["Running", "Automatically starts"], ["Reload", "Restart", "Stop", "Disallow running (mask)"], True)
        b.wait_in_text('#service-log', "START")
        b.wait_in_text('#service-log', "WORKING")

        # Stop and disable and back again
        self.toggle_onoff()
        self.check_service_details(["Disabled"], ["Start", "Disallow running (mask)"], False)
        self.toggle_onoff()
        self.check_service_details(["Running", "Automatically starts"], ["Reload", "Restart", "Stop", "Disallow running (mask)"], True)

        # Check without permissions
        self.allow_authorize_journal_messages()
        b.relogin('/system/services#/test.service', authorized=False)
        self.check_service_details(["Read-only", "Running", "Automatically starts"], [], False, onoff=False, kebab=False)

        b.relogin('/system/services#/test.service', authorized=True)

        self.toggle_onoff() # later on we need some disabled test
        self.check_service_details(["Disabled"], ["Start", "Disallow running (mask)"], False)

        # Check service that fails to start
        b.go("#/")
        b.click(svc_sel("test-fail.service"))
        self.check_service_details(["Disabled"], ["Start", "Disallow running (mask)"], False)
        self.toggle_onoff()
        self.check_service_details(["Failed to start", "Automatically starts"], ["Start", "Clear 'Failed to start'", "Disallow running (mask)"], True)
        b.click(".action-button:contains('Start Service')")
        b.go("#/")
        self.wait_service_present("test-fail.service")
        wait_service_state("test-fail.service", "failed")

        # Check static service
        b.click(svc_sel("systemd-halt.service"))
        self.check_service_details(["Static", "Not running"], ["Start", "Disallow running (mask)"], True, onoff=False)

        # Mask and unmask
        self.do_action("Disallow running (mask)")
        b.click(".modal-dialog .btn-danger")
        self.check_service_details(["Masked"], ["Allow running (unmask)"], False, onoff=False)
        self.do_action("Allow running (unmask)")
        self.check_service_details(["Static", "Not running"], ["Start", "Disallow running (mask)"], True, onoff=False)


        # Instantiate a template
        b.go("#/")
        self.wait_service_present("test-template@.service")
        b.click(svc_sel("test-template@.service"))
        b.set_input_text("#service-details input", "//param-f//o//o//")
        b.click("#service-details button")
        b.wait_text(".service-name", "Test Template for param-f/o/o")
        self.check_service_details(["Static", "Not running"], ["Start", "Disallow running (mask)"], True, onoff=False)
        b.wait_in_text("#service-details", "Instance of template: test-template@.service")
        b.click("#service-details .ct-form a:contains('test-template@.service')")
        b.wait_visible("#service-details input")

        def wait_service_not_present(service):
            b.wait_not_present(svc_sel(service))

        def init_filter_state():
            b.set_input_text("#services-text-filter", "")
            self.wait_service_present("test.service")
            self.wait_service_present("test-fail.service")
            b.select_from_dropdown("#services-dropdown", "All")

        b.go("#/")

        # Filter by id
        init_filter_state()
        b.set_input_text("#services-text-filter", "fail.ser")
        wait_service_not_present("test.service")
        self.wait_service_present("test-fail.service")

        # Filter by description
        init_filter_state()
        b.set_input_text("#services-text-filter", "failing")
        wait_service_not_present("test.service")
        self.wait_service_present("test-fail.service")

        # Select only static services
        init_filter_state()
        b.select_from_dropdown("#services-dropdown", "Static")
        wait_service_not_present("test.service")
        wait_service_not_present("test-fail.service")
        self.wait_service_present("systemd-halt.service")

        # Select only disabled services
        b.select_from_dropdown("#services-dropdown", "Disabled")
        self.wait_service_present("test.service")
        wait_service_not_present("test-fail.service")
        wait_service_not_present("systemd-halt.service")

        # Check filtering and selecting together - empty state
        b.set_input_text("#services-text-filter", "failing")
        wait_service_not_present("test.service")
        b.wait_visible("#empty-search")

        # Check resetting filter
        b.click("#clear-all-filters")
        self.wait_service_present("test.service")
        self.wait_service_present("test-fail.service")
        self.wait_service_present("systemd-halt.service")
        self.assertEqual(b.val("#services-text-filter"), "")
        self.assertEqual(b.val("#services-dropdown"), "0")

        # Check that journalbox shows empty state
        b.click(svc_sel("systemd-halt.service"))
        b.wait_text('#service-log', "No log entries")

    def testApi(self):
        m = self.machine
        b = self.browser

        m.write("/etc/systemd/system/test.service",
                """
[Unit]
Description=Test Service

[Service]
ExecStart=/bin/sh -c "while true; do sleep 5; done"

[Install]
WantedBy=default.target
""")

        # After writing files out tell systemd about them
        m.execute("systemctl daemon-reload")

        self.login_and_go("/playground/service#/test")

        b.wait_text('#exists', 'true')
        b.wait_text('#state', '"stopped"')
        b.wait_text('#enabled', 'false')

        b.click('#start')
        b.wait_text('#state', '"running"')
        b.click('#stop')
        b.wait_text('#state', '"stopped"')

        b.click('#enable')
        b.wait_text('#enabled', 'true')
        b.click('#disable')
        b.wait_text('#enabled', 'false')

        b.go('#/foo')
        b.wait_text('#exists', 'false')

    def testCreateTimer(self):
        m = self.machine
        b = self.browser

        def svc_sel(service):
            return 'tr[data-goto-unit="%s"]' % service

        def wait_systemctl_timer(time):
            with testvm.Timeout(seconds=20, machine=m, error_message="Timeout while waiting for systemctl list-timers"):
                m.execute("cmd='systemctl list-timers'; until $cmd | grep -m 1 '%s'; do sleep 1; done" % time)

        # HACK: pmie and pmlogger take a looooong time to shutdown (https://bugzilla.redhat.com/show_bug.cgi?id=1703348)
        # so disable them for this test, we don't test PCP here
        m.execute("systemctl disable --now pmie pmlogger || true")

        m.execute("timedatectl set-timezone UTC")
        m.execute("timedatectl set-ntp off")
        wait(lambda: "false" in m.execute(
            "busctl get-property org.freedesktop.timedate1 /org/freedesktop/timedate1 org.freedesktop.timedate1 NTP"))
        m.execute("timedatectl set-time '2020-01-01 15:30:00'")
        self.login_and_go("/system/services")
        b.wait_not_visible("#loading-fallback")
        self.pick_tab(4)
        b.wait_visible("#create-timer")
        b.click('#create-timer')
        b.wait_popup("timer-dialog")
        b.set_val("#servicename", "testing timer")
        b.set_val("#command", "/bin/sh -c '/bin/date >> /tmp/date'")
        b.click("#boot-or-specific-time button")
        b.click("#boot-or-specific-time .dropdown-menu li[value=2] a")
        b.wait_in_text("#boot-or-specific-time button", "At specific time")
        b.set_val("#hr", "24")
        b.set_val("#min", "6s")
        b.click("#timer-save-button")
        # checks for invalid input messages
        b.wait_text("#servicename-error", "Only alphabets, numbers, : , _ , . , @ , - are allowed.")
        b.wait_text("#description-error", "This field cannot be empty.")
        b.wait_text("#min-error", "Minute needs to be a number between 0-59")
        b.wait_text("#hr-error", "Hour needs to be a number between 0-23")
        b.wait_not_visible("#command-error")
        b.click("#drop-repeat button")
        b.click("#drop-repeat .dropdown-menu li[value=10080] a")
        b.click("[data-content='add'][data-index=0]")
        b.wait_present("[data-content='minutes'][data-index=1]")
        b.wait_present("[data-content='hours'][data-index=0]")
        b.set_val("[data-index=0][data-content='hours']", "s")
        b.set_val("[data-index=1][data-content='minutes']", "w")
        b.click("#timer-save-button")
        b.wait_text("[data-content='hr-error'][data-index=0]", "Hour needs to be a number between 0-23")
        b.wait_text("[data-content='min-error'][data-index=1]", "Minute needs to be a number between 0-59")
        # creates a new yearly timer at 2020-01-01 16:00 and at 2021-01-01 01:22
        b.set_val("#servicename", "yearly_timer")
        b.set_val("#description", "Yearly timer")
        b.click("#drop-repeat button")
        b.click("#drop-repeat .dropdown-menu li[value=525600] a")
        b.click("[data-content='add'][data-index=0]")
        b.wait_present("[data-content='datepicker'][data-index=0]")
        b.set_val(".bootstrap-datepicker[data-index=0]", "2020-01-01")
        b.set_val("[data-index=0][data-content='hours']", "16")
        b.set_val("[data-index=0][data-content='minutes']", "00")
        b.wait_present("[data-content='datepicker'][data-index=1]")
        b.set_val(".bootstrap-datepicker[data-index=1]", "2021-01-01")
        b.set_val("[data-index=1][data-content='hours']", "01")
        b.set_val("[data-index=1][data-content='minutes']", "22")
        b.click("#timer-save-button")
        b.wait_popdown("timer-dialog")
        b.wait_present(svc_sel('yearly_timer.timer'))

        m.execute("timedatectl set-time '2020-01-01 15:30:00'")
        b.wait_present(svc_sel('yearly_timer.timer'))
        b.wait_in_text(svc_sel('yearly_timer.timer'), "Yearly timer")
        wait_systemctl_timer("Wed 2020-01-01 16:00")
        self.assertIn("Wed 2020-01-01 16:00", m.execute("systemctl list-timers"))
        m.execute("timedatectl set-time '2020-01-01 16:10:00'")
        # checks if yearly timer repeats yearly on 2021-01-01 01:22
        wait_systemctl_timer("Fri 2021-01-01 01:22")
        self.assertIn("Fri 2021-01-01 01:22", m.execute("systemctl list-timers"))
        # creates a new monthly timer that runs on 6th at 14:12 and 8th at 21:12 of every month
        b.wait_visible("#create-timer")
        b.click('#create-timer')
        b.wait_popup("timer-dialog")
        b.set_val("#servicename", "monthly_timer")
        b.set_val("#description", "Monthly timer")
        b.set_val("#command", "/bin/sh -c '/bin/date >> /tmp/date'")
        b.click("#boot-or-specific-time button")
        b.click("#boot-or-specific-time .dropdown-menu li[value=2] a")
        b.click("#drop-repeat button")
        b.click("#drop-repeat .dropdown-menu li[value=44640] a")
        b.wait_present("[data-content='month-days'][data-index=0]")
        b.click("[data-content='add'][data-index=0]")
        b.click("[data-index=0][data-content='month-days'] button")
        b.click("[data-index=0][data-content='month-days'] .dropdown-menu li[value=6] a")
        b.set_val("[data-index=0][data-content='hours']", "14")
        b.set_val("[data-index=0][data-content='minutes']", "12")
        b.wait_present("[data-content='month-days'][data-index=1]")
        b.click("[data-index=1][data-content='month-days'] button")
        b.click("[data-index=1][data-content='month-days'] .dropdown-menu li[value=8] a")
        b.set_val("[data-index=1][data-content='hours']", "21")
        b.set_val("[data-index=1][data-content='minutes']", "12")
        b.click("#timer-save-button")
        b.wait_popdown("timer-dialog")
        b.wait_present(svc_sel('monthly_timer.timer'))

        m.execute("timedatectl set-time '2020-01-01 16:15:00'")
        b.wait_present(svc_sel('monthly_timer.timer'))
        b.wait_in_text(svc_sel('monthly_timer.timer'), "Monthly timer")
        wait_systemctl_timer("Mon 2020-01-06 14:12")
        self.assertIn("Mon 2020-01-06 14:12", m.execute("systemctl list-timers"))
        m.execute("timedatectl set-time '2020-01-07 00:00:00'")
        wait_systemctl_timer("Wed 2020-01-08 21:12")
        self.assertIn("Wed 2020-01-08 21:12", m.execute("systemctl list-timers"))
        # checks if timer runs on next month February 2020 on same dates
        m.execute("timedatectl set-time '2020-01-08 21:23'")
        wait_systemctl_timer("Thu 2020-02-06 14:12")
        self.assertIn("Thu 2020-02-06 14:12", m.execute("systemctl list-timers"))
        # checks if timer runs on 8th March 2020 at 21:12
        m.execute("timedatectl set-time '2020-03-07 00:00:00'")
        wait_systemctl_timer("Sun 2020-03-08 21:12")
        self.assertIn("Sun 2020-03-08 21:12", m.execute("systemctl list-timers"))
        # creates a new weekly timer that runs on Fri at 12:45 and Sun at 20:12 every week
        b.wait_visible("#create-timer")
        b.click('#create-timer')
        b.wait_popup("timer-dialog")
        b.set_val("#servicename", "weekly_timer")
        b.set_val("#description", "Weekly timer")
        b.set_val("#command", "/bin/sh -c '/bin/date >> /tmp/date'")
        b.click("#boot-or-specific-time button")
        b.click("#boot-or-specific-time .dropdown-menu li[value=2] a")
        b.click("#drop-repeat button")
        b.click("#drop-repeat .dropdown-menu li[value=10080] a")
        b.wait_present("[data-content='week-days'][data-index=0]")
        b.click("[data-content='add'][data-index=0]")
        b.click("[data-index=0][data-content='week-days'] button")
        b.click("[data-index=0][data-content='week-days'] .dropdown-menu li[value=5] a")
        b.set_val("[data-index=0][data-content='hours']", "12")
        b.set_val("[data-index=0][data-content='minutes']", "45")
        b.wait_present("[data-content='week-days'][data-index=1]")
        b.click("[data-index=1][data-content='week-days'] button")
        b.click("[data-index=1][data-content='week-days'] .dropdown-menu li[value=7] a")
        b.set_val("[data-index=1][data-content='hours']", "20")
        b.set_val("[data-index=1][data-content='minutes']", "12")
        b.click("#timer-save-button")
        b.wait_popdown("timer-dialog")
        b.wait_present(svc_sel('weekly_timer.timer'))

        m.execute("timedatectl set-time '2020-03-07 00:00:00'")
        b.wait_present(svc_sel('weekly_timer.timer'))
        b.wait_in_text(svc_sel('weekly_timer.timer'), "Weekly timer")
        wait_systemctl_timer("Sun 2020-03-08 20:12")
        self.assertIn("Sun 2020-03-08 20:12", m.execute("systemctl list-timers"))
        m.execute("timedatectl set-time '2020-03-09 00:00:00'")
        wait_systemctl_timer("Fri 2020-03-13 12:45")
        self.assertIn("Fri 2020-03-13 12:45", m.execute("systemctl list-timers"))
        # checks if timer runs on next week's Friday and Sunday
        m.execute("timedatectl set-time '2020-03-14 00:00:00'")
        wait_systemctl_timer("Sun 2020-03-15 20:12")
        self.assertIn("Sun 2020-03-15 20:12", m.execute("systemctl list-timers"))
        m.execute("timedatectl set-time '2020-03-17 00:00:00'")
        wait_systemctl_timer("Fri 2020-03-20 12:45")
        self.assertIn("Fri 2020-03-20 12:45", m.execute("systemctl list-timers"))
        # creates a new daily timer that runs at 2:40 and at 21:15 every day
        b.wait_visible("#create-timer")
        b.click('#create-timer')
        b.wait_popup("timer-dialog")
        b.set_val("#servicename", "daily_timer")
        b.set_val("#description", "Daily timer")
        b.set_val("#command", "/bin/sh -c '/bin/date >> /tmp/date'")
        b.click("#boot-or-specific-time button")
        b.click("#boot-or-specific-time .dropdown-menu li[value=2] a")
        b.click("#drop-repeat button")
        b.click("#drop-repeat .dropdown-menu li[value=1440] a")
        b.click("[data-content='add'][data-index=0]")
        b.set_val("[data-index=0][data-content='hours']", "2")
        b.set_val("[data-index=0][data-content='minutes']", "40")
        b.set_val("[data-index=1][data-content='hours']", "21")
        b.set_val("[data-index=1][data-content='minutes']", "15")
        b.click("#timer-save-button")
        b.wait_popdown("timer-dialog")
        b.wait_present(svc_sel('daily_timer.timer'))

        m.execute("timedatectl set-time '2020-03-17 00:00:00'")
        b.wait_present(svc_sel('daily_timer.timer'))
        b.wait_in_text(svc_sel('daily_timer.timer'), "Daily timer")
        wait_systemctl_timer("Tue 2020-03-17 02:40")
        self.assertIn("Tue 2020-03-17 02:40", m.execute("systemctl list-timers"))
        m.execute("timedatectl set-time '2020-03-17 03:00:00'")
        wait_systemctl_timer("Tue 2020-03-17 21:15")
        self.assertIn("Tue 2020-03-17 21:15", m.execute("systemctl list-timers"))
        # checks if timer runs on friday 2020-04-10 at 02:40 and 21:15
        m.execute("timedatectl set-time '2020-04-10 00:00:00'")
        wait_systemctl_timer("Fri 2020-04-10 02:40")
        self.assertIn("Fri 2020-04-10 02:40", m.execute("systemctl list-timers"))
        m.execute("timedatectl set-time '2020-04-10 03:00:00'")
        wait_systemctl_timer("Fri 2020-04-10 21:15")
        self.assertIn("Fri 2020-04-10 21:15", m.execute("systemctl list-timers"))
        # creates a new houry timer that runs at *:05 and at *:26
        b.wait_visible("#create-timer")
        b.click('#create-timer')
        b.wait_popup("timer-dialog")
        b.set_val("#servicename", "hourly_timer")
        b.set_val("#description", "Hourly timer")
        b.set_val("#command", "/bin/sh -c '/bin/date >> /tmp/date'")
        b.click("#boot-or-specific-time button")
        b.click("#boot-or-specific-time .dropdown-menu li[value=2] a")
        b.click("#drop-repeat button")
        b.click("#drop-repeat .dropdown-menu li[value=60] a")
        b.click("[data-content='add'][data-index=0]")
        b.set_val("[data-index=0][data-content='minutes']", "05")
        b.set_val("[data-index=1][data-content='minutes']", "26")
        b.click("#timer-save-button")
        b.wait_popdown("timer-dialog")
        b.wait_present(svc_sel('hourly_timer.timer'))

        m.execute("timedatectl set-time '2020-04-10 03:00:00'")
        b.wait_present(svc_sel('hourly_timer.timer'))
        b.wait_in_text(svc_sel('hourly_timer.timer'), "Hourly timer")
        wait_systemctl_timer("Fri 2020-04-10 03:05")
        self.assertIn("Fri 2020-04-10 03:05", m.execute("systemctl list-timers"))
        m.execute("timedatectl set-time '2020-04-10 03:07:00'")
        wait_systemctl_timer("Fri 2020-04-10 03:26")
        self.assertIn("Fri 2020-04-10 03:26", m.execute("systemctl list-timers"))
        m.execute("timedatectl set-time '2020-04-10 04:00:00'")
        wait_systemctl_timer("Fri 2020-04-10 04:05")
        # checks if timer runs on next hour at 5 min and 26 min
        self.assertIn("Fri 2020-04-10 04:05", m.execute("systemctl list-timers"))
        m.execute("timedatectl set-time '2020-04-10 04:10:00'")
        wait_systemctl_timer("Fri 2020-04-10 04:26")
        self.assertIn("Fri 2020-04-10 04:26", m.execute("systemctl list-timers"))
        # creates a new timer that runs at today at 23:59
        b.wait_visible("#create-timer")
        b.click('#create-timer')
        b.wait_popup("timer-dialog")
        b.set_val("#servicename", "no_repeat_timer")
        b.set_val("#description", "No repeat timer")
        b.set_val("#command", "/bin/sh -c '/bin/date >> /tmp/date'")
        b.click("#boot-or-specific-time button")
        b.click("#boot-or-specific-time .dropdown-menu li[value=2] a")
        b.set_val("#hr", "23")
        b.set_val("#min", "59")
        b.click("#timer-save-button")
        b.wait_popdown("timer-dialog")
        b.wait_present(svc_sel('no_repeat_timer.timer'))
        b.wait_in_text(svc_sel('no_repeat_timer.timer'), "No repeat timer")

        m.execute("timedatectl set-time '2020-04-10 04:10:00'")
        b.wait_present(svc_sel('no_repeat_timer.timer'))
        wait_systemctl_timer("Fri 2020-04-10 23:59")
        self.assertIn("Fri 2020-04-10 23:59", m.execute("systemctl list-timers"))

        # creates a boot timer that runs after 10 sec from boot
        b.wait_visible("#create-timer")
        b.click('#create-timer')
        b.wait_popup("timer-dialog")
        b.set_val("#servicename", "boot_timer")
        b.set_val("#description", "Boot timer")
        b.set_val("#command", "/bin/sh -c 'echo hello >> /tmp/hello'")
        b.click("#boot-or-specific-time button")
        b.click("#boot-or-specific-time .dropdown-menu li[value=1] a")
        b.set_val("#boot-time", "2")
        b.click("#timer-save-button")
        b.wait_popdown("timer-dialog")
        b.wait_present(svc_sel('boot_timer.timer'))
        m.spawn("sync && sync && sync && sleep 0.1 && reboot", "reboot")
        m.wait_reboot()
        m.start_cockpit()
        with testvm.Timeout(seconds=15, machine=m, error_message="Timeout while waiting for boot timer to run"):
            m.execute("while [ ! -f /tmp/hello ] ; do sleep 0.5; done")
        self.assertIn("hello", m.execute("cat /tmp/hello"))

        m.execute("timedatectl set-ntp on")
        self.allow_restart_journal_messages()

    @skipImage("Reworked in #12265", "rhel-8-1-distropkg")
    def testConditions(self):
        m = self.machine
        b = self.browser
        m.write("/etc/systemd/system/condtest.service",
                """
[Unit]
Description=Test Service
ConditionDirectoryNotEmpty=/var/tmp/empty

[Service]
ExecStart=/bin/sh -c "while true; do sleep 5; done"

[Install]
WantedBy=multi-user.target
""")

        m.execute("mkdir -p /var/tmp/empty")
        m.execute("rm -rf /var/tmp/empty/*")

        # After writing files out tell systemd about them
        m.execute("systemctl daemon-reload")
        self.machine.execute("systemctl start multi-user.target")

        # This does not work for not enabled services. See:
        # https://github.com/systemd/systemd/issues/2234
        self.machine.execute("systemctl enable condtest")

        self.login_and_go("/system/services")

        def svc_sel(service):
            return 'tr[data-goto-unit="%s"]' % service

        # Selects Services tab
        self.pick_tab(1)

        b.wait_in_text(svc_sel("condtest.service"), "Enabled")
        b.click(svc_sel("condtest.service"))
        self.check_service_details(["Not running", "Automatically starts"], ["Start", "Disallow running (mask)"], True)
        self.do_action("Start")
        b.wait_text("#condition", "Condition ConditionDirectoryNotEmpty=/var/tmp/empty was not met")

        # If the condition succeeds the message disappears
        m.execute("touch /var/tmp/empty/non-empty")
        self.do_action("Start")
        self.check_service_details(["Running", "Automatically starts"], ["Restart", "Stop", "Disallow running (mask)"], True)
        b.wait_not_present("#condition")

    @skipImage("Reworked in #12265", "rhel-8-1-distropkg")
    def testRelationships(self):
        m = self.machine
        b = self.browser

        m.write("/etc/systemd/system/test-a.service",
                """
[Unit]
Description=A Service
Before=test-b.service
Conflicts=test-c.service

[Service]
ExecStart=/bin/sh -c "while true; do sleep 5; done"
""")

        m.write("/etc/systemd/system/test-b.service",
                """
[Unit]
Description=B Service
After=test-a.service

[Service]
ExecStart=/bin/sh -c "while true; do sleep 5; done"
""")

        m.write("/etc/systemd/system/test-c.service",
                """
[Unit]
Description=C Service
Conflicts=test-a.service
PartOf=test-b.service

[Service]
ExecStart=/bin/sh -c "while true; do sleep 5; done"
""")

        # After writing files out tell systemd about them
        m.execute("systemctl daemon-reload")

        def svc_sel(service):
            return 'tr[data-goto-unit="%s"]' % service

        def rel_sel(reltype, service):
            return "#{0} a:contains('{1}')".format("".join(reltype.split(" ")), service)

        # services page
        self.login_and_go("/system/services")
        self.wait_service_present("test-a.service")
        b.click(svc_sel("test-a.service"))

        # service a
        b.wait_js_cond('window.location.hash === "#/test-a.service"')
        b.wait_visible(rel_sel("Before", "test-b.service"))
        b.wait_visible(rel_sel("Conflicts", "test-c.service"))
        b.click(rel_sel("Before", "test-b.service"))

        # service b
        b.wait_js_cond('window.location.hash === "#/test-b.service"')
        b.wait_visible(rel_sel("After", "test-a.service"))
        b.click(rel_sel("After", "test-a.service"))

        # service a
        b.wait_js_cond('window.location.hash === "#/test-a.service"')
        b.wait_visible(rel_sel("Conflicts", "test-c.service"))
        b.click(rel_sel("Conflicts", "test-c.service"))

        # service c
        b.wait_js_cond('window.location.hash === "#/test-c.service"')
        b.wait_visible(rel_sel("Conflicts", "test-a.service"))
        b.wait_visible(rel_sel("Part Of", "test-b.service"))
        b.click(rel_sel("Part Of", "test-b.service"))

        # service b
        b.wait_js_cond('window.location.hash === "#/test-b.service"')
        b.wait_visible(rel_sel("After", "test-a.service"))

    def testNotFound(self):
        m = self.machine
        b = self.browser

        m.write("/etc/systemd/system/test.service",
                """
[Unit]
Description=Test Service
Requires=not-found.service

[Service]
ExecStart=/bin/true
""")
        m.execute("systemctl daemon-reload")
        self.machine.execute("systemctl start default.target")

        self.login_and_go("/system/services")

        def svc_sel(service):
            return 'tr[data-goto-unit="%s"]' % service

        b.wait_present(svc_sel("test.service"))
        b.wait_not_present(svc_sel("not-found.service"))

        b.click(svc_sel("test.service"))
        b.wait_js_cond('window.location.hash === "#/test.service"')

        if m.image not in ["rhel-8-1-distropkg"]: # Changed in #12265
            b.wait_attr("#Requires a:contains('not-found.service')", "class", "disabled")

    @skipImage("Implemented in #12585", "rhel-8-1-distropkg")
    def testResetFailed(self):
        m = self.machine
        b = self.browser

        # Put it in /run instead of in /etc so that we can mask it,
        # which needs to put a symlink into /etc.
        m.write("/run/systemd/system/test-fail.service",
                """
[Unit]
Description=Failing Test Service

[Service]
ExecStart=/usr/bin/false

[Install]
WantedBy=default.target
""")

        m.execute("systemctl daemon-reload")
        self.machine.execute("systemctl start default.target")

        self.login_and_go("/system/services")

        def svc_sel(service):
            return 'tr[data-goto-unit="%s"]' % service

        b.click(svc_sel("test-fail.service"))
        b.wait_js_cond('window.location.hash === "#/test-fail.service"')

        self.check_service_details(["Disabled"], ["Start", "Disallow running (mask)"], False)

        self.wait_onoff(False)
        self.toggle_onoff()

        self.check_service_details(["Failed to start", "Automatically starts"],
                                   ["Start", "Clear 'Failed to start'", "Disallow running (mask)"], True)

        # Disabling should reset the "Failed to start" status
        self.wait_onoff(True)
        self.toggle_onoff()

        self.check_service_details(["Disabled"], ["Start", "Disallow running (mask)"], False)

        self.wait_onoff(False)
        self.toggle_onoff()

        self.check_service_details(["Failed to start", "Automatically starts"],
                                   ["Start", "Clear 'Failed to start'", "Disallow running (mask)"], True)

        # Just reset the failed status, but leave it enabled
        self.do_action("Clear")

        self.check_service_details(["Not running", "Automatically starts"],
                                   ["Start", "Disallow running (mask)"], True)

        self.do_action("Start")
        self.check_service_details(["Failed to start", "Automatically starts"],
                                   ["Start", "Clear 'Failed to start'", "Disallow running (mask)"], True)

        # Masking should also clear the failed status
        self.do_action("Disallow running (mask)")
        b.click(".modal-dialog .btn-danger")
        self.check_service_details(["Masked"], ["Allow running (unmask)"], False, onoff=False)

    @skipImage("Implemented in #12585", "rhel-8-1-distropkg")
    def testNotifyFailed(self):
        m = self.machine
        b = self.browser

        m.write("/etc/systemd/system/test-fail.service",
                """
[Unit]
Description=Failing Test Service

[Service]
ExecStart=/usr/bin/false
""")

        m.execute("systemctl daemon-reload")
        self.machine.execute("systemctl start default.target")
        self.machine.execute("systemctl reset-failed")

        self.login_and_go("/system/services")

        def svc_sel(service):
            return 'tr[data-goto-unit="%s"]' % service

        # Start test-fail
        m.execute("systemctl start test-fail")

        # Test-fail should be at the top, and have failed
        b.wait_present('tr:nth-child(1)[data-goto-unit="test-fail.service"]')
        b.wait_present('tr[data-goto-unit="test-fail.service"] td:contains("failed")')

        # Services tab should have icon
        b.wait_present('a:contains("System Services") .fa-exclamation-triangle')

        # Nav link should have icon
        b.switch_to_top()
        b.wait_present('a[href="/system/services"] .fa-exclamation-triangle')

        # System page should have notification
        b.click('#host-apps a[href="/system"]')
        b.enter_page('/system')
        b.wait_present('#page_status_notifications:contains("1 service has failed")')

        # Clear test-fail
        m.execute("systemctl reset-failed test-fail")

        # Test-fail should not be at the top
        b.switch_to_top()
        b.click('#host-apps a[href="/system/services"]')
        b.enter_page('/system/services')
        b.wait_not_present('tr:nth-child(1)[data-goto-unit="test-fail.service"]')

        # Services tab should not have icon
        b.wait_not_present('a:contains("System Services") .fa-exclamation-triangle')

        # Nav link should not have icon
        b.switch_to_top()
        b.wait_not_present('a[href="/system/services"] .fa-exclamation-triangle')

        # System page should not have notification
        b.click('#host-apps a[href="/system"]')
        b.enter_page('/system')
        b.wait_not_present('#page_status_notifications:contains("1 service has failed")')

if __name__ == '__main__':
    test_main()
