-
Notifications
You must be signed in to change notification settings - Fork 199
AGENT-1207: automate OVE cluster installation #1796
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
base: master
Are you sure you want to change the base?
Changes from all commits
ed2f14f
f57120c
a99c2a3
9cc6012
f687a9f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,9 +5,10 @@ SCRIPTDIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )/.." && pwd )" | |
|
||
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 | ||
exit 0 | ||
oc wait clusterversion version --for=condition=Available=True --timeout=60m | ||
oc get csv -A | ||
oc get packagemanifests -n openshift-marketplace | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What are these two commands for? It doesn't seem they are testing anything. I was expecting a more explicit check on the list of installed operators There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Could you suggest what else we should be testing? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @andfasano Created this task https://issues.redhat.com/browse/AGENT-1304 |
||
fi | ||
|
||
installed_control_plane_nodes=$(oc get nodes --selector=node-role.kubernetes.io/master | grep -v AGE | wc -l) | ||
|
@@ -18,3 +19,5 @@ if (( $NUM_MASTERS != $installed_control_plane_nodes )); then | |
echo "Post install validation failed. Expected $NUM_MASTERS control plane nodes but found $installed_control_plane_nodes." | ||
exit 1 | ||
fi | ||
|
||
oc get clusterversion | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. And also this one There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I thought this to be a good way to see the cluster version |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,324 @@ | ||
package main | ||
|
||
import ( | ||
"fmt" | ||
"log" | ||
"os" | ||
"path" | ||
"path/filepath" | ||
|
||
"errors" | ||
"strings" | ||
"time" | ||
|
||
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" | ||
|
||
"github.com/sirupsen/logrus" | ||
) | ||
|
||
var ( | ||
rendezvousIP = os.Getenv("RENDEZVOUS_IP") | ||
ocpDir = os.Getenv("OCP_DIR") | ||
baseURL = fmt.Sprintf("http://%s:3001", rendezvousIP) | ||
clustersURL = fmt.Sprintf("%s%s", baseURL, path.Join("/api/assisted-install/v2/clusters")) | ||
) | ||
|
||
func main() { | ||
logrus.Info("Launching headless browser...") | ||
url := launcher.New().NoSandbox(true).Headless(true).MustLaunch() | ||
browser := rod.New().ControlURL(url).MustConnect() | ||
|
||
defer browser.MustClose() | ||
|
||
page := browser.MustPage(baseURL) | ||
page.MustWaitLoad() | ||
|
||
cwd, err := os.Getwd() | ||
if err != nil { | ||
log.Fatal(err) | ||
} | ||
screenshotPath := filepath.Join(cwd, ocpDir) | ||
|
||
logrus.Info("Enter cluster details") | ||
err = clusterDetails(page, filepath.Join(screenshotPath, "01-cluster-details.png")) | ||
if err != nil { | ||
log.Fatalf("failed to enter cluster details: %v", err) | ||
} | ||
|
||
next(page) | ||
|
||
logrus.Info("Select virtualization bundle") | ||
err = virtualizationBundle(page, filepath.Join(screenshotPath, "02-operators.png")) | ||
if err != nil { | ||
log.Fatalf("failed to select virtualization bundle: %v", err) | ||
} | ||
|
||
next(page) | ||
|
||
logrus.Info("Await host discovery") | ||
err = hostDiscovery(page, filepath.Join(screenshotPath, "03-hostDiscovery.png")) | ||
if err != nil { | ||
log.Fatalf("failed awaiting host discovery: %v", err) | ||
} | ||
|
||
next(page) | ||
|
||
logrus.Info("Verify storage") | ||
err = verifyStorage(page, filepath.Join(screenshotPath, "04-storage.png")) | ||
if err != nil { | ||
log.Fatalf("failed awaiting host discovery: %v", err) | ||
} | ||
|
||
next(page) | ||
|
||
logrus.Info("Enter networking details") | ||
err = networkingDetails(page, filepath.Join(screenshotPath, "05-networking.png")) | ||
if err != nil { | ||
log.Fatalf("failed entering networking details: %v", err) | ||
} | ||
|
||
next(page) | ||
|
||
logrus.Info("Download credentials") | ||
client := resty.New() | ||
err = downloadCredentials(page, client, filepath.Join(screenshotPath, "06-credentials.png")) | ||
if err != nil { | ||
log.Fatalf("failed downloading credentials: %v", err) | ||
} | ||
|
||
next(page) | ||
|
||
logrus.Info("Review") | ||
err = review(page, filepath.Join(screenshotPath, "07-review.png")) | ||
if err != nil { | ||
log.Fatalf("failed review page: %v", err) | ||
} | ||
|
||
logrus.Info("Start Cluster Installation") | ||
err = startInstallation(page, client, filepath.Join(screenshotPath, "08-installation.png")) | ||
if err != nil { | ||
log.Fatalf("failed to start installation page: %v", err) | ||
} | ||
logrus.Info("Cluster installation started successfully.") | ||
|
||
err = waitForClusterConsoleLink(page, filepath.Join(screenshotPath, "09-installation-progress.png")) | ||
if err != nil { | ||
log.Fatalf("%v", err) | ||
} | ||
} | ||
|
||
func clusterDetails(page *rod.Page, path 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) | ||
|
||
err = saveFullPageScreenshot(page, path) | ||
if err != nil { | ||
return err | ||
} | ||
return nil | ||
} | ||
|
||
func virtualizationBundle(page *rod.Page, path string) error { | ||
page.MustElement(`#bundle-virtualization`).MustClick().MustWaitEnabled() | ||
err := saveFullPageScreenshot(page, path) | ||
if err != nil { | ||
return err | ||
} | ||
return nil | ||
} | ||
|
||
func hostDiscovery(page *rod.Page, path string) error { | ||
page.MustElement(`button[name="next"]`).MustWaitEnabled() | ||
err := saveFullPageScreenshot(page, path) | ||
if err != nil { | ||
return err | ||
} | ||
return nil | ||
} | ||
|
||
func verifyStorage(page *rod.Page, path string) error { | ||
err := saveFullPageScreenshot(page, path) | ||
if err != nil { | ||
return err | ||
} | ||
return nil | ||
} | ||
|
||
func networkingDetails(page *rod.Page, path string) error { | ||
apiVip := os.Getenv("API_VIP") | ||
ingressVip := os.Getenv("INGRESS_VIP") | ||
page.MustElement("#form-input-apiVips-0-ip-field").MustInput(apiVip) | ||
page.MustElement("#form-input-ingressVips-0-ip-field").MustInput(ingressVip) | ||
page.MustElement(`button[name="next"]`).MustWaitEnabled() | ||
|
||
err := saveFullPageScreenshot(page, path) | ||
if err != nil { | ||
return err | ||
} | ||
return nil | ||
} | ||
|
||
func downloadCredentials(page *rod.Page, client *resty.Client, path string) error { | ||
page.MustElement(`#credentials-download-agreement`).MustClick() | ||
page.MustElement(`button[name="next"]`).MustWaitEnabled() | ||
|
||
err := saveFullPageScreenshot(page, path) | ||
if err != nil { | ||
return err | ||
} | ||
time.Sleep(30 * time.Second) | ||
|
||
clusterID, err := getClusterID(client, clustersURL) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
logrus.Info("Download credentials via api request") | ||
|
||
fileURL := fmt.Sprintf("%s/%s/downloads/credentials?file_name=", clustersURL, clusterID) | ||
saveCredentials(client, fileURL, "kubeadmin-password") | ||
time.Sleep(15 * time.Second) | ||
|
||
saveCredentials(client, fileURL, "kubeconfig") | ||
time.Sleep(15 * time.Second) | ||
return nil | ||
} | ||
|
||
func review(page *rod.Page, path string) error { | ||
err := saveFullPageScreenshot(page, path) | ||
if err != nil { | ||
return err | ||
} | ||
page.MustElement(`button[name="install"]`).MustClick() | ||
logrus.Info("Install button clicked") | ||
return nil | ||
} | ||
|
||
func startInstallation(page *rod.Page, client *resty.Client, path string) error { | ||
page.MustElementR(`h2[data-ouia-component-type="PF5/Text"]`, `Installation progress`) | ||
err := saveFullPageScreenshot(page, path) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
return nil | ||
} | ||
|
||
func waitForClusterConsoleLink(page *rod.Page, path string) error { | ||
for { | ||
failMsg, _ := page.Timeout(5 * time.Second).ElementR("#cluster-progress-status-value", `Failed on`) | ||
if failMsg != nil { | ||
if visible, _ := failMsg.Visible(); visible { | ||
if err := saveFullPageScreenshot(page, path); err != nil { | ||
return err | ||
} | ||
return errors.New("cluster installation failed") | ||
} | ||
} | ||
|
||
consoleURL, _ := page.Timeout(5 * time.Second).ElementR("button.pf-v5-c-button", `https://console-openshift-console.apps.abi-ove-isobuilder.redhat.com`) | ||
if consoleURL != nil { | ||
if visible, _ := consoleURL.Visible(); visible { | ||
logrus.Info("Console URL is available.") | ||
break | ||
} | ||
} | ||
|
||
logrus.Info("Cluster installation in progress. Waiting for console URL to be available.") | ||
time.Sleep(5 * time.Minute) | ||
} | ||
|
||
if err := saveFullPageScreenshot(page, path); err != nil { | ||
return err | ||
} | ||
|
||
return nil | ||
} | ||
|
||
func next(page *rod.Page) { | ||
page.MustElement(`button[name="next"]`).MustWaitEnabled().MustClick() | ||
} | ||
|
||
// saveFullPageScreenshot captures a full-page screenshot and saves it to the given path. | ||
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) | ||
} | ||
logrus.Info("Screenshot saved to", path, ", with type of image/png") | ||
return nil | ||
} | ||
|
||
// getClusterID fetches the first cluster ID from the given URL using the provided client. | ||
func getClusterID(client *resty.Client, url string) (string, error) { | ||
var clusters []struct { | ||
ID string `json:"id"` | ||
} | ||
|
||
_, err := client.R().SetResult(&clusters).Get(url) | ||
if err != nil { | ||
return "", err | ||
} | ||
|
||
return clusters[0].ID, nil | ||
} | ||
|
||
// saveCredentials downloads a file from the given URL and saves it under the auth directory. | ||
func saveCredentials(client *resty.Client, url, filename string) { | ||
logrus.Info("Downloading ", filename) | ||
|
||
fileURL := fmt.Sprintf("%s%s", url, filename) | ||
resp, err := client.R().Get(fileURL) | ||
if err != nil { | ||
logrus.Info("Request failed:", err) | ||
return | ||
} | ||
|
||
downloadedFile := fmt.Sprintf("%s/auth/%s", ocpDir, filename) | ||
err = os.WriteFile(downloadedFile, resp.Body(), 0644) | ||
if err != nil { | ||
logrus.Errorf("Failed to save file %s: %v", downloadedFile, err) | ||
return | ||
} | ||
logrus.Info("File ", downloadedFile, " downloaded successfully") | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not a blocking point, but wouldn't it have been simpler to pass them as a regular args?