Skip to content
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

Automation mssql #141

Open
wants to merge 89 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 84 commits
Commits
Show all changes
89 commits
Select commit Hold shift + click to select a range
b8dfc3f
Added basic automation
manavrajvanshi Jun 15, 2023
07fce5b
Updated teardown
manavrajvanshi Jun 15, 2023
e714be1
Merge branch 'main' into automation
manavrajvanshi Jun 22, 2023
00b762c
Addressed review comments
manavrajvanshi Jun 27, 2023
eb01630
Added minor comment to DatabaseInterface
mazin-s Jul 25, 2023
c11ff90
Changed databaseInstanceName
mazin-s Jul 25, 2023
5fab2f5
added comments, and error check
mazin-s Jul 26, 2023
25e7785
Changed comments
mazin-s Jul 26, 2023
5954e08
Changed function names
mazin-s Jul 26, 2023
8f1cf7c
Deleted some error handelining not needed
mazin-s Jul 26, 2023
33d7b43
Setup Path
mazin-s Jul 26, 2023
fcc409a
changed to setuppath
mazin-s Jul 26, 2023
2d637f5
added setupconfig structure
mazin-s Jul 26, 2023
5cdb09d
Added setupConfig case to ConverBytesToType, ConvertBytesToSetUpConfi…
mazin-s Jul 26, 2023
2d1e67e
Removed simple comments
mazin-s Jul 26, 2023
8707e09
added sandbox for putting secrets
mazin-s Jul 26, 2023
33827ed
added comment to test suite
mazin-s Jul 26, 2023
1cdb04b
As suggested by Yash, method should not be exported
mazin-s Jul 26, 2023
b765942
save changes
mazin-s Jul 27, 2023
96c5669
removed a log i was using to help me
mazin-s Jul 27, 2023
dc8a10f
Not needed methods I created
mazin-s Jul 27, 2023
8cfe133
Helpers changed
mazin-s Aug 3, 2023
56ee0f9
Renamed SetupPaths
mazin-s Aug 3, 2023
654339f
Added some logs and deleted some logs
mazin-s Aug 3, 2023
d8cdf9b
Added generic function
mazin-s Aug 3, 2023
1ac5eec
Add public key
mazin-s Aug 3, 2023
56e2c5f
Changed makefile to test pg_si
mazin-s Aug 3, 2023
0217d3a
Changed method names to capital
mazin-s Aug 3, 2023
e5ac2b5
Restructured tests
mazin-s Aug 3, 2023
bf1aabd
Changed to test mssql_si
mazin-s Aug 3, 2023
b3c58c5
Added logs
mazin-s Aug 3, 2023
eee5843
rough copy of tests
mazin-s Aug 3, 2023
2b75a6b
changed yaml
mazin-s Aug 3, 2023
7381762
mssql e2e support
mazin-s Aug 4, 2023
3d560ab
constant files added
mazin-s Aug 4, 2023
570c73a
Hiding mssql 2e2 test
mazin-s Aug 4, 2023
4e9c5ed
Removed mssql test
mazin-s Aug 4, 2023
6d3a13f
e2e postgres passes
mazin-s Aug 4, 2023
2fdef75
Merge branch 'main' into automation
mazin-s Aug 4, 2023
78ab12c
Added profiles
mazin-s Aug 4, 2023
60d8bb2
delted line in git ignore
mazin-s Aug 4, 2023
7122b24
Merge branch 'main' into automation
manavrajvanshi Aug 5, 2023
662520e
Error check added
mazin-s Aug 7, 2023
2db14f9
Made make-test run whole automation file instead of indivdual test
mazin-s Aug 7, 2023
dd47d8c
Removed ssh
mazin-s Aug 7, 2023
6537b77
CreateTypeFromPath instead of CreateGeneric
mazin-s Aug 7, 2023
867da7a
Removed some logs
mazin-s Aug 7, 2023
bf5f28a
removed because not using right now, will include in next PR
mazin-s Aug 8, 2023
25755f3
changed make file test
mazin-s Aug 8, 2023
810ab59
Renamed from generic to theType
mazin-s Aug 8, 2023
547a88e
Added comments
mazin-s Aug 8, 2023
f3b55d6
Deleted a comment and added one
mazin-s Aug 8, 2023
67d9d2c
changed comment
mazin-s Aug 8, 2023
52f1fde
Moved yaml files to pg_si test
mazin-s Aug 8, 2023
b73c202
Change yaml property names
mazin-s Aug 8, 2023
74b1ac7
Refactored comments and code structure
mazin-s Aug 8, 2023
d346c0d
Added comments
mazin-s Aug 9, 2023
274fea9
Added more comments
mazin-s Aug 9, 2023
272e536
Better commenting and spacing
mazin-s Aug 9, 2023
204ea48
.gitignore changed
mazin-s Aug 9, 2023
64aaa8e
Refactored config file names to make them consistent, added a const f…
mazin-s Aug 10, 2023
9930810
Changed makefile and .ignote to accoutn for extra tests folder
mazin-s Aug 10, 2023
ae09d20
changed file path
mazin-s Aug 10, 2023
b0207ae
Edited CreateTypeFromPath to throw and error if theType is not a poin…
mazin-s Aug 14, 2023
f582d11
Elimnated unecessary line
mazin-s Aug 14, 2023
3e1fabc
Deleted readYamlFile function and using new os package because old me…
mazin-s Aug 14, 2023
0da4e2b
Addded 'Provisioning' in front of TestSetup and TestTeardown functions
mazin-s Aug 14, 2023
ae4b3c8
Merge branch 'main' into automation
mazin-s Aug 15, 2023
338bc94
Got rid of suite.kubeconfig
mazin-s Aug 15, 2023
9562dba
Added better error messages
mazin-s Aug 15, 2023
42e800d
Renamed 'setup' file to 'provisioning' file and moved waitAndRetryOpe…
mazin-s Aug 15, 2023
b58b9a1
Refactored function logic
mazin-s Aug 16, 2023
b038884
Basic mssql-si_test scafolding
mazin-s Aug 16, 2023
42cd5aa
mssql-si_test
mazin-s Aug 17, 2023
1d1685b
changed mssql pod
mazin-s Aug 17, 2023
7b97312
Change sized
mazin-s Aug 22, 2023
90d1fc9
Deleted nodePort and changed size
mazin-s Aug 22, 2023
d806b0e
appConnectivity uses kubectl port-forward
mazin-s Aug 23, 2023
012c841
Deleted not needed services
mazin-s Aug 23, 2023
339c943
Deleting service before database in teardown. Also removing service c…
mazin-s Aug 23, 2023
b753c74
Removing service creation
mazin-s Aug 23, 2023
c180f10
Removed ID
mazin-s Aug 23, 2023
bfc5fc0
Changed GetAppResponse arguments
mazin-s Aug 23, 2023
89f569a
public key placeholder
mazin-s Aug 23, 2023
d818f43
Made timeout to 90 instead of 60
mazin-s Aug 28, 2023
e950fce
waitAndRetry with 80 argument instead of 60
mazin-s Aug 28, 2023
682c867
using my image instead
mazin-s Aug 28, 2023
c25e70b
added a description field
mazin-s Aug 28, 2023
56c4de9
Moved appConnectivity, provisioning, and setup to util, changed file …
mazin-s Sep 13, 2023
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
66 changes: 66 additions & 0 deletions automation/appConnectivity.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package automation

import (
"context"
"errors"
"fmt"
"io"
"net/http"
"os/exec"
"time"

v1 "k8s.io/api/core/v1"
"k8s.io/client-go/kubernetes"
)

// Tests if 'manavrajvanshinx/best-app:latest' is able to connect to database
func GetAppResponse(ctx context.Context, clientset *kubernetes.Clientset, localPort string) (res http.Response, err error) {
logger := GetLogger(ctx)
logger.Println("TestAppConnectivity() started...")

// Create appPod template to retrieve the pod name and targetPort
appPod := &v1.Pod{}
if err := CreateTypeFromPath(appPod, APP_POD_PATH); err != nil {
return http.Response{}, fmt.Errorf("GetAppResponse() ended! App Pod with path %s failed! %v. ", APP_POD_PATH, err)
} else {
logger.Printf("App Pod with path %s created. ", APP_POD_PATH)
}

// Retrieve te pod name and targetPort
podName := appPod.Name
podTargetPort := appPod.Spec.Containers[0].Ports[0].ContainerPort

// Run port-forward command using kubectl
cmd := exec.Command("kubectl", "port-forward", podName, fmt.Sprintf("%s:%d", localPort, podTargetPort))
err = cmd.Start()
if err != nil {
return http.Response{}, fmt.Errorf("'kubectl port-forward %s %s:%d' failed! %v. ", podName, localPort, podTargetPort, err)
} else {
logger.Printf("kubectl port-forward %s %s:%d succesful.", podName, localPort, podTargetPort)
}

// Wait for a brief period to let port-forwarding start
time.Sleep(2 * time.Second)

// Verify the forwarded port by making an HTTP request
url := fmt.Sprintf("http://localhost:%s", localPort)
mazin-s marked this conversation as resolved.
Show resolved Hide resolved
resp, err := http.Get(url)

if err != nil {
return http.Response{}, fmt.Errorf("http://localhost:%s failed! %v,", localPort, err)
} else {
logger.Printf("http://localhost:%s succesful.", localPort)
}

defer resp.Body.Close()

// Read and print the response body
body, err := io.ReadAll(resp.Body)
if err != nil {
return http.Response{}, errors.New(fmt.Sprintf("GetAppResponse() ended! Error while reading response body: %s", err))
} else {
logger.Println("Response: ", string(body))
}

return *resp, nil
}
295 changes: 295 additions & 0 deletions automation/provisioning.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,295 @@
package automation

import (
"context"
"errors"
"fmt"
"os"
"testing"
"time"

"github.com/nutanix-cloud-native/ndb-operator/api/v1alpha1"
ndbv1alpha1 "github.com/nutanix-cloud-native/ndb-operator/api/v1alpha1"
clientsetv1alpha1 "github.com/nutanix-cloud-native/ndb-operator/automation/clientset/v1alpha1"
"github.com/nutanix-cloud-native/ndb-operator/common"
"github.com/nutanix-cloud-native/ndb-operator/ndb_api"
"github.com/nutanix-cloud-native/ndb-operator/ndb_client"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes"
)

const namespace_default = "default"

// This function is called from the SetupSuite() function of all testsuites.
// It loads environment variables, instantiate resources, waits for db to be ready, and pod to start.
func ProvisioningTestSetup(ctx context.Context, st *SetupTypes, clientset *kubernetes.Clientset, v1alpha1ClientSet *clientsetv1alpha1.V1alpha1Client, t *testing.T) (err error) {
logger := GetLogger(ctx)
logger.Println("ProvisioningTestSetup() starting...")

// Nil check
if st == nil || clientset == nil || v1alpha1ClientSet == nil {
errMsg := "Error: ProvisioningTestSetup() starting ended! "
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we use a better statement here instead of starting ended?
Perhaps something like nil parameters/arguments or initialization failed etc?

if st == nil {
errMsg += "st is nil! "
}
if clientset == nil {
errMsg += "clientset is nil! "
}
if v1alpha1ClientSet == nil {
errMsg += "v1alpha1ClientSet is nil! "
}

return errors.New(errMsg)
}

ns := namespace_default
if st.Database != nil && st.Database.Namespace != "" {
ns = st.Database.Namespace
}

// Create Secrets
if st.DbSecret != nil {
st.DbSecret.StringData[common.SECRET_DATA_KEY_PASSWORD] = os.Getenv("DB_SECRET_PASSWORD")
_, err = clientset.CoreV1().Secrets(ns).Create(ctx, st.DbSecret, metav1.CreateOptions{})
if err != nil {
logger.Printf("Error while creating secret %s: %s\n", st.DbSecret.Name, err)
} else {
logger.Printf("DB Secret %s created.\n", st.DbSecret.Name)
}
}
if st.NdbSecret != nil {
st.NdbSecret.StringData[common.SECRET_DATA_KEY_USERNAME] = os.Getenv("NDB_SECRET_USERNAME")
st.NdbSecret.StringData[common.SECRET_DATA_KEY_PASSWORD] = os.Getenv("NDB_SECRET_PASSWORD")
_, err = clientset.CoreV1().Secrets(ns).Create(context.TODO(), st.NdbSecret, metav1.CreateOptions{})
if err != nil {
logger.Printf("Error while creating secret %s: %s\n", st.NdbSecret.Name, err)
} else {
logger.Printf("NDB Secret %s created.\n", st.NdbSecret.Name)
}
}

// Create Database
if st.Database != nil {
st.Database.Spec.NDB.Server = os.Getenv("NDB_SERVER")
st.Database.Spec.NDB.ClusterId = os.Getenv("NDB_CLUSTER_ID")
st.Database, err = v1alpha1ClientSet.Databases(st.Database.Namespace).Create(st.Database)
if err != nil {
logger.Printf("Error while creating Database %s: %s\n", st.Database.Name, err)
} else {
logger.Printf("Database %s created.\n", st.Database.Name)
}
}

// Create Application
if st.AppPod != nil {
st.AppPod, err = clientset.CoreV1().Pods(ns).Create(context.TODO(), st.AppPod, metav1.CreateOptions{})
if err != nil {
logger.Printf("Error while creating Pod %s: %s\n", st.AppPod.Name, err)
} else {
logger.Printf("Pod %s created.\n", st.AppPod.Name)
}
}

// Wait for DB to get Ready
if st.Database != nil {
err = waitAndRetryOperation(ctx, time.Minute, 60, func() (err error) {
st.Database, err = v1alpha1ClientSet.Databases(st.Database.Namespace).Get(st.Database.Name, metav1.GetOptions{})
if err != nil {
return
}
statusMessage := "DB " + st.Database.Name + " is in '" + st.Database.Status.Status + "' status."
if st.Database.Status.Status == common.DATABASE_CR_STATUS_READY {
logger.Println(statusMessage)
return
}
err = errors.New(statusMessage)
return
})
if err == nil {
logger.Println("Database is ready")
} else {
logger.Println(err)
}
}
// Wait for Application Pod to start
if st.AppPod != nil {
err = waitAndRetryOperation(ctx, time.Second, 300, func() (err error) {
st.AppPod, err = clientset.CoreV1().Pods(ns).Get(context.TODO(), st.AppPod.Name, metav1.GetOptions{})
if err != nil {
return
}
statusMessage := "Pod " + st.AppPod.Name + " is in '" + string(st.AppPod.Status.Phase) + "' status."
if st.AppPod.Status.Phase == "Running" {
logger.Println(statusMessage)
return
}
err = errors.New(statusMessage)
return
})
if err == nil {
logger.Println("Pod is ready")
} else {
logger.Println(err)
return
}
}

logger.Println("test_setup() ended.")

return
}

// This function is called from the TeardownSuite() function of all testsuites.
// Delete resources and de-provision database.
func ProvisioningTestTeardown(ctx context.Context, st *SetupTypes, clientset *kubernetes.Clientset, v1alpha1ClientSet *clientsetv1alpha1.V1alpha1Client, t *testing.T) (err error) {
logger := GetLogger(ctx)
logger.Println("ProvisioningTestTeardown() starting...")

ns := namespace_default
if st.Database != nil && st.Database.Namespace != "" {
ns = st.Database.Namespace
}

// Delete Service
svcName := st.Database.Name + "-svc"
logger.Printf("Attempting to delete service: %s...", svcName)
err = clientset.CoreV1().Services(ns).Delete(context.TODO(), svcName, metav1.DeleteOptions{})
if err != nil {
logger.Printf("Error while deleting service %s: %s\n", svcName, err)
} else {
logger.Printf("Service %s deleted.\n", svcName)
}

// Delete Database
if st.Database != nil {
logger.Printf("Attempting to delete database: %s...", st.Database.Name)
st.Database.Spec.NDB.Server = os.Getenv("NDB-SERVER")
st.Database.Spec.NDB.ClusterId = os.Getenv("NDB-CLUSTER-ID")
err := v1alpha1ClientSet.Databases(st.Database.Namespace).Delete(st.Database.Name, &metav1.DeleteOptions{})
if err != nil {
logger.Printf("Error while deleting Database %s: %s!\n", st.Database.Name, err)
} else {
logger.Printf("Database %s deleted\n", st.Database.Name)
}
waitAndRetryOperation(ctx, time.Minute, 10, func() (err error) {
st.Database, err = v1alpha1ClientSet.Databases(st.Database.Namespace).Get(st.Database.Name, metav1.GetOptions{})
if err != nil {
return nil
}
if (st.Database == &ndbv1alpha1.Database{}) {
logger.Println("Received empty database")
return nil
}
statusMessage := "DB " + st.Database.Name + " is not yet deleted"
logger.Println(statusMessage)
err = errors.New(statusMessage)
return
})
}

// Delete Secrets
if st.DbSecret != nil {
logger.Printf("Attempting to delete db secret: %s...", st.DbSecret.Name)
st.DbSecret.StringData[common.SECRET_DATA_KEY_USERNAME] = os.Getenv("DB-SECRET-USERNAME")
st.DbSecret.StringData[common.SECRET_DATA_KEY_PASSWORD] = os.Getenv("DB-SECRET-PASSWORD")
err = clientset.CoreV1().Secrets(ns).Delete(context.TODO(), st.DbSecret.Name, metav1.DeleteOptions{})
if err != nil {
logger.Printf("Error while deleting secret %s: %s!\n", st.DbSecret.Name, err)
} else {
logger.Printf("Secret %s deleted.\n", st.DbSecret.Name)
}
}
if st.NdbSecret != nil {
logger.Printf("Attempting to delete ndb secret: %s...", st.NdbSecret.Name)
st.NdbSecret.StringData[common.SECRET_DATA_KEY_USERNAME] = os.Getenv("NDB-SECRET-USERNAME")
st.NdbSecret.StringData[common.SECRET_DATA_KEY_PASSWORD] = os.Getenv("NDB-SECRET-PASSWORD")
err = clientset.CoreV1().Secrets(ns).Delete(context.TODO(), st.NdbSecret.Name, metav1.DeleteOptions{})
if err != nil {
logger.Printf("Error while deleting secret %s: %s!\n", st.NdbSecret.Name, err)
} else {
logger.Printf("Secret %s deleted.\n", st.NdbSecret.Name)
}
}

// Delete Application
if st.AppPod != nil {
logger.Printf("Attempting to delete application: %s...", st.AppPod.Name)
err := clientset.CoreV1().Pods(ns).Delete(context.TODO(), st.AppPod.Name, metav1.DeleteOptions{})
if err != nil {
logger.Printf("Error while deleting pod %s: %s!\n", st.AppPod.Name, err)
} else {
logger.Printf("Pod %s deleted.\n", st.AppPod.Name)
}
}

logger.Println("ProvisioningTestTeardown() ended!")

return
}

// Wrapper function called in all TestSuite GetDatabaseResponse methods. Returns a DatabaseResponse which indicates if provison was succesful
func GetDatabaseResponseFromCR(ctx context.Context, clientset *kubernetes.Clientset, v1alpha1ClientSet *clientsetv1alpha1.V1alpha1Client) (databaseResponse ndb_api.DatabaseResponse, err error) {
logger := GetLogger(ctx)
logger.Println("GetDatabaseResponseFromCR() starting...")

// Get db template from yaml to acquire database name
database := &v1alpha1.Database{}
err = CreateTypeFromPath(database, DATABASE_PATH)
if err != nil {
return ndb_api.DatabaseResponse{}, fmt.Errorf("Error: GetDatabaseResponseFromCR() ended! Database with path %s failed! %v. ", DATABASE_PATH, err)
} else {
logger.Printf("Database with path %s created.", DATABASE_PATH)
}

// Get database CR from above database name
database, err = v1alpha1ClientSet.Databases(database.Namespace).Get(database.Name, metav1.GetOptions{})
if err != nil {
return ndb_api.DatabaseResponse{}, fmt.Errorf("Error: GetDatabaseResponseFromCR() ended! Could not fetch database '%s' CR! %s\n", database.Name, err)
} else {
logger.Printf("Retrieved database '%s' CR from v1alpha1ClientSet", database.Name)
}

// Get NDB username and password from NDB CredentialSecret
ndb_secret_name := database.Spec.NDB.CredentialSecret
secret, err := clientset.CoreV1().Secrets(database.Namespace).Get(context.TODO(), ndb_secret_name, metav1.GetOptions{})
username, password := string(secret.Data[common.SECRET_DATA_KEY_USERNAME]), string(secret.Data[common.SECRET_DATA_KEY_PASSWORD])
if err != nil || username == "" || password == "" {
return ndb_api.DatabaseResponse{}, fmt.Errorf("Error: GetDatabaseResponseFromCR() ended! Could not fetch data from secret! %s\n", err)
}

// Create ndbClient and getting databaseResponse
ndbClient := ndb_client.NewNDBClient(username, password, database.Spec.NDB.Server, "", true)
databaseResponse, err = ndb_api.GetDatabaseById(context.TODO(), ndbClient, database.Status.Id)
if err != nil {
return ndb_api.DatabaseResponse{}, fmt.Errorf("Error: GetDatabaseResponseFromCR() ended! Database response from ndb_api failed! %s\n", err)
}

logger.Printf("Database response.status: %s.\n", databaseResponse.Status)
logger.Println("GetDatabaseResponseFromCR() ended!")

return databaseResponse, nil
}

// Performs an operation a certain number of times with a given interval
func waitAndRetryOperation(ctx context.Context, interval time.Duration, retries int, operation func() error) (err error) {
logger := GetLogger(ctx)
logger.Println("waitAndRetryOperation() starting...")

for i := 0; i < retries; i++ {
if i != 0 {
logger.Printf("Retrying, attempt # %d\n", i)
}
err = operation()
if err == nil {
return nil
} else {
logger.Printf("Error: %s\n", err)
}
time.Sleep(interval)
}

logger.Println("waitAndRetryOperation() ended!")

// Operation failed after all retries, return the last error received
return err
}
Loading