diff --git a/agent/05_agent_configure.sh b/agent/05_agent_configure.sh index 56db3be28..66cf15853 100755 --- a/agent/05_agent_configure.sh +++ b/agent/05_agent_configure.sh @@ -46,7 +46,8 @@ function add_dns_entry { fi # Add entries to etc/hosts for SNO IPV6 to sucessfully run the openshift conformance tests - if [[ $NUM_MASTERS == 1 && $IP_STACK == "v6" ]]; then + # Temp change added to also check v4 in case of SNO for OVE + if [[ $NUM_MASTERS == 1 ]] && [[ $IP_STACK == "v4" || $IP_STACK == "v6" ]]; then AGENT_NODE0_IPSV6=${ip} echo "${ip} console-openshift-console.apps.${CLUSTER_DOMAIN}" | sudo tee -a /etc/hosts echo "${ip} oauth-openshift.apps.${CLUSTER_DOMAIN}" | sudo tee -a /etc/hosts @@ -682,7 +683,9 @@ if [[ "${AGENT_E2E_TEST_BOOT_MODE}" != "ISO_NO_REGISTRY" ]] ; then else # For ISO_NO_REGISTRY mode with workers, add api-int to libvirt DNS # Workers need to resolve api-int during bootstrap before joining the cluster + if [[ "${NUM_MASTERS}" > "1" ]]; then add_dns_entry "${API_VIPS%${VIPS_SEPARATOR}*}" "api-int" + fi fi enable_isolated_baremetal_network diff --git a/agent/06_agent_create_cluster.sh b/agent/06_agent_create_cluster.sh index 6e06ffee9..ef3746f7c 100755 --- a/agent/06_agent_create_cluster.sh +++ b/agent/06_agent_create_cluster.sh @@ -212,7 +212,7 @@ function automate_rendezvousIP_selection(){ function check_assisted_install_UI(){ local rendezvousIP=$(getRendezvousIP) - local url="http://$rendezvousIP:3001" + local url="http://$(wrap_if_ipv6 ${rendezvousIP}):3001" while true; do if curl -s -o /dev/null -w "%{http_code}" "$url" | grep -q "^200$"; then echo "Assisted install UI is up: $url" diff --git a/agent/cleanup.sh b/agent/cleanup.sh index b463ffb37..3979630b4 100755 --- a/agent/cleanup.sh +++ b/agent/cleanup.sh @@ -47,7 +47,7 @@ sudo firewall-cmd --zone libvirt --remove-port=${MACHINE_CONFIG_SERVER_PORT}/tcp sudo firewall-cmd --zone libvirt --remove-port=${KUBE_API_PORT}/tcp sudo firewall-cmd --zone libvirt --remove-port=${INGRESS_ROUTER_PORT}/tcp -if [[ $NUM_MASTERS == 1 && $IP_STACK == "v6" ]]; then +if [[ $NUM_MASTERS == 1 ]] && [[ $IP_STACK == "v4" || $IP_STACK == "v6" ]]; then sudo sed -i "/${AGENT_NODE0_IPSV6} console-openshift-console.apps.${CLUSTER_DOMAIN}/d" /etc/hosts sudo sed -i "/${AGENT_NODE0_IPSV6} oauth-openshift.apps.${CLUSTER_DOMAIN}/d" /etc/hosts sudo sed -i "/${AGENT_NODE0_IPSV6} thanos-querier-openshift-monitoring.apps.${CLUSTER_DOMAIN}/d" /etc/hosts diff --git a/agent/common.sh b/agent/common.sh index ee39207f0..3dc67b119 100644 --- a/agent/common.sh +++ b/agent/common.sh @@ -68,7 +68,7 @@ function getReleaseImage() { # If not installing from src, let's use the current version from the binary elif [ -z "$KNI_INSTALL_FROM_GIT" ]; then local openshift_install="$(realpath "${OCP_DIR}/openshift-install")" - releaseImage=$("${openshift_install}" --dir="${OCP_DIR}" version | grep "release image" | cut -d " " -f 3) + releaseImage=$("${openshift_install}" --dir="${OCP_DIR}" version | grep "release image" | cut -d " " -f 3) fi echo ${releaseImage} } @@ -92,11 +92,16 @@ if [ "${AGENT_E2E_TEST_BOOT_MODE}" == "ISO_NO_REGISTRY" ] ; then if [[ -z "${NETWORKING_MODE}" ]]; then export NETWORKING_MODE="DHCP" fi + export MIRROR_IMAGES=false fi function getRendezvousIP() { node_zero_mac_address=$(sudo virsh domiflist ${AGENT_RENDEZVOUS_NODE_HOSTNAME} | awk '$3 == "ostestbm" {print $5}') rendezvousIP=$(ip neigh | grep $node_zero_mac_address | awk '{print $1}') + if [[ "${AGENT_E2E_TEST_BOOT_MODE}" == "ISO_NO_REGISTRY" ]] && [[ "${IP_STACK}" == "v6" ]]; then + # Filter out link-local addresses and get global/ULA IPv6 + rendezvousIP=$(echo "$rendezvousIP" | tr ' ' '\n' | grep -vE '^fe[89ab][0-9a-f]:' | head -n1) + fi echo $rendezvousIP | awk '{print $1}' } diff --git a/agent/isobuilder/ui_driven_cluster_installation/main.go b/agent/isobuilder/ui_driven_cluster_installation/main.go index 0b73e938e..d020d25e1 100644 --- a/agent/isobuilder/ui_driven_cluster_installation/main.go +++ b/agent/isobuilder/ui_driven_cluster_installation/main.go @@ -45,7 +45,7 @@ var ( ipStack = os.Getenv("IP_STACK") workers = os.Getenv("NUM_WORKERS") baseURL = fmt.Sprintf("http://%s:3001", rendezvousIP) - clustersURL = fmt.Sprintf("%s%s", baseURL, path.Join("/api/assisted-install/v2/clusters")) + clustersURL = fmt.Sprintf("%s%s", getBaseURL(ipStack, rendezvousIP), path.Join("/api/assisted-install/v2/clusters")) ) // timestampedPath adds a timestamp to the filename @@ -179,7 +179,7 @@ func main() { browser := rod.New().ControlURL(url).MustConnect() defer browser.MustClose() - page := browser.MustPage(baseURL) + page := browser.MustPage(getBaseURL(ipStack, rendezvousIP)) page.MustWaitLoad() cwd, err := os.Getwd() @@ -203,18 +203,15 @@ func main() { // Wait for page to load and check if cluster creation completed in time logrus.Info("Waiting for operators page to load...") - time.Sleep(3 * time.Second) + wait(10 * time.Second) + reload(page) // Check if we got an error page because cluster wasn't created in time errorMsg, _ := page.Timeout(2*time.Second).ElementR("div", "Cluster details not found") if errorMsg != nil { logrus.Info("Cluster not ready yet, waiting and reloading...") - // Wait longer for cluster creation to complete - time.Sleep(5 * time.Second) - // Reload the page - page.MustReload() - page.MustWaitLoad() - logrus.Info("Page reloaded, continuing...") + wait(5 * time.Second) + reload(page) } logrus.Info("Select virtualization bundle") @@ -271,7 +268,7 @@ func main() { stepNum++ // Wait for page to load - time.Sleep(3 * time.Second) + wait(5 * time.Second) // Check if we're on Custom manifests page (4.22+) or Review page (< 4.22) customManifestsHeading, _ := page.Timeout(2*time.Second).ElementR("h2", "Custom manifests") @@ -362,7 +359,7 @@ func clusterDetails(page *rod.Page, path string) error { // Clear template content and enter pull secret pullSecretField.MustSelectAllText() pullSecretField.MustInput("") - time.Sleep(500 * time.Millisecond) + wait(500 * time.Millisecond) pullSecretField.MustInput(`{"auths":{"":{"auth":"dXNlcjpwYXNz"}}}`) } else { @@ -380,7 +377,7 @@ func clusterDetails(page *rod.Page, path string) error { } // Allow UI enough time to complete the background API call to create the cluster - time.Sleep(10 * time.Second) + wait(10 * time.Second) page.MustElement("button[name='next']").MustWaitEnabled() err = saveFullPageScreenshot(page, timestampedPath(path, "end")) @@ -471,12 +468,35 @@ func virtualizationBundle(page *rod.Page, path string) error { return err } - checkbox := page.MustElement("#bundle-virtualization") - checkbox.MustScrollIntoView() - checkbox.MustClick() - // Allow UI enough time to complete the background API call - time.Sleep(2 * time.Second) - page.MustElement("button[name='next']").MustWaitEnabled() + if getControlPlaneCount() > 1 { + checkbox := page.MustElement("#bundle-virtualization") + checkbox.MustScrollIntoView() + checkbox.MustClick() + // Allow UI enough time to complete the background API call + wait(5 * time.Second) + } else { + logrus.Info("Looking for Single Operators button (SNO)...") + wait(5 * time.Second) + + singleOpsButton, err := page.Timeout(10*time.Second).ElementR("button", "Single Operators") + if err != nil { + return fmt.Errorf("Single Operators button not found: %v", err) + } + + err = singleOpsButton.WaitVisible() + if err != nil { + return fmt.Errorf("Single Operators button not visible: %v", err) + } + + logrus.Info("Found Single Operators button") + err = selectAllEnabledOperators(page, singleOpsButton) + if err != nil { + return err + } + } + + wait(5 * time.Second) + page.MustElement("button[name='next']").MustWaitEnabled().MustScrollIntoView().MustWaitEnabled() err = saveFullPageScreenshot(page, timestampedPath(path, "end")) if err != nil { @@ -505,6 +525,7 @@ func verifyStorage(page *rod.Page, path string) error { if err != nil { return err } + page.MustElement("button[name='next']").MustWaitEnabled() err = saveFullPageScreenshot(page, timestampedPath(path, "end")) if err != nil { @@ -522,8 +543,15 @@ func networkingDetails(page *rod.Page, path string) error { apiVip := apiVips ingressVip := ingressVips - if ipStack == "v4v6" { + if ipStack == "v4" { + page.MustElement("#form-radio-stackType-singleStack-field") + logrus.Info("Selected IPV4 networking stack") + if getControlPlaneCount() == 1 { + logrus.Infof("Using default single-stack IPv4 networking (IP_STACK=%s) for SNO", ipStack) + } + } else if ipStack == "v4v6" { dualStackRadio := page.MustElement("#form-radio-stackType-dualStack-field") + logrus.Info("Selected dualstack networking stack") dualStackRadio.MustClick() err = saveFullPageScreenshot(page, timestampedPath(path, "after-dualstack")) if err != nil { @@ -531,26 +559,26 @@ func networkingDetails(page *rod.Page, path string) error { } apiVip = strings.Split(apiVips, ",")[0] ingressVip = strings.Split(ingressVips, ",")[0] - } else { - logrus.Infof("Using default single-stack IPv4 networking (IP_STACK=%s)", ipStack) } - apiVipField := page.MustElement("#form-input-apiVips-0-ip-field") - apiVipField.MustWaitVisible() - apiVipField.MustInput(apiVip) + if getControlPlaneCount() > 1 { + apiVipField := page.MustElement("#form-input-apiVips-0-ip-field") + apiVipField.MustWaitVisible() + apiVipField.MustInput(apiVip) - err = saveFullPageScreenshot(page, timestampedPath(path, "after-apivip")) - if err != nil { - return err - } + err = saveFullPageScreenshot(page, timestampedPath(path, "after-apivip")) + if err != nil { + return err + } - ingressVipField := page.MustElement("#form-input-ingressVips-0-ip-field") - ingressVipField.MustWaitVisible() - ingressVipField.MustInput(ingressVip) + ingressVipField := page.MustElement("#form-input-ingressVips-0-ip-field") + ingressVipField.MustWaitVisible() + ingressVipField.MustInput(ingressVip) - err = saveFullPageScreenshot(page, timestampedPath(path, "after-ingressvip")) - if err != nil { - return err + err = saveFullPageScreenshot(page, timestampedPath(path, "after-ingressvip")) + if err != nil { + return err + } } page.MustElement("#form-input-sshPublicKey-field").MustInput(sshPublicKey) @@ -586,7 +614,7 @@ func downloadCredentials(page *rod.Page, client *resty.Client, path string) erro } page.MustElement("#credentials-download-agreement").MustClick() - time.Sleep(5 * time.Second) + wait(5 * time.Second) page.MustElementR("button", "Download credentials").MustWaitEnabled().MustClick() @@ -645,7 +673,7 @@ func waitForClusterConsoleLink(page *rod.Page, path string) error { return err } i++ - time.Sleep(5 * time.Minute) + wait(5 * time.Minute) } return nil @@ -718,7 +746,7 @@ func saveCredentials(client *resty.Client, url, filename string) error { logrus.Infof("%s download attempts %d/%d", filename, i+1, downloadAttempts) resp, err := client.R().Get(fileURL) if err != nil || resp.StatusCode() != http.StatusOK { - time.Sleep(10 * time.Second) + wait(10 * time.Second) continue } if resp.StatusCode() == http.StatusOK { @@ -796,7 +824,7 @@ func runDownloadLogs() { defer browser.MustClose() // Navigate to base URL - UI will redirect to current cluster - page := browser.MustPage(baseURL) + page := browser.MustPage(getBaseURL(ipStack, rendezvousIP)) page.MustWaitLoad() logrus.Info("Connected to Assisted Installer UI") @@ -814,3 +842,55 @@ func runDownloadLogs() { logrus.Info("Download-logs mode complete") } + +func wait(timeToWait time.Duration) { + logrus.Infof("Waiting for %s ...", timeToWait) + time.Sleep(timeToWait) +} + +func reload(page *rod.Page) { + page.MustReload() + page.MustWaitLoad() + logrus.Info("Page reloaded, continuing...") +} + +func selectAllEnabledOperators(page *rod.Page, expandButton *rod.Element) error { + expandButton.MustWaitStable() + logrus.Info("Single Operators button is stable") + + ariaExpanded, _ := expandButton.Attribute("aria-expanded") + if ariaExpanded == nil || *ariaExpanded == "false" { + logrus.Info("Expanding Single Operators section...") + expandButton.MustClick() + + // Wait for expansion animation + wait(1 * time.Second) + + logrus.Info("Single Operators section expanded") + } else { + logrus.Info("Single Operators section already expanded") + } + + // Select all enabled, unchecked checkboxes + checkboxes := page.MustElements(`input[data-testid^="operator-checkbox-"]:not([disabled])`) + logrus.Infof("Found %d enabled checkboxes", len(checkboxes)) + + for i, checkbox := range checkboxes { + checked, _ := checkbox.Property("checked") + if !checked.Bool() { + checkbox.MustClick() + logrus.Infof("Checked checkbox %d", i+1) + wait(100 * time.Millisecond) + } + } + + logrus.Info("All enabled operators selected") + return nil +} + +func getBaseURL(ipStack, rendezvousIP string) string { + if ipStack == "v6" { + return fmt.Sprintf("http://[%s]:3001", rendezvousIP) + } + return fmt.Sprintf("http://%s:3001", rendezvousIP) +} diff --git a/network.sh b/network.sh index 353c8ddd6..ce270f7c0 100755 --- a/network.sh +++ b/network.sh @@ -313,7 +313,7 @@ function configure_dnsmasq() { rm -f "${PATH_CONF_DNSMASQ}" add_dnsmasq_multi_entry "apivip" "${apiVips}" - if [[ "${AGENT_E2E_TEST_BOOT_MODE}" == "ISO_NO_REGISTRY" ]] ; then + if [[ ${AGENT_E2E_TEST_BOOT_MODE} == "ISO_NO_REGISTRY" ]] && [[ "${NUM_MASTERS}" > "1" ]]; then add_dnsmasq_multi_entry "apiintvip" "${apiVips}" fi add_dnsmasq_multi_entry "ingressvip" "${ingressVips}"