#!/usr/bin/python2
# -*- coding: utf-8 -*-

# 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
from testlib import *
from common import testvm

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 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

[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
""")
        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
""")

        # 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_present(svc_sel(service));
            b.wait_in_text(svc_sel(service), state)

        def wait_service_in_panel(service, title):
            b.wait_in_text(".panel:contains('%s') .panel-heading" % service, title)

        b.wait_in_text("#services", "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")

        unit_action_btn = '#service-valid .list-group-item:nth-child(1) .btn-group'
        file_action_btn = '#service-valid .list-group-item:nth-child(2) .btn-group'
        # Selects Timer tab
        b.click('#services-filter :nth-child(4)')
        b.wait_present(svc_sel('test.timer'))
        m.execute("systemctl start test.timer")
        b.wait_in_text(svc_sel('test.timer'), "Today")
        b.wait_in_text(svc_sel('test.timer'), "unknown")
        m.execute("systemctl stop test.timer")
        # Selects Services tab
        b.click('#services-filter :nth-child(2)')
        b.wait_present(svc_sel('test.service'))
        b.click(svc_sel("test.service"))
        b.wait_visible('#service-valid')
        b.wait_in_text('#service-valid', "inactive")
        b.wait_action_btn(unit_action_btn, "Start")
        b.click_action_btn(unit_action_btn)
        b.wait_in_text('#service-valid', "active")
        b.wait_action_btn(unit_action_btn, "Stop")
        b.wait_in_text('#service-log', "START")
        b.wait_in_text('#service-log', "WORKING")

        b.wait_in_text('#service-valid', "enabled")
        b.wait_action_btn(file_action_btn, "Disable")
        b.click_action_btn(file_action_btn)
        b.wait_in_text('#service-valid', "disabled")
        b.wait_action_btn(file_action_btn, "Enable")

        b.go("#/")
        b.wait_in_text("#services", "test-fail.service")
        b.wait_visible(svc_sel("test-fail.service"))
        b.click(svc_sel("test-fail.service"))
        b.wait_visible('#service-valid')
        b.wait_in_text('#service-valid', "inactive")
        b.wait_action_btn(unit_action_btn, "Start")
        b.click_action_btn(unit_action_btn)
        b.wait_in_text('#service-valid', "failed")

        b.go("#/")
        b.wait_in_text("#services", "test-fail.service")
        wait_service_state("test-fail.service", "failed")

        # Instantiate a template
        b.wait_in_text("#services", "test-template@.service")
        b.wait_visible(svc_sel("test-template@.service"))
        b.click(svc_sel("test-template@.service"))
        b.wait_visible('#service-template')
        b.set_val("#service-template input", "//param-f//o//o//")
        b.click("#service-template button")
        b.wait_visible("#service-valid")
        b.wait_text("#service-unit .panel-heading", "Test Template for param-f/o/o")
        b.wait_text("#service-valid a[data-goto-unit]", "test-template@.service")
        b.click("#service-valid a[data-goto-unit]")
        b.wait_visible("#service-template")

        self.allow_journal_messages("Failed to get realtime timestamp: Cannot assign requested address")

    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)

        m.execute("timedatectl set-timezone UTC")
        m.execute("timedatectl set-ntp off; timedatectl set-time '2020-01-01 15:30:00'")
        self.login_and_go("/system/services")
        b.click('#services-filter :nth-child(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("#command-error", "")
        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.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_present("[data-content='hr-error'][data-index=0]")
        b.wait_text("[data-content='hr-error'][data-index=0]", "Hour needs to be a number between 0-23")
        b.wait_present("[data-content='min-error'][data-index=1]")
        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'))

        # cockpit-system 138 on RHEL 7.4 doesn't enable/start timers automatically
        def rhel74_execute(cmd):
            if m.image == 'rhel-7-4':
                m.execute(cmd)

        m.execute("timedatectl set-time '2020-01-01 15:30:00'")
        rhel74_execute("systemctl start yearly_timer.timer")
        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'")
        rhel74_execute("systemctl start monthly_timer.timer")
        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'")
        rhel74_execute("systemctl start weekly_timer.timer")
        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'")
        rhel74_execute("systemctl start daily_timer.timer")
        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'")
        rhel74_execute("systemctl start hourly_timer.timer")
        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'")
        rhel74_execute("systemctl start no_repeat_timer.timer")
        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'))
        rhel74_execute("systemctl enable 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("Not available with Cockpit 138", "rhel-7-4")
    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

        unit_action_btn = '#service-valid .list-group-item:nth-child(1) .btn-group'

        # Selects Services tab
        b.click('#services-filter :nth-child(2)')
        b.wait_present(svc_sel('condtest.service'))
        b.click(svc_sel("condtest.service"))
        b.wait_visible('#service-valid')
        b.wait_in_text('#service-valid', "inactive")
        b.wait_action_btn(unit_action_btn, "Start")
        b.click_action_btn(unit_action_btn)

        b.wait_present("#service-unit div.cond-fail")
        b.wait_in_text("#service-unit div.cond-fail", "ConditionDirectoryNotEmpty")

        # If the condition succeeds the message disappears
        m.execute("touch /var/tmp/empty/non-empty")

        b.wait_visible('#service-valid')
        b.wait_in_text('#service-valid', "inactive")
        b.wait_action_btn(unit_action_btn, "Start")
        b.click_action_btn(unit_action_btn)
        b.wait_in_text('#service-valid', "active")

        b.wait_not_present("#service-unit div.cond-fail")


if __name__ == '__main__':
    test_main()
