From 19adffb0c88fda8d1ba4360dc23bd5c4d5eddf8a Mon Sep 17 00:00:00 2001 From: Martin Muzikar Date: Mon, 18 Mar 2024 09:23:00 +0100 Subject: [PATCH] e2e: Add tests for hawtio-operator --- .../features/config/TestConfiguration.java | 16 +- .../tests/features/hooks/DeployAppHook.java | 5 +- .../tests/features/hooks/ScreenshotHook.java | 16 + .../features/openshift/HawtioOnlineUtils.java | 64 ++- .../features/pageobjects/fragments/Tree.java | 15 + .../fragments/about/AboutModalWindow.java | 32 ++ .../camel/tabs/common/CamelOperations.java | 32 +- .../fragments/online/DiscoverTab.java | 1 + .../pageobjects/pages/HawtioPage.java | 13 + .../tests/features/setup/LoginLogout.java | 9 +- .../setup/deployment/OpenshiftDeployment.java | 1 + .../panel/about/AboutModalWindowStepDefs.java | 10 +- .../tests/features/utils}/Attachments.java | 10 +- .../tests/openshift/HawtioOperatorTest.java | 449 +++++++++++++++++ .../openshift/utils/SelenideTestWatcher.java | 2 +- .../utils/rp/RPTestExecutionListener.java | 2 + .../panel/about/about_modal_window.feature | 2 +- .../io/hawt/tests/openshift/acl.yaml | 477 ++++++++++++++++++ 18 files changed, 1104 insertions(+), 52 deletions(-) rename tests/hawtio-test-suite/src/{test/java/io/hawt/tests/utils/rp => main/java/io/hawt/tests/features/utils}/Attachments.java (88%) create mode 100644 tests/hawtio-test-suite/src/test/java/io/hawt/tests/openshift/HawtioOperatorTest.java create mode 100644 tests/hawtio-test-suite/src/test/resources/io/hawt/tests/openshift/acl.yaml diff --git a/tests/hawtio-test-suite/src/main/java/io/hawt/tests/features/config/TestConfiguration.java b/tests/hawtio-test-suite/src/main/java/io/hawt/tests/features/config/TestConfiguration.java index b0a4c7a86b..475509b107 100644 --- a/tests/hawtio-test-suite/src/main/java/io/hawt/tests/features/config/TestConfiguration.java +++ b/tests/hawtio-test-suite/src/main/java/io/hawt/tests/features/config/TestConfiguration.java @@ -81,7 +81,7 @@ public static AppDeployment getAppDeploymentMethod() { throw new RuntimeException("Containerized testsuite can't run maven application from inside the container, specify URL or a Docker image"); } - if (getBoolean(USE_OPENSHIFT)) { + if (useOpenshift()) { deployment = new OpenshiftDeployment(); return deployment; } @@ -130,6 +130,10 @@ public static String getHawtioOnlineSHA() { return getProperty("io.hawt.test.online.sha"); } + public static String getHawtioOnlineImageRepository() { + return getProperty("io.hawt.test.online.image.repository"); + } + public static String getConnectAppUsername() { return getProperty(CONNECT_APP_USERNAME, TestConfiguration::getAppUsername); } @@ -147,15 +151,15 @@ public static String getAppPassword() { } public static boolean useOpenshift() { - return getBoolean(USE_OPENSHIFT); + return getBoolean(USE_OPENSHIFT, false); } public static String getIndexImage() { - return getRequiredProperty(OPENSHIFT_INDEX_IMAGE); + return getProperty(OPENSHIFT_INDEX_IMAGE); } - private static Boolean getBoolean(String name) { - return getOptionalProperty(name).map(Boolean::parseBoolean).orElse(false); + private static Boolean getBoolean(String name, boolean defaultValue) { + return getOptionalProperty(name).map(Boolean::parseBoolean).orElse(defaultValue); } public static String getOpenshiftUrl() { @@ -184,7 +188,7 @@ public static Path openshiftKubeconfig() { } public static boolean openshiftNamespaceDelete() { - return getBoolean(OPENSHIFT_NAMESPACE_DELETE); + return getBoolean(OPENSHIFT_NAMESPACE_DELETE, true); } diff --git a/tests/hawtio-test-suite/src/main/java/io/hawt/tests/features/hooks/DeployAppHook.java b/tests/hawtio-test-suite/src/main/java/io/hawt/tests/features/hooks/DeployAppHook.java index 5cb0f3cf82..d335c7579a 100644 --- a/tests/hawtio-test-suite/src/main/java/io/hawt/tests/features/hooks/DeployAppHook.java +++ b/tests/hawtio-test-suite/src/main/java/io/hawt/tests/features/hooks/DeployAppHook.java @@ -32,7 +32,10 @@ public static void appSetup() { app = TestConfiguration.getAppDeploymentMethod(); app.start(); - Runtime.getRuntime().addShutdownHook(new Thread(app::stop)); + Runtime.getRuntime().addShutdownHook(new Thread(() -> { + LOG.info("Cleaning up"); + app.stop(); + })); } catch (Throwable e) { startupFailure = e; LOG.error("Failed to start the test app", e); diff --git a/tests/hawtio-test-suite/src/main/java/io/hawt/tests/features/hooks/ScreenshotHook.java b/tests/hawtio-test-suite/src/main/java/io/hawt/tests/features/hooks/ScreenshotHook.java index 4cafa3bac3..06ed2a3393 100644 --- a/tests/hawtio-test-suite/src/main/java/io/hawt/tests/features/hooks/ScreenshotHook.java +++ b/tests/hawtio-test-suite/src/main/java/io/hawt/tests/features/hooks/ScreenshotHook.java @@ -6,8 +6,15 @@ import com.codeborne.selenide.Selenide; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLDecoder; +import java.nio.charset.StandardCharsets; +import java.nio.file.Path; + import io.cucumber.java.AfterStep; import io.cucumber.java.Scenario; +import io.hawt.tests.features.utils.Attachments; public class ScreenshotHook { @@ -18,9 +25,18 @@ public void afterStep(Scenario scenario) { if (scenario.isFailed()) { try { scenario.attach(Selenide.screenshot(OutputType.BYTES), "image/png", "screenshot"); + addScreenshotToReport(scenario.getName()); } catch (Exception e) { LOG.error("Failed to take a screenshoot", e); } } } + + public static void addScreenshotToReport(String name) { + try { + Attachments.addAttachment(Path.of(new URL(URLDecoder.decode(Selenide.screenshot(name), StandardCharsets.UTF_8)).getPath())); + } catch (MalformedURLException e) { + throw new RuntimeException(e); + } + } } diff --git a/tests/hawtio-test-suite/src/main/java/io/hawt/tests/features/openshift/HawtioOnlineUtils.java b/tests/hawtio-test-suite/src/main/java/io/hawt/tests/features/openshift/HawtioOnlineUtils.java index 5192871e52..dd7f65f313 100644 --- a/tests/hawtio-test-suite/src/main/java/io/hawt/tests/features/openshift/HawtioOnlineUtils.java +++ b/tests/hawtio-test-suite/src/main/java/io/hawt/tests/features/openshift/HawtioOnlineUtils.java @@ -16,6 +16,7 @@ import io.fabric8.kubernetes.client.dsl.Resource; import io.fabric8.openshift.api.model.operatorhub.lifecyclemanager.v1.PackageManifest; import io.fabric8.openshift.api.model.operatorhub.v1.OperatorGroupBuilder; +import io.fabric8.openshift.api.model.operatorhub.v1alpha1.CatalogSource; import io.fabric8.openshift.api.model.operatorhub.v1alpha1.CatalogSourceBuilder; import io.fabric8.openshift.api.model.operatorhub.v1alpha1.ClusterServiceVersion; import io.fabric8.openshift.api.model.operatorhub.v1alpha1.SubscriptionBuilder; @@ -28,8 +29,6 @@ public class HawtioOnlineUtils { private static final Logger LOG = LoggerFactory.getLogger(HawtioOnlineUtils.class); - private static final boolean OPERATOR_WORKAROUND = TestConfiguration.getHawtioOnlineSHA() != null; - public static Deployment deployApplication(String name, String runtime, String namespace, String tag) { //@formatter:off List envVars = new LinkedList<>(); @@ -89,29 +88,34 @@ public static Deployment deployApplication(String name, String runtime, String n } public static void deployOperator() { - //@formatter:off final OpenShiftOperatorHubAPIGroupDSL operatorhub = OpenshiftClient.get().operatorHub(); - operatorhub.catalogSources().createOrReplace(new CatalogSourceBuilder() - .editOrNewMetadata() - .withName("hawtio-catalog") - .endMetadata() - .editOrNewSpec() - .withImage(TestConfiguration.getIndexImage()) - .withSourceType("grpc") - .endSpec() - .build()); - - WaitUtils.waitFor(() -> operatorhub.catalogSources().withName("hawtio-catalog") - .get() - .getStatus() - .getConnectionState() - .getLastObservedState() - .equalsIgnoreCase("READY"), - "Waiting for the catalog to get ready", Duration.ofMinutes(2)); - - var catalog = operatorhub.catalogSources().withName("hawtio-catalog").get(); + CatalogSource catalog = null; + if (TestConfiguration.getIndexImage() != null) { + //@formatter:off + operatorhub.catalogSources().createOrReplace(new CatalogSourceBuilder() + .editOrNewMetadata() + .withName("hawtio-catalog") + .endMetadata() + .editOrNewSpec() + .withImage(TestConfiguration.getIndexImage()) + .withSourceType("grpc") + .endSpec() + .build()); + + WaitUtils.waitFor(() -> operatorhub.catalogSources().withName("hawtio-catalog") + .get() + .getStatus() + .getConnectionState() + .getLastObservedState() + .equalsIgnoreCase("READY"), + "Waiting for the catalog to get ready", Duration.ofMinutes(2)); + catalog = operatorhub.catalogSources().withName("hawtio-catalog").get(); + } else { + catalog = operatorhub.catalogSources().inNamespace("openshift-marketplace").withName("redhat-operators").get(); + } + CatalogSource finalCatalog = catalog; final PackageManifest packageManifest = WaitUtils.withRetry(() -> operatorhub.packageManifests() - .withLabel("catalog", "hawtio-catalog") + .withLabel("catalog", finalCatalog.getMetadata().getName()) .list() .getItems() .stream() @@ -139,7 +143,7 @@ public static void deployOperator() { .withName(subscriptonName) .endMetadata() .editOrNewSpec() - .withChannel("preview") + .withChannel(defaultChannel) .withInstallPlanApproval("Automatic") .withName(subscriptonName) .withSource(catalog.getMetadata().getName()) @@ -159,11 +163,15 @@ public static void deployOperator() { .getStatus().getPhase().equals("Complete"); }, "Waiting for the installplan to finish", Duration.ofMinutes(3)); - if (OPERATOR_WORKAROUND) { + if (TestConfiguration.getHawtioOnlineSHA() != null) { WaitUtils.withRetry(() -> { final ClusterServiceVersion csv = operatorhub.clusterServiceVersions().withName(startingCSV).get(); csv.getSpec().getInstall().getSpec().getDeployments().get(0).getSpec().getTemplate().getSpec().getContainers().get(0).getEnv() .add(new EnvVar("IMAGE_VERSION", TestConfiguration.getHawtioOnlineSHA(), null)); + if (TestConfiguration.getHawtioOnlineImageRepository() != null) { + csv.getSpec().getInstall().getSpec().getDeployments().get(0).getSpec().getTemplate().getSpec().getContainers().get(0).getEnv() + .add(new EnvVar("IMAGE_REPOSITORY", TestConfiguration.getHawtioOnlineImageRepository(), null)); + } operatorhub.clusterServiceVersions().withName(startingCSV).patch(csv); }, 5, Duration.ofSeconds(5)); @@ -175,6 +183,7 @@ public static void deployOperator() { } public static String deployHawtioCR(Hawtio hawtio) { + hawtio.getMetadata().getFinalizers().clear(); OpenshiftClient.get().resources(Hawtio.class).createOrReplace(hawtio); WaitUtils.waitFor(() -> { @@ -184,7 +193,6 @@ public static String deployHawtioCR(Hawtio hawtio) { resource.get().getStatus().getURL() != null; }, "Waiting for hawtio deployment to succeed", Duration.ofMinutes(2)); - patchHawtioResource("e2e-hawtio", h -> h.getMetadata().getFinalizers().clear()); return OpenshiftClient.get().resources(Hawtio.class).withName(hawtio.getMetadata().getName()).get().getStatus() .getURL(); } @@ -225,4 +233,8 @@ public static void patchHawtioResource(String name, Hawtio value) { OpenshiftClient.get().resources(Hawtio.class).withName(name).patch(value); }, 5, Duration.ofMillis(500)); } + + public static void deleteHawtio(Hawtio hawtio) { + OpenshiftClient.get().resource(hawtio).delete(); + } } diff --git a/tests/hawtio-test-suite/src/main/java/io/hawt/tests/features/pageobjects/fragments/Tree.java b/tests/hawtio-test-suite/src/main/java/io/hawt/tests/features/pageobjects/fragments/Tree.java index f2c73196cc..f57be556a6 100644 --- a/tests/hawtio-test-suite/src/main/java/io/hawt/tests/features/pageobjects/fragments/Tree.java +++ b/tests/hawtio-test-suite/src/main/java/io/hawt/tests/features/pageobjects/fragments/Tree.java @@ -17,9 +17,13 @@ import static com.codeborne.selenide.Selenide.$$; import static com.codeborne.selenide.Selenide.page; +import org.openqa.selenium.By; + import com.codeborne.selenide.ElementsCollection; import com.codeborne.selenide.SelenideElement; +import java.time.Duration; + /** * Represents Tree menu in Hawtio (e.g. Camel, JMX). */ @@ -35,6 +39,7 @@ public class Tree { * @return the given page object class */ public

P expandSpecificFolder(Class

pageObjectClass, String folderPartialId) { + assureLoaded(); if (!$("[id*='" + folderPartialId + "']").has(cssClass("pf-m-expanded"))) { $("[id*='" + folderPartialId + "']").$("[class$='node-toggle']").shouldBe(interactable).click(); } @@ -47,6 +52,7 @@ public

P expandSpecificFolder(Class

pageObjectClass, String folderPartial * @param itemPartialId of the item to be selected */ public void selectSpecificItem(String itemPartialId) { + assureLoaded(); $("[id*='" + itemPartialId + "']").$("[class$='node-text']").shouldBe(interactable).click(); } @@ -56,6 +62,7 @@ public void selectSpecificItem(String itemPartialId) { * @param fullId of the item to be selected. */ public void selectSpecificItemByExactId(String fullId) { + assureLoaded(); $(byId(fullId)).$("[class$='node-text']").shouldBe(interactable).click(); } @@ -88,6 +95,7 @@ public void collapseTree() { * Expand and collapse tree. */ private void toggleExpandCollapseTree() { + assureLoaded(); // there is only one button responsible for expanding and collapsing, it works as toggle button expandCollapseBtn.shouldBe(enabled).click(); } @@ -98,6 +106,7 @@ private void toggleExpandCollapseTree() { * @param state of the tree nodes */ public void allTreeNodesState(String state) { + assureLoaded(); if (state.contains("expanded")) { // when the tree is expanded, all list items should contain expanded class camelTreeNodes.should(allMatch("Each node is expanded", e -> e.getAttribute("class").contains(state))); @@ -114,6 +123,7 @@ public void allTreeNodesState(String state) { * @param value to filter the tree */ public void filterTree(String value) { + assureLoaded(); $(byId("input-search")).shouldBe(enabled).setValue(value); } @@ -123,6 +133,11 @@ public void filterTree(String value) { * @param value by which the tree is filtered */ public void treeIsFiltered(String value) { + assureLoaded(); $(byTagAndText("button", value)).should(exist).shouldBe(visible); } + + private void assureLoaded() { + $(By.className("pf-c-tree-view__list")).should(exist, Duration.ofSeconds(10)); + } } diff --git a/tests/hawtio-test-suite/src/main/java/io/hawt/tests/features/pageobjects/fragments/about/AboutModalWindow.java b/tests/hawtio-test-suite/src/main/java/io/hawt/tests/features/pageobjects/fragments/about/AboutModalWindow.java index d35383fedf..a839f8761f 100644 --- a/tests/hawtio-test-suite/src/main/java/io/hawt/tests/features/pageobjects/fragments/about/AboutModalWindow.java +++ b/tests/hawtio-test-suite/src/main/java/io/hawt/tests/features/pageobjects/fragments/about/AboutModalWindow.java @@ -4,14 +4,46 @@ import static com.codeborne.selenide.Selectors.byAttribute; import static com.codeborne.selenide.Selenide.$; +import org.openqa.selenium.By; + +import com.codeborne.selenide.SelenideElement; + +import java.util.HashMap; +import java.util.Map; + /** * Represents About modal window on Hawtio page. */ public class AboutModalWindow { + + + public static final By MODAL_DIALOG = By.className("pf-c-about-modal-box"); + /** * Click on Close button. */ public void close() { $(byAttribute("aria-label", "Close Dialog")).shouldBe(enabled).click(); } + + public String getHeaderText() { + return $("[id*='pf-about-modal-title']").text(); + } + + public Map getAppComponents() { + Map ret = new HashMap<>(); + for (SelenideElement dt : $(By.id("hawtio-about-product-info")).$$(By.tagName("dt"))) { + ret.put(dt.getText(), dt.sibling(0).text()); + } + return ret; + } + + public String getCopyright() { + return $(MODAL_DIALOG).$(By.className("pf-c-about-modal-box__strapline")).getText(); + } + + public SelenideElement getBrandImage() { + return $(MODAL_DIALOG).$(By.className("pf-c-about-modal-box__brand-image")); + } + } diff --git a/tests/hawtio-test-suite/src/main/java/io/hawt/tests/features/pageobjects/fragments/camel/tabs/common/CamelOperations.java b/tests/hawtio-test-suite/src/main/java/io/hawt/tests/features/pageobjects/fragments/camel/tabs/common/CamelOperations.java index 72ca2ec3d8..a6714e047e 100644 --- a/tests/hawtio-test-suite/src/main/java/io/hawt/tests/features/pageobjects/fragments/camel/tabs/common/CamelOperations.java +++ b/tests/hawtio-test-suite/src/main/java/io/hawt/tests/features/pageobjects/fragments/camel/tabs/common/CamelOperations.java @@ -5,27 +5,35 @@ import static com.codeborne.selenide.Selectors.byXpath; import static com.codeborne.selenide.Selenide.$; +import org.openqa.selenium.By; + +import com.codeborne.selenide.Condition; import com.codeborne.selenide.SelenideElement; +import com.codeborne.selenide.WebElementCondition; import io.hawt.tests.features.pageobjects.pages.camel.CamelPage; +import io.hawt.tests.features.utils.ByUtils; /** * Represents Operations Tab page in Camel. */ public class CamelOperations extends CamelPage { + + private static final By EXPAND_BUTTON = ByUtils.byAttribute("button", "aria-label", "Details"); + private static final By EXECUTE_BUTTON = ByUtils.byText("button", "Execute"); /** * Open some method window and execute it. * * @param method method name */ public void executeMethod(String method) { - final SelenideElement operation = $(byXpath(".//li[@aria-labelledby='operation " + method + "']")); + final SelenideElement operation = $(operation(method)); // Expand the operation section - operation.$(byXpath(".//button[@aria-label='Details']")).shouldBe(enabled).click(); + operation.$(EXPAND_BUTTON).shouldBe(enabled).click(); // Click on Execute of the given expanded operation section - operation.$(byXpath(".//button[text()='Execute']")).shouldBe(enabled).click(); + operation.$(EXECUTE_BUTTON).shouldBe(enabled).click(); } /** @@ -35,7 +43,7 @@ public void executeMethod(String method) { * @param result expected result of an operation */ public void checkResultOfExecutedOperation(String method, String result) { - final SelenideElement operation = $(byXpath(".//li[@aria-labelledby='operation " + method + "']")); + final SelenideElement operation = $(operation(method)); operation.$(byXpath(".//pre")).shouldHave(exactText(result)); } @@ -46,7 +54,21 @@ public void checkResultOfExecutedOperation(String method, String result) { * @return result of the operation as String */ public String getResultOfExecutedOperation(String method) { - final SelenideElement operation = $(byXpath(".//li[@aria-labelledby='operation " + method + "']")); + final SelenideElement operation = $(operation(method)); return operation.$(byXpath(".//pre")).getText(); } + + public void checkOperation(String method, WebElementCondition condition) { + final SelenideElement operation = $(operation(method)); + if (condition == Condition.disabled) { + operation.$(By.cssSelector(".pf-c-data-list__item-content svg")).should(Condition.exist); + } + operation.$(EXPAND_BUTTON).click(); + + operation.$(EXECUTE_BUTTON).shouldBe(condition); + } + + private By operation(String method) { + return ByUtils.byAttribute("li", "aria-labelledby", "operation " + method); + } } diff --git a/tests/hawtio-test-suite/src/main/java/io/hawt/tests/features/pageobjects/fragments/online/DiscoverTab.java b/tests/hawtio-test-suite/src/main/java/io/hawt/tests/features/pageobjects/fragments/online/DiscoverTab.java index 98520ee075..e0c036710e 100644 --- a/tests/hawtio-test-suite/src/main/java/io/hawt/tests/features/pageobjects/fragments/online/DiscoverTab.java +++ b/tests/hawtio-test-suite/src/main/java/io/hawt/tests/features/pageobjects/fragments/online/DiscoverTab.java @@ -89,5 +89,6 @@ public DeploymentEntry assertContainsDeployment(String name) { private void waitForPageLoaded() { $(ByUtils.byDataTestId("loading")).shouldNot(Condition.exist, Duration.ofSeconds(30)); + $(By.className("pf-c-accordion")).should(Condition.exist, Duration.ofSeconds(30)); } } diff --git a/tests/hawtio-test-suite/src/main/java/io/hawt/tests/features/pageobjects/pages/HawtioPage.java b/tests/hawtio-test-suite/src/main/java/io/hawt/tests/features/pageobjects/pages/HawtioPage.java index 7f626eba92..33851b8433 100644 --- a/tests/hawtio-test-suite/src/main/java/io/hawt/tests/features/pageobjects/pages/HawtioPage.java +++ b/tests/hawtio-test-suite/src/main/java/io/hawt/tests/features/pageobjects/pages/HawtioPage.java @@ -11,6 +11,8 @@ import static com.codeborne.selenide.Selectors.byXpath; import static com.codeborne.selenide.Selenide.$; +import org.openqa.selenium.By; + import com.codeborne.selenide.Selenide; import com.codeborne.selenide.SelenideElement; @@ -21,6 +23,9 @@ * Represents Hawtio page with common methods. */ public class HawtioPage { + + private static final By HEADER_SELECTOR = By.id("hawtio-header-brand"); + private final Menu menu; private final Panel panel; @@ -113,4 +118,12 @@ public void openTab(String tab) { tabElement.should(exist).shouldNotBe(hidden).click(); } } + + public SelenideElement getLogo() { + return $(HEADER_SELECTOR).$(By.className("pf-c-brand")); + } + + public String getAppName() { + return $(HEADER_SELECTOR).$(By.tagName("h1")).text(); + } } diff --git a/tests/hawtio-test-suite/src/main/java/io/hawt/tests/features/setup/LoginLogout.java b/tests/hawtio-test-suite/src/main/java/io/hawt/tests/features/setup/LoginLogout.java index d4002ab67b..0bfa713a8a 100644 --- a/tests/hawtio-test-suite/src/main/java/io/hawt/tests/features/setup/LoginLogout.java +++ b/tests/hawtio-test-suite/src/main/java/io/hawt/tests/features/setup/LoginLogout.java @@ -3,26 +3,25 @@ import static com.codeborne.selenide.Condition.exist; import static com.codeborne.selenide.Condition.interactable; import static com.codeborne.selenide.Selenide.$; -import static com.codeborne.selenide.Selenide.sleep; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.codeborne.selenide.Selenide; +import java.time.Duration; + import io.hawt.tests.features.config.TestConfiguration; import io.hawt.tests.features.hooks.DeployAppHook; import io.hawt.tests.features.openshift.OpenshiftClient; import io.hawt.tests.features.pageobjects.fragments.Panel; import io.hawt.tests.features.pageobjects.pages.ConnectPage; +import io.hawt.tests.features.pageobjects.pages.LoginPage; import io.hawt.tests.features.pageobjects.pages.openshift.HawtioOnlineLoginPage; import io.hawt.tests.features.pageobjects.pages.openshift.HawtioOnlinePage; -import io.hawt.tests.features.pageobjects.pages.LoginPage; import io.hawt.tests.features.setup.deployment.AppDeployment; import io.hawt.tests.features.setup.deployment.OpenshiftDeployment; -import java.time.Duration; - public class LoginLogout { private static final Panel panel = new Panel(); private static final Logger LOG = LoggerFactory.getLogger(LoginLogout.class); @@ -79,7 +78,7 @@ public static String getUrlFromParameters() { * Check that Hawtio page is properly and fully loaded. */ public static void hawtioIsLoaded() { - $("img.pf-c-brand").should(exist).shouldBe(interactable); + $("img.pf-c-brand").should(exist, Duration.ofSeconds(20)).shouldBe(interactable); $("#vertical-nav-toggle").should(exist).shouldBe(interactable); } } diff --git a/tests/hawtio-test-suite/src/main/java/io/hawt/tests/features/setup/deployment/OpenshiftDeployment.java b/tests/hawtio-test-suite/src/main/java/io/hawt/tests/features/setup/deployment/OpenshiftDeployment.java index c7e00a5a92..5d7c80f4c5 100644 --- a/tests/hawtio-test-suite/src/main/java/io/hawt/tests/features/setup/deployment/OpenshiftDeployment.java +++ b/tests/hawtio-test-suite/src/main/java/io/hawt/tests/features/setup/deployment/OpenshiftDeployment.java @@ -29,6 +29,7 @@ public void start() { @Override public void stop() { if (TestConfiguration.openshiftNamespaceDelete()) { + LOG.info("Undeploying Hawtio project {}", TestConfiguration.getOpenshiftNamespace()); OpenshiftClient.get().namespaces().withName(TestConfiguration.getOpenshiftNamespace()).delete(); } } diff --git a/tests/hawtio-test-suite/src/main/java/io/hawt/tests/features/stepdefinitions/panel/about/AboutModalWindowStepDefs.java b/tests/hawtio-test-suite/src/main/java/io/hawt/tests/features/stepdefinitions/panel/about/AboutModalWindowStepDefs.java index f15e8257d7..84e3cc12af 100644 --- a/tests/hawtio-test-suite/src/main/java/io/hawt/tests/features/stepdefinitions/panel/about/AboutModalWindowStepDefs.java +++ b/tests/hawtio-test-suite/src/main/java/io/hawt/tests/features/stepdefinitions/panel/about/AboutModalWindowStepDefs.java @@ -1,20 +1,26 @@ package io.hawt.tests.features.stepdefinitions.panel.about; +import static org.assertj.core.api.Assertions.assertThat; + import static com.codeborne.selenide.Condition.exactText; import static com.codeborne.selenide.Condition.exist; import static com.codeborne.selenide.Selectors.byXpath; import static com.codeborne.selenide.Selenide.$; + +import org.assertj.core.api.Assertions; + import io.cucumber.java.en.And; import io.cucumber.java.en.Then; +import io.hawt.tests.features.pageobjects.fragments.about.AboutModalWindow; public class AboutModalWindowStepDefs { @Then("^The \"([^\"]*)\" header is presented in About modal window$") public void aboutModalWindowHeaderIsPresented(String header) { - $("[id*='pf-about-modal-title']").shouldHave(exactText(header)); + assertThat(new AboutModalWindow().getHeaderText()).isEqualTo(header); } @And("^The \"([^\"]*)\" is presented in About modal window$") public void hawtioComponentIsPresented(String hawtioComponent) { - $(byXpath("//dt[normalize-space(text())='" + hawtioComponent + "']")).should(exist); + assertThat(new AboutModalWindow().getAppComponents()).containsKey(hawtioComponent); } } diff --git a/tests/hawtio-test-suite/src/test/java/io/hawt/tests/utils/rp/Attachments.java b/tests/hawtio-test-suite/src/main/java/io/hawt/tests/features/utils/Attachments.java similarity index 88% rename from tests/hawtio-test-suite/src/test/java/io/hawt/tests/utils/rp/Attachments.java rename to tests/hawtio-test-suite/src/main/java/io/hawt/tests/features/utils/Attachments.java index ff91e57fc8..da42d75505 100644 --- a/tests/hawtio-test-suite/src/test/java/io/hawt/tests/utils/rp/Attachments.java +++ b/tests/hawtio-test-suite/src/main/java/io/hawt/tests/features/utils/Attachments.java @@ -1,4 +1,4 @@ -package io.hawt.tests.utils.rp; +package io.hawt.tests.features.utils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -22,15 +22,15 @@ public final class Attachments { private Attachments() { } - static void startTestClass(String testClass) { + public static void startTestClass(String testClass) { currentTestClass = testClass; } - static void startTestCase(String testCase) { + public static void startTestCase(String testCase) { currentTestCase = testCase; } - static void endTestCase(boolean failure) { + public static void endTestCase(boolean failure) { if (failure) { createAttachments(Stream.concat(testClassAttachments.stream(), testCaseAttachments.stream()).collect(Collectors.toList()), currentTestClass + "." + currentTestCase); @@ -39,7 +39,7 @@ static void endTestCase(boolean failure) { currentTestCase = null; } - static void endTestClass() { + public static void endTestClass() { testClassAttachments.clear(); currentTestClass = null; } diff --git a/tests/hawtio-test-suite/src/test/java/io/hawt/tests/openshift/HawtioOperatorTest.java b/tests/hawtio-test-suite/src/test/java/io/hawt/tests/openshift/HawtioOperatorTest.java new file mode 100644 index 0000000000..1feaccfe62 --- /dev/null +++ b/tests/hawtio-test-suite/src/test/java/io/hawt/tests/openshift/HawtioOperatorTest.java @@ -0,0 +1,449 @@ +package io.hawt.tests.openshift; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import org.apache.commons.io.IOUtils; +import org.apache.commons.lang3.RandomStringUtils; +import org.assertj.core.api.Assertions; +import org.assertj.core.api.SoftAssertions; +import org.awaitility.Awaitility; +import org.openqa.selenium.NoSuchElementException; + +import com.codeborne.selenide.Condition; +import com.codeborne.selenide.Selenide; +import com.codeborne.selenide.WebDriverRunner; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; +import java.time.Duration; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.Arrays; +import java.util.Base64; +import java.util.List; +import java.util.Map; +import java.util.function.Consumer; +import java.util.stream.Collectors; + +import io.fabric8.kubernetes.api.model.ConfigMapBuilder; +import io.fabric8.kubernetes.api.model.IntOrString; +import io.fabric8.kubernetes.api.model.NamespaceBuilder; +import io.fabric8.kubernetes.api.model.Pod; +import io.fabric8.kubernetes.api.model.Quantity; +import io.fabric8.kubernetes.api.model.apps.Deployment; +import io.fabric8.kubernetes.client.dsl.PodResource; +import io.hawt.tests.features.config.TestConfiguration; +import io.hawt.tests.features.openshift.HawtioOnlineUtils; +import io.hawt.tests.features.openshift.OpenshiftClient; +import io.hawt.tests.features.openshift.WaitUtils; +import io.hawt.tests.features.pageobjects.fragments.about.AboutModalWindow; +import io.hawt.tests.features.pageobjects.fragments.camel.tabs.common.CamelOperations; +import io.hawt.tests.features.pageobjects.fragments.online.DiscoverTab; +import io.hawt.tests.features.pageobjects.fragments.openshift.DeploymentEntry; +import io.hawt.tests.features.pageobjects.pages.HawtioPage; +import io.hawt.tests.features.pageobjects.pages.camel.CamelPage; +import io.hawt.tests.features.pageobjects.pages.openshift.HawtioOnlineLoginPage; +import io.hawt.tests.features.setup.LoginLogout; +import io.hawt.tests.openshift.utils.BaseHawtioOnlineTest; +import io.hawt.tests.utils.HawtioOnlineTestUtils; +import io.hawt.v1alpha1.Hawtio; +import io.hawt.v1alpha1.HawtioSpec; +import io.hawt.v1alpha1.hawtiospec.Auth; +import io.hawt.v1alpha1.hawtiospec.Config; +import io.hawt.v1alpha1.hawtiospec.MetadataPropagation; +import io.hawt.v1alpha1.hawtiospec.Nginx; +import io.hawt.v1alpha1.hawtiospec.Rbac; +import io.hawt.v1alpha1.hawtiospec.Resources; +import io.hawt.v1alpha1.hawtiospec.config.About; +import io.hawt.v1alpha1.hawtiospec.config.Branding; +import io.hawt.v1alpha1.hawtiospec.config.Online; +import io.hawt.v1alpha1.hawtiospec.config.about.ProductInfo; +import io.hawt.v1alpha1.hawtiospec.config.online.ConsoleLink; + +public class HawtioOperatorTest extends BaseHawtioOnlineTest { + + private static Deployment deployment; + private static String podName; + + private static Hawtio hawtio; + + @BeforeAll + public static void setupApp() { + String name = "camel-app-" + RandomStringUtils.randomAlphabetic(5).toLowerCase(); + deployment = HawtioOnlineUtils.deployApplication(name, "springboot", TestConfiguration.getOpenshiftNamespace(), "21"); + podName = OpenshiftClient.get().pods().withLabel("app", name).list().getItems().get(0).getMetadata().getName(); + } + + @AfterEach + public void closeBrowser() { + while (WebDriverRunner.getWebDriver().getWindowHandles().size() != 1) { + Selenide.closeWindow(); + Selenide.switchTo().window(0); + } + } + + @AfterAll + public static void cleanup() { + OpenshiftClient.get().resource(deployment).delete(); + } + + @Test + public void testDisabledRoutes() { + runTest(s -> { + Config config = new Config(); + config.setDisabledRoutes(List.of("/camel", "/quartz")); + s.setConfig(config); + }, (sa) -> { + var discoverTab = new DiscoverTab(); + discoverTab.assertContainsDeployment(deployment.getMetadata().getName()); + discoverTab.connectTo(podName); + + WaitUtils.waitForPageLoad(); + + var hawtio = new HawtioPage(); + Assertions.assertThatCode(() -> { + hawtio.menu().navigateTo("Camel"); + }).hasCauseInstanceOf(NoSuchElementException.class); + }); + } + + @Test + public void testUICustomization() { + Config config = new Config(); + runTest(s -> { + About about = new About(); + about.setImgSrc("https://i1.sndcdn.com/artworks-zyYqA8D0BdfuyH28-WeeHrw-t500x500.jpg"); + about.setTitle("MeowIO About"); + about.setAdditionalInfo("Hello world"); + final ProductInfo productInfo = new ProductInfo(); + productInfo.setName("Testsuite"); + productInfo.setValue("latest"); + + about.setProductInfo(List.of(productInfo)); + about.setCopyright("Hawtio QE team"); + config.setAbout(about); + + Branding branding = new Branding(); + branding.setAppLogoUrl("https://i1.sndcdn.com/artworks-zyYqA8D0BdfuyH28-WeeHrw-t500x500.jpg"); + branding.setAppName("MeowIO"); + + config.setBranding(branding); + s.setConfig(config); + }, (sa) -> { + var discoverTab = new DiscoverTab(); + discoverTab.assertContainsDeployment(deployment.getMetadata().getName()); + discoverTab.connectTo(podName); + + WaitUtils.waitForPageLoad(); + var hawtio = new HawtioPage(); + + hawtio.panel().openMenuItemUnderQuestionMarkDropDownMenu("About"); + var aboutPanel = new AboutModalWindow(); + sa.assertThat(aboutPanel.getBrandImage().getAttribute("src")).isEqualTo(config.getAbout().getImgSrc()); + sa.assertThat(aboutPanel.getBrandImage().getAttribute("alt")).isEqualTo(config.getAbout().getTitle()); + + sa.assertThat(aboutPanel.getAppComponents()).containsEntry("Testsuite", "latest"); + sa.assertThat(aboutPanel.getHeaderText()).isEqualTo(config.getAbout().getTitle()); + aboutPanel.close(); + + sa.assertThat(hawtio.getAppName()).isEqualTo(config.getBranding().getAppName()); + sa.assertThat(hawtio.getLogo().getAttribute("src")).isEqualTo(config.getBranding().getAppLogoUrl()); + sa.assertThat(hawtio.getLogo().getAttribute("alt")).isEqualTo(config.getBranding().getAppName()); + }); + } + + @Test + public void testResources() { + runTest(spec -> { + Resources resources = new Resources(); + resources.setRequests(Map.of("memory", new IntOrString("1G"), "cpu", new IntOrString("0.5"))); + resources.setLimits(Map.of("memory", new IntOrString("2G"), "cpu", new IntOrString("1.0"))); + spec.setResources(resources); + + spec.setReplicas(2); + }, sa -> { + waitForHawtioReady(); + sa.assertThat(OpenshiftClient.get().pods().withLabel("deployment", hawtio.getMetadata().getName()).list().getItems()) + .hasSizeGreaterThanOrEqualTo(2) + .allSatisfy(pod -> { + final Map requests = pod.getSpec().getContainers().get(0).getResources().getRequests(); + assertThat(requests.get("memory")).satisfies(memory -> { + assertThat(memory.getAmount()).isEqualTo("1"); + assertThat(memory.getFormat()).isEqualTo("G"); + }); + + assertThat(requests.get("cpu")).satisfies(cpu -> { + assertThat(cpu.getAmount()).isEqualTo("500"); + assertThat(cpu.getFormat()).isEqualTo("m"); + }); + + final Map limits = pod.getSpec().getContainers().get(0).getResources().getLimits(); + + assertThat(limits.get("memory")).satisfies(memory -> { + assertThat(memory.getAmount()).isEqualTo("2"); + assertThat(memory.getFormat()).isEqualTo("G"); + }); + + assertThat(limits.get("cpu")).satisfies(cpu -> { + assertThat(cpu.getAmount()).isEqualTo("1"); + assertThat(cpu.getFormat()).isEqualTo(""); + }); + }); + }, false); + } + + @Test + public void testProjectSelector() { + final String namespace = "selector-tests-" + RandomStringUtils.randomAlphabetic(5).toLowerCase(); + + OpenshiftClient.get() + .resource(new NamespaceBuilder().withNewMetadata().withName(namespace).addToLabels("myLabel", namespace).endMetadata().build()).create(); + + HawtioOnlineTestUtils.withCleanup(() -> { + HawtioOnlineUtils.deployApplication("selector-test-springboot", "quarkus", namespace, "21"); + runTest(spec -> { + + Config config = new Config(); + Online online = new Online(); + + online.setProjectSelector("myLabel=" + namespace); + spec.setType(HawtioSpec.Type.CLUSTER); + + config.setOnline(online); + spec.setConfig(config); + }, sa -> { + + var discoverTab = new DiscoverTab(); + final Map deployments = discoverTab.getDeployments(); + + sa.assertThat(deployments).hasSize(1); + sa.assertThat(deployments).allSatisfy((key, value) -> { + assertThat(value.getPods().get(0).getNamespace()).isEqualTo(namespace); + }); + }); + }, () -> { + OpenshiftClient.get().projects().withName(namespace).delete(); + }); + } + + @Test + public void testConsoleLink() { + runTest(spec -> { + Config config = new Config(); + Online online = new Online(); + + ConsoleLink consoleLink = new ConsoleLink(); + + consoleLink.setText("My link"); + + online.setConsoleLink(consoleLink); + + config.setOnline(online); + spec.setConfig(config); + }, sa -> { + + sa.assertThat( + OpenshiftClient.get().console().consoleLinks().withName(hawtio.getMetadata().getName() + "-" + hawtio.getMetadata().getNamespace())) + .satisfies(link -> assertThat(link.get().getSpec().getText()).isEqualTo("My link")); + }); + } + + @Test + public void testMetadataPropagation() { + HawtioOnlineTestUtils.withCleanup(() -> { + + hawtio = + HawtioOnlineUtils.withBaseHawtio("operator-test-" + RandomStringUtils.randomAlphabetic(5).toLowerCase(), + TestConfiguration.getOpenshiftNamespace(), + h -> { + h.getSpec().setType(HawtioSpec.Type.NAMESPACE); + h.getMetadata().setLabels(Map.of("key", "value", "allowed_label", "value", "not_allowed_label", "value")); + h.getMetadata().setLabels(Map.of("key", "value", "allowed_annotation", "value", "not_allowed_annotation", "value")); + + MetadataPropagation propagation = new MetadataPropagation(); + + propagation.setLabels(List.of("allowed_label", "key")); + propagation.setAnnotations(List.of("allowed_annotation", "key")); + + h.getSpec().setMetadataPropagation(propagation); + }); + + SoftAssertions sa = new SoftAssertions(); + HawtioOnlineUtils.deployHawtioCR(hawtio); + + sa.assertThat(OpenshiftClient.get().pods().withLabel("key").list().getItems()).hasSize(1); + sa.assertThat(OpenshiftClient.get().services().withLabel("allowed_label").list().getItems()).hasSize(1); + sa.assertThat(OpenshiftClient.get().services().withLabel("not_allowed_label").list().getItems()).hasSize(0); + + sa.assertThat(OpenshiftClient.get().apps().deployments().withLabel("allowed_label").list().getItems()).hasSize(1).allSatisfy(d -> { + assertThat(d.getMetadata().getAnnotations()).containsKey("key").containsKey("allowed_annotation") + .doesNotContainKey("not_allowed_annotation"); + }); + }, () -> { + HawtioOnlineUtils.deleteHawtio(hawtio); + }); + } + + @Test + public void testCustomHostName() { + runTest(spec -> { + spec.setRouteHostName("my-route"); + }, sa -> { + String url = WaitUtils.withRetry(() -> OpenshiftClient.get().resource(hawtio).get().getStatus().getURL(), 5, Duration.ofSeconds(1)); + sa.assertThat(url).contains("my-route"); + sa.assertThat(OpenshiftClient.get().routes().withName(hawtio.getMetadata().getName()).get().getStatus().getIngress().get(0).getHost()) + .contains("my-route"); + }, false); + } + + @Test + public void testNginxResources() { + final String clientBufferSize = "512k"; + final String proxyBuffers = "32 256k"; + final String outputBufferSize = "20m"; + runTest(spec -> { + var nginx = new Nginx(); + nginx.setClientBodyBufferSize(clientBufferSize); + nginx.setProxyBuffers(proxyBuffers); + nginx.setSubrequestOutputBufferSize(outputBufferSize); + spec.setNginx(nginx); + }, sa -> { + + final String podName = + OpenshiftClient.get().pods().withLabel("deployment", hawtio.getMetadata().getName()).list().getItems().get(0).getMetadata().getName(); + final PodResource pod = OpenshiftClient.get().pods().withName(podName); + + try { + String config = IOUtils.toString(pod.file("/etc/nginx/conf.d/nginx.conf").read(), StandardCharsets.UTF_8); + sa.assertThat(config) + .containsPattern("subrequest_output_buffer_size\\s+" + outputBufferSize) + .containsPattern("client_body_buffer_size\\s+" + clientBufferSize) + .containsPattern("proxy_buffers\\s+" + proxyBuffers); + } catch (IOException e) { + + sa.fail("Couldn't get contents of nginx config", e); + } + Awaitility.waitAtMost(Duration.ofSeconds(10)).untilAsserted(() -> { + assertThat(pod.getLog()).contains("kube-probe"); + }); + }, false); + } + + @Test + public void testRBAC() throws IOException { + OpenshiftClient.get().resource(new ConfigMapBuilder() + .withNewMetadata() + .withName("rbac-test") + .endMetadata() + .addToData("ACL.yaml", IOUtils.toString(getClass().getResource("/io/hawt/tests/openshift/acl.yaml"), StandardCharsets.UTF_8)) + .build() + ).create(); + runTest(spec -> { + var rbac = new Rbac(); + rbac.setConfigMap("rbac-test"); + spec.setRbac(rbac); + }, sa -> { + var discoverTab = new DiscoverTab(); + discoverTab.assertContainsDeployment(deployment.getMetadata().getName()); + discoverTab.connectTo(podName); + + WaitUtils.waitForPageLoad(); + var hawtio = new HawtioPage(); + + hawtio.menu().navigateTo("Camel"); + final CamelPage camelPage = new CamelPage(); + + camelPage.tree().selectSpecificItem("CamelContexts-folder-SampleCamel-folder"); + camelPage.openTab("Operations"); + + final CamelOperations camelOperations = new CamelOperations(); + camelOperations.checkOperation("stop()", Condition.disabled); + camelOperations.checkOperation("getTotalRoutes()", Condition.enabled); + + hawtio.panel().logout(); + new HawtioOnlineLoginPage().login("viewer", "viewer"); + + LoginLogout.hawtioIsLoaded(); + camelPage.tree().selectSpecificItem("CamelContexts-folder-SampleCamel-folder"); + camelPage.openTab("Operations"); + + camelOperations.checkOperation("stop()", Condition.disabled); + camelOperations.checkOperation("restart()", Condition.disabled); + camelOperations.checkOperation("getTotalRoutes()", Condition.enabled); + }); + } + + @Test + public void testAuthConfig() { + runTest(spec -> { + Auth auth = new Auth(); + auth.setClientCertCommonName("my.hawtio.svc"); + auth.setClientCertCheckSchedule("* * * * *"); + auth.setClientCertExpirationPeriod(48L); + auth.setClientCertExpirationDate(LocalDateTime.now().plusYears(1).format(DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss'Z'"))); + spec.setAuth(auth); + }, sa -> { + sa.assertThat(OpenshiftClient.get().batch().v1().cronjobs().withName(hawtio.getMetadata().getName() + "-certificate-expiry-check").get()) + .isNotNull() + .matches(job -> job.getSpec().getSchedule().equals("* * * * *")) + .matches(job -> job.getSpec().getJobTemplate().getSpec().getTemplate().getSpec().getContainers().get(0).getArgs().contains("48")); + + Assertions.assertThatCode(() -> { + final String source = new String(Base64.getDecoder().decode( + OpenshiftClient.get().secrets().withName(hawtio.getMetadata().getName() + "-tls-proxying").get().getData().get("tls.crt"))); + final CertificateFactory cf = CertificateFactory.getInstance("X.509"); + final X509Certificate certificate = + (X509Certificate) cf.generateCertificate(new ByteArrayInputStream(source.getBytes(StandardCharsets.UTF_8))); + assertThat(certificate.getSubjectDN().getName()).isEqualTo("CN=my.hawtio.svc"); + }).doesNotThrowAnyException(); + }, false); + } + + private static void runTest(Consumer consumer, Consumer testFunction) { + runTest(consumer, testFunction, true); + } + + private static void waitForHawtioReady() { + WaitUtils.waitFor(() -> OpenshiftClient.get().resource(hawtio).get().getStatus().getSelector() != null, + "Wait for Hawtio to be deployed", Duration.ofSeconds(5)); + final String selector = OpenshiftClient.get().resource(hawtio).get().getStatus().getSelector(); + final Map labels = + Arrays.stream(selector.split(",")).map(expr -> expr.split("=")).collect(Collectors.toMap(pair -> pair[0], pair -> pair[1])); + WaitUtils.waitFor(() -> { + final List pods = OpenshiftClient.get().pods().withLabels(labels).list().getItems(); + return pods.stream().filter(p -> p.getStatus().getPhase().equalsIgnoreCase("running")).count() == hawtio.getSpec().getReplicas(); + }, "Waiting for all pods to be deployed", Duration.ofMinutes(1)); + } + + private static void runTest(Consumer consumer, Consumer testFunction, boolean startBrowser) { + hawtio = + HawtioOnlineUtils.withBaseHawtio("operator-test-" + RandomStringUtils.randomAlphabetic(5).toLowerCase(), + TestConfiguration.getOpenshiftNamespace(), + h -> { + h.getSpec().setType(HawtioSpec.Type.NAMESPACE); + consumer.accept(h.getSpec()); + }); + var hawtioUrl = HawtioOnlineUtils.deployHawtioCR(hawtio); + if (startBrowser) { + Selenide.open(hawtioUrl, HawtioOnlineLoginPage.class) + .login(TestConfiguration.getOpenshiftUsername(), TestConfiguration.getOpenshiftPassword()); + } + + HawtioOnlineTestUtils.withCleanup(() -> { + SoftAssertions sa = new SoftAssertions(); + testFunction.accept(sa); + + sa.assertAll(); + }, () -> { + HawtioOnlineUtils.deleteHawtio(hawtio); + }); + } +} diff --git a/tests/hawtio-test-suite/src/test/java/io/hawt/tests/openshift/utils/SelenideTestWatcher.java b/tests/hawtio-test-suite/src/test/java/io/hawt/tests/openshift/utils/SelenideTestWatcher.java index 125b57d932..fd93356424 100644 --- a/tests/hawtio-test-suite/src/test/java/io/hawt/tests/openshift/utils/SelenideTestWatcher.java +++ b/tests/hawtio-test-suite/src/test/java/io/hawt/tests/openshift/utils/SelenideTestWatcher.java @@ -9,7 +9,7 @@ import java.nio.charset.StandardCharsets; import java.nio.file.Path; -import io.hawt.tests.utils.rp.Attachments; +import io.hawt.tests.features.utils.Attachments; public class SelenideTestWatcher implements TestWatcher { diff --git a/tests/hawtio-test-suite/src/test/java/io/hawt/tests/utils/rp/RPTestExecutionListener.java b/tests/hawtio-test-suite/src/test/java/io/hawt/tests/utils/rp/RPTestExecutionListener.java index 817b7fae6a..6b9162acfe 100644 --- a/tests/hawtio-test-suite/src/test/java/io/hawt/tests/utils/rp/RPTestExecutionListener.java +++ b/tests/hawtio-test-suite/src/test/java/io/hawt/tests/utils/rp/RPTestExecutionListener.java @@ -7,6 +7,8 @@ import com.google.auto.service.AutoService; +import io.hawt.tests.features.utils.Attachments; + @AutoService(TestExecutionListener.class) public class RPTestExecutionListener implements TestExecutionListener { diff --git a/tests/hawtio-test-suite/src/test/resources/io/hawt/tests/features/panel/about/about_modal_window.feature b/tests/hawtio-test-suite/src/test/resources/io/hawt/tests/features/panel/about/about_modal_window.feature index 632d748929..b2ef24ec6e 100644 --- a/tests/hawtio-test-suite/src/test/resources/io/hawt/tests/features/panel/about/about_modal_window.feature +++ b/tests/hawtio-test-suite/src/test/resources/io/hawt/tests/features/panel/about/about_modal_window.feature @@ -14,7 +14,7 @@ Feature: Check whether all data is presented and displayed correctly in About mo @online Scenario Outline: Check that the titles are presented in About modal window Given User clicks on "About" option in Question mark drop-down menu - Then The "Red Hat build of Hawtio" header is presented in About modal window + Then The "Red Hat build of HawtIO" header is presented in About modal window And The "" is presented in About modal window Then About modal window is closed diff --git a/tests/hawtio-test-suite/src/test/resources/io/hawt/tests/openshift/acl.yaml b/tests/hawtio-test-suite/src/test/resources/io/hawt/tests/openshift/acl.yaml new file mode 100644 index 0000000000..247042b631 --- /dev/null +++ b/tests/hawtio-test-suite/src/test/resources/io/hawt/tests/openshift/acl.yaml @@ -0,0 +1,477 @@ +# This file defines the roles allowed for MBean operations +# +# The definition of ACLs for JMX operations works as follows: +# +# Based on the ObjectName of the JMX MBean, a key composed with the ObjectName +# domain, followed by the 'type' attribute optionally, can be declared, using the +# convention .. +# For example, the 'java.lang.Threading' key for the MBean with the following +# objectName: java.lang:type=Threading can be declared. A more generic key with +# the domain only can be declared (e.g. java.lang). A 'default' top-level key can +# also be declared. +# A key can either be an unordered or ordered map, whose keys can either be +# string or regexp, and whose values can either be string or array of strings, +# that represent roles that are allowed to invoke the MBean member. +# +# The system looks for allowed roles using the following process: +# +# The most specific key is tried first. E.g. for the +# above example, the java.lang.Threading key is looked up first. +# If the most specific key does not exist, the domain-only key is looked up, +# otherwise, the 'default' key is looked up. +# Using the matching key, the system looks up its map value for: +# 1. An exact match for the operation invocation, using the operation +# signature, and the invocation arguments, e.g.: +# uninstall(java.lang.String)[0]: [] # no roles can perform this operation +# 2. A regexp match for the operation invocation, using the operation +# signature, and the invocation arguments, e.g.: +# /update\(java\.lang\.String,java\.lang\.String\)\[[1-4]?[0-9],.*\]/: admin +# Note that, if the value is an ordered map, the iteration order is guaranteed, +# and the first matching regexp key is selected; +# 3. An exact match for the operation invocation, using the operation +# signature, without the invocation arguments, e.g.: +# delete(java.lang.String): admin +# 4. An exact match for the operation invocation, using the operation +# name, e.g.: +# dumpStatsAsXml: admin, viewer +# If the key matches the operation invocation, it is used and the process will not +# look for any other keys. So the most specific key always takes precedence. +# Its value is used to match the role that impersonates the request, against the roles +# that are allowed to invoke the operation. +# If the current key does not match, the less specific key is looked up +# and matched following the steps 1 to 4 above, up until the 'default' key. +# Otherwise, the operation invocation is denied. +# +# For the time being, only the viewer and admin roles are supported. Once the current +# invocation is authenticated, these roles are inferred from the permissions the user +# impersonating the request is granted for the pod hosting the operation being invoked. +# A user that's granted the 'update' verb on the pod resource is bound to the 'admin' role. +# Else, a user granted the 'get' verb on the pod resource is bound the 'viewer' role. +# Otherwise the user is not bound any roles. + +# Default, generic rules, declared as an ordered map, so that the most specific keys +# are tested first. +default: + - version: admin, viewer + - list: admin, viewer + - read: admin, viewer + - search: admin, viewer + - /list.*/: admin, viewer + - /get.*/: admin, viewer + - /is.*/: admin, viewer + - /set.*/: admin + - /.*/: admin + +com.sun.management: + dumpHeap: admin, viewer + getVMOption: admin, viewer + setVMOption: admin + +connector: + stop: admin + start: admin + +hawtio.plugin: + /.*/: /.*/ +hawtio.ConfigAdmin: + configAdminUpdate: admin +hawtio.OSGiTools: + getResourceURL: admin, viewer + getLoadClassOrigin: admin, viewer +hawtio.QuartzFacade: + updateSimpleTrigger: admin + updateCronTrigger: admin +hawtio.SchemaLookup: + getSchemaForClass: admin, viewer +hawtio.security: + canInvoke: admin, viewer + +java.lang.Memory: + gc: admin +java.lang.MemoryPool: + resetPeakUsage: admin +java.lang.Threading: + /find.*/: admin, viewer + dumpAllThreads: admin, viewer + resetPeakThreadCount: admin +java.util.logging: + getLoggerLevel: admin, viewer + getParentLoggerName: admin, viewer + setLoggerLevel: admin + +jolokia.Config.hawtio: + /is.*/: admin, viewer + /get.*/: admin, viewer + /set.*/: admin + debugInfo: admin, viewer + setHistoryEntriesForAttribute: admin + setHistoryEntriesForOperation: admin + setHistoryLimitForOperation: admin + resetHistoryEntries: admin + resetDebugInfo: admin + setHistoryLimitForAttribute: admin +jolokia.Discovery: + lookupAgents: admin, viewer + lookupAgentsWithTimeout: admin, viewer +jolokia.ServerHandler.hawtio: + mBeanServersInfo: admin, viewer + +org.apache.aries.blueprint.blueprintMetadata: + /get.*/: admin, viewer +org.apache.aries.blueprint.blueprintState: + /get.*/: admin, viewer + +org.apache.camel: + /.*/: /.*/ +org.apache.camel.components: + getState: admin, viewer + getComponentName: admin, viewer + getCamelId: admin, viewer +org.apache.camel.consumers: + isSuspended: admin, viewer + getState: admin, viewer + getServiceType: admin, viewer + isSupportSuspension: admin, viewer + isStaticService: admin, viewer + getCamelId: admin, viewer + getRouteId: admin, viewer + getInflightExchanges: admin, viewer + getEndpointUri: admin, viewer + stop: admin + start: admin + suspend: admin + resume: admin +org.apache.camel.context: + getResetTimestamp: admin, viewer + getExchangesTotal: admin + getTotalProcessingTime: admin, viewer + isStatisticsEnabled: admin, viewer + setStatisticsEnabled: admin + getExchangesCompleted: admin, viewer + getExchangesFailed: admin, viewer + getFailuresHandled: admin, viewer + getRedeliveries: admin, viewer + getExternalRedeliveries: admin, viewer + getMinProcessingTime: admin, viewer + getMeanProcessingTime: admin, viewer + getMaxProcessingTime: admin, viewer + getLastProcessingTime: admin, viewer + getDeltaProcessingTime: admin, viewer + getLastExchangeCompletedTimestamp: admin, viewer + getLastExchangeCompletedExchangeId: admin, viewer + getFirstExchangeCompletedTimestamp: admin, viewer + getFirstExchangeCompletedExchangeId: admin, viewer + getLastExchangeFailureTimestamp: admin, viewer + getLastExchangeFailureExchangeId: admin, viewer + getFirstExchangeFailureTimestamp: admin, viewer + getFirstExchangeFailureExchangeId: admin, viewer + getCamelId: admin, viewer + getTimeout: admin, viewer + setTimeout: admin + getProperties: admin, viewer + getState: admin, viewer + getUptime: admin, viewer + getInflightExchanges: admin, viewer + getTracing: admin, viewer + setTracing: admin + getLoad01: admin, viewer + getLoad05: admin, viewer + getLoad15: admin, viewer + getCamelVersion: admin, viewer + getApplicationContextClassName: admin, viewer +# getTotalRoutes: admin, viewer + getStartedRoutes: admin, viewer + isMessageHistory: admin, viewer + getTimeUnit: admin, viewer + setTimeUnit: admin + getClassResolver: admin, viewer + getManagementName: admin, viewer + getPackageScanClassResolver: admin, viewer + isUseMDCLogging: admin, viewer + isAllowUseOriginalMessage: admin, viewer + isUseBreadcrumb: admin, viewer + isShutdownNowOnTimeout: admin, viewer + setShutdownNowOnTimeout: admin + reset: admin + dumpStatsAsXml: admin, viewer + setProperty: admin + getProperty: admin, viewer + createEndpoint: admin + restart: admin + stop: [] + start: admin + suspend: admin + resume: admin + sendStringBody: admin + requestStringBody: admin + dumpRoutesAsXml: admin, viewer + addOrUpdateRoutesFromXml: admin + findComponentNames: admin, viewer + dumpRoutesStatsAsXml: admin, viewer + componentParameterJsonSchema: admin, viewer + sendBody: admin + sendBodyAndHeaders: admin + requestBody: admin + requestBodyAndHeaders: admin + findComponents: admin, viewer + getComponentDocumentation: admin, viewer + removeEndpoints: admin + completeEndpointPath: admin, viewer +org.apache.camel.endpoints: + /is.*/: admin, viewer + /get.*/: admin, viewer + /set.*/: admin +org.apache.camel.errorhandlers: + /is.*/: admin, viewer + /get.*/: admin, viewer + /set.*/: admin +org.apache.camel.eventnotifiers: + /set.*/: admin +org.apache.camel.processors: + dumpStatsAsXml: admin, viewer + /get.*/: admin, viewer + /is.*/: admin, viewer + /set.*/: admin + reset: admin + stop: admin + start: admin +org.apache.camel.routes: + /is.*/: admin, viewer + /get.*/: admin, viewer + /set.*/: admin + reset: admin + /dump.*/: admin, viewer + remove: admin + shutdown: admin + stop: admin + start: admin + suspend: admin + resume: admin + updateRouteFromXml: admin +org.apache.camel.services: + /is.*/: admin, viewer + /get.*/: admin, viewer + /set.*/: admin + stop: admin + start: admin + suspend: admin + resume: admin + purge: admin + hasTypeConverter: admin, viewer + resetTypeConversionCounters: admin + listTypeConverters: admin, viewer +org.apache.camel.threadpools: + /is.*/: admin, viewer + /get.*/: admin, viewer + /set.*/: admin + purge: admin +org.apache.camel.tracer: + /is.*/: admin, viewer + /get.*/: admin, viewer + /set.*/: admin + step: admin, viewer + enableDebugger: admin + disableDebugger: admin + /.*Breakpoint.*/: admin, viewer + resumeAll: admin, viewer + resetDebugCounter: admin + /dump.*/: admin, viewer + clear: admin + resetTraceCounter: admin + +org.apache.cxf.Bus: + shutdown: admin +org.apache.cxf.Bus.Service.Endpoint: + destroy: admin + start: admin + stop: admin + getState: admin, viewer + getTransportId: admin, viewer + getAddress: admin, viewer +org.apache.cxf.WorkQueueManager: + shutdown: admin + +org.apache.karaf.bundle: + capabilities: admin, viewer + diag: admin, viewer + getStartLevel: admin, viewer + install: admin + refresh: admin + resolve: admin + restart: admin + status: admin, viewer + /setStartLevel\(java\.lang\.String,int\)\[[1-4]?[0-9],.*\]/: admin + setStartLevel: admin + /start\(java\.lang\.String\)\[[1-4]?[0-9]\]/: admin + start: admin + /stop\(java\.lang\.String\)\[[1-4]?[0-9]\]/: admin + stop: admin + uninstall(java.lang.String)[0]: [ ] # no roles can perform this operation + uninstall: admin + /update\(java\.lang\.String\)\[[1-4]?[0-9]\]/: admin + /update\(java\.lang\.String,java\.lang\.String\)\[[1-4]?[0-9],.*\]/: admin + update: admin +org.apache.karaf.config: + listProperties: admin, viewer + /appendProperty\(java\.lang\.String,java\.lang\.String,java\.lang\.String\)\[jmx\.acl\..*,.*,.*\]/: admin + /appendProperty\(java\.lang\.String,java\.lang\.String,java\.lang\.String\)\[org\.apache\.karaf\.command\.acl\..+,.*,.*\]/: admin + /appendProperty\(java\.lang\.String,java\.lang\.String,java\.lang\.String\)\[org\.apache\.karaf\.service\.acl\..+,.*,.*\]/: admin + appendProperty(java.lang.String,java.lang.String,java.lang.String): admin + /create\(java\.lang\.String\)\[jmx\.acl\..*\]/: admin + /create\(java\.lang\.String\)\[org\.apache\.karaf\.command\.acl\..+\]/: admin + /create\(java\.lang\.String\)\[org\.apache\.karaf\.service\.acl\..+\]/: admin + create(java.lang.String): admin + /delete\(java\.lang\.String\)\[jmx\.acl\..*\]/: admin + /delete\(java\.lang\.String\)\[org\.apache\.karaf\.command\.acl\..+\]/: admin + /delete\(java\.lang\.String\)\[org\.apache\.karaf\.service\.acl\..+\]/: admin + delete(java.lang.String): admin + /deleteProperty\(java\.lang\.String,java\.lang\.String\)\[jmx\.acl\..*,.*\]/: admin + /deleteProperty\(java\.lang\.String,java\.lang\.String\)\[org\.apache\.karaf\.command\.acl\..+,.*\]/: admin + /deleteProperty\(java\.lang\.String,java\.lang\.String\)\[org\.apache\.karaf\.service\.acl\..+,.*\]/: admin + deleteProperty(java.lang.String,java.lang.String): admin + /setProperty\(java\.lang\.String,java\.lang\.String,java\.lang\.String\)\[jmx\.acl\..*,.*,.*\]/: admin + /setProperty\(java\.lang\.String,java\.lang\.String,java\.lang\.String\)\[org\.apache\.karaf\.command\.acl\..+,.*,.*\]/: admin + /setProperty\(java\.lang\.String,java\.lang\.String,java\.lang\.String\)\[org\.apache\.karaf\.service\.acl\..+,.*,.*\]/: admin + setProperty(java.lang.String,java.lang.String,java.lang.String): admin + /update\(java\.lang\.String,java\.util\.Map\)\[jmx\.acl\..*,.*\]/: admin + /update\(java\.lang\.String,java\.util\.Map\)\[org\.apache\.karaf\.command\.acl\..+,.*\]/: admin + /update\(java\.lang\.String,java\.util\.Map\)\[org\.apache\.karaf\.service\.acl\..+,.*\]/: admin + update(java.lang.String,java.util.Map): admin +org.apache.karaf.diagnostic: + createDump: admin, viewer +org.apache.karaf.feature: + infoFeature: admin, viewer + removeRepository: admin + addRepository: admin + uninstallFeature: admin + installFeature: admin + refreshRepository: admin +org.apache.karaf.instance: + createInstance: admin + destroyInstance: admin + startInstance: admin + stopInstance: admin + cloneInstance: admin + renameInstance: admin + changeSshPort: admin + changeSshHost: admin + ChangeRmiRegistryPort: admin + changeRmiServerPort: admin + changeJavaOpts: admin +org.apache.karaf.log: + getLevel: admin, viewer + setLevel: admin +org.apache.karaf.package: + getImports: admin, viewer + getExports: admin, viewer +org.apache.karaf.service: + getServices: admin, viewer +org.apache.karaf.system: + setProperty: admin + /getPropert.*/: admin, viewer + halt: admin + reboot: admin + rebootCleanCache: admin + rebootCleanAll: admin + +osgi.compendium.cm: + /createFactoryConfiguration\(java\.lang\.String\)\[jmx\.acl\..*\]/: admin + /createFactoryConfiguration\(java\.lang\.String\)\[org\.apache\.karaf\.command\.acl\.\..+\]/: admin + /createFactoryConfiguration\(java\.lang\.String\)\[org\.apache\.karaf\.service\.acl\.\..+\]/: admin + createFactoryConfiguration(java.lang.String): admin + /createFactoryConfigurationForLocation\(java\.lang\.String,java\.lang\.String\)\[jmx\.acl\..*,.*\]/: admin + /createFactoryConfigurationForLocation\(java\.lang\.String,java\.lang\.String\)\[org\.apache\.karaf\.command\.acl\..+,.*\]/: admin + /createFactoryConfigurationForLocation\(java\.lang\.String,java\.lang\.String\)\[org\.apache\.karaf\.service\.acl\..+,.*\]/: admin + createFactoryConfigurationForLocation(java.lang.String,java.lang.String): admin + /delete\(java\.lang\.String\)\[jmx\.acl\..*\]/: admin + /delete\(java\.lang\.String\)\[org\.apache\.karaf\.command\.acl\..+\]/: admin + /delete\(java\.lang\.String\)\[org\.apache\.karaf\.service\.acl\..+\]/: admin + delete(java.lang.String): admin + deleteConfigurations: admin + /deleteForLocation\(java\.lang\.String,java\.lang\.String\)\[jmx\.acl\..*,.*\]/: admin + /deleteForLocation\(java\.lang\.String,java\.lang\.String\)\[org\.apache\.karaf\.command\.acl\..+,.*\]/: admin + /deleteForLocation\(java\.lang\.String,java\.lang\.String\)\[org\.apache\.karaf\.service\.acl\..+,.*\]/: admin + deleteForLocation(java.lang.String,java.lang.String): admin + /update\(java\.lang\.String,javax\.management\.openmbean\.TabularData\)\[jmx\.acl\..*,.*\]/: admin + /update\(java\.lang\.String,javax\.management\.openmbean\.TabularData\)\[org\.apache\.karaf\.command\.acl\..+,.*\]/: admin + /update\(java\.lang\.String,javax\.management\.openmbean\.TabularData\)\[org\.apache\.karaf\.service\.acl\..+,.*\]/: admin + update(java.lang.String,javax.management.openmbean.TabularData): admin + /updateForLocation\(java\.lang\.String,java\.lang\.String,javax\.management\.openmbean\.TabularData\)\[jmx\.acl\..*,.*,.*\]/: admin + /updateForLocation\(java\.lang\.String,java\.lang\.String,javax\.management\.openmbean\.TabularData\)\[org\.apache\.karaf\.command\.acl\..+,.*,.*\]/: admin + /updateForLocation\(java\.lang\.String,java\.lang\.String,javax\.management\.openmbean\.TabularData\)\[org\.apache\.karaf\.service\.acl\..+,.*,.*\]/: admin + updateForLocation(java.lang.String,java.lang.String,javax.management.openmbean.TabularData): admin +osgi.core.bundleState: + getRequiredBundles: admin, viewer + getHosts: admin, viewer + getLocation: admin, viewer + getState: admin, viewer + getBundle: admin, viewer + getVersion: admin, viewer + getSymbolicName: admin, viewer + getRegisteredServices: admin, viewer + getServicesInUse: admin, viewer + getFragments: admin, viewer + getLastModified: admin, viewer + getHeaders: admin, viewer + getHeader: admin, viewer + getStartLevel: admin, viewer + getExportedPackages: admin, viewer + getRequiringBundles: admin, viewer + isFragment: admin, viewer + isRemovalPending: admin, viewer + isPersistentlyStarted: admin, viewer + isActivationPolicyUsed: admin, viewer + getImportedPackages: admin, viewer + isRequired: admin, viewer + listBundles: admin, viewer +osgi.core.framework: + startBundles: admin + getProperty: admin, viewer + resolve: admin + installBundle: admin + setBundleStartLevel: admin + startBundle: admin + updateBundle: admin + stopBundle: admin + stopBundle(long)[0]: [ ] # no roles can perform this operation + uninstallBundle: admin + resolveBundles: admin + getDependencyClosure: admin, viewer + refreshBundle: admin + refreshBundles: admin + installBundleFromURL: admin + installBundles: admin + installBundlesFromURL: admin + refreshBundleAndWait: admin + refreshBundlesAndWait: admin + resolveBundle: admin + restartFramework: admin + setBundleStartLevels: admin + shutdownFramework: admin + stopBundles: admin + uninstallBundles: admin + updateBundleFromURL: admin + updateBundles: admin + updateBundlesFromURL: admin +osgi.core.packageState: + getExportingBundles: admin, viewer + listPackages: admin, viewer + getImportingBundles: admin, viewer + isRemovalPending: admin, viewer +osgi.core.serviceState: + getProperty: admin, viewer + getProperties: admin, viewer + getService: admin, viewer + getBundleIdentifier: admin, viewer + getObjectClass: admin, viewer + listServices: admin, viewer + getUsingBundles: admin, viewer +osgi.core.wiringState: + getCurrentRevisionDeclaredCapabilities: admin, viewer + getCurrentWiringClosure: admin, viewer + getRevisionsDeclaredRequirements: admin, viewer + getRevisionsDeclaredCapabilities: admin, viewer + getRevisionsWiring: admin, viewer + getRevisionsWiringClosure: admin, viewer + getCurrentRevisionDeclaredRequirements: admin, viewer + getCurrentWiring: admin, viewer