Skip to content

NO-ISSUE: Working POC to start cluster installation via web ui #1781

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 12 additions & 32 deletions agent/06_agent_create_cluster.sh
Original file line number Diff line number Diff line change
Expand Up @@ -633,41 +633,21 @@ case "${AGENT_E2E_TEST_BOOT_MODE}" in

check_assisted_install_UI

# Temporarily create a dummy kubeconfig and kubeadmin-password file for the CI
auth_dir=$SCRIPTDIR/$OCP_DIR/auth
mkdir -p $auth_dir
cfg=$auth_dir/kubeconfig
cat << EOF >> ${cfg}
clusters:
- cluster:
certificate-authority-data: LS0tLS1CRUdJTiBGSUNBVLS0tLQo=
server: https://api.test.redhat.com:6443
name: test
contexts:
- context:
cluster: test
user: admin
name: admin
current-context: admin
preferences: {}
users:
- name: admin
user:
client-certificate-data: LS0tLS1CRUdJTiBNBVEUtLS0tLQo=
client-key-data: LS0tLS1CRUdJTiURSBVktLS0tLQo=
EOF
echo "dummy-kubeadmin-password" > $auth_dir/kubeadmin-password
go get github.com/go-rod/rod \
github.com/go-resty/resty/v2 \
github.com/go-rod/rod/lib/launcher \
github.com/go-rod/rod/lib/proto \
github.com/go-rod/rod/lib/utils

mkdir -p $OCP_DIR/auth
rendezvousIP=$(getRendezvousIP)
# Simulate user actions as done on the webUI and start cluster installation
RENDEZVOUS_IP=$rendezvousIP OCP_DIR=$OCP_DIR go run agent/isobuilder/ui_driven_cluster_installation.go
# To Do: Wait for the cluster install to finish
exit 0
;;
esac

if [[ "${AGENT_E2E_TEST_BOOT_MODE}" == "ISO_NO_REGISTRY" ]]; then
# Current goal is to only verify if the nodes are booted fine,
# TUI sets the rendezvous IP correctly and UI is accessible.
# The next goal is to simulate adding the cluster details via UI
# and complete the cluster installation.
exit 0
fi

if [ ! -z "${AGENT_TEST_CASES:-}" ]; then
run_agent_test_cases
fi
Expand Down
2 changes: 2 additions & 0 deletions agent/agent_post_install_validation.sh
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ source $SCRIPTDIR/common.sh

# Temp code skip the execution flow as cluster is not really installed
if [[ "${AGENT_E2E_TEST_BOOT_MODE}" == "ISO_NO_REGISTRY" ]]; then
# To Do: Using export KUBECONFIG="${SCRIPTDIR}/ocp/$CLUSTER_NAME/auth/kubeconfig" which is set after downloading the kubeconfig,
# wait untill we get nodes and cluster is completely installed
exit 0
fi

Expand Down
284 changes: 284 additions & 0 deletions agent/isobuilder/ui_driven_cluster_installation.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,284 @@
package main

import (
"fmt"
"os"
"path/filepath"
"strings"
"time"
"log"

resty "github.com/go-resty/resty/v2"
"github.com/go-rod/rod"
"github.com/go-rod/rod/lib/launcher"
"github.com/go-rod/rod/lib/proto"
"github.com/go-rod/rod/lib/utils"
)

func main() {
fmt.Println("Launching headless browser...")
url := launcher.New().Headless(true).MustLaunch()
browser := rod.New().ControlURL(url).MustConnect()
defer browser.MustClose()

rendezvousIP := os.Getenv("RENDEZVOUS_IP")
page := browser.MustPage(fmt.Sprintf("http://%s:3001/", rendezvousIP))
page.MustWaitLoad()

cwd, err := os.Getwd()
if err != nil {
log.Fatal(err)
}
ocpDir := os.Getenv("OCP_DIR")
screenshotPath := filepath.Join(cwd, ocpDir)

fmt.Println("==== Enter cluster details ====")
err = cluster_details(page, screenshotPath)
if err != nil {
log.Fatalf("failed to enter cluster details: %v", err)
}
next(page)

fmt.Println("==== Select virtualization bundle ====")
err = virtualization_bundle(page, screenshotPath)
if err != nil {
log.Fatalf("failed to select virtualization bundle: %v", err)
}

next(page)

fmt.Println("==== Await host discovery ====")
err = host_discovery(page, screenshotPath)
if err != nil {
log.Fatalf("failed awaiting host discovery: %v", err)
}

next(page)

fmt.Println("==== Verify storage ====")
err = verify_storage(page, screenshotPath)
if err != nil {
log.Fatalf("failed awaiting host discovery: %v", err)
}

next(page)

fmt.Println("==== Enter networking details ====")
err = networking_details(page, screenshotPath)
if err != nil {
log.Fatalf("failed entering networking details: %v", err)
}

next(page)

fmt.Println("==== Download credentials ====")
err = download_credentials(page, screenshotPath, rendezvousIP)
if err != nil {
log.Fatalf("failed downloading credentials: %v", err)
}
next(page)

fmt.Println("==== Review ====")
err =review(page, screenshotPath)
if err != nil {
log.Fatalf("failed review page: %v", err)
}

fmt.Println("==== Installation progress ====")
err = installation_progress(page, screenshotPath)
if err != nil {
log.Fatalf("failed review page: %v", err)
}
fmt.Println("Cluster installation started successfully.")
}

func cluster_details(page *rod.Page, screenshotPath string) error {
page.MustElement("#form-input-name-field").MustInput("abi-ove-isobuilder")
page.MustElement("#form-input-baseDnsDomain-field").MustInput("redhat.com")

pullSecretPath := os.Getenv("PULL_SECRET_FILE")
secretBytes, err := os.ReadFile(pullSecretPath)
if err != nil {
return fmt.Errorf("failed to read pull secret file: %v", err)
}
pullSecret := strings.TrimSpace(string(secretBytes))
page.MustElement("#form-input-pullSecret-field").MustInput(pullSecret)

path := filepath.Join(screenshotPath, "01-cluster-details.png")
err = saveFullPageScreenshot(page, path)
if err != nil {
return err
}
return nil
}

func virtualization_bundle(page *rod.Page, screenshotPath string) error {
page.MustElement(`#bundle-virtualization`).MustClick().MustWaitEnabled()
path := filepath.Join(screenshotPath, "02-operators.png")
err := saveFullPageScreenshot(page, path)
if err != nil {
return err
}
return nil
}

func host_discovery(page *rod.Page, screenshotPath string) error {
page.MustElement(`button[name="next"]`).MustWaitEnabled()
path := filepath.Join(screenshotPath, "03-hostDiscovery.png")
err := saveFullPageScreenshot(page, path)
if err != nil {
return err
}
return nil
}

func verify_storage(page *rod.Page, screenshotPath string) error {
path := filepath.Join(screenshotPath, "04-storage.png")
err := saveFullPageScreenshot(page, path)
if err != nil {
return err
}
return nil
}

func networking_details(page *rod.Page, screenshotPath string) error {
// To Do: Do not hardcode the VIPS
page.MustElement("#form-input-apiVips-0-ip-field").MustInput("192.168.111.50")
page.MustElement("#form-input-ingressVips-0-ip-field").MustInput("192.168.111.51")
page.MustElement(`button[name="next"]`).MustWaitEnabled()

path := filepath.Join(screenshotPath, "05-networking.png")
err := saveFullPageScreenshot(page, path)
if err != nil {
return err
}
return nil
}

func download_credentials(page *rod.Page, screenshotPath, rendezvousIP string) error {
page.MustElement(`#credentials-download-agreement`).MustClick()
page.MustElement(`button[name="next"]`).MustWaitEnabled()

path := filepath.Join(screenshotPath, "06-credentials.png")
err := saveFullPageScreenshot(page, path)
if err != nil {
return err
}
time.Sleep(30 * time.Second)

client := resty.New()
apiURL := fmt.Sprintf("http://%s:3001/api/assisted-install/v2/clusters/", rendezvousIP)
clusterID, err := getClusterID(client, apiURL)
if err != nil {
return err
}
apiURL = apiURL+clusterID

fmt.Println("Download credentials via api request")

downloadCredentials(client, apiURL, "kubeadmin-password")
time.Sleep(15 * time.Second)

downloadCredentials(client, apiURL, "kubeconfig")
time.Sleep(15 * time.Second)
return nil
}

func review(page *rod.Page, screenshotPath string) error {
path := filepath.Join(screenshotPath, "07-review.png")
err := saveFullPageScreenshot(page, path)
if err != nil {
return err
}
page.MustElement(`button[name="install"]`).MustClick()
fmt.Println("install button clicked...")
return nil
}

func installation_progress(page *rod.Page, screenshotPath string) error {
page.MustElementR(`h2[data-ouia-component-type="PF5/Text"]`, `Installation progress`)
path := filepath.Join(screenshotPath, "08-installation.png")
err := saveFullPageScreenshot(page, path)
if err != nil {
return err
}
return nil
}

func saveFullPageScreenshot(page *rod.Page, path string) error {
result, err := page.Evaluate(rod.Eval(`() => {
return {
width: document.body.scrollWidth,
height: document.body.scrollHeight
}
}`))
if err != nil {
return fmt.Errorf("failed to evaluate page size: %w", err)
}

width := int(result.Value.Get("width").Int())
height := int(result.Value.Get("height").Int())

err = page.SetViewport(&proto.EmulationSetDeviceMetricsOverride{
Width: int(width),
Height: int(height),
DeviceScaleFactor: 1,
Mobile: false,
})
if err != nil {
return fmt.Errorf("failed to set viewport: %w", err)
}

screenshot, err := page.Screenshot(false, nil)
if err != nil {
return fmt.Errorf("failed to take screenshot: %w", err)
}

if err := utils.OutputFile(path, screenshot); err != nil {
return fmt.Errorf("failed to save screenshot: %w", err)
}
fmt.Println("Screenshot saved to", path, ", with type of image/png")
return nil
}

func next(page *rod.Page){
page.MustElement(`button[name="next"]`).MustWaitEnabled().MustClick()
}

func getClusterID(client *resty.Client, apiURL string) (string, error) {
var clusters []struct {
ID string `json:"id"`
}

resp, err := client.R().
SetResult(&clusters).
Get(apiURL)
if err != nil {
return "", err
}

if resp.IsError() {
return "", err
}
return clusters[0].ID, nil
}

func downloadCredentials(client *resty.Client, apiURL, filename string){
fmt.Println("Downloading ", filename)

apiURL = apiURL+"/downloads/credentials?file_name="+filename
resp, err := client.R().Get(apiURL)
if err != nil {
fmt.Println("Request failed:", err)
return
}

downloadedFile:="/home/test/dev-scripts/ocp/ostest/auth/"+filename
err = os.WriteFile(downloadedFile, resp.Body(), 0644)
if err != nil {
fmt.Println("Failed to save file %s:", downloadedFile, err)
return
}
fmt.Println("File", downloadedFile, "downloaded successfully")
}

12 changes: 11 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,14 @@ require (
github.com/openshift/installer v1.4.17
)

require github.com/pkg/errors v0.9.1 // indirect
require (
github.com/go-resty/resty/v2 v2.16.5 // indirect
github.com/go-rod/rod v0.116.2 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/ysmood/fetchup v0.2.3 // indirect
github.com/ysmood/goob v0.4.0 // indirect
github.com/ysmood/got v0.40.0 // indirect
github.com/ysmood/gson v0.7.3 // indirect
github.com/ysmood/leakless v0.9.0 // indirect
golang.org/x/net v0.33.0 // indirect
)
17 changes: 17 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,6 +1,23 @@
github.com/apparentlymart/go-cidr v1.1.0 h1:2mAhrMoF+nhXqxTzSZMUzDHkLjmIHC+Zzn4tdgBZjnU=
github.com/apparentlymart/go-cidr v1.1.0/go.mod h1:EBcsNrHc3zQeuaeCeCtQruQm+n9/YjEn/vI25Lg7Gwc=
github.com/go-resty/resty/v2 v2.16.5 h1:hBKqmWrr7uRc3euHVqmh1HTHcKn99Smr7o5spptdhTM=
github.com/go-resty/resty/v2 v2.16.5/go.mod h1:hkJtXbA2iKHzJheXYvQ8snQES5ZLGKMwQ07xAwp/fiA=
github.com/go-rod/rod v0.116.2 h1:A5t2Ky2A+5eD/ZJQr1EfsQSe5rms5Xof/qj296e+ZqA=
github.com/go-rod/rod v0.116.2/go.mod h1:H+CMO9SCNc2TJ2WfrG+pKhITz57uGNYU43qYHh438Mg=
github.com/openshift/installer v1.4.17 h1:63iijBBgYqQX/p2+Q74gPqnfBN5VNSWX5LxQKuLlj6g=
github.com/openshift/installer v1.4.17/go.mod h1:CtlMEGKJDVMZl4qVBC/xMUXM24YnleT6bakI+KXFAhk=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/ysmood/fetchup v0.2.3 h1:ulX+SonA0Vma5zUFXtv52Kzip/xe7aj4vqT5AJwQ+ZQ=
github.com/ysmood/fetchup v0.2.3/go.mod h1:xhibcRKziSvol0H1/pj33dnKrYyI2ebIvz5cOOkYGns=
github.com/ysmood/goob v0.4.0 h1:HsxXhyLBeGzWXnqVKtmT9qM7EuVs/XOgkX7T6r1o1AQ=
github.com/ysmood/goob v0.4.0/go.mod h1:u6yx7ZhS4Exf2MwciFr6nIM8knHQIE22lFpWHnfql18=
github.com/ysmood/got v0.40.0 h1:ZQk1B55zIvS7zflRrkGfPDrPG3d7+JOza1ZkNxcc74Q=
github.com/ysmood/got v0.40.0/go.mod h1:W7DdpuX6skL3NszLmAsC5hT7JAhuLZhByVzHTq874Qg=
github.com/ysmood/gotrace v0.6.0/go.mod h1:TzhIG7nHDry5//eYZDYcTzuJLYQIkykJzCRIo4/dzQM=
github.com/ysmood/gson v0.7.3 h1:QFkWbTH8MxyUTKPkVWAENJhxqdBa4lYTQWqZCiLG6kE=
github.com/ysmood/gson v0.7.3/go.mod h1:3Kzs5zDl21g5F/BlLTNcuAGAYLKt2lV5G8D1zF3RNmg=
github.com/ysmood/leakless v0.9.0 h1:qxCG5VirSBvmi3uynXFkcnLMzkphdh3xx5FtrORwDCU=
github.com/ysmood/leakless v0.9.0/go.mod h1:R8iAXPRaG97QJwqxs74RdwzcRHT1SWCGTNqY8q0JvMQ=
golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I=
golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=