682 lines
29 KiB
Python
Executable File
682 lines
29 KiB
Python
Executable File
#!/usr/bin/python3
|
|
|
|
# This file is part of Cockpit.
|
|
#
|
|
# Copyright (C) 2014 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 json
|
|
import os
|
|
|
|
import parent
|
|
from testlib import *
|
|
|
|
|
|
@skipPackage("cockpit-docker")
|
|
@skipImage("No docker packaged", "rhel-8-1", "rhel-8-1-distropkg", "rhel-8-2")
|
|
@skipImage("Not supporting cockpit-docker on CoreOS", "fedora-coreos")
|
|
@skipImage("Docker not available", "fedora-31") # https://github.com/cockpit-project/cockpit/issues/12670
|
|
class TestDocker(MachineCase):
|
|
|
|
def setUp(self):
|
|
MachineCase.setUp(self)
|
|
m = self.machine
|
|
|
|
m.execute("systemctl start docker || systemctl start docker-latest")
|
|
|
|
def confirm(self):
|
|
b = self.browser
|
|
b.wait_popup("confirmation-dialog")
|
|
b.click("#confirmation-dialog-confirm")
|
|
# popdown should be really quick
|
|
with b.wait_timeout(5):
|
|
b.wait_popdown("confirmation-dialog")
|
|
|
|
def deleteImageConfirm(self):
|
|
b = self.browser
|
|
b.wait_popup('delete-image-confirmation-dialog')
|
|
b.click('#delete-image-confirmation-dialog-confirm')
|
|
# popdown could be delayed because of running containers
|
|
with b.wait_timeout(20):
|
|
b.wait_popdown('delete-image-confirmation-dialog')
|
|
|
|
def del_container_from_details(self, container_name):
|
|
b = self.browser
|
|
b.click("#container-details-delete")
|
|
with b.wait_timeout(20):
|
|
try:
|
|
self.confirm()
|
|
b.wait_not_in_text("#containers-containers", container_name)
|
|
except Error:
|
|
# we may have to confirm again for forced deletion
|
|
self.confirm()
|
|
b.wait_not_in_text("#containers-containers", container_name)
|
|
|
|
def wait_for_message(self, message):
|
|
b = self.browser
|
|
# sometimes container output is missing from the logs
|
|
# https://github.com/docker/docker/issues/10617
|
|
b.wait_in_text("#container-terminal", message)
|
|
|
|
def testBasic(self):
|
|
b = self.browser
|
|
m = self.machine
|
|
|
|
m.execute("systemctl start docker || systemctl start docker-latest")
|
|
|
|
self.login_and_go("/docker")
|
|
b.wait_in_text("#containers-images", "busybox:latest")
|
|
|
|
message = "HelloMessage."
|
|
|
|
# show all containers to interact with stopped ones
|
|
b.wait_in_text("#containers-containers-filter", "Images and running containers")
|
|
b.set_val("#containers-containers-filter", "all")
|
|
b.wait_in_text("#containers-containers-filter", "Everything")
|
|
|
|
# Run it
|
|
b.click('#containers-images tr:contains("busybox:latest") button.fa-play')
|
|
b.wait_popup("containers_run_image_dialog")
|
|
b.set_val("#containers-run-image-name", "PROBE1")
|
|
b.set_val("#containers-run-image-command", """/bin/sh -c 'echo "%s"; sleep 5' """ % message)
|
|
b.click("#containers-run-image-run")
|
|
b.wait_popdown("containers_run_image_dialog")
|
|
b.wait_in_text("#containers-containers", "PROBE1")
|
|
b.wait_in_text("#containers-containers", "busybox:latest")
|
|
|
|
# Check output of the probe
|
|
b.click('#containers-containers tr:contains("PROBE1")')
|
|
b.wait_visible('#container-details')
|
|
# The terminal uses NO-BREAK SPACE so we check for that here
|
|
self.wait_for_message(message)
|
|
b.wait_in_text("#container-details-state", "Exited")
|
|
|
|
self.del_container_from_details("PROBE1")
|
|
|
|
# Run it without TTY and make sure it keeps running so that we
|
|
# can link to it.
|
|
b.click('#containers-images tr:contains("busybox:latest") button.fa-play')
|
|
b.wait_popup("containers_run_image_dialog")
|
|
b.set_val("#containers-run-image-name", "PROBE1")
|
|
b.set_val("#containers-run-image-command", "/bin/sh -c 'echo \"%s\"; sleep 10000'" % (message))
|
|
b.click("#containers-run-image-with-terminal")
|
|
b.click("#containers-run-image-run")
|
|
b.wait_popdown("containers_run_image_dialog")
|
|
b.wait_in_text("#containers-containers", "PROBE1")
|
|
|
|
# Check not linked
|
|
info = json.loads(m.execute('docker inspect PROBE1'))
|
|
self.assertEqual(info[0]["HostConfig"]["Links"], None)
|
|
|
|
# Create another container linked to old one
|
|
b.click('#containers-images tr:contains("busybox:latest") button.fa-play')
|
|
b.wait_popup("containers_run_image_dialog")
|
|
b.set_val("#containers-run-image-name", "PROBE2")
|
|
b.set_val("#containers-run-image-command", "/bin/echo -e '%s\n%s\n'" % (message, message))
|
|
b.click("#containers-run-image-with-terminal")
|
|
b.wait_visible("#select-linked-containers:empty")
|
|
b.click("#link-containers")
|
|
b.wait_visible("#select-linked-containers:parent")
|
|
b.click("#select-linked-containers form:last-child .link-container button")
|
|
b.click("#select-linked-containers form:last-child .link-container a[value='PROBE1']")
|
|
b.set_val("#select-linked-containers form:last-child input[name='alias']", "alias1")
|
|
b.click("#select-linked-containers form:last-child button.fa-plus")
|
|
# new line should be the second group
|
|
b.wait_present("#select-linked-containers .form-group:eq(1)")
|
|
b.click("#select-linked-containers form:last-child .link-container button")
|
|
b.click("#select-linked-containers form:last-child .link-container a[value='PROBE1']")
|
|
b.set_val("#select-linked-containers form:last-child input[name='alias']", "alias2")
|
|
b.click("#containers-run-image-run")
|
|
b.wait_popdown("containers_run_image_dialog")
|
|
b.wait_in_text("#containers-containers", "PROBE2")
|
|
|
|
# Wait for PROBE2 to Exit
|
|
b.click('#containers-containers tr:contains("PROBE2")')
|
|
b.wait_in_text("#container-details-state", "Exited")
|
|
|
|
# Check links
|
|
info = json.loads(m.execute('docker inspect PROBE2'))
|
|
self.assertEqual(set(info[0]["HostConfig"]["Links"]),
|
|
set(["/PROBE1:/PROBE2/alias1", "/PROBE1:/PROBE2/alias2"]))
|
|
|
|
# delete PROBE2 from dash
|
|
b.click("#container-details .content-filter a")
|
|
b.wait_visible("#containers-containers")
|
|
b.wait_in_text('#containers-containers', "PROBE2")
|
|
b.click('#containers-containers tbody tr:contains("PROBE2") td.listing-ct-toggle')
|
|
b.click('#containers-containers tbody tr:contains("PROBE2") + tr button.btn-delete')
|
|
with b.wait_timeout(20):
|
|
try:
|
|
b.wait_not_in_text('#containers-containers', "PROBE2")
|
|
except Error:
|
|
self.confirm()
|
|
b.wait_not_in_text('#containers-containers', "PROBE2")
|
|
|
|
# Check output of PROBE1
|
|
b.click('#containers-containers tr:contains("PROBE1")')
|
|
b.wait_visible("#container-details")
|
|
b.wait_in_text('#container-details', "busybox:latest")
|
|
self.wait_for_message(message)
|
|
b.call_js_func("ph_count_check", ".console-ct pre", 1)
|
|
b.click("#container-details-stop")
|
|
b.wait_in_text("#container-details-state", "Exited")
|
|
|
|
# Make sure after restart we only have one
|
|
b.click("#container-details-start")
|
|
self.wait_for_message("%s\n%s" % (message, message))
|
|
b.call_js_func("ph_count_check", ".console-ct pre", 1)
|
|
b.click("#container-details-stop")
|
|
b.wait_in_text("#container-details-state", "Exited")
|
|
|
|
# Delete the container from the image-details page
|
|
b.click("#container-details .content-filter a")
|
|
b.click('#containers-images tr:contains("busybox:latest")')
|
|
b.wait_visible("#image-details")
|
|
b.wait_present(".image-details-used")
|
|
b.wait_visible(".image-details-used tr td:first-child")
|
|
b.click(".image-details-used .enable-danger")
|
|
b.click(".image-details-used .btn-delete")
|
|
b.wait_not_present(".image-details-used tr td")
|
|
|
|
# Delete image itself
|
|
b.click("#image-details-delete")
|
|
self.deleteImageConfirm()
|
|
b.wait_visible("#containers")
|
|
b.wait_not_in_text('#containers-images', 'busybox:latest')
|
|
|
|
def testDeleteImages(self):
|
|
b = self.browser
|
|
m = self.machine
|
|
m.execute("systemctl start docker || systemctl start docker-latest")
|
|
self.login_and_go("/docker")
|
|
b.wait_in_text("#containers-images", "busybox:latest")
|
|
|
|
# Deleting an image without depending containers has no container list
|
|
b.click('#containers-images tr:contains("busybox:latest") td.listing-ct-toggle')
|
|
b.click('#containers-images tr:contains("busybox:latest") + tr .btn-delete')
|
|
b.wait_popup('delete-image-confirmation-dialog')
|
|
self.assertFalse(b.is_visible('#delete-image-confirmation-dialog-containers'))
|
|
b.click('#delete-image-confirmation-dialog-cancel')
|
|
b.wait_popdown('delete-image-confirmation-dialog')
|
|
b.click('#containers-images tr:contains("busybox:latest") td.listing-ct-toggle')
|
|
|
|
# Create some containers
|
|
b.click('#containers-images tr:contains("busybox:latest") button.fa-play')
|
|
b.wait_popup('containers_run_image_dialog')
|
|
b.set_val('#containers-run-image-name', 'C1')
|
|
b.click('#containers-run-image-run')
|
|
b.wait_popdown('containers_run_image_dialog')
|
|
b.wait_in_text('#containers-containers', 'C1')
|
|
b.wait_in_text('#containers-containers', 'busybox:latest')
|
|
b.click('#containers-images tr:contains("busybox:latest") button.fa-play')
|
|
b.wait_popup('containers_run_image_dialog')
|
|
b.set_val('#containers-run-image-name', 'C2')
|
|
b.click('#containers-run-image-run')
|
|
b.wait_popdown('containers_run_image_dialog')
|
|
b.wait_in_text('#containers-containers', 'C2')
|
|
b.wait_in_text('#containers-containers', 'busybox:latest')
|
|
|
|
# Delete image itself without deleting depending containers
|
|
b.click('#containers-images tr:contains("busybox:latest")')
|
|
b.click('#image-details-delete')
|
|
b.wait_popup('delete-image-confirmation-dialog')
|
|
b.wait_visible('#delete-image-confirmation-dialog-containers')
|
|
|
|
# Delete image with running containers
|
|
b.click('#image-details-delete')
|
|
b.wait_popup('delete-image-confirmation-dialog')
|
|
b.wait_in_text('#delete-image-confirmation-dialog-confirm', 'Stop and delete')
|
|
self.deleteImageConfirm()
|
|
b.wait_visible('#containers')
|
|
b.wait_not_present('#containers-images tr:contains("busybox:latest")')
|
|
|
|
def testExpose(self):
|
|
b = self.browser
|
|
m = self.machine
|
|
self.allow_journal_messages('.*denied.*name_connect.*docker.*')
|
|
|
|
m.execute("systemctl start docker || systemctl start docker-latest")
|
|
|
|
self.login_and_go("/docker")
|
|
b.wait_in_text("#containers-images", "busybox:latest")
|
|
|
|
port = 3380
|
|
message = "HelloMessage."
|
|
|
|
# Create an updated image, expose a port
|
|
m.execute("mkdir /var/tmp/container-probe")
|
|
m.upload(["verify/files/listen-on-port.sh"], "/var/tmp/container-probe")
|
|
m.execute("""echo -e '
|
|
FROM busybox
|
|
MAINTAINER cockpit
|
|
EXPOSE %(port)d
|
|
ADD listen-on-port.sh /listen-on-port.sh
|
|
CMD ["/listen-on-port.sh", "%(port)d", "%(message)s"]
|
|
' > /var/tmp/container-probe/Dockerfile""" % {'message': message, 'port': port})
|
|
image_name = "test/container-probe"
|
|
m.execute("docker build -t %s /var/tmp/container-probe" % (image_name))
|
|
m.execute("rm -rf /var/tmp/container-probe")
|
|
|
|
# Wait for it to appear
|
|
b.wait_in_text("#containers-images", image_name)
|
|
|
|
nport = port + 1
|
|
|
|
# Run it and expose additional port via the ui
|
|
b.click("#containers-images tr:contains(\"%s\") button.fa-play" % (image_name))
|
|
b.wait_popup("containers_run_image_dialog")
|
|
b.set_val("#containers-run-image-name", "PROBE2")
|
|
# Tell our probe to listen on both ports
|
|
second_message = "%s" % (message)
|
|
b.set_val("#containers-run-image-command",
|
|
("/bin/sh -c \"/listen-on-port.sh %(port)d %(message)s; " +
|
|
"/listen-on-port.sh %(second_port)d %(second_message)s\"")
|
|
% {'port': port,
|
|
'message': message,
|
|
'second_port': nport,
|
|
'second_message': second_message})
|
|
b.set_val(".containers-run-portmapping form:eq(0) input:eq(1)", port)
|
|
b.click(".containers-run-portmapping form:eq(0) .fa-plus")
|
|
b.set_val(".containers-run-portmapping form:eq(1) input:eq(0)", nport)
|
|
b.set_val(".containers-run-portmapping form:eq(1) input:eq(1)", nport)
|
|
|
|
# Add a mount point
|
|
b.click("#mount-volumes")
|
|
b.set_val("#select-mounted-volumes form:eq(0) input:eq(0)", "/blah")
|
|
b.set_val("#select-mounted-volumes form:eq(0) input:eq(1)", "/mnt")
|
|
|
|
b.click("#containers-run-image-run")
|
|
b.wait_popdown("containers_run_image_dialog")
|
|
b.wait_in_text("#containers-containers", "PROBE2")
|
|
|
|
# Check output of the probe
|
|
b.click('#containers-containers tr:contains("PROBE2")')
|
|
b.wait_in_text("#container-details-ports", ":%d -> %d/tcp" % (port, port))
|
|
b.wait_in_text("#container-details-ports", ":%d -> %d/tcp" % (nport, nport))
|
|
b.wait_in_text("#container-details-volumes", "/mnt:/blah")
|
|
self.wait_for_message(message)
|
|
|
|
# Check connection on first port, remove trailing whitespace
|
|
data = m.execute("exec 3<>/dev/tcp/localhost/%d; cat <&3" % (port)).rstrip()
|
|
self.assertEqual(data, message)
|
|
|
|
self.wait_for_message(second_message)
|
|
|
|
# Check connection on second port, remove trailing whitespace
|
|
data = m.execute("exec 3<>/dev/tcp/localhost/%d; cat <&3" % (nport)).rstrip()
|
|
self.assertEqual(data, second_message)
|
|
|
|
# Wait for exit
|
|
b.wait_in_text("#container-details-state", "Exited")
|
|
|
|
self.del_container_from_details("PROBE2")
|
|
|
|
def testVolumesAndEnvironment(self):
|
|
b = self.browser
|
|
m = self.machine
|
|
|
|
m.execute("systemctl start docker || systemctl start docker-latest")
|
|
|
|
self.login_and_go("/docker")
|
|
b.wait_in_text("#containers-images", "busybox:latest")
|
|
|
|
# Select to view all running containers
|
|
b.set_val("#containers-containers-filter", "all")
|
|
|
|
# Create an updated image, add environment variable
|
|
m.execute("mkdir /var/tmp/container-probe")
|
|
m.execute("""echo -e '
|
|
FROM busybox
|
|
ENV zero=GGG
|
|
CMD ["/bin/sh"]
|
|
' > /var/tmp/container-probe/Dockerfile""")
|
|
image_name = "test/container-probe"
|
|
m.execute("docker build -t %s /var/tmp/container-probe" % (image_name))
|
|
m.execute("rm -rf /var/tmp/container-probe")
|
|
|
|
# Wait for it to appear
|
|
b.wait_in_text("#containers-images", image_name)
|
|
|
|
# Run it and add another environment variable via the ui
|
|
b.click("#containers-images tr:contains(\"%s\") button.fa-play" % (image_name))
|
|
b.wait_popup("containers_run_image_dialog")
|
|
b.set_val("#containers-run-image-name", "PROBE4")
|
|
b.set_val("#containers-run-image-command", "/bin/sh")
|
|
|
|
# Two environment variables
|
|
b.wait_val("#select-claimed-envvars form:eq(1) input:eq(0)", "zero")
|
|
b.wait_val("#select-claimed-envvars form:eq(1) input:eq(1)", "GGG")
|
|
b.click("#select-claimed-envvars form:eq(0) .fa-plus")
|
|
b.set_val("#select-claimed-envvars form:eq(2) input:eq(0)", "SECOND")
|
|
b.set_val("#select-claimed-envvars form:eq(2) input:eq(1)", "marmalade")
|
|
|
|
# And a mount point
|
|
b.click("#mount-volumes")
|
|
b.set_val("#select-mounted-volumes form:eq(0) input:eq(0)", "/blah")
|
|
b.set_val("#select-mounted-volumes form:eq(0) input:eq(1)", "/mnt")
|
|
if m.image not in ["debian-stable", "debian-testing", "ubuntu-1804", "ubuntu-stable"]:
|
|
m.execute("chcon --dereference -HRt svirt_sandbox_file_t /mnt")
|
|
m.execute("touch /mnt/dripping.txt")
|
|
|
|
b.click("#containers-run-image-run")
|
|
b.wait_popdown("containers_run_image_dialog")
|
|
b.wait_in_text("#containers-containers", "PROBE4")
|
|
|
|
# Check output of the probe
|
|
b.click('#containers-containers tr:contains("PROBE4")')
|
|
b.wait_visible("#container-details")
|
|
|
|
b.wait_present("#container-terminal")
|
|
b.wait_present("#container-terminal .console-ct")
|
|
b.wait_present("#container-terminal .console-ct .terminal")
|
|
|
|
b.focus('#container-terminal .console-ct .terminal')
|
|
|
|
# Wait for the container to wake up
|
|
try:
|
|
b.key_press("\r\r\r")
|
|
b.wait_in_text("#container-terminal", "#")
|
|
except Error as ex:
|
|
if not ex.msg.startswith('timeout'):
|
|
raise
|
|
|
|
b.key_press("clear\r")
|
|
b.wait_in_text("#container-terminal", "#")
|
|
|
|
b.focus('#container-terminal .console-ct .terminal')
|
|
b.key_press("env\r")
|
|
b.wait_in_text("#container-terminal", "zero=GGG")
|
|
b.wait_in_text("#container-terminal", "SECOND=marmalade")
|
|
|
|
b.focus('#container-terminal .console-ct .terminal')
|
|
b.key_press("ls /blah/\r")
|
|
b.wait_in_text("#container-terminal", "dripping.txt")
|
|
|
|
self.allow_journal_messages('.*denied.*name_connect.*docker.*')
|
|
|
|
@skipImage("No cockpit-docker on i386", "fedora-i386")
|
|
def testFailure(self):
|
|
b = self.browser
|
|
m = self.machine
|
|
|
|
# Try to access docker without admin (wheel, sudo, ...) group
|
|
# also need to disable Ubuntu's legacy "admin" sudo group as it happens
|
|
# to have the same name as our admin test user
|
|
m.execute("usermod -G '' admin; sed -i '/^%admin/d' /etc/sudoers")
|
|
|
|
self.allow_journal_messages("http:///var/run/docker.sock/.*: couldn't connect: .*")
|
|
|
|
self.login_and_go("/docker", authorized=False)
|
|
|
|
# We can not become root, so we can't access docker.
|
|
b.wait_text("#curtain h1", "Not authorized to access Docker on this system")
|
|
b.wait_visible("#curtain button[data-action='docker-connect']")
|
|
|
|
# Give "admin" access via the admin group and login again
|
|
m.execute("usermod -G %s admin" % m.get_admin_group())
|
|
m.execute("systemctl stop docker")
|
|
b.relogin("/docker", authorized=True)
|
|
self.allow_restart_journal_messages()
|
|
|
|
# Now we can become root and start docker
|
|
b.wait_text("#curtain h1", "Docker is not installed or activated on the system")
|
|
b.click("#curtain button[data-action='docker-start']")
|
|
with b.wait_timeout(120):
|
|
b.wait_visible("#containers-containers")
|
|
|
|
self.allow_authorize_journal_messages()
|
|
self.allow_journal_messages("cannot reauthorize identity.*:.*unix-user:root.*")
|
|
self.allow_journal_messages("cannot reauthorize identity.*:.*unix-user:builder.*")
|
|
|
|
def testRestart(self):
|
|
b = self.browser
|
|
m = self.machine
|
|
|
|
# This happens due to the Docker restart
|
|
self.allow_journal_messages('.*received truncated HTTP response.*')
|
|
|
|
m.execute("systemctl start docker || systemctl start docker-latest")
|
|
|
|
self.login_and_go("/docker")
|
|
b.wait_in_text("#containers-images", "busybox:latest")
|
|
|
|
# Run one container without restarting
|
|
b.wait_visible("#containers-images")
|
|
b.click('#containers-images tr:contains("busybox:latest") button.fa-play')
|
|
b.wait_popup("containers_run_image_dialog")
|
|
b.set_val("#containers-run-image-name", "NORESTART")
|
|
b.set_val("#containers-run-image-command", "/bin/sh -c 'echo \"no-restart\"; sleep 10000'")
|
|
b.click("#containers-run-image-with-terminal")
|
|
b.click("#containers-run-image-run")
|
|
b.wait_popdown("containers_run_image_dialog")
|
|
|
|
# Run another container with restarting
|
|
b.click('#containers-images tr:contains("busybox:latest") button.fa-play')
|
|
b.wait_popup("containers_run_image_dialog")
|
|
b.set_val("#containers-run-image-name", "YAYRESTART")
|
|
b.set_val("#containers-run-image-command", "/bin/sh -c 'echo \"yay-restart\"; sleep 10000'")
|
|
b.click("#containers-run-image-with-terminal")
|
|
b.click("#restart-policy-select button")
|
|
b.click("#restart-policy-select a[data-value=always]")
|
|
b.wait_in_text("#restart-policy-select button", "Always")
|
|
b.click("#containers-run-image-run")
|
|
b.wait_popdown("containers_run_image_dialog")
|
|
|
|
# Make sure they've started
|
|
b.wait_in_text("#containers-containers", "NORESTART")
|
|
b.wait_in_text("#containers-containers", "YAYRESTART")
|
|
|
|
# Restart docker, docker.socket needs special handling
|
|
b.logout()
|
|
m.execute("(systemctl kill --signal=9 docker-containerd && systemctl restart docker-containerd ) || systemctl restart docker || systemctl restart docker-latest")
|
|
self.login_and_go("/docker")
|
|
|
|
# show all containers to interact with stopped ones
|
|
with b.wait_timeout(120):
|
|
b.wait_visible("#containers-containers")
|
|
b.set_val("#containers-containers-filter", "all")
|
|
|
|
# Now check that one restarts
|
|
b.wait_in_text("#containers-images", "busybox:latest")
|
|
b.wait_present('#containers-containers tr:contains("YAYRESTART") td:contains("running")')
|
|
b.wait_present('#containers-containers tr:contains("NORESTART") td:contains("exited")')
|
|
|
|
def testResources(self):
|
|
b = self.browser
|
|
m = self.machine
|
|
|
|
m.execute("systemctl start docker || systemctl start docker-latest")
|
|
|
|
self.login_and_go("/docker")
|
|
b.wait_in_text("#containers-images", "busybox:latest")
|
|
|
|
# Run one container without restarting
|
|
b.click('#containers-images tr:contains("busybox:latest") button.fa-play')
|
|
b.wait_popup("containers_run_image_dialog")
|
|
b.set_val("#containers-run-image-name", "RESOURCE")
|
|
b.set_val("#containers-run-image-command", "/bin/sleep 1000000")
|
|
b.click("#containers-run-image-with-terminal")
|
|
b.click("#containers-run-image-memory input[type='checkbox']")
|
|
b.set_val("#containers-run-image-memory input.size-text-ct", "236")
|
|
b.click("#containers-run-image-cpu input[type='checkbox']")
|
|
b.set_val("#containers-run-image-cpu input.size-text-ct", "512")
|
|
b.click("#containers-run-image-run")
|
|
b.wait_popdown("containers_run_image_dialog")
|
|
|
|
# Make sure they've started
|
|
b.wait_in_text("#containers-containers", "RESOURCE")
|
|
|
|
# Check output of PROBE1
|
|
b.click('#containers-containers tr:contains("RESOURCE")')
|
|
b.wait_in_text("#container-details-memory-row", "/ 236 MiB")
|
|
b.wait_in_text("#container-details-cpu-row", "512 shares")
|
|
|
|
# Now adjust things
|
|
b.click("#container-details-resource-row button")
|
|
b.wait_popup("container-resources-dialog")
|
|
b.set_val(".memory-slider input.size-text-ct", "246")
|
|
b.set_val(".cpu-slider input.size-text-ct", "256")
|
|
b.click("#container-resources-dialog .btn-primary")
|
|
b.wait_popdown("container-resources-dialog")
|
|
|
|
# This should be updated
|
|
b.wait_in_text("#container-details-memory-row", "/ 246")
|
|
b.wait_in_text("#container-details-cpu-row", "256 shares")
|
|
|
|
# Remove the restrictions
|
|
b.click("#container-details-resource-row button")
|
|
b.wait_popup("container-resources-dialog")
|
|
b.click(".memory-slider input[type='checkbox']")
|
|
b.click(".cpu-slider input[type='checkbox']")
|
|
b.click("#container-resources-dialog .btn-primary")
|
|
b.wait_popdown("container-resources-dialog")
|
|
|
|
# This should be updated
|
|
b.wait_in_text("#container-details-cpu-row", "1024 shares")
|
|
|
|
@skipImage("No cockpit-docker on i386", "fedora-i386")
|
|
@skipImage("ABRT not available", "debian-stable", "debian-testing", "ubuntu-stable",
|
|
"ubuntu-1804", "rhel-8-1", "rhel-8-1-distropkg", "rhel-8-2")
|
|
def testContainerProblems(self):
|
|
b = self.browser
|
|
m = self.machine
|
|
|
|
m.execute("systemctl start docker || systemctl start docker-latest")
|
|
|
|
# start container
|
|
m.execute('docker run -dit --name crashing_container busybox /bin/sh')
|
|
|
|
# crash in container
|
|
m.execute('docker exec crashing_container sh -c "sleep 5 & sleep 1; pkill -ABRT sleep"')
|
|
self.allow_journal_messages('Process.*sleep.*dumped core.*')
|
|
self.allow_journal_messages('Stack trace.*')
|
|
self.allow_journal_messages('#0.*sleep.*')
|
|
|
|
# login and go to docker page (Docker Containers)
|
|
self.login_and_go("/docker")
|
|
|
|
sel = "#containers-containers .listing-ct-item .listing-ct-toggle"
|
|
b.click(sel)
|
|
|
|
# wait for the Problems tab then click it
|
|
sel = '#containers-containers .listing-ct-panel .listing-ct-head ul li a:contains("Problems")'
|
|
b.click(sel)
|
|
|
|
# open link to logs
|
|
sel = "#containers-containers .listing-ct-panel .listing-ct-body a.list-group-item"
|
|
b.wait_in_text(sel, "sleep")
|
|
b.click(sel + ':contains("sleep")')
|
|
|
|
# wait for the page to load
|
|
b.enter_page("/system/logs")
|
|
|
|
# check for abrtd service in text
|
|
b.wait_in_text("#journal-entry-heading", "sleep")
|
|
b.wait_in_text("#journal-entry-fields", "abrtd.service")
|
|
|
|
|
|
@skipImage("Skip on systems without atomic and ones with missing deps", "debian-stable",
|
|
"debian-testing", "ubuntu-1804", "ubuntu-stable", "fedora-coreos", "fedora-i386",
|
|
"fedora-31")
|
|
@skipImage("No docker packaged", "rhel-8-1", "rhel-8-1-distropkg", "rhel-8-2")
|
|
@skipPackage("cockpit-docker")
|
|
class TestAtomicScan(MachineCase):
|
|
|
|
def setUp(self):
|
|
MachineCase.setUp(self)
|
|
|
|
self.machine.execute("systemctl start docker")
|
|
|
|
def testBasic(self):
|
|
b = self.browser
|
|
m = self.machine
|
|
|
|
# HACK put scan results for one of the images into /var/lib/atomic
|
|
#
|
|
# This mimics what `atomic scan` would put there, without us having to install
|
|
# a known vulnerable image and initiating a scan from this test.
|
|
|
|
infos = json.loads(m.execute("docker inspect busybox"))
|
|
image_id = infos[0]["Id"]
|
|
short_id = image_id.replace("sha256:", "")
|
|
result_path = os.path.join("/var/lib/atomic/atomic_scan_openscap/2016-12-11-12-09-57-674487/", short_id)
|
|
result_filename = os.path.join(result_path, "json")
|
|
|
|
scan_summary = {
|
|
short_id: {
|
|
"Scan Type": "Standards Compliance",
|
|
"json_file": result_filename,
|
|
"Scanner": "openscap",
|
|
"Time": "2016-12-11T12:09:59",
|
|
"Vulnerable": True,
|
|
"UUID": short_id
|
|
}
|
|
}
|
|
|
|
scan_result = {
|
|
"Scanner": "openscap",
|
|
"Vulnerabilities": [
|
|
{
|
|
"Description": "\nThe RPM package management system can check file access\npermissions of installed software packages, including many that are\nimportant to system security. \nAfter locating a file with incorrect permissions, run the following command to determine which package owns it:\n",
|
|
"Title": "Verify and Correct File Permissions with RPM",
|
|
"Custom": {
|
|
"XCCDF result": "fail"
|
|
},
|
|
"Severity": "Low"
|
|
},
|
|
{
|
|
"Description": "\nSystem executables are stored in the following directories by default:\n",
|
|
"Title": "Verify that System Executables Have Restrictive Permissions",
|
|
"Custom": {
|
|
"XCCDF result": "fail"
|
|
},
|
|
"Severity": "Moderate"
|
|
}
|
|
],
|
|
"Finished Time": "2016-12-11T12:10:27",
|
|
"UUID": "/scanin/d6fe5fdc49c2886ccad1c781d38a0c9fe679c6e7ca0297d23186c385873e86fa",
|
|
"Scan Type": "Standard Compliance",
|
|
"Time": "2016-12-11T12:09:59",
|
|
"Successful": "true"
|
|
}
|
|
|
|
m.execute("mkdir -p " + result_path)
|
|
m.execute("echo '%s' > /var/lib/atomic/scan_summary.json" % json.dumps(scan_summary))
|
|
m.execute("echo '%s' > %s" % (json.dumps(scan_result), result_filename))
|
|
|
|
self.login_and_go("/docker")
|
|
|
|
row_selector = '#containers-images tr[data-row-id="%s"]' % image_id
|
|
|
|
# warning badge should exist
|
|
b.wait_present(row_selector + ' td span.pficon-warning-triangle-o')
|
|
b.wait_visible(row_selector + ' td span.pficon-warning-triangle-o')
|
|
|
|
# Security tab should exist
|
|
b.click(row_selector + ' td.listing-ct-toggle')
|
|
b.wait_visible(row_selector + ' + tr a:contains("Security")')
|
|
b.click(row_selector + ' + tr a:contains("Security")')
|
|
b.wait_in_text(row_selector + ' + tr', 'Verify and Correct File Permissions with RPM')
|
|
|
|
# Collapse the tab and click through to the image
|
|
b.click(row_selector + ' td.listing-ct-toggle')
|
|
b.click(row_selector + ' th')
|
|
b.wait_in_text("#image-details", "Security")
|
|
b.wait_in_text("#image-details", "Verify and Correct File Permissions with RPM")
|
|
|
|
|
|
if __name__ == '__main__':
|
|
test_main()
|