Blob Blame History Raw
#!/usr/bin/python3

# checkpoint:
# 1. create supported image

import os
import unittest
import composerlib
import testlib


@testlib.nondestructive
@testlib.timeout(2400)
@testlib.no_retry_when_changed
class TestImage(composerlib.ComposerCase):

    def testImageStep(self):
        b = self.browser

        self.login_and_go("/composer", superuser=True)
        b.wait_visible("#main")

        # create image wizard (no upload support)
        b.click("li[data-blueprint=httpd-server] #create-image-button")
        b.wait_text("#create-image-upload-wizard #blueprint-name", "httpd-server")
        # check ? (Process length help) button
        b.click("button[aria-label='Process length help']")
        b.wait_text(".pf-c-popover__body", "This process can take a while. "
                    "Images are built in the order they are started.")
        b.click(".pf-c-popover__content button")
        b.wait_not_present(".pf-c-popover__body")
        # check non upload image action (Create only)
        b.wait_js_cond('ph_select("#image-type option").length > 1')
        b.set_val("#image-type", "qcow2")
        b.wait_val("#image-type", "qcow2")
        # check ? (image size help) button
        b.click("button[aria-label='Image size help']")
        b.wait_text(".pf-c-popover__body",
                    "Set the size that you want the image to be when instantiated. The total "
                    "package size and target destination of your image should be considered when "
                    "setting the image size.")
        b.click(".pf-c-popover__content button")
        b.wait_not_present(".pf-c-popover__body")
        # groups action done
        b.wait_text("#continue-button", "Create")
        # check ? (image size help) button
        b.click("button[aria-label='Image size help']")
        b.wait_text(".pf-c-popover__body",
                    "Set the size that you want the image to be when instantiated. The total "
                    "package size and target destination of your image should be considered when "
                    "setting the image size.")
        b.click(".pf-c-popover__content button")
        b.wait_not_present(".pf-c-popover__body")
        # default size = 2GB for qcow2 image
        b.wait_val("#image-size-input", 2)
        b.focus("#image-size-input")
        # delete 2 and input 1
        b.key_press("\b")
        b.key_press("1")
        # error if less than 2 GB
        b.wait_attr("#create-image-upload-wizard button:contains('Create')", "disabled", "")
        b.wait_attr_contains("#image-size-input-helper", "class", "pf-m-error")
        # delete 1 and input 2001
        b.key_press("\b")
        b.key_press("2001")
        # error if greater than 2000 GB
        b.wait_in_text("#image-size-input-helper",
                       "The size specified is large. We recommend that you check whether your "
                       "target destination has any restrictions on image size.")
        # delete 2001 and input 2
        b.key_press("\b\b\b\b")
        b.key_press("2")
        # close wizard by clicking X button
        b.click(".pf-c-wizard__close")
        b.wait_not_present("#create-image-upload-wizard")

        # Test cancel button
        b.click("li[data-blueprint=httpd-server] #create-image-button")
        b.wait_text("#create-image-upload-wizard #blueprint-name", "httpd-server")
        b.click("button:contains('Cancel')")
        b.wait_not_present("#create-image-upload-wizard")

        # collect code coverage result
        self.check_coverage()

    def testAWSStep(self):
        b = self.browser

        self.login_and_go("/composer", superuser=True)
        b.wait_visible("#main")

        # create image wizard
        b.click("li[data-blueprint=httpd-server] #create-image-button")
        b.wait_js_cond('ph_select("#image-type option").length > 1')
        b.set_val("#image-type", "ami")
        b.wait_val("#image-type", "ami")
        # groups action done
        # still keep Create if upload image not selected
        b.wait_text("#continue-button", "Create")
        # default size = 6GB for ami image
        b.wait_val("#image-size-input", 6)
        # check ? (AWS upload image help) button
        b.click("button[aria-label='Upload image help']")
        b.wait_in_text(".pf-c-popover__body",
                       " Image Builder can upload images you create to an S3 bucket in AWS and "
                       "then import them into EC2. When the image build is complete and the upload"
                       " action is successful, the image file is available in the AMI section of "
                       "EC2. Most of the values required to upload the image can be found in the "
                       "AWS Management Console.  This upload process requires that you have an "
                       "Identity and Access Management (IAM) role named vmimport to ensure that "
                       "the image can be imported from the S3 bucket into EC2. For more details, "
                       "refer to the AWS Required Service Role. ")
        b.click(".pf-c-popover__content button")
        b.wait_not_present(".pf-c-popover__body")
        # continue button changes to Next when upload image selected
        b.click("#aws-checkbox")
        b.click("button:contains('Next')")
        # Back button is click-able and back to last page
        b.click("button:contains('Back')")
        b.wait_text("#create-image-upload-wizard #blueprint-name", "httpd-server")
        # go to each setion by clicking link directly
        b.click("button:contains('Authentication')")
        b.click("button:contains('Destination')")
        b.click("button:contains('Review')")
        b.wait_in_text(".pf-c-alert__title",
                       "There are one or more fields that require your attention.")
        b.wait_attr("#continue-button", "disabled", "")
        # check and enter authentication fields
        b.click("button:contains('Authentication')")
        b.wait_visible("label:contains('Access key ID')")
        # check access key id help button
        b.click("button[aria-label='Access key ID help']")
        b.wait_text(".pf-c-popover__body",
                    "You can create and find existing Access key IDs on the "
                    "Identity and Access Management (IAM) page in the AWS console.")
        b.click(".pf-c-popover__content button")
        b.wait_not_present(".pf-c-popover__body")
        # enter access key id value
        b.focus("input[id='access-key-id-input']")
        b.key_press("never")
        b.wait_visible("label:contains('Secret access key')")
        # check secret access key help button
        b.click("button[aria-label='Secret access key help']")
        b.wait_text(".pf-c-popover__body",
                    "You can view the Secret access key only when you create a new Access key ID "
                    "on the Identity and Access Management (IAM) page in the AWS console.")
        b.click(".pf-c-popover__content button")
        b.wait_not_present(".pf-c-popover__body")
        # enter secret access key value
        b.focus("input[id='secret-access-key-input']")
        b.key_press("gunna")
        # check and enter destination fields
        b.click("button:contains('Destination')")
        b.wait_visible("label:contains('Image name')")
        # check image name help button
        b.click("button[aria-label='Image name help']")
        b.wait_text(".pf-c-popover__body",
                    "Provide a file name to be used for the image file that will be uploaded.")
        b.click(".pf-c-popover__content button")
        b.wait_not_present(".pf-c-popover__body")
        # enter access key id value
        b.focus("input[id='image-name-input']")
        b.key_press("give")
        b.wait_visible("label:contains('Amazon S3 bucket')")
        # check amazon s3 bucket help button
        b.click("button[aria-label='S3 Bucket help']")
        b.wait_text(".pf-c-popover__body",
                    " Provide the S3 bucket name to which the image file will be uploaded before "
                    "being imported into EC2.  The bucket must already exist in the Region where "
                    "you want to import your image. You can find a list of buckets on the S3 "
                    "buckets page in the Amazon S3 storage service in the AWS console. ")
        b.click(".pf-c-popover__content button")
        b.wait_not_present(".pf-c-popover__body")
        # enter secret access key value
        b.focus("input[id='bucket-input']")
        b.key_press("you")
        b.wait_visible("label:contains('AWS region')")
        # check image name help button
        b.click("button[aria-label='AWS region help']")
        b.wait_text(".pf-c-popover__body",
                    "Provide the AWS Region where you want to import your image. "
                    "This must be the same region where the S3 bucket exists.")
        b.click(".pf-c-popover__content button")
        b.wait_not_present(".pf-c-popover__body")
        # enter access key id value
        b.focus("input[id='region-input']")
        b.key_press("up")
        # Verify AWS Review page
        b.click("button:contains('Review')")
        # check ? (AWS upload image help) button
        b.click("button[aria-label='AWS help']")
        b.wait_in_text(".pf-c-popover__body",
                       " Image Builder can upload images you create to an S3 bucket in AWS and "
                       "then import them into EC2. When the image build is complete and the "
                       "upload action is successful, the image file is available in the AMI "
                       "section of EC2. Most of the values required to upload the image can "
                       "be found in the AWS Management Console.  This upload process requires "
                       "that you have an Identity and Access Management (IAM) role named vmimport "
                       "to ensure that the image can be imported from the S3 bucket into EC2. For "
                       "more details, refer to the AWS Required Service Role. ")
        b.click(".pf-c-popover__content button")
        b.wait_not_present(".pf-c-popover__body")
        # Check that the expected fields and values are present
        b.wait_in_text("#aws-content", "Access key ID")
        b.wait_in_text("#aws-content", "*****")
        b.wait_in_text("#aws-content", "Secret access key")
        b.wait_in_text("#aws-content", "*****")
        b.wait_in_text("#aws-content", "Image name")
        b.wait_in_text("#aws-content", "give")
        b.wait_in_text("#aws-content", "Amazon S3 bucket")
        b.wait_in_text("#aws-content", "you")
        b.wait_in_text("#aws-content", "AWS region")
        b.wait_in_text("#aws-content", "up")
        # continue button changes to Finish when upload has valid fields
        b.wait_visible("button:contains('Finish'):enabled")
        # Close wizard
        b.click("button:contains('Cancel')")
        b.wait_not_present("#create-image-upload-wizard")

        # collect code coverage result
        self.check_coverage()

    def testAzureStep(self):
        b = self.browser

        self.login_and_go("/composer", superuser=True)
        b.wait_visible("#main")

        # create image wizard
        b.click("li[data-blueprint=httpd-server] #create-image-button")
        b.wait_js_cond('ph_select("#image-type option").length > 1')
        b.set_val("#image-type", "vhd")
        b.wait_val("#image-type", "vhd")
        # groups action done
        # still keep Create if upload image not selected
        b.wait_text("#continue-button", "Create")
        # default size = 6GB for azure vhd images
        b.wait_val("#image-size-input", 2)
        # check ? (Azure upload image help) button
        b.click("button[aria-label='Upload image help']")
        print(b.text(".pf-c-popover__body"))
        b.wait_in_text(".pf-c-popover__body",
                       " Image Builder can upload images you create to a Blob container in "
                       "Microsoft Azure. When the image build is complete  and the upload action "
                       "is successful, the image file is available in the Storage account and Blob "
                       "container that you specified. ")
        b.click(".pf-c-popover__content button")
        b.wait_not_present(".pf-c-popover__body")
        # continue button changes to Next when upload image selected
        b.click("#azure-checkbox")
        b.click("button:contains('Next')")
        # check and enter authentication fields
        b.wait_visible("label:contains('Storage account')")
        # check storage account help button
        b.click("button[aria-label='Storage account help']")
        b.wait_text(".pf-c-popover__body",
                    "Provide the name of a storage account. You can find storage accounts on the "
                    "Storage accounts page in the Azure portal.")
        b.click(".pf-c-popover__content button")
        b.wait_not_present(".pf-c-popover__body")
        # enter storage account value
        b.focus("input[id='storage-account-input']")
        b.key_press("never")
        b.wait_visible("label:contains('Storage access key')")
        # check storage access key help button
        b.click("button[aria-label='Storage access key help']")
        b.wait_text(".pf-c-popover__body",
                    " Provide the access key for the desired storage account. You can find the "
                    "access key on the Access keys  page of the storage account. You can find "
                    "storage accounts on the Storage accounts page in the Azure portal. ")
        b.click(".pf-c-popover__content button")
        b.wait_not_present(".pf-c-popover__body")
        # enter storage access key value
        b.focus("input[id='storage-access-key-input']")
        b.key_press("gunna")
        # check and enter destination fields
        b.click("button:contains('Destination')")
        b.wait_visible("label:contains('Image name')")
        # check image name help button
        b.click("button[aria-label='Image name help']")
        b.wait_text(".pf-c-popover__body",
                    "Provide a file name to be used for the image file that will be uploaded.")
        b.click(".pf-c-popover__content button")
        b.wait_not_present(".pf-c-popover__body")
        # enter image name id value
        b.focus("input[id='image-name-input']")
        b.key_press("give")
        b.wait_visible("label:contains('Storage container')")
        # check amazon Storage container help button
        b.click("button[aria-label='Storage container help']")
        b.text(".pf-c-popover__body")
        b.wait_text(".pf-c-popover__body",
                    " Provide the Blob container to which the image file will be uploaded. You can "
                    "find containers under the Blob service  section of a storage account. You can "
                    "find storage accounts on the Storage accounts page in the Azure portal. ")
        b.click(".pf-c-popover__content button")
        b.wait_not_present(".pf-c-popover__body")
        # enter storage container value
        b.focus("input[id='storage-container-input']")
        b.key_press("you-up")
        # Verify Azure Review page
        b.click("button:contains('Review')")
        # check ? (Azure upload image help) button
        b.click("button[aria-label='Azure help']")
        b.wait_in_text(".pf-c-popover__body",
                       " Image Builder can upload images you create to a Blob container in "
                       "Microsoft Azure. When the image build is complete  and the upload action "
                       "is successful, the image file is available in the Storage account and Blob "
                       "container that you specified. ")
        b.click(".pf-c-popover__content button")
        b.wait_not_present(".pf-c-popover__body")
        # Check that the expected fields and values are present
        b.wait_in_text("#azure-content", "Storage account")
        b.wait_in_text("#azure-content", "*****")
        b.wait_in_text("#azure-content", "Storage access key")
        b.wait_in_text("#azure-content", "*****")
        b.wait_in_text("#azure-content", "Image name")
        b.wait_in_text("#azure-content", "give")
        b.wait_in_text("#azure-content", "Storage container")
        b.wait_in_text("#azure-content", "you-up")
        # continue button changes to Finish when upload has valid fields
        b.wait_visible("button:contains('Finish'):enabled")
        # Close wizard
        b.click("button:contains('Cancel')")
        b.wait_not_present("#create-image-upload-wizard")

        # collect code coverage result
        self.check_coverage()

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

        self.login_and_go("/composer", superuser=True)
        b.wait_visible("#main")

        # create image wizard
        b.click("li[data-blueprint=httpd-server] #create-image-button")
        b.wait_text("#create-image-upload-wizard #blueprint-name", "httpd-server")
        b.wait_js_cond('ph_select("#image-type option").length > 1')
        b.set_val("#image-type", "openstack")
        b.wait_val("#image-type", "openstack")
        # group actions end
        b.focus("#image-size-input")
        # delete 2 and input 4
        b.key_press("\b")
        b.key_press("4")
        b.click("#continue-button")
        b.wait_not_present("#create-image-upload-wizard")

        # toast notification
        b.wait_visible("#cmpsr-toast-imageWaiting .pficon-info")
        b.click("#cmpsr-toast-imageWaiting .pficon-close")
        b.wait_not_present("#cmpsr-toast-imageWaiting .pficon-info")

        # got to images tab
        b.click("#httpd-server-name")
        # correct image name and type
        with b.wait_timeout(300):
            b.click("#blueprint-tabs-tab-images")
            b.wait_visible("ul[data-list=images]")
        # get uuid as part of css selector
        uuid = m.execute("""
            composer-cli compose list | grep httpd-server | awk '{print $1}' | head -1
            """).rstrip()
        selector = "{}-compose-name".format(uuid)

        # NOTE work around a bug in osbuild-composer-11, which got the
        # capitalization wrong. This can be changed to `openstack` once we
        # depend on a newer version of osbuild-composer.
        image_type = b.attr("li[aria-labelledby={}] [data-image-type]".format(selector),
                            "data-image-type")
        self.assertEqual(image_type.lower(), "openstack")

        # image building needs more time
        with b.wait_timeout(1800):
            b.wait_text("li[aria-labelledby={}] [data-status=true]".format(selector),
                        "Image build complete")
        # image size should be 4GB
        b.wait_in_text("li[aria-labelledby={}] ".format(selector), "4 GB")
        # get image size from backend
        image_size = m.execute(
            "composer-cli compose info {} | head -1 | awk '{{print $6}}'".format(uuid)
        ).rstrip()
        self.assertEqual(int(image_size), 4 * 1024 * 1024 * 1024)
        # log should contains rpm, hostname, users stages and tar assembler
        b.click("button:contains('Logs')")
        b.wait_in_text("#{}-logs".format(uuid), "Stage org.osbuild.rpm")
        b.wait_in_text("#{}-logs".format(uuid), "Stage: org.osbuild.hostname")
        b.wait_in_text("#{}-logs".format(uuid), "Stage: org.osbuild.users")
        b.wait_in_text("#{}-logs".format(uuid), "Assembler org.osbuild.qemu")
        # close logs
        b.click("button:contains('Logs')")

        # download image
        b.click("#{}-actions".format(uuid))
        b.click("a:contains('Download')")

        # delete image cancel first always
        b.click("#{}-actions".format(uuid))
        b.wait_attr("#{}-actions".format(uuid), "aria-expanded", "true")
        b.click("li[aria-labelledby={}] a:contains('Delete')".format(selector))
        b.wait_attr("#{}-actions".format(uuid), "aria-expanded", "false")
        b.click("#cmpsr-modal-delete button:contains('Cancel')")
        b.wait_not_present("#cmpsr-modal-delete")
        # delete here
        b.click("#{}-actions".format(uuid))
        b.wait_attr("#{}-actions".format(uuid), "aria-expanded", "true")
        b.click("li[aria-labelledby={}] a:contains('Delete')".format(selector))
        b.wait_attr("#{}-actions".format(uuid), "aria-expanded", "false")
        b.click("#cmpsr-modal-delete button:contains('Delete image')")
        b.wait_not_present("#{}".format(selector))

        # collect code coverage result
        self.check_coverage()

    @unittest.skipIf(os.environ.get("TEST_OS") == "fedora-31", "Does not support ostree image")
    def testOSTree(self):
        b = self.browser
        m = self.machine

        distro = os.environ.get("TEST_OS")
        if (distro == "fedora-32" or distro == "fedora-33"):
            image_type_ostree = "fedora-iot-commit"
        elif (distro == "rhel-8-3" or distro == "rhel-8-4"):
            image_type_ostree = "rhel-edge-commit"

        self.login_and_go("/composer", superuser=True)
        b.wait_visible("#main")

        # create image wizard
        b.click("li[data-blueprint=httpd-server] #create-image-button")
        b.wait_text("#create-image-upload-wizard #blueprint-name", "httpd-server")
        b.wait_js_cond('ph_select("#image-type option").length > 1')
        b.set_val("#image-type", image_type_ostree)
        b.wait_val("#image-type", image_type_ostree)
        b.click("#continue-button")
        b.wait_not_present("#create-image-upload-wizard")
        # toast notification
        b.wait_visible("#cmpsr-toast-imageWaiting .pficon-info")
        b.click("#cmpsr-toast-imageWaiting .pficon-close")
        b.wait_not_present("#cmpsr-toast-imageWaiting .pficon-info")

        # got to images tab
        b.click("#httpd-server-name")
        # correct image name and type
        with b.wait_timeout(300):
            b.click("#blueprint-tabs-tab-images")
            b.wait_visible("ul[data-list=images]")
        # get uuid as part of css selector
        uuid = m.execute("""
            composer-cli compose list | grep httpd-server | awk '{print $1}' | head -1
            """).rstrip()
        selector = "{}-compose-name".format(uuid)

        image_type = b.attr("li[aria-labelledby={}] [data-image-type]".format(selector),
                            "data-image-type")
        self.assertEqual(image_type, image_type_ostree)

        # image building needs more time
        with b.wait_timeout(1800):
            b.wait_text("li[aria-labelledby={}] [data-status=true]".format(selector),
                        "Image build complete")
        # log should contains rpm, hostname, users stages and tar assembler
        b.click("button:contains('Logs')")
        b.wait_in_text("#{}-logs".format(uuid), "Stage org.osbuild.rpm")
        b.wait_in_text("#{}-logs".format(uuid), "Stage: org.osbuild.rpm-ostree")
        b.wait_in_text("#{}-logs".format(uuid), "Assembler org.osbuild.ostree.commit")
        # close logs
        b.click("button:contains('Logs')")

        # download image
        b.click("#{}-actions".format(uuid))
        b.click("a:contains('Download')")

        # delete image cancel first always
        b.click("#{}-actions".format(uuid))
        b.wait_attr("#{}-actions".format(uuid), "aria-expanded", "true")
        b.click("li[aria-labelledby={}] a:contains('Delete')".format(selector))
        b.wait_attr("#{}-actions".format(uuid), "aria-expanded", "false")
        b.click("#cmpsr-modal-delete button:contains('Cancel')")
        b.wait_not_present("#cmpsr-modal-delete")
        # delete here
        b.click("#{}-actions".format(uuid))
        b.wait_attr("#{}-actions".format(uuid), "aria-expanded", "true")
        b.click("li[aria-labelledby={}] a:contains('Delete')".format(selector))
        b.wait_attr("#{}-actions".format(uuid), "aria-expanded", "false")
        b.click("#cmpsr-modal-delete button:contains('Delete image')")
        b.wait_not_present("#{}".format(selector))
        self.allow_journal_messages(".*avc:  denied.*",
                                    ".*audit: .*seresult=denied .*")
        # collect code coverage result
        self.check_coverage()

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

        self.login_and_go("/composer", superuser=True)
        b.wait_visible("#main")

        # create image wizard
        b.click("li[data-blueprint=httpd-server] #create-image-button")
        b.wait_text("#create-image-upload-wizard #blueprint-name", "httpd-server")
        b.wait_js_cond('ph_select("#image-type option").length > 1')
        b.set_val("#image-type", "openstack")
        b.wait_val("#image-type", "openstack")
        # group actions end
        b.click("#continue-button")
        b.wait_not_present("#create-image-upload-wizard")

        # toast notification
        b.wait_visible("#cmpsr-toast-imageWaiting .pficon-info")
        b.click("#cmpsr-toast-imageWaiting .pficon-close")
        b.wait_not_present("#cmpsr-toast-imageWaiting .pficon-info")

        # got to images tab
        b.click("#httpd-server-name")
        # correct image name and type
        with b.wait_timeout(300):
            b.click("#blueprint-tabs-tab-images")
            b.wait_visible("ul[data-list=images]")
        # get uuid as part of css selector
        uuid = m.execute("""
            composer-cli compose list | grep httpd-server | awk '{print $1}' | head -1
            """).rstrip()
        selector = "{}-compose-name".format(uuid)
        # stop image build
        b.click("#{}-actions".format(uuid))
        b.wait_attr("#{}-actions".format(uuid), "aria-expanded", "true")
        b.click("li[aria-labelledby={}] a:contains('Stop')".format(selector))
        b.wait_attr("#{}-actions".format(uuid), "aria-expanded", "false")
        b.click("#cmpsr-modal-delete button:contains('Stop build')")
        b.wait_not_present("#cmpsr-modal-delete")
        # delete canceled image build
        b.click("#{}-actions".format(uuid))
        b.wait_attr("#{}-actions".format(uuid), "aria-expanded", "true")
        b.click("li[aria-labelledby={}] a:contains('Remove')".format(selector))
        b.wait_not_present("#{}".format(selector))

        # collect code coverage result
        self.check_coverage()


if __name__ == '__main__':
    testlib.test_main()