Skip to content

Commit

Permalink
Added integration test for MSSQL app (#1180)
Browse files Browse the repository at this point in the history
* Added integration test for mssql blueprint

* Added clusterrolebinding for service account and cleanup

* cleanup

* added custom clusterrole specific to required operation and addressing review comments

* fixing review comments and added mssql app in integration test

* cleanup

* typo

* removing test image

* import order

* addressing review comments

* removing utils file

* lint fix

* addressing review comments

* moving connection string to constant

* review fixes
  • Loading branch information
chaitanya-baraskar authored Jan 14, 2022
1 parent f889396 commit 13e0bbe
Show file tree
Hide file tree
Showing 6 changed files with 457 additions and 12 deletions.
2 changes: 1 addition & 1 deletion build/integration-test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ TEST_TIMEOUT="30m"
# Set default options
TEST_OPTIONS="-tags=integration -timeout ${TEST_TIMEOUT} -check.suitep ${DOP}"
# Regex to match apps to run in short mode
SHORT_APPS="^PostgreSQL$|^PITRPostgreSQL|^MySQL$|Elasticsearch|^MongoDB$|Maria"
SHORT_APPS="^PostgreSQL$|^PITRPostgreSQL|^MySQL$|Elasticsearch|^MongoDB$|Maria|^MSSQL$"
# OCAPPS has all the apps that are to be tested against openshift cluster
OC_APPS3_11="MysqlDBDepConfig$|MongoDBDepConfig$|PostgreSQLDepConfig$"
OC_APPS4_4="MysqlDBDepConfig4_4|MongoDBDepConfig4_4|PostgreSQLDepConfig4_4"
Expand Down
1 change: 0 additions & 1 deletion examples/stable/mssql/mssql-blueprint.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ apiVersion: cr.kanister.io/v1alpha1
kind: Blueprint
metadata:
name: mssql-blueprint
namespace: kanister
actions:
backup:
outputArtifacts:
Expand Down
347 changes: 347 additions & 0 deletions pkg/app/mssql.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,347 @@
package app

import (
"context"
"fmt"
"strconv"
"strings"
"time"

"github.com/pkg/errors"
appsv1 "k8s.io/api/apps/v1"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/yaml"
"k8s.io/client-go/kubernetes"

crv1alpha1 "github.com/kanisterio/kanister/pkg/apis/cr/v1alpha1"
"github.com/kanisterio/kanister/pkg/field"
"github.com/kanisterio/kanister/pkg/kube"
"github.com/kanisterio/kanister/pkg/log"
)

const (
mssqlWaitTimeout = 5 * time.Minute
dbUserName = "sa"
dbPass = "MyC0m9l&xP@ssw0rd"
connString = "/opt/mssql-tools/bin/sqlcmd -S localhost -U %s -P \"%s\" -Q "
)

type MssqlDB struct {
cli kubernetes.Interface
namespace string
name string
deployment *appsv1.Deployment
service *v1.Service
pvc *v1.PersistentVolumeClaim
secret *v1.Secret
}

func NewMssqlDB(name string) App {
return &MssqlDB{
name: name,
}
}

func (m *MssqlDB) ConfigMaps() map[string]crv1alpha1.ObjectReference {
return nil
}

func (m *MssqlDB) Secrets() map[string]crv1alpha1.ObjectReference {
return map[string]crv1alpha1.ObjectReference{
"mssql": {
Kind: "Secret",
Name: m.name,
Namespace: m.namespace,
},
}
}

func (m *MssqlDB) Init(ctx context.Context) error {
cfg, err := kube.LoadConfig()
if err != nil {
return err
}

m.cli, err = kubernetes.NewForConfig(cfg)
return err
}

func (m *MssqlDB) Install(ctx context.Context, namespace string) error {
m.namespace = namespace
secret, err := m.cli.CoreV1().Secrets(namespace).Create(ctx, m.getSecretObj(), metav1.CreateOptions{})
if err != nil {
return err
}
log.Print("Secret created successfully", field.M{"app": m.name, "secret": secret.Name})
m.secret = secret

pvcObj, err := m.getPVCObj()
if err != nil {
return err
}
pvc, err := m.cli.CoreV1().PersistentVolumeClaims(namespace).Create(ctx, pvcObj, metav1.CreateOptions{})
if err != nil {
return err
}
log.Print("PVC created successfully", field.M{"app": m.name, "pvc": pvc.Name})
m.pvc = pvc

deploymentObj, err := m.getDeploymentObj()
if err != nil {
return err
}
deployment, err := m.cli.AppsV1().Deployments(namespace).Create(ctx, deploymentObj, metav1.CreateOptions{})
if err != nil {
return err
}
log.Print("Deployment created successfully", field.M{"app": m.name, "deployment": deployment.Name})
m.deployment = deployment

serviceObj, err := m.getServiceObj()
if err != nil {
return err
}
service, err := m.cli.CoreV1().Services(namespace).Create(ctx, serviceObj, metav1.CreateOptions{})
if err != nil {
return err
}
log.Print("Service created successfully", field.M{"app": m.name, "service": service.Name})
m.service = service

return nil
}

func (m *MssqlDB) IsReady(ctx context.Context) (bool, error) {
log.Print("Waiting for the mssql application to be ready.", field.M{"app": m.name})
ctx, cancel := context.WithTimeout(ctx, mssqlWaitTimeout)
defer cancel()

err := kube.WaitOnDeploymentReady(ctx, m.cli, m.namespace, m.deployment.Name)
if err != nil {
return false, err
}
log.Print("Application instance is ready.", field.M{"app": m.name})
return true, nil
}

func (m *MssqlDB) Object() crv1alpha1.ObjectReference {
return crv1alpha1.ObjectReference{
Kind: "deployment",
Name: "mssql-deployment",
Namespace: m.namespace,
}
}

func (m *MssqlDB) Uninstall(ctx context.Context) error {
err := m.cli.AppsV1().Deployments(m.namespace).Delete(ctx, m.deployment.Name, metav1.DeleteOptions{})
if err != nil {
return err
}
log.Print("Deployment deleted successfully", field.M{"app": m.name})

err = m.cli.CoreV1().PersistentVolumeClaims(m.namespace).Delete(ctx, m.pvc.Name, metav1.DeleteOptions{})
if err != nil {
return err
}
log.Print("PVC deleted successfully", field.M{"app": m.name})

err = m.cli.CoreV1().Services(m.namespace).Delete(ctx, m.service.Name, metav1.DeleteOptions{})
if err != nil {
return err
}
log.Print("Service deleted successfully", field.M{"app": m.name})

err = m.cli.CoreV1().Secrets(m.namespace).Delete(ctx, m.secret.Name, metav1.DeleteOptions{})
if err != nil {
return err
}
log.Print("Secret deleted successfully", field.M{"app": m.name})
return nil
}

func (m *MssqlDB) Ping(ctx context.Context) error {
log.Print("Pinging mssql database", field.M{"app": m.name})
count := fmt.Sprintf(connString+
"\"SELECT name FROM sys.databases WHERE name NOT IN ('master','model','msdb','tempdb')\" -b -s \",\" -h -1", dbUserName, dbPass)

loginMssql := []string{"sh", "-c", count}
_, stderr, err := m.execCommand(ctx, loginMssql)
if err != nil {
return errors.Wrapf(err, "Error while Pinging the database: %s", stderr)
}
log.Print("Ping to the application was success.", field.M{"app": m.name})
return nil
}

func (m *MssqlDB) Insert(ctx context.Context) error {
log.Print("Adding entry to database", field.M{"app": m.name})
insert := fmt.Sprintf(connString+
"\"USE test; INSERT INTO Inventory VALUES (1, 'banana', 150)\"", dbUserName, dbPass)

insertQuery := []string{"sh", "-c", insert}
_, stderr, err := m.execCommand(ctx, insertQuery)
if err != nil {
return errors.Wrapf(err, "Error while inserting data into table: %s", stderr)
}
return nil
}

func (m *MssqlDB) Count(ctx context.Context) (int, error) {
log.Print("Counting entries from database", field.M{"app": m.name})
insert := fmt.Sprintf(connString+
"\"SET NOCOUNT ON; USE test; SELECT COUNT(*) FROM Inventory\" -h -1", dbUserName, dbPass)

insertQuery := []string{"sh", "-c", insert}
stdout, stderr, err := m.execCommand(ctx, insertQuery)
if err != nil {
return 0, errors.Wrapf(err, "Error while inserting data into table: %s", stderr)
}
rowsReturned, err := strconv.Atoi(strings.TrimSpace(strings.Split(stdout, "\n")[1]))
if err != nil {
return 0, errors.Wrapf(err, "Error while converting response of count query: %s", stderr)
}
return rowsReturned, nil
}

func (m *MssqlDB) Reset(ctx context.Context) error {
log.Print("Reseting database", field.M{"app": m.name})
delete := fmt.Sprintf(connString+"\"DROP DATABASE test\"", dbUserName, dbPass)
deleteQuery := []string{"sh", "-c", delete}
_, stderr, err := m.execCommand(ctx, deleteQuery)
if err != nil {
return errors.Wrapf(err, "Error while inserting data into table: %s", stderr)
}
return nil
}

func (m *MssqlDB) Initialize(ctx context.Context) error {
log.Print("Initializing database", field.M{"app": m.name})
createDB := fmt.Sprintf(connString+"\"CREATE DATABASE test\"", dbUserName, dbPass)

createTable := fmt.Sprintf(connString+
"\"USE test; CREATE TABLE Inventory (id INT, name NVARCHAR(50), quantity INT)\"", dbUserName, dbPass)

execQuery := []string{"sh", "-c", createDB}
_, stderr, err := m.execCommand(ctx, execQuery)
if err != nil {
return errors.Wrapf(err, "Error while creating the database: %s", stderr)
}

execQuery = []string{"sh", "-c", createTable}
_, stderr, err = m.execCommand(ctx, execQuery)
if err != nil {
return errors.Wrapf(err, "Error while creating table: %s", stderr)
}
return nil
}

func (m *MssqlDB) GetClusterScopedResources(ctx context.Context) []crv1alpha1.ObjectReference {
return nil
}

func (m MssqlDB) execCommand(ctx context.Context, command []string) (string, string, error) {
podName, containerName, err := kube.GetPodContainerFromDeployment(ctx, m.cli, m.namespace, m.deployment.Name)
if err != nil || podName == "" {
return "", "", errors.Wrapf(err, "Error getting pod and container name for app %s.", m.name)
}
return kube.Exec(m.cli, m.namespace, podName, containerName, command, nil)
}

func (m *MssqlDB) getDeploymentObj() (*appsv1.Deployment, error) {
deploymentManifest :=
`apiVersion: apps/v1
kind: Deployment
metadata:
name: mssql-deployment
spec:
replicas: 1
selector:
matchLabels:
app: mssql
template:
metadata:
labels:
app: mssql
spec:
terminationGracePeriodSeconds: 30
hostname: mssqlinst
securityContext:
fsGroup: 10001
containers:
- name: mssql
image: mcr.microsoft.com/mssql/server:2019-latest
ports:
- containerPort: 1433
env:
- name: MSSQL_PID
value: "Developer"
- name: ACCEPT_EULA
value: "Y"
- name: SA_PASSWORD
valueFrom:
secretKeyRef:
name: mssql
key: SA_PASSWORD
volumeMounts:
- name: mssqldb
mountPath: /var/opt/mssql
volumes:
- name: mssqldb
persistentVolumeClaim:
claimName: mssql-data`

var deployment *appsv1.Deployment
err := yaml.Unmarshal([]byte(deploymentManifest), &deployment)
return deployment, err
}

func (m *MssqlDB) getPVCObj() (*v1.PersistentVolumeClaim, error) {
pvcmaniFest :=
`kind: PersistentVolumeClaim
apiVersion: v1
metadata:
name: mssql-data
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 4Gi`

var pvc *v1.PersistentVolumeClaim
err := yaml.Unmarshal([]byte(pvcmaniFest), &pvc)
return pvc, err
}

func (m *MssqlDB) getServiceObj() (*v1.Service, error) {
serviceManifest :=
`apiVersion: v1
kind: Service
metadata:
name: mssql-deployment
spec:
selector:
app: mssql
ports:
- protocol: TCP
port: 1433
targetPort: 1433
type: ClusterIP`

var service *v1.Service
err := yaml.Unmarshal([]byte(serviceManifest), &service)
return service, err
}

func (m MssqlDB) getSecretObj() *v1.Secret {
return &v1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: m.name,
},
Data: map[string][]byte{
"SA_PASSWORD": []byte(dbPass),
},
Type: "Opaque",
}
}
1 change: 1 addition & 0 deletions pkg/blueprint/blueprints/mssql-blueprint.yaml
14 changes: 14 additions & 0 deletions pkg/testing/integration_register.go
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,20 @@ var _ = Suite(&RDSPostgreSQLSnap{
},
})

type MSSQL struct {
IntegrationSuite
}

var _ = Suite(&MSSQL{
IntegrationSuite{
name: "mssql",
namespace: "mssql-test",
app: app.NewMssqlDB("mssql"),
bp: app.NewBlueprint("mssql", "", true),
profile: newSecretProfile(),
},
})

// OpenShift apps for version 3.11
// Mysql Instance that is deployed through DeploymentConfig on OpenShift cluster
type MysqlDBDepConfig struct {
Expand Down
Loading

0 comments on commit 13e0bbe

Please sign in to comment.