mirror of
https://github.com/TECHNOFAB11/zfs-localpv.git
synced 2025-12-12 06:20:11 +01:00
test(zfspv): minikube setup for travis
to run integration test cases Signed-off-by: Pawan <pawan@mayadata.io>
This commit is contained in:
parent
7ab6156b98
commit
d933b47c75
27 changed files with 4933 additions and 5 deletions
30
.travis.yml
30
.travis.yml
|
|
@ -1,6 +1,13 @@
|
|||
sudo: required
|
||||
dist: xenial
|
||||
env:
|
||||
global:
|
||||
- CHANGE_MINIKUBE_NONE_USER=true
|
||||
- MINIKUBE_WANTUPDATENOTIFICATION=false
|
||||
- MINIKUBE_WANTREPORTERRORPROMPT=false
|
||||
- MINIKUBE_HOME=$HOME
|
||||
- CHANGE_MINIKUBE_NONE_USER=true
|
||||
- KUBECONFIG=$HOME/.kube/config
|
||||
services:
|
||||
- docker
|
||||
language: go
|
||||
|
|
@ -10,12 +17,31 @@ go:
|
|||
addons:
|
||||
apt:
|
||||
update: true
|
||||
|
||||
before_install:
|
||||
- sudo apt-get update -qq
|
||||
- sudo apt-get install -y zfsutils-linux
|
||||
- truncate -s 100G /tmp/disk.img
|
||||
- sudo zpool create -f zfspv-pool /tmp/disk.img
|
||||
install:
|
||||
- make bootstrap
|
||||
- make format
|
||||
- curl -Lo kubectl https://storage.googleapis.com/kubernetes-release/release/v1.16.0/bin/linux/amd64/kubectl
|
||||
&& chmod +x kubectl && sudo mv kubectl /usr/local/bin/
|
||||
- curl -Lo minikube https://storage.googleapis.com/minikube/releases/v1.4.0/minikube-linux-amd64
|
||||
&& chmod +x minikube && sudo mv minikube /usr/local/bin/
|
||||
- mkdir -p $HOME/.kube $HOME/.minikube
|
||||
- touch $KUBECONFIG
|
||||
- sudo minikube start --vm-driver=none --kubernetes-version=v1.16.0
|
||||
- sudo chown -R $USER $HOME/.minikube
|
||||
- sudo chown -R $USER $HOME/.kube
|
||||
- JSONPATH='{range .items[*]}{@.metadata.name}:{range @.status.conditions[*]}{@.type}={@.status};{end}{end}';
|
||||
until kubectl get nodes -o jsonpath="$JSONPATH" 2>&1 | grep -q "Ready=True"; do
|
||||
sleep 1; done
|
||||
- kubectl cluster-info
|
||||
before_script:
|
||||
- "./buildscripts/travis-build.sh"
|
||||
script:
|
||||
- ./buildscripts/travis-build.sh
|
||||
- make ci
|
||||
after_success:
|
||||
- make deploy-images
|
||||
- bash <(curl -s https://codecov.io/bash)
|
||||
|
|
|
|||
11
Makefile
11
Makefile
|
|
@ -36,10 +36,11 @@ CSI_DRIVER=zfs-driver
|
|||
BUILD_DATE = $(shell date +'%Y%m%d%H%M%S')
|
||||
|
||||
.PHONY: all
|
||||
all: test zfs-driver-image
|
||||
all: zfs-driver-image
|
||||
|
||||
.PHONY: clean
|
||||
clean:
|
||||
@echo "--> Cleaning Directory" ;
|
||||
go clean -testcache
|
||||
rm -rf bin
|
||||
rm -rf ${GOPATH}/bin/${CSI_DRIVER}
|
||||
|
|
@ -132,9 +133,9 @@ informer:
|
|||
--go-header-file ./buildscripts/custom-boilerplate.go.txt
|
||||
|
||||
.PHONY: zfs-driver
|
||||
zfs-driver:
|
||||
zfs-driver: format
|
||||
@echo "--------------------------------"
|
||||
@echo "+ Building ${CSI_DRIVER} "
|
||||
@echo "--> Building ${CSI_DRIVER} "
|
||||
@echo "--------------------------------"
|
||||
@PNAME=${CSI_DRIVER} CTLNAME=${CSI_DRIVER} sh -c "'$(PWD)/buildscripts/build.sh'"
|
||||
|
||||
|
|
@ -147,6 +148,10 @@ zfs-driver-image: zfs-driver
|
|||
cd buildscripts/${CSI_DRIVER} && sudo docker build -t openebs/${CSI_DRIVER}:${IMAGE_TAG} --build-arg BUILD_DATE=${BUILD_DATE} . && sudo docker tag openebs/${CSI_DRIVER}:${IMAGE_TAG} quay.io/openebs/${CSI_DRIVER}:${IMAGE_TAG}
|
||||
@rm buildscripts/${CSI_DRIVER}/${CSI_DRIVER}
|
||||
|
||||
.PHONY: ci
|
||||
ci:
|
||||
@echo "--> Running ci test";
|
||||
$(PWD)/ci/ci-test.sh
|
||||
# Push images
|
||||
deploy-images:
|
||||
@DIMAGE="openebs/zfs-driver" ./buildscripts/push
|
||||
|
|
|
|||
87
ci/ci-test.sh
Executable file
87
ci/ci-test.sh
Executable file
|
|
@ -0,0 +1,87 @@
|
|||
# Copyright 2019 The OpenEBS Authors.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -e
|
||||
|
||||
export OPENEBS_NAMESPACE="openebs"
|
||||
export NodeID=$HOSTNAME
|
||||
|
||||
ZFS_OPERATOR=deploy/zfs-operator.yaml
|
||||
TEST_DIR="tests"
|
||||
|
||||
# Prepare env for runnging BDD tests
|
||||
# Minikube is already running
|
||||
kubectl apply -f $ZFS_OPERATOR
|
||||
|
||||
dumpAgentLogs() {
|
||||
NR=$1
|
||||
AgentPOD=$(kubectl get pods -l app=openebs-zfs-node -o jsonpath='{.items[0].metadata.name}' -n kube-system)
|
||||
kubectl describe po $AgentPOD -n kube-system
|
||||
printf "\n\n"
|
||||
kubectl logs --tail=${NR} $AgentPOD -n kube-system -c openebs-zfs-plugin
|
||||
printf "\n\n"
|
||||
}
|
||||
|
||||
dumpControllerLogs() {
|
||||
NR=$1
|
||||
ControllerPOD=$(kubectl get pods -l app=openebs-zfs-controller -o jsonpath='{.items[0].metadata.name}' -n kube-system)
|
||||
kubectl describe po $ControllerPOD -n kube-system
|
||||
printf "\n\n"
|
||||
kubectl logs --tail=${NR} $ControllerPOD -n kube-system -c openebs-zfs-plugin
|
||||
printf "\n\n"
|
||||
}
|
||||
|
||||
# wait for zfs driver to be UP
|
||||
sleep 20
|
||||
|
||||
cd $TEST_DIR
|
||||
|
||||
kubectl get po -n kube-system
|
||||
|
||||
set +e
|
||||
|
||||
echo "running ginkgo test case"
|
||||
|
||||
ginkgo -v
|
||||
|
||||
if [ $? -ne 0 ]; then
|
||||
|
||||
sudo zpool status
|
||||
|
||||
sudo zfs list -t all
|
||||
|
||||
echo "******************** ZFS Controller logs***************************** "
|
||||
dumpControllerLogs 1000
|
||||
|
||||
echo "********************* ZFS Agent logs *********************************"
|
||||
dumpAgentLogs 1000
|
||||
|
||||
echo "get all the pods"
|
||||
kubectl get pods -owide --all-namespaces
|
||||
|
||||
echo "get pvc and pv details"
|
||||
kubectl get pvc,pv --all-namespaces
|
||||
|
||||
echo "get sc details"
|
||||
kubectl get sc --all-namespaces -oyaml
|
||||
|
||||
echo "get zfs volume details"
|
||||
kubectl get zfsvolumes.openebs.io -n openebs -oyaml
|
||||
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "\n\n######### All test cases passed #########\n\n"
|
||||
|
|
@ -18,6 +18,7 @@ package v1alpha1
|
|||
|
||||
import (
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
env "github.com/openebs/zfs-localpv/pkg/common/env"
|
||||
"github.com/pkg/errors"
|
||||
|
|
@ -140,6 +141,21 @@ func New(opts ...OptionFunc) *Client {
|
|||
return c
|
||||
}
|
||||
|
||||
var (
|
||||
instance *Client
|
||||
once sync.Once
|
||||
)
|
||||
|
||||
// Instance returns a singleton instance of
|
||||
// this client
|
||||
func Instance(opts ...OptionFunc) *Client {
|
||||
once.Do(func() {
|
||||
instance = New(opts...)
|
||||
})
|
||||
|
||||
return instance
|
||||
}
|
||||
|
||||
func withDefaults(c *Client) {
|
||||
if c.getInClusterConfig == nil {
|
||||
c.getInClusterConfig = rest.InClusterConfig
|
||||
|
|
|
|||
420
tests/container/container.go
Normal file
420
tests/container/container.go
Normal file
|
|
@ -0,0 +1,420 @@
|
|||
/*
|
||||
Copyright 2019 The OpenEBS Authors
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package container
|
||||
|
||||
import (
|
||||
"github.com/openebs/zfs-localpv/pkg/common/errors"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
)
|
||||
|
||||
type container struct {
|
||||
corev1.Container // kubernetes container type
|
||||
}
|
||||
|
||||
// OptionFunc is a typed function that abstracts anykind of operation
|
||||
// against the provided container instance
|
||||
//
|
||||
// This is the basic building block to create functional operations
|
||||
// against the container instance
|
||||
type OptionFunc func(*container)
|
||||
|
||||
// Predicate abstracts conditional logic w.r.t the container instance
|
||||
//
|
||||
// NOTE:
|
||||
// Predicate is a functional approach versus traditional approach to mix
|
||||
// conditions such as *if-else* within blocks of business logic
|
||||
//
|
||||
// NOTE:
|
||||
// Predicate approach enables clear separation of conditionals from
|
||||
// imperatives i.e. actions that form the business logic
|
||||
type Predicate func(*container) (nameOrMsg string, ok bool)
|
||||
|
||||
// predicateFailedError returns the provided predicate as an error
|
||||
func predicateFailedError(message string) error {
|
||||
return errors.Errorf("predicatefailed: %s", message)
|
||||
}
|
||||
|
||||
var (
|
||||
errorvalidationFailed = errors.New("container validation failed")
|
||||
)
|
||||
|
||||
// asContainer transforms this container instance into corresponding kubernetes
|
||||
// container type
|
||||
func (c *container) asContainer() corev1.Container {
|
||||
return corev1.Container{
|
||||
Name: c.Name,
|
||||
Image: c.Image,
|
||||
Command: c.Command,
|
||||
Args: c.Args,
|
||||
WorkingDir: c.WorkingDir,
|
||||
Ports: c.Ports,
|
||||
EnvFrom: c.EnvFrom,
|
||||
Env: c.Env,
|
||||
Resources: c.Resources,
|
||||
VolumeMounts: c.VolumeMounts,
|
||||
VolumeDevices: c.VolumeDevices,
|
||||
LivenessProbe: c.LivenessProbe,
|
||||
ReadinessProbe: c.ReadinessProbe,
|
||||
Lifecycle: c.Lifecycle,
|
||||
TerminationMessagePath: c.TerminationMessagePath,
|
||||
TerminationMessagePolicy: c.TerminationMessagePolicy,
|
||||
ImagePullPolicy: c.ImagePullPolicy,
|
||||
SecurityContext: c.SecurityContext,
|
||||
Stdin: c.Stdin,
|
||||
StdinOnce: c.StdinOnce,
|
||||
TTY: c.TTY,
|
||||
}
|
||||
}
|
||||
|
||||
// New returns a new kubernetes container
|
||||
func New(opts ...OptionFunc) corev1.Container {
|
||||
c := &container{}
|
||||
for _, o := range opts {
|
||||
o(c)
|
||||
}
|
||||
return c.asContainer()
|
||||
}
|
||||
|
||||
// Builder provides utilities required to build a kubernetes container type
|
||||
type Builder struct {
|
||||
con *container // container instance
|
||||
checks []Predicate // validations to be done while building the container instance
|
||||
errors []error // errors found while building the container instance
|
||||
}
|
||||
|
||||
// NewBuilder returns a new instance of builder
|
||||
func NewBuilder() *Builder {
|
||||
return &Builder{
|
||||
con: &container{},
|
||||
}
|
||||
}
|
||||
|
||||
// validate will run checks against container instance
|
||||
func (b *Builder) validate() error {
|
||||
for _, c := range b.checks {
|
||||
if m, ok := c(b.con); !ok {
|
||||
b.errors = append(b.errors, predicateFailedError(m))
|
||||
}
|
||||
}
|
||||
if len(b.errors) == 0 {
|
||||
return nil
|
||||
}
|
||||
return errorvalidationFailed
|
||||
}
|
||||
|
||||
// Build returns the final kubernetes container
|
||||
func (b *Builder) Build() (corev1.Container, error) {
|
||||
err := b.validate()
|
||||
if err != nil {
|
||||
return corev1.Container{}, err
|
||||
}
|
||||
return b.con.asContainer(), nil
|
||||
}
|
||||
|
||||
// AddCheck adds the predicate as a condition to be validated against the
|
||||
// container instance
|
||||
func (b *Builder) AddCheck(p Predicate) *Builder {
|
||||
b.checks = append(b.checks, p)
|
||||
return b
|
||||
}
|
||||
|
||||
// AddChecks adds the provided predicates as conditions to be validated against
|
||||
// the container instance
|
||||
func (b *Builder) AddChecks(p []Predicate) *Builder {
|
||||
for _, check := range p {
|
||||
b.AddCheck(check)
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
// WithName sets the name of the container
|
||||
func (b *Builder) WithName(name string) *Builder {
|
||||
if len(name) == 0 {
|
||||
b.errors = append(
|
||||
b.errors,
|
||||
errors.New("failed to build container object: missing name"),
|
||||
)
|
||||
return b
|
||||
}
|
||||
WithName(name)(b.con)
|
||||
return b
|
||||
}
|
||||
|
||||
// WithName sets the name of the container
|
||||
func WithName(name string) OptionFunc {
|
||||
return func(c *container) {
|
||||
c.Name = name
|
||||
}
|
||||
}
|
||||
|
||||
// WithImage sets the image of the container
|
||||
func (b *Builder) WithImage(img string) *Builder {
|
||||
if len(img) == 0 {
|
||||
b.errors = append(
|
||||
b.errors,
|
||||
errors.New("failed to build container object: missing image"),
|
||||
)
|
||||
return b
|
||||
}
|
||||
WithImage(img)(b.con)
|
||||
return b
|
||||
}
|
||||
|
||||
// WithImage sets the image of the container
|
||||
func WithImage(img string) OptionFunc {
|
||||
return func(c *container) {
|
||||
c.Image = img
|
||||
}
|
||||
}
|
||||
|
||||
// WithCommandNew sets the command of the container
|
||||
func (b *Builder) WithCommandNew(cmd []string) *Builder {
|
||||
if cmd == nil {
|
||||
b.errors = append(
|
||||
b.errors,
|
||||
errors.New("failed to build container object: nil command"),
|
||||
)
|
||||
return b
|
||||
}
|
||||
|
||||
if len(cmd) == 0 {
|
||||
b.errors = append(
|
||||
b.errors,
|
||||
errors.New("failed to build container object: missing command"),
|
||||
)
|
||||
return b
|
||||
}
|
||||
|
||||
newcmd := []string{}
|
||||
newcmd = append(newcmd, cmd...)
|
||||
|
||||
b.con.Command = newcmd
|
||||
return b
|
||||
}
|
||||
|
||||
// WithArgumentsNew sets the command arguments of the container
|
||||
func (b *Builder) WithArgumentsNew(args []string) *Builder {
|
||||
if args == nil {
|
||||
b.errors = append(
|
||||
b.errors,
|
||||
errors.New("failed to build container object: nil arguments"),
|
||||
)
|
||||
return b
|
||||
}
|
||||
|
||||
if len(args) == 0 {
|
||||
b.errors = append(
|
||||
b.errors,
|
||||
errors.New("failed to build container object: missing arguments"),
|
||||
)
|
||||
return b
|
||||
}
|
||||
|
||||
newargs := []string{}
|
||||
newargs = append(newargs, args...)
|
||||
|
||||
b.con.Args = newargs
|
||||
return b
|
||||
}
|
||||
|
||||
// WithVolumeMountsNew sets the command arguments of the container
|
||||
func (b *Builder) WithVolumeMountsNew(volumeMounts []corev1.VolumeMount) *Builder {
|
||||
if volumeMounts == nil {
|
||||
b.errors = append(
|
||||
b.errors,
|
||||
errors.New("failed to build container object: nil volumemounts"),
|
||||
)
|
||||
return b
|
||||
}
|
||||
|
||||
if len(volumeMounts) == 0 {
|
||||
b.errors = append(
|
||||
b.errors,
|
||||
errors.New("failed to build container object: missing volumemounts"),
|
||||
)
|
||||
return b
|
||||
}
|
||||
newvolumeMounts := []corev1.VolumeMount{}
|
||||
newvolumeMounts = append(newvolumeMounts, volumeMounts...)
|
||||
b.con.VolumeMounts = newvolumeMounts
|
||||
return b
|
||||
}
|
||||
|
||||
// WithImagePullPolicy sets the image pull policy of the container
|
||||
func (b *Builder) WithImagePullPolicy(policy corev1.PullPolicy) *Builder {
|
||||
if len(policy) == 0 {
|
||||
b.errors = append(
|
||||
b.errors,
|
||||
errors.New(
|
||||
"failed to build container object: missing imagepullpolicy",
|
||||
),
|
||||
)
|
||||
return b
|
||||
}
|
||||
|
||||
b.con.ImagePullPolicy = policy
|
||||
return b
|
||||
}
|
||||
|
||||
// WithPrivilegedSecurityContext sets securitycontext of the container
|
||||
func (b *Builder) WithPrivilegedSecurityContext(privileged *bool) *Builder {
|
||||
if privileged == nil {
|
||||
b.errors = append(
|
||||
b.errors,
|
||||
errors.New(
|
||||
"failed to build container object: missing securitycontext",
|
||||
),
|
||||
)
|
||||
return b
|
||||
}
|
||||
|
||||
newprivileged := *privileged
|
||||
newsecuritycontext := &corev1.SecurityContext{
|
||||
Privileged: &newprivileged,
|
||||
}
|
||||
|
||||
b.con.SecurityContext = newsecuritycontext
|
||||
return b
|
||||
}
|
||||
|
||||
// WithResources sets resources of the container
|
||||
func (b *Builder) WithResources(
|
||||
resources *corev1.ResourceRequirements,
|
||||
) *Builder {
|
||||
if resources == nil {
|
||||
b.errors = append(
|
||||
b.errors,
|
||||
errors.New("failed to build container object: missing resources"),
|
||||
)
|
||||
return b
|
||||
}
|
||||
|
||||
newresources := *resources
|
||||
b.con.Resources = newresources
|
||||
return b
|
||||
}
|
||||
|
||||
// WithResourcesByValue sets resources of the container
|
||||
func (b *Builder) WithResourcesByValue(resources corev1.ResourceRequirements) *Builder {
|
||||
b.con.Resources = resources
|
||||
return b
|
||||
}
|
||||
|
||||
// WithPortsNew sets ports of the container
|
||||
func (b *Builder) WithPortsNew(ports []corev1.ContainerPort) *Builder {
|
||||
if ports == nil {
|
||||
b.errors = append(
|
||||
b.errors,
|
||||
errors.New("failed to build container object: nil ports"),
|
||||
)
|
||||
return b
|
||||
}
|
||||
|
||||
if len(ports) == 0 {
|
||||
b.errors = append(
|
||||
b.errors,
|
||||
errors.New("failed to build container object: missing ports"),
|
||||
)
|
||||
return b
|
||||
}
|
||||
|
||||
newports := []corev1.ContainerPort{}
|
||||
newports = append(newports, ports...)
|
||||
|
||||
b.con.Ports = newports
|
||||
return b
|
||||
}
|
||||
|
||||
// WithEnvsNew sets the envs of the container
|
||||
func (b *Builder) WithEnvsNew(envs []corev1.EnvVar) *Builder {
|
||||
if envs == nil {
|
||||
b.errors = append(
|
||||
b.errors,
|
||||
errors.New("failed to build container object: nil envs"),
|
||||
)
|
||||
return b
|
||||
}
|
||||
|
||||
if len(envs) == 0 {
|
||||
b.errors = append(
|
||||
b.errors,
|
||||
errors.New("failed to build container object: missing envs"),
|
||||
)
|
||||
return b
|
||||
}
|
||||
|
||||
newenvs := []corev1.EnvVar{}
|
||||
newenvs = append(newenvs, envs...)
|
||||
|
||||
b.con.Env = newenvs
|
||||
return b
|
||||
}
|
||||
|
||||
// WithEnvs sets the envs of the container
|
||||
func (b *Builder) WithEnvs(envs []corev1.EnvVar) *Builder {
|
||||
if envs == nil {
|
||||
b.errors = append(
|
||||
b.errors,
|
||||
errors.New("failed to build container object: nil envs"),
|
||||
)
|
||||
return b
|
||||
}
|
||||
|
||||
if len(envs) == 0 {
|
||||
b.errors = append(
|
||||
b.errors,
|
||||
errors.New("failed to build container object: missing envs"),
|
||||
)
|
||||
return b
|
||||
}
|
||||
|
||||
if b.con.Env == nil {
|
||||
b.WithEnvsNew(envs)
|
||||
return b
|
||||
}
|
||||
|
||||
b.con.Env = append(b.con.Env, envs...)
|
||||
return b
|
||||
}
|
||||
|
||||
// WithLivenessProbe sets the liveness probe of the container
|
||||
func (b *Builder) WithLivenessProbe(liveness *corev1.Probe) *Builder {
|
||||
if liveness == nil {
|
||||
b.errors = append(
|
||||
b.errors,
|
||||
errors.New("failed to build container object: nil liveness probe"),
|
||||
)
|
||||
return b
|
||||
}
|
||||
|
||||
b.con.LivenessProbe = liveness
|
||||
return b
|
||||
}
|
||||
|
||||
// WithLifeCycle sets the life cycle of the container
|
||||
func (b *Builder) WithLifeCycle(lc *corev1.Lifecycle) *Builder {
|
||||
if lc == nil {
|
||||
b.errors = append(
|
||||
b.errors,
|
||||
errors.New("failed to build container object: nil lifecycle"),
|
||||
)
|
||||
return b
|
||||
}
|
||||
|
||||
b.con.Lifecycle = lc
|
||||
return b
|
||||
}
|
||||
556
tests/deploy/deployment.go
Normal file
556
tests/deploy/deployment.go
Normal file
|
|
@ -0,0 +1,556 @@
|
|||
/*
|
||||
Copyright 2019 The OpenEBS Authors
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package deploy
|
||||
|
||||
import (
|
||||
"github.com/openebs/zfs-localpv/pkg/common/errors"
|
||||
templatespec "github.com/openebs/zfs-localpv/tests/pts"
|
||||
"github.com/openebs/zfs-localpv/tests/stringer"
|
||||
appsv1 "k8s.io/api/apps/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
// Predicate abstracts conditional logic w.r.t the deployment instance
|
||||
//
|
||||
// NOTE:
|
||||
// predicate is a functional approach versus traditional approach to mix
|
||||
// conditions such as *if-else* within blocks of business logic
|
||||
//
|
||||
// NOTE:
|
||||
// predicate approach enables clear separation of conditionals from
|
||||
// imperatives i.e. actions that form the business logic
|
||||
type Predicate func(*Deploy) bool
|
||||
|
||||
// Deploy is the wrapper over k8s deployment Object
|
||||
type Deploy struct {
|
||||
// kubernetes deployment instance
|
||||
object *appsv1.Deployment
|
||||
}
|
||||
|
||||
// Builder enables building an instance of
|
||||
// deployment
|
||||
type Builder struct {
|
||||
deployment *Deploy // kubernetes deployment instance
|
||||
checks []Predicate // predicate list for deploy
|
||||
errors []error
|
||||
}
|
||||
|
||||
// PredicateName type is wrapper over string.
|
||||
// It is used to refer predicate and status msg.
|
||||
type PredicateName string
|
||||
|
||||
const (
|
||||
// PredicateProgressDeadlineExceeded refer to
|
||||
// predicate IsProgressDeadlineExceeded.
|
||||
PredicateProgressDeadlineExceeded PredicateName = "ProgressDeadlineExceeded"
|
||||
// PredicateNotSpecSynced refer to predicate IsNotSpecSynced
|
||||
PredicateNotSpecSynced PredicateName = "NotSpecSynced"
|
||||
// PredicateOlderReplicaActive refer to predicate IsOlderReplicaActive
|
||||
PredicateOlderReplicaActive PredicateName = "OlderReplicaActive"
|
||||
// PredicateTerminationInProgress refer to predicate IsTerminationInProgress
|
||||
PredicateTerminationInProgress PredicateName = "TerminationInProgress"
|
||||
// PredicateUpdateInProgress refer to predicate IsUpdateInProgress.
|
||||
PredicateUpdateInProgress PredicateName = "UpdateInProgress"
|
||||
)
|
||||
|
||||
// String implements the stringer interface
|
||||
func (d *Deploy) String() string {
|
||||
return stringer.Yaml("deployment", d.object)
|
||||
}
|
||||
|
||||
// GoString implements the goStringer interface
|
||||
func (d *Deploy) GoString() string {
|
||||
return d.String()
|
||||
}
|
||||
|
||||
// NewBuilder returns a new instance of builder meant for deployment
|
||||
func NewBuilder() *Builder {
|
||||
return &Builder{
|
||||
deployment: &Deploy{
|
||||
object: &appsv1.Deployment{},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// WithName sets the Name field of deployment with provided value.
|
||||
func (b *Builder) WithName(name string) *Builder {
|
||||
if len(name) == 0 {
|
||||
b.errors = append(
|
||||
b.errors,
|
||||
errors.New("failed to build deployment: missing name"),
|
||||
)
|
||||
return b
|
||||
}
|
||||
b.deployment.object.Name = name
|
||||
return b
|
||||
}
|
||||
|
||||
// WithNamespace sets the Namespace field of deployment with provided value.
|
||||
func (b *Builder) WithNamespace(namespace string) *Builder {
|
||||
if len(namespace) == 0 {
|
||||
b.errors = append(
|
||||
b.errors,
|
||||
errors.New("failed to build deployment: missing namespace"),
|
||||
)
|
||||
return b
|
||||
}
|
||||
b.deployment.object.Namespace = namespace
|
||||
return b
|
||||
}
|
||||
|
||||
// WithAnnotations merges existing annotations if any
|
||||
// with the ones that are provided here
|
||||
func (b *Builder) WithAnnotations(annotations map[string]string) *Builder {
|
||||
if len(annotations) == 0 {
|
||||
b.errors = append(
|
||||
b.errors,
|
||||
errors.New("failed to build deployment object: missing annotations"),
|
||||
)
|
||||
return b
|
||||
}
|
||||
|
||||
if b.deployment.object.Annotations == nil {
|
||||
return b.WithAnnotationsNew(annotations)
|
||||
}
|
||||
|
||||
for key, value := range annotations {
|
||||
b.deployment.object.Annotations[key] = value
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
// WithAnnotationsNew resets existing annotaions if any with
|
||||
// ones that are provided here
|
||||
func (b *Builder) WithAnnotationsNew(annotations map[string]string) *Builder {
|
||||
if len(annotations) == 0 {
|
||||
b.errors = append(
|
||||
b.errors,
|
||||
errors.New("failed to build deployment object: no new annotations"),
|
||||
)
|
||||
return b
|
||||
}
|
||||
|
||||
// copy of original map
|
||||
newannotations := map[string]string{}
|
||||
for key, value := range annotations {
|
||||
newannotations[key] = value
|
||||
}
|
||||
|
||||
// override
|
||||
b.deployment.object.Annotations = newannotations
|
||||
return b
|
||||
}
|
||||
|
||||
// WithNodeSelector Sets the node selector with the provided argument.
|
||||
func (b *Builder) WithNodeSelector(selector map[string]string) *Builder {
|
||||
if len(selector) == 0 {
|
||||
b.errors = append(
|
||||
b.errors,
|
||||
errors.New("failed to build deployment object: no node selector"),
|
||||
)
|
||||
return b
|
||||
}
|
||||
if b.deployment.object.Spec.Template.Spec.NodeSelector == nil {
|
||||
return b.WithNodeSelectorNew(selector)
|
||||
}
|
||||
|
||||
for key, value := range selector {
|
||||
b.deployment.object.Spec.Template.Spec.NodeSelector[key] = value
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
// WithNodeSelector Sets the node selector with the provided argument.
|
||||
func (b *Builder) WithNodeSelectorNew(selector map[string]string) *Builder {
|
||||
if len(selector) == 0 {
|
||||
b.errors = append(
|
||||
b.errors,
|
||||
errors.New("failed to build deployment object: no new node selector"),
|
||||
)
|
||||
return b
|
||||
}
|
||||
|
||||
b.deployment.object.Spec.Template.Spec.NodeSelector = selector
|
||||
return b
|
||||
}
|
||||
|
||||
// WithOwnerReferenceNew sets ownerreference if any with
|
||||
// ones that are provided here
|
||||
func (b *Builder) WithOwnerReferenceNew(ownerRefernce []metav1.OwnerReference) *Builder {
|
||||
if len(ownerRefernce) == 0 {
|
||||
b.errors = append(
|
||||
b.errors,
|
||||
errors.New("failed to build deployment object: no new ownerRefernce"),
|
||||
)
|
||||
return b
|
||||
}
|
||||
|
||||
b.deployment.object.OwnerReferences = ownerRefernce
|
||||
return b
|
||||
}
|
||||
|
||||
// WithLabels merges existing labels if any
|
||||
// with the ones that are provided here
|
||||
func (b *Builder) WithLabels(labels map[string]string) *Builder {
|
||||
if len(labels) == 0 {
|
||||
b.errors = append(
|
||||
b.errors,
|
||||
errors.New("failed to build deployment object: missing labels"),
|
||||
)
|
||||
return b
|
||||
}
|
||||
|
||||
if b.deployment.object.Labels == nil {
|
||||
return b.WithLabelsNew(labels)
|
||||
}
|
||||
|
||||
for key, value := range labels {
|
||||
b.deployment.object.Labels[key] = value
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
// WithLabelsNew resets existing labels if any with
|
||||
// ones that are provided here
|
||||
func (b *Builder) WithLabelsNew(labels map[string]string) *Builder {
|
||||
if len(labels) == 0 {
|
||||
b.errors = append(
|
||||
b.errors,
|
||||
errors.New("failed to build deployment object: no new labels"),
|
||||
)
|
||||
return b
|
||||
}
|
||||
|
||||
// copy of original map
|
||||
newlbls := map[string]string{}
|
||||
for key, value := range labels {
|
||||
newlbls[key] = value
|
||||
}
|
||||
|
||||
// override
|
||||
b.deployment.object.Labels = newlbls
|
||||
return b
|
||||
}
|
||||
|
||||
// WithSelectorMatchLabels merges existing matchlabels if any
|
||||
// with the ones that are provided here
|
||||
func (b *Builder) WithSelectorMatchLabels(matchlabels map[string]string) *Builder {
|
||||
if len(matchlabels) == 0 {
|
||||
b.errors = append(
|
||||
b.errors,
|
||||
errors.New("failed to build deployment object: missing matchlabels"),
|
||||
)
|
||||
return b
|
||||
}
|
||||
|
||||
if b.deployment.object.Spec.Selector == nil {
|
||||
return b.WithSelectorMatchLabelsNew(matchlabels)
|
||||
}
|
||||
|
||||
for key, value := range matchlabels {
|
||||
b.deployment.object.Spec.Selector.MatchLabels[key] = value
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
// WithSelectorMatchLabelsNew resets existing matchlabels if any with
|
||||
// ones that are provided here
|
||||
func (b *Builder) WithSelectorMatchLabelsNew(matchlabels map[string]string) *Builder {
|
||||
if len(matchlabels) == 0 {
|
||||
b.errors = append(
|
||||
b.errors,
|
||||
errors.New("failed to build deployment object: no new matchlabels"),
|
||||
)
|
||||
return b
|
||||
}
|
||||
|
||||
// copy of original map
|
||||
newmatchlabels := map[string]string{}
|
||||
for key, value := range matchlabels {
|
||||
newmatchlabels[key] = value
|
||||
}
|
||||
|
||||
newselector := &metav1.LabelSelector{
|
||||
MatchLabels: newmatchlabels,
|
||||
}
|
||||
|
||||
// override
|
||||
b.deployment.object.Spec.Selector = newselector
|
||||
return b
|
||||
}
|
||||
|
||||
// WithReplicas sets the replica field of deployment
|
||||
func (b *Builder) WithReplicas(replicas *int32) *Builder {
|
||||
|
||||
if replicas == nil {
|
||||
b.errors = append(
|
||||
b.errors,
|
||||
errors.New("failed to build deployment object: nil replicas"),
|
||||
)
|
||||
return b
|
||||
}
|
||||
|
||||
newreplicas := *replicas
|
||||
|
||||
if newreplicas < 0 {
|
||||
b.errors = append(
|
||||
b.errors,
|
||||
errors.Errorf(
|
||||
"failed to build deployment object: invalid replicas {%d}",
|
||||
newreplicas,
|
||||
),
|
||||
)
|
||||
return b
|
||||
}
|
||||
|
||||
b.deployment.object.Spec.Replicas = &newreplicas
|
||||
return b
|
||||
}
|
||||
|
||||
//WithStrategyType sets the strategy field of the deployment
|
||||
func (b *Builder) WithStrategyType(
|
||||
strategytype appsv1.DeploymentStrategyType,
|
||||
) *Builder {
|
||||
if len(strategytype) == 0 {
|
||||
b.errors = append(
|
||||
b.errors,
|
||||
errors.New("failed to build deployment object: missing strategytype"),
|
||||
)
|
||||
return b
|
||||
}
|
||||
|
||||
b.deployment.object.Spec.Strategy.Type = strategytype
|
||||
return b
|
||||
}
|
||||
|
||||
// WithPodTemplateSpecBuilder sets the template field of the deployment
|
||||
func (b *Builder) WithPodTemplateSpecBuilder(
|
||||
tmplbuilder *templatespec.Builder,
|
||||
) *Builder {
|
||||
if tmplbuilder == nil {
|
||||
b.errors = append(
|
||||
b.errors,
|
||||
errors.New("failed to build deployment: nil templatespecbuilder"),
|
||||
)
|
||||
return b
|
||||
}
|
||||
|
||||
templatespecObj, err := tmplbuilder.Build()
|
||||
|
||||
if err != nil {
|
||||
b.errors = append(
|
||||
b.errors,
|
||||
errors.Wrap(
|
||||
err,
|
||||
"failed to build deployment",
|
||||
),
|
||||
)
|
||||
return b
|
||||
}
|
||||
|
||||
b.deployment.object.Spec.Template = *templatespecObj.Object
|
||||
return b
|
||||
}
|
||||
|
||||
type deployBuildOption func(*Deploy)
|
||||
|
||||
// NewForAPIObject returns a new instance of builder
|
||||
// for a given deployment Object
|
||||
func NewForAPIObject(
|
||||
obj *appsv1.Deployment,
|
||||
opts ...deployBuildOption,
|
||||
) *Deploy {
|
||||
d := &Deploy{object: obj}
|
||||
for _, o := range opts {
|
||||
o(d)
|
||||
}
|
||||
return d
|
||||
}
|
||||
|
||||
// Build returns a deployment instance
|
||||
func (b *Builder) Build() (*appsv1.Deployment, error) {
|
||||
err := b.validate()
|
||||
// TODO: err in Wrapf is not logged. Fix is required
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err,
|
||||
"failed to build a deployment: %s",
|
||||
b.deployment.object.Name)
|
||||
}
|
||||
return b.deployment.object, nil
|
||||
}
|
||||
|
||||
func (b *Builder) validate() error {
|
||||
if len(b.errors) != 0 {
|
||||
return errors.Errorf(
|
||||
"failed to validate: build errors were found: %+v",
|
||||
b.errors,
|
||||
)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// IsRollout range over rolloutChecks map and check status of each predicate
|
||||
// also it generates status message from rolloutStatuses using predicate key
|
||||
func (d *Deploy) IsRollout() (PredicateName, bool) {
|
||||
for pk, p := range rolloutChecks {
|
||||
if p(d) {
|
||||
return pk, false
|
||||
}
|
||||
}
|
||||
return "", true
|
||||
}
|
||||
|
||||
// FailedRollout returns rollout status message for fail condition
|
||||
func (d *Deploy) FailedRollout(name PredicateName) *RolloutOutput {
|
||||
return &RolloutOutput{
|
||||
Message: rolloutStatuses[name](d),
|
||||
IsRolledout: false,
|
||||
}
|
||||
}
|
||||
|
||||
// SuccessRollout returns rollout status message for success condition
|
||||
func (d *Deploy) SuccessRollout() *RolloutOutput {
|
||||
return &RolloutOutput{
|
||||
Message: "deployment successfully rolled out",
|
||||
IsRolledout: true,
|
||||
}
|
||||
}
|
||||
|
||||
// RolloutStatus returns rollout message of deployment instance
|
||||
func (d *Deploy) RolloutStatus() (op *RolloutOutput, err error) {
|
||||
pk, ok := d.IsRollout()
|
||||
if ok {
|
||||
return d.SuccessRollout(), nil
|
||||
}
|
||||
return d.FailedRollout(pk), nil
|
||||
}
|
||||
|
||||
// RolloutStatusRaw returns rollout message of deployment instance
|
||||
// in byte format
|
||||
func (d *Deploy) RolloutStatusRaw() (op []byte, err error) {
|
||||
message, err := d.RolloutStatus()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return NewRollout(
|
||||
withOutputObject(message)).
|
||||
Raw()
|
||||
}
|
||||
|
||||
// AddCheck adds the predicate as a condition to be validated
|
||||
// against the deployment instance
|
||||
func (b *Builder) AddCheck(p Predicate) *Builder {
|
||||
b.checks = append(b.checks, p)
|
||||
return b
|
||||
}
|
||||
|
||||
// AddChecks adds the provided predicates as conditions to be
|
||||
// validated against the deployment instance
|
||||
func (b *Builder) AddChecks(p []Predicate) *Builder {
|
||||
for _, check := range p {
|
||||
b.AddCheck(check)
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
// IsProgressDeadlineExceeded is used to check update is timed out or not.
|
||||
// If `Progressing` condition's reason is `ProgressDeadlineExceeded` then
|
||||
// it is not rolled out.
|
||||
func IsProgressDeadlineExceeded() Predicate {
|
||||
return func(d *Deploy) bool {
|
||||
return d.IsProgressDeadlineExceeded()
|
||||
}
|
||||
}
|
||||
|
||||
// IsProgressDeadlineExceeded is used to check update is timed out or not.
|
||||
// If `Progressing` condition's reason is `ProgressDeadlineExceeded` then
|
||||
// it is not rolled out.
|
||||
func (d *Deploy) IsProgressDeadlineExceeded() bool {
|
||||
for _, cond := range d.object.Status.Conditions {
|
||||
if cond.Type == appsv1.DeploymentProgressing &&
|
||||
cond.Reason == "ProgressDeadlineExceeded" {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// IsOlderReplicaActive check if older replica's are still active or not if
|
||||
// Status.UpdatedReplicas < *Spec.Replicas then some of the replicas are
|
||||
// updated and some of them are not.
|
||||
func IsOlderReplicaActive() Predicate {
|
||||
return func(d *Deploy) bool {
|
||||
return d.IsOlderReplicaActive()
|
||||
}
|
||||
}
|
||||
|
||||
// IsOlderReplicaActive check if older replica's are still active or not if
|
||||
// Status.UpdatedReplicas < *Spec.Replicas then some of the replicas are
|
||||
// updated and some of them are not.
|
||||
func (d *Deploy) IsOlderReplicaActive() bool {
|
||||
return d.object.Spec.Replicas != nil &&
|
||||
d.object.Status.UpdatedReplicas < *d.object.Spec.Replicas
|
||||
}
|
||||
|
||||
// IsTerminationInProgress checks for older replicas are waiting to
|
||||
// terminate or not. If Status.Replicas > Status.UpdatedReplicas then
|
||||
// some of the older replicas are in running state because newer
|
||||
// replicas are not in running state. It waits for newer replica to
|
||||
// come into running state then terminate.
|
||||
func IsTerminationInProgress() Predicate {
|
||||
return func(d *Deploy) bool {
|
||||
return d.IsTerminationInProgress()
|
||||
}
|
||||
}
|
||||
|
||||
// IsTerminationInProgress checks for older replicas are waiting to
|
||||
// terminate or not. If Status.Replicas > Status.UpdatedReplicas then
|
||||
// some of the older replicas are in running state because newer
|
||||
// replicas are not in running state. It waits for newer replica to
|
||||
// come into running state then terminate.
|
||||
func (d *Deploy) IsTerminationInProgress() bool {
|
||||
return d.object.Status.Replicas > d.object.Status.UpdatedReplicas
|
||||
}
|
||||
|
||||
// IsUpdateInProgress Checks if all the replicas are updated or not.
|
||||
// If Status.AvailableReplicas < Status.UpdatedReplicas then all the
|
||||
//older replicas are not there but there are less number of availableReplicas
|
||||
func IsUpdateInProgress() Predicate {
|
||||
return func(d *Deploy) bool {
|
||||
return d.IsUpdateInProgress()
|
||||
}
|
||||
}
|
||||
|
||||
// IsUpdateInProgress Checks if all the replicas are updated or not.
|
||||
// If Status.AvailableReplicas < Status.UpdatedReplicas then all the
|
||||
// older replicas are not there but there are less number of availableReplicas
|
||||
func (d *Deploy) IsUpdateInProgress() bool {
|
||||
return d.object.Status.AvailableReplicas < d.object.Status.UpdatedReplicas
|
||||
}
|
||||
|
||||
// IsNotSyncSpec compare generation in status and spec and check if
|
||||
// deployment spec is synced or not. If Generation <= Status.ObservedGeneration
|
||||
// then deployment spec is not updated yet.
|
||||
func IsNotSyncSpec() Predicate {
|
||||
return func(d *Deploy) bool {
|
||||
return d.IsNotSyncSpec()
|
||||
}
|
||||
}
|
||||
|
||||
// IsNotSyncSpec compare generation in status and spec and check if
|
||||
// deployment spec is synced or not. If Generation <= Status.ObservedGeneration
|
||||
// then deployment spec is not updated yet.
|
||||
func (d *Deploy) IsNotSyncSpec() bool {
|
||||
return d.object.Generation > d.object.Status.ObservedGeneration
|
||||
}
|
||||
417
tests/deploy/kubernetes.go
Normal file
417
tests/deploy/kubernetes.go
Normal file
|
|
@ -0,0 +1,417 @@
|
|||
/*
|
||||
Copyright 2019 The OpenEBS Authors
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
package deploy
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"strings"
|
||||
|
||||
client "github.com/openebs/zfs-localpv/pkg/common/kubernetes/client"
|
||||
"github.com/pkg/errors"
|
||||
appsv1 "k8s.io/api/apps/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
types "k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
)
|
||||
|
||||
// getClientsetFn is a typed function
|
||||
// that abstracts fetching of kubernetes clientset
|
||||
type getClientsetFn func() (*kubernetes.Clientset, error)
|
||||
|
||||
// getClientsetFromPathFn is a typed function that
|
||||
// abstracts fetching of clientset from kubeconfig path
|
||||
type getClientsetForPathFn func(path string) (*kubernetes.Clientset, error)
|
||||
|
||||
// getFn is a typed function that abstracts fetching a
|
||||
// deployment instance from kubernetes cluster
|
||||
type getFn func(
|
||||
cli *kubernetes.Clientset,
|
||||
name string,
|
||||
namespace string,
|
||||
opts *metav1.GetOptions,
|
||||
) (*appsv1.Deployment, error)
|
||||
|
||||
// listFn is a typed function that abstracts listing
|
||||
// deployment instances from kubernetes cluster
|
||||
type listFn func(
|
||||
cli *kubernetes.Clientset,
|
||||
namespace string,
|
||||
opts *metav1.ListOptions,
|
||||
) (*appsv1.DeploymentList, error)
|
||||
|
||||
// createFn is a typed function that abstracts
|
||||
// creating a deployment instance in kubernetes cluster
|
||||
type createFn func(
|
||||
cli *kubernetes.Clientset,
|
||||
namespace string,
|
||||
deploy *appsv1.Deployment,
|
||||
) (*appsv1.Deployment, error)
|
||||
|
||||
// deleteFn is a typed function that abstracts
|
||||
// deleting a deployment from kubernetes cluster
|
||||
type deleteFn func(
|
||||
cli *kubernetes.Clientset,
|
||||
namespace string,
|
||||
name string,
|
||||
opts *metav1.DeleteOptions,
|
||||
) error
|
||||
|
||||
// patchFn is a typed function that abstracts
|
||||
// patching a deployment from kubernetes cluster
|
||||
type patchFn func(
|
||||
cli *kubernetes.Clientset,
|
||||
name, namespace string,
|
||||
pt types.PatchType,
|
||||
data []byte,
|
||||
subresources ...string,
|
||||
) (*appsv1.Deployment, error)
|
||||
|
||||
// rolloutStatusFn is a typed function that abstracts
|
||||
// fetching rollout status of a deployment instance from
|
||||
// kubernetes cluster
|
||||
type rolloutStatusFn func(d *appsv1.Deployment) (*RolloutOutput, error)
|
||||
|
||||
// rolloutStatusfFn is a typed function that abstracts
|
||||
// fetching rollout status of a deployment instance from
|
||||
// kubernetes cluster
|
||||
type rolloutStatusfFn func(d *appsv1.Deployment) ([]byte, error)
|
||||
|
||||
// defaultGetClientset is the default implementation to
|
||||
// get kubernetes clientset instance
|
||||
func defaultGetClientset() (*kubernetes.Clientset, error) {
|
||||
return client.Instance().Clientset()
|
||||
}
|
||||
|
||||
// defaultGetClientsetForPath is the default implementation to
|
||||
// get kubernetes clientset instance based on the given
|
||||
// kubeconfig path
|
||||
func defaultGetClientsetForPath(path string) (*kubernetes.Clientset, error) {
|
||||
return client.New(client.WithKubeConfigPath(path)).Clientset()
|
||||
}
|
||||
|
||||
// defaultGet is the default implementation to get a
|
||||
// deployment instance from kubernetes cluster
|
||||
func defaultGet(
|
||||
cli *kubernetes.Clientset,
|
||||
name string,
|
||||
namespace string,
|
||||
opts *metav1.GetOptions,
|
||||
) (*appsv1.Deployment, error) {
|
||||
|
||||
return cli.AppsV1().Deployments(namespace).Get(name, *opts)
|
||||
}
|
||||
|
||||
// defaultList is the default implementation to list
|
||||
// deployment instances from kubernetes cluster
|
||||
func defaultList(
|
||||
cli *kubernetes.Clientset,
|
||||
namespace string,
|
||||
opts *metav1.ListOptions,
|
||||
) (*appsv1.DeploymentList, error) {
|
||||
|
||||
return cli.AppsV1().Deployments(namespace).List(*opts)
|
||||
}
|
||||
|
||||
// defaultCreate is the default implementation to create
|
||||
// a deployment instance in kubernetes cluster
|
||||
func defaultCreate(
|
||||
cli *kubernetes.Clientset,
|
||||
namespace string,
|
||||
deploy *appsv1.Deployment,
|
||||
) (*appsv1.Deployment, error) {
|
||||
|
||||
return cli.AppsV1().Deployments(namespace).Create(deploy)
|
||||
}
|
||||
|
||||
// defaultDel is the default implementation to delete a
|
||||
// deployment instance in kubernetes cluster
|
||||
func defaultDel(
|
||||
cli *kubernetes.Clientset,
|
||||
namespace string,
|
||||
name string,
|
||||
opts *metav1.DeleteOptions,
|
||||
) error {
|
||||
|
||||
return cli.AppsV1().Deployments(namespace).Delete(name, opts)
|
||||
}
|
||||
|
||||
func defaultPatch(
|
||||
cli *kubernetes.Clientset,
|
||||
name, namespace string,
|
||||
pt types.PatchType,
|
||||
data []byte,
|
||||
subresources ...string,
|
||||
) (*appsv1.Deployment, error) {
|
||||
return cli.AppsV1().Deployments(namespace).Patch(name, pt, data, subresources...)
|
||||
}
|
||||
|
||||
// defaultRolloutStatus is the default implementation to
|
||||
// fetch rollout status of a deployment instance from kubernetes
|
||||
// cluster
|
||||
func defaultRolloutStatus(d *appsv1.Deployment) (*RolloutOutput, error) {
|
||||
b := NewForAPIObject(d)
|
||||
return b.RolloutStatus()
|
||||
}
|
||||
|
||||
// defaultRolloutStatusf is the default implementation to fetch
|
||||
// rollout status of a deployment instance from kubernetes cluster
|
||||
func defaultRolloutStatusf(d *appsv1.Deployment) ([]byte, error) {
|
||||
b := NewForAPIObject(d)
|
||||
return b.RolloutStatusRaw()
|
||||
}
|
||||
|
||||
// Kubeclient enables kubernetes API operations on deployment instance
|
||||
type Kubeclient struct {
|
||||
// clientset refers to kubernetes clientset
|
||||
//
|
||||
// It enables CRUD operations of a deployment instance
|
||||
// against a kubernetes cluster
|
||||
clientset *kubernetes.Clientset
|
||||
|
||||
namespace string
|
||||
|
||||
// kubeconfig path to get kubernetes clientset
|
||||
kubeConfigPath string
|
||||
|
||||
// functions useful during mocking
|
||||
getClientset getClientsetFn
|
||||
getClientsetForPath getClientsetForPathFn
|
||||
get getFn
|
||||
list listFn
|
||||
create createFn
|
||||
del deleteFn
|
||||
patch patchFn
|
||||
rolloutStatus rolloutStatusFn
|
||||
rolloutStatusf rolloutStatusfFn
|
||||
}
|
||||
|
||||
// KubeclientBuildOption defines the abstraction to build a
|
||||
// kubeclient instance
|
||||
type KubeclientBuildOption func(*Kubeclient)
|
||||
|
||||
// withDefaults sets kubeclient instance's fields with defaults
|
||||
// if these fields are not set
|
||||
func (k *Kubeclient) withDefaults() {
|
||||
|
||||
if k.getClientset == nil {
|
||||
k.getClientset = defaultGetClientset
|
||||
}
|
||||
if k.getClientsetForPath == nil {
|
||||
k.getClientsetForPath = defaultGetClientsetForPath
|
||||
}
|
||||
if k.get == nil {
|
||||
k.get = defaultGet
|
||||
}
|
||||
if k.list == nil {
|
||||
k.list = defaultList
|
||||
}
|
||||
if k.create == nil {
|
||||
k.create = defaultCreate
|
||||
}
|
||||
if k.del == nil {
|
||||
k.del = defaultDel
|
||||
}
|
||||
if k.patch == nil {
|
||||
k.patch = defaultPatch
|
||||
}
|
||||
if k.rolloutStatus == nil {
|
||||
k.rolloutStatus = defaultRolloutStatus
|
||||
}
|
||||
if k.rolloutStatusf == nil {
|
||||
k.rolloutStatusf = defaultRolloutStatusf
|
||||
}
|
||||
}
|
||||
|
||||
// WithClientset sets the kubernetes client against the kubeclient instance
|
||||
func WithClientset(c *kubernetes.Clientset) KubeclientBuildOption {
|
||||
return func(k *Kubeclient) {
|
||||
k.clientset = c
|
||||
}
|
||||
}
|
||||
|
||||
// WithNamespace sets the kubernetes client against
|
||||
// the provided namespace
|
||||
func (k *Kubeclient) WithNamespace(namespace string) *Kubeclient {
|
||||
k.namespace = namespace
|
||||
return k
|
||||
}
|
||||
|
||||
// WithKubeConfigPath sets the kubeConfig path
|
||||
// against client instance
|
||||
func WithKubeConfigPath(path string) KubeclientBuildOption {
|
||||
return func(k *Kubeclient) {
|
||||
k.kubeConfigPath = path
|
||||
}
|
||||
}
|
||||
|
||||
// WithNamespace set namespace in kubeclient object
|
||||
func WithNamespace(namespace string) KubeclientBuildOption {
|
||||
return func(k *Kubeclient) {
|
||||
k.namespace = namespace
|
||||
}
|
||||
}
|
||||
|
||||
// NewKubeClient returns a new instance of kubeclient meant for deployment.
|
||||
// caller can configure it with different kubeclientBuildOption
|
||||
func NewKubeClient(opts ...KubeclientBuildOption) *Kubeclient {
|
||||
k := &Kubeclient{}
|
||||
for _, o := range opts {
|
||||
o(k)
|
||||
}
|
||||
|
||||
k.withDefaults()
|
||||
return k
|
||||
}
|
||||
|
||||
func (k *Kubeclient) getClientsetForPathOrDirect() (*kubernetes.Clientset, error) {
|
||||
if k.kubeConfigPath != "" {
|
||||
return k.getClientsetForPath(k.kubeConfigPath)
|
||||
}
|
||||
|
||||
return k.getClientset()
|
||||
}
|
||||
|
||||
// getClientOrCached returns either a new instance
|
||||
// of kubernetes client or its cached copy
|
||||
func (k *Kubeclient) getClientOrCached() (*kubernetes.Clientset, error) {
|
||||
if k.clientset != nil {
|
||||
return k.clientset, nil
|
||||
}
|
||||
|
||||
c, err := k.getClientsetForPathOrDirect()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
k.clientset = c
|
||||
return k.clientset, nil
|
||||
}
|
||||
|
||||
// Get returns deployment object for given name
|
||||
func (k *Kubeclient) Get(name string) (*appsv1.Deployment, error) {
|
||||
cli, err := k.getClientOrCached()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return k.get(cli, name, k.namespace, &metav1.GetOptions{})
|
||||
}
|
||||
|
||||
// List returns deployment object for given name
|
||||
func (k *Kubeclient) List(opts *metav1.ListOptions) (*appsv1.DeploymentList, error) {
|
||||
cli, err := k.getClientOrCached()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return k.list(cli, k.namespace, opts)
|
||||
}
|
||||
|
||||
// Patch patches deployment object for given name
|
||||
func (k *Kubeclient) Patch(
|
||||
name string,
|
||||
pt types.PatchType,
|
||||
data []byte,
|
||||
subresources ...string,
|
||||
) (*appsv1.Deployment, error) {
|
||||
cli, err := k.getClientOrCached()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return k.patch(cli, name, k.namespace, pt, data, subresources...)
|
||||
}
|
||||
|
||||
// GetRaw returns deployment object for given name
|
||||
func (k *Kubeclient) GetRaw(name string) ([]byte, error) {
|
||||
cli, err := k.getClientOrCached()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
d, err := k.get(cli, name, k.namespace, &metav1.GetOptions{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return json.Marshal(d)
|
||||
}
|
||||
|
||||
// Delete deletes a deployment instance from the
|
||||
// kubernetes cluster
|
||||
func (k *Kubeclient) Delete(name string, opts *metav1.DeleteOptions) error {
|
||||
|
||||
if strings.TrimSpace(name) == "" {
|
||||
return errors.New("failed to delete deployment: missing deployment name")
|
||||
}
|
||||
|
||||
cli, err := k.getClientOrCached()
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "failed to delete deployment {%s}", name)
|
||||
}
|
||||
|
||||
return k.del(cli, k.namespace, name, opts)
|
||||
}
|
||||
|
||||
// Create creates a deployment in specified namespace in kubernetes cluster
|
||||
func (k *Kubeclient) Create(deployment *appsv1.Deployment) (*appsv1.Deployment, error) {
|
||||
|
||||
if deployment == nil {
|
||||
return nil, errors.New("failed to create deployment: nil deployment object")
|
||||
}
|
||||
|
||||
cli, err := k.getClientOrCached()
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(
|
||||
err,
|
||||
"failed to create deployment {%s} in namespace {%s}",
|
||||
deployment.Name,
|
||||
deployment.Namespace,
|
||||
)
|
||||
}
|
||||
|
||||
return k.create(cli, k.namespace, deployment)
|
||||
}
|
||||
|
||||
// RolloutStatusf returns deployment's rollout status for given name
|
||||
// in raw bytes
|
||||
func (k *Kubeclient) RolloutStatusf(name string) (op []byte, err error) {
|
||||
cli, err := k.getClientOrCached()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
d, err := k.get(cli, name, k.namespace, &metav1.GetOptions{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return k.rolloutStatusf(d)
|
||||
}
|
||||
|
||||
// RolloutStatus returns deployment's rollout status for given name
|
||||
func (k *Kubeclient) RolloutStatus(name string) (*RolloutOutput, error) {
|
||||
cli, err := k.getClientOrCached()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
d, err := k.get(cli, name, k.namespace, &metav1.GetOptions{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return k.rolloutStatus(d)
|
||||
}
|
||||
135
tests/deploy/rollout_status.go
Normal file
135
tests/deploy/rollout_status.go
Normal file
|
|
@ -0,0 +1,135 @@
|
|||
/*
|
||||
Copyright 2019 The OpenEBS Authors
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package deploy
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// rolloutStatus is a typed function that
|
||||
// abstracts status message formation logic
|
||||
type rolloutStatus func(*Deploy) string
|
||||
|
||||
// rolloutStatuses contains a group of status message for
|
||||
// each predicate checks. It uses predicateName as key.
|
||||
var rolloutStatuses = map[PredicateName]rolloutStatus{
|
||||
// PredicateProgressDeadlineExceeded refer to rolloutStatus
|
||||
// for predicate IsProgressDeadlineExceeded.
|
||||
PredicateProgressDeadlineExceeded: func(d *Deploy) string {
|
||||
return "deployment exceeded its progress deadline"
|
||||
},
|
||||
// PredicateOlderReplicaActive refer to rolloutStatus for
|
||||
// predicate IsOlderReplicaActive.
|
||||
PredicateOlderReplicaActive: func(d *Deploy) string {
|
||||
if d.object.Spec.Replicas == nil {
|
||||
return "replica update in-progress: some older replicas were updated"
|
||||
}
|
||||
return fmt.Sprintf(
|
||||
"replica update in-progress: %d of %d new replicas were updated",
|
||||
d.object.Status.UpdatedReplicas, *d.object.Spec.Replicas)
|
||||
},
|
||||
// PredicateTerminationInProgress refer rolloutStatus
|
||||
// for predicate IsTerminationInProgress.
|
||||
PredicateTerminationInProgress: func(d *Deploy) string {
|
||||
return fmt.Sprintf(
|
||||
"replica termination in-progress: %d old replicas are pending termination",
|
||||
d.object.Status.Replicas-d.object.Status.UpdatedReplicas)
|
||||
},
|
||||
// PredicateUpdateInProgress refer to rolloutStatus for predicate IsUpdateInProgress.
|
||||
PredicateUpdateInProgress: func(d *Deploy) string {
|
||||
return fmt.Sprintf(
|
||||
"replica update in-progress: %d of %d updated replicas are available",
|
||||
d.object.Status.AvailableReplicas, d.object.Status.UpdatedReplicas)
|
||||
},
|
||||
// PredicateNotSpecSynced refer to status rolloutStatus for predicate IsNotSyncSpec.
|
||||
PredicateNotSpecSynced: func(d *Deploy) string {
|
||||
return "deployment rollout in-progress: waiting for deployment spec update"
|
||||
},
|
||||
}
|
||||
|
||||
// rolloutChecks contains a group of predicate it uses predicateName as key.
|
||||
var rolloutChecks = map[PredicateName]Predicate{
|
||||
// PredicateProgressDeadlineExceeded refer to predicate IsProgressDeadlineExceeded.
|
||||
PredicateProgressDeadlineExceeded: IsProgressDeadlineExceeded(),
|
||||
// PredicateOlderReplicaActive refer to predicate IsOlderReplicaActive.
|
||||
PredicateOlderReplicaActive: IsOlderReplicaActive(),
|
||||
// PredicateTerminationInProgress refer to predicate IsTerminationInProgress.
|
||||
PredicateTerminationInProgress: IsTerminationInProgress(),
|
||||
// PredicateUpdateInProgress refer to predicate IsUpdateInProgress.
|
||||
PredicateUpdateInProgress: IsUpdateInProgress(),
|
||||
// PredicateNotSpecSynced refer to predicate IsSyncSpec.
|
||||
PredicateNotSpecSynced: IsNotSyncSpec(),
|
||||
}
|
||||
|
||||
// RolloutOutput struct contains message and boolean value to show rolloutstatus
|
||||
type RolloutOutput struct {
|
||||
IsRolledout bool `json:"isRolledout"`
|
||||
Message string `json:"message"`
|
||||
}
|
||||
|
||||
// rawFn is a typed function that abstracts
|
||||
// conversion of rolloutOutput struct to raw byte
|
||||
type rawFn func(r *RolloutOutput) ([]byte, error)
|
||||
|
||||
// Rollout enables getting various output format of rolloutOutput
|
||||
type Rollout struct {
|
||||
output *RolloutOutput
|
||||
raw rawFn
|
||||
}
|
||||
|
||||
// rolloutBuildOption defines the
|
||||
// abstraction to build a rollout instance
|
||||
type rolloutBuildOption func(*Rollout)
|
||||
|
||||
// NewRollout returns new instance of rollout meant for
|
||||
// rolloutOutput. caller can configure it with different
|
||||
// rolloutOutputBuildOption
|
||||
func NewRollout(opts ...rolloutBuildOption) *Rollout {
|
||||
r := &Rollout{}
|
||||
for _, o := range opts {
|
||||
o(r)
|
||||
}
|
||||
r.withDefaults()
|
||||
return r
|
||||
}
|
||||
|
||||
// withOutputObject sets rolloutOutput in rollout instance
|
||||
func withOutputObject(o *RolloutOutput) rolloutBuildOption {
|
||||
return func(r *Rollout) {
|
||||
r.output = o
|
||||
}
|
||||
}
|
||||
|
||||
// withDefaults sets the default options of rolloutBuilder instance
|
||||
func (r *Rollout) withDefaults() {
|
||||
if r.raw == nil {
|
||||
r.raw = func(o *RolloutOutput) ([]byte, error) {
|
||||
return json.Marshal(o)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Raw returns raw bytes outpot of rollout
|
||||
func (r *Rollout) Raw() ([]byte, error) {
|
||||
if r.output == nil {
|
||||
return nil, errors.New("unable to get rollout status output")
|
||||
}
|
||||
return r.raw(r.output)
|
||||
}
|
||||
139
tests/k8svolume/build.go
Normal file
139
tests/k8svolume/build.go
Normal file
|
|
@ -0,0 +1,139 @@
|
|||
/*
|
||||
Copyright 2019 The OpenEBS Authors
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package k8svolume
|
||||
|
||||
import (
|
||||
"github.com/openebs/zfs-localpv/pkg/common/errors"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
)
|
||||
|
||||
// Builder is the builder object for Volume
|
||||
type Builder struct {
|
||||
volume *Volume
|
||||
errs []error
|
||||
}
|
||||
|
||||
// NewBuilder returns new instance of Builder
|
||||
func NewBuilder() *Builder {
|
||||
return &Builder{volume: &Volume{object: &corev1.Volume{}}}
|
||||
}
|
||||
|
||||
// WithName sets the Name field of Volume with provided value.
|
||||
func (b *Builder) WithName(name string) *Builder {
|
||||
if len(name) == 0 {
|
||||
b.errs = append(
|
||||
b.errs,
|
||||
errors.New("failed to build Volume object: missing Volume name"),
|
||||
)
|
||||
return b
|
||||
}
|
||||
b.volume.object.Name = name
|
||||
return b
|
||||
}
|
||||
|
||||
// WithHostDirectory sets the VolumeSource field of Volume with provided hostpath
|
||||
// as type directory.
|
||||
func (b *Builder) WithHostDirectory(path string) *Builder {
|
||||
if len(path) == 0 {
|
||||
b.errs = append(
|
||||
b.errs,
|
||||
errors.New("failed to build volume object: missing volume path"),
|
||||
)
|
||||
return b
|
||||
}
|
||||
volumeSource := corev1.VolumeSource{
|
||||
HostPath: &corev1.HostPathVolumeSource{
|
||||
Path: path,
|
||||
},
|
||||
}
|
||||
|
||||
b.volume.object.VolumeSource = volumeSource
|
||||
return b
|
||||
}
|
||||
|
||||
// WithHostPathAndType sets the VolumeSource field of Volume with provided
|
||||
// hostpath as directory path and type as directory type
|
||||
func (b *Builder) WithHostPathAndType(
|
||||
dirpath string,
|
||||
dirtype *corev1.HostPathType,
|
||||
) *Builder {
|
||||
if dirtype == nil {
|
||||
b.errs = append(
|
||||
b.errs,
|
||||
errors.New("failed to build volume object: nil volume type"),
|
||||
)
|
||||
return b
|
||||
}
|
||||
if len(dirpath) == 0 {
|
||||
b.errs = append(
|
||||
b.errs,
|
||||
errors.New("failed to build volume object: missing volume path"),
|
||||
)
|
||||
return b
|
||||
}
|
||||
newdirtype := *dirtype
|
||||
volumeSource := corev1.VolumeSource{
|
||||
HostPath: &corev1.HostPathVolumeSource{
|
||||
Path: dirpath,
|
||||
Type: &newdirtype,
|
||||
},
|
||||
}
|
||||
|
||||
b.volume.object.VolumeSource = volumeSource
|
||||
return b
|
||||
}
|
||||
|
||||
// WithPVCSource sets the Volume field of Volume with provided pvc
|
||||
func (b *Builder) WithPVCSource(pvcName string) *Builder {
|
||||
if len(pvcName) == 0 {
|
||||
b.errs = append(
|
||||
b.errs,
|
||||
errors.New("failed to build volume object: missing pvc name"),
|
||||
)
|
||||
return b
|
||||
}
|
||||
volumeSource := corev1.VolumeSource{
|
||||
PersistentVolumeClaim: &corev1.PersistentVolumeClaimVolumeSource{
|
||||
ClaimName: pvcName,
|
||||
},
|
||||
}
|
||||
b.volume.object.VolumeSource = volumeSource
|
||||
return b
|
||||
}
|
||||
|
||||
// WithEmptyDir sets the EmptyDir field of the Volume with provided dir
|
||||
func (b *Builder) WithEmptyDir(dir *corev1.EmptyDirVolumeSource) *Builder {
|
||||
if dir == nil {
|
||||
b.errs = append(
|
||||
b.errs,
|
||||
errors.New("failed to build volume object: nil dir"),
|
||||
)
|
||||
return b
|
||||
}
|
||||
|
||||
newdir := *dir
|
||||
b.volume.object.EmptyDir = &newdir
|
||||
return b
|
||||
}
|
||||
|
||||
// Build returns the Volume API instance
|
||||
func (b *Builder) Build() (*corev1.Volume, error) {
|
||||
if len(b.errs) > 0 {
|
||||
return nil, errors.Errorf("%+v", b.errs)
|
||||
}
|
||||
return b.volume.object, nil
|
||||
}
|
||||
71
tests/k8svolume/volume.go
Normal file
71
tests/k8svolume/volume.go
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
// Copyright 2019 The OpenEBS Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package k8svolume
|
||||
|
||||
import (
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
)
|
||||
|
||||
// Volume is a wrapper over named volume api object, used
|
||||
// within Pods. It provides build, validations and other common
|
||||
// logic to be used by various feature specific callers.
|
||||
type Volume struct {
|
||||
object *corev1.Volume
|
||||
}
|
||||
|
||||
type volumeBuildOption func(*Volume)
|
||||
|
||||
// NewForAPIObject returns a new instance of Volume
|
||||
func NewForAPIObject(obj *corev1.Volume, opts ...volumeBuildOption) *Volume {
|
||||
v := &Volume{object: obj}
|
||||
for _, o := range opts {
|
||||
o(v)
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
// Predicate defines an abstraction
|
||||
// to determine conditional checks
|
||||
// against the provided volume instance
|
||||
type Predicate func(*Volume) bool
|
||||
|
||||
// IsNil returns true if the Volume instance
|
||||
// is nil
|
||||
func (v *Volume) IsNil() bool {
|
||||
return v.object == nil
|
||||
}
|
||||
|
||||
// IsNil is predicate to filter out nil Volume
|
||||
// instances
|
||||
func IsNil() Predicate {
|
||||
return func(v *Volume) bool {
|
||||
return v.IsNil()
|
||||
}
|
||||
}
|
||||
|
||||
// PredicateList holds a list of predicate
|
||||
type PredicateList []Predicate
|
||||
|
||||
// all returns true if all the predicates
|
||||
// succeed against the provided pvc
|
||||
// instance
|
||||
func (l PredicateList) all(v *Volume) bool {
|
||||
for _, pred := range l {
|
||||
if !pred(v) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
184
tests/pod/build.go
Normal file
184
tests/pod/build.go
Normal file
|
|
@ -0,0 +1,184 @@
|
|||
/*
|
||||
Copyright 2019 The OpenEBS Authors
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package pod
|
||||
|
||||
import (
|
||||
"github.com/openebs/zfs-localpv/pkg/common/errors"
|
||||
"github.com/openebs/zfs-localpv/tests/container"
|
||||
volume "github.com/openebs/zfs-localpv/tests/k8svolume"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
)
|
||||
|
||||
const (
|
||||
// k8sNodeLabelKeyHostname is the label key used by Kubernetes
|
||||
// to store the hostname on the node resource.
|
||||
k8sNodeLabelKeyHostname = "kubernetes.io/hostname"
|
||||
)
|
||||
|
||||
// Builder is the builder object for Pod
|
||||
type Builder struct {
|
||||
pod *Pod
|
||||
errs []error
|
||||
}
|
||||
|
||||
// NewBuilder returns new instance of Builder
|
||||
func NewBuilder() *Builder {
|
||||
return &Builder{pod: &Pod{object: &corev1.Pod{}}}
|
||||
}
|
||||
|
||||
// WithName sets the Name field of Pod with provided value.
|
||||
func (b *Builder) WithName(name string) *Builder {
|
||||
if len(name) == 0 {
|
||||
b.errs = append(
|
||||
b.errs,
|
||||
errors.New("failed to build Pod object: missing Pod name"),
|
||||
)
|
||||
return b
|
||||
}
|
||||
b.pod.object.Name = name
|
||||
return b
|
||||
}
|
||||
|
||||
// WithNamespace sets the Namespace field of Pod with provided value.
|
||||
func (b *Builder) WithNamespace(namespace string) *Builder {
|
||||
if len(namespace) == 0 {
|
||||
b.errs = append(
|
||||
b.errs,
|
||||
errors.New("failed to build Pod object: missing namespace"),
|
||||
)
|
||||
return b
|
||||
}
|
||||
b.pod.object.Namespace = namespace
|
||||
return b
|
||||
}
|
||||
|
||||
// WithContainerBuilder adds a container to this pod object.
|
||||
//
|
||||
// NOTE:
|
||||
// container details are present in the provided container
|
||||
// builder object
|
||||
func (b *Builder) WithContainerBuilder(
|
||||
containerBuilder *container.Builder,
|
||||
) *Builder {
|
||||
containerObj, err := containerBuilder.Build()
|
||||
if err != nil {
|
||||
b.errs = append(b.errs, errors.Wrap(err, "failed to build pod"))
|
||||
return b
|
||||
}
|
||||
b.pod.object.Spec.Containers = append(
|
||||
b.pod.object.Spec.Containers,
|
||||
containerObj,
|
||||
)
|
||||
return b
|
||||
}
|
||||
|
||||
// WithVolumeBuilder sets Volumes field of deployment.
|
||||
func (b *Builder) WithVolumeBuilder(volumeBuilder *volume.Builder) *Builder {
|
||||
vol, err := volumeBuilder.Build()
|
||||
if err != nil {
|
||||
b.errs = append(b.errs, errors.Wrap(err, "failed to build deployment"))
|
||||
return b
|
||||
}
|
||||
b.pod.object.Spec.Volumes = append(
|
||||
b.pod.object.Spec.Volumes,
|
||||
*vol,
|
||||
)
|
||||
return b
|
||||
}
|
||||
|
||||
// WithRestartPolicy sets the RestartPolicy field in Pod with provided arguments
|
||||
func (b *Builder) WithRestartPolicy(
|
||||
restartPolicy corev1.RestartPolicy,
|
||||
) *Builder {
|
||||
b.pod.object.Spec.RestartPolicy = restartPolicy
|
||||
return b
|
||||
}
|
||||
|
||||
// WithNodeName sets the NodeName field of Pod with provided value.
|
||||
func (b *Builder) WithNodeName(nodeName string) *Builder {
|
||||
if len(nodeName) == 0 {
|
||||
b.errs = append(
|
||||
b.errs,
|
||||
errors.New("failed to build Pod object: missing Pod node name"),
|
||||
)
|
||||
return b
|
||||
}
|
||||
b.pod.object.Spec.NodeName = nodeName
|
||||
return b
|
||||
}
|
||||
|
||||
// WithNodeSelectorHostnameNew sets the Pod NodeSelector to the provided hostname value
|
||||
// This function replaces (resets) the NodeSelector to use only hostname selector
|
||||
func (b *Builder) WithNodeSelectorHostnameNew(hostname string) *Builder {
|
||||
if len(hostname) == 0 {
|
||||
b.errs = append(
|
||||
b.errs,
|
||||
errors.New("failed to build Pod object: missing Pod hostname"),
|
||||
)
|
||||
return b
|
||||
}
|
||||
|
||||
b.pod.object.Spec.NodeSelector = map[string]string{
|
||||
k8sNodeLabelKeyHostname: hostname,
|
||||
}
|
||||
|
||||
return b
|
||||
}
|
||||
|
||||
// WithContainers sets the Containers field in Pod with provided arguments
|
||||
func (b *Builder) WithContainers(containers []corev1.Container) *Builder {
|
||||
if len(containers) == 0 {
|
||||
b.errs = append(
|
||||
b.errs,
|
||||
errors.New("failed to build Pod object: missing containers"),
|
||||
)
|
||||
return b
|
||||
}
|
||||
b.pod.object.Spec.Containers = containers
|
||||
return b
|
||||
}
|
||||
|
||||
// WithContainer sets the Containers field in Pod with provided arguments
|
||||
func (b *Builder) WithContainer(container corev1.Container) *Builder {
|
||||
return b.WithContainers([]corev1.Container{container})
|
||||
}
|
||||
|
||||
// WithVolumes sets the Volumes field in Pod with provided arguments
|
||||
func (b *Builder) WithVolumes(volumes []corev1.Volume) *Builder {
|
||||
if len(volumes) == 0 {
|
||||
b.errs = append(
|
||||
b.errs,
|
||||
errors.New("failed to build Pod object: missing volumes"),
|
||||
)
|
||||
return b
|
||||
}
|
||||
b.pod.object.Spec.Volumes = volumes
|
||||
return b
|
||||
}
|
||||
|
||||
// WithVolume sets the Volumes field in Pod with provided arguments
|
||||
func (b *Builder) WithVolume(volume corev1.Volume) *Builder {
|
||||
return b.WithVolumes([]corev1.Volume{volume})
|
||||
}
|
||||
|
||||
// Build returns the Pod API instance
|
||||
func (b *Builder) Build() (*corev1.Pod, error) {
|
||||
if len(b.errs) > 0 {
|
||||
return nil, errors.Errorf("%+v", b.errs)
|
||||
}
|
||||
return b.pod.object, nil
|
||||
}
|
||||
82
tests/pod/buildlist.go
Normal file
82
tests/pod/buildlist.go
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
/*
|
||||
Copyright 2019 The OpenEBS Authors
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package pod
|
||||
|
||||
import (
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
)
|
||||
|
||||
// ListBuilder enables building an instance of
|
||||
// Podlist
|
||||
type ListBuilder struct {
|
||||
list *PodList
|
||||
filters predicateList
|
||||
}
|
||||
|
||||
// NewListBuilder returns a instance of ListBuilder
|
||||
func NewListBuilder() *ListBuilder {
|
||||
return &ListBuilder{list: &PodList{items: []*Pod{}}}
|
||||
}
|
||||
|
||||
// ListBuilderForAPIList returns a instance of ListBuilder from API PodList
|
||||
func ListBuilderForAPIList(pods *corev1.PodList) *ListBuilder {
|
||||
b := &ListBuilder{list: &PodList{}}
|
||||
if pods == nil {
|
||||
return b
|
||||
}
|
||||
for _, p := range pods.Items {
|
||||
p := p
|
||||
b.list.items = append(b.list.items, &Pod{object: &p})
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
// ListBuilderForObjectList returns a instance of ListBuilder from API Pods
|
||||
func ListBuilderForObjectList(pods ...*Pod) *ListBuilder {
|
||||
b := &ListBuilder{list: &PodList{}}
|
||||
if pods == nil {
|
||||
return b
|
||||
}
|
||||
for _, p := range pods {
|
||||
p := p
|
||||
b.list.items = append(b.list.items, p)
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
// List returns the list of pod
|
||||
// instances that was built by this
|
||||
// builder
|
||||
func (b *ListBuilder) List() *PodList {
|
||||
if b.filters == nil || len(b.filters) == 0 {
|
||||
return b.list
|
||||
}
|
||||
filtered := &PodList{}
|
||||
for _, pod := range b.list.items {
|
||||
if b.filters.all(pod) {
|
||||
filtered.items = append(filtered.items, pod)
|
||||
}
|
||||
}
|
||||
return filtered
|
||||
}
|
||||
|
||||
// WithFilter add filters on which the pod
|
||||
// has to be filtered
|
||||
func (b *ListBuilder) WithFilter(pred ...Predicate) *ListBuilder {
|
||||
b.filters = append(b.filters, pred...)
|
||||
return b
|
||||
}
|
||||
406
tests/pod/kubernetes.go
Normal file
406
tests/pod/kubernetes.go
Normal file
|
|
@ -0,0 +1,406 @@
|
|||
// Copyright 2019 The OpenEBS Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package pod
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
|
||||
"github.com/openebs/zfs-localpv/pkg/common/errors"
|
||||
client "github.com/openebs/zfs-localpv/pkg/common/kubernetes/client"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
clientset "k8s.io/client-go/kubernetes"
|
||||
"k8s.io/client-go/kubernetes/scheme"
|
||||
"k8s.io/client-go/rest"
|
||||
"k8s.io/client-go/tools/remotecommand"
|
||||
)
|
||||
|
||||
// getClientsetFn is a typed function that
|
||||
// abstracts fetching of clientset
|
||||
type getClientsetFn func() (*clientset.Clientset, error)
|
||||
|
||||
// getClientsetFromPathFn is a typed function that
|
||||
// abstracts fetching of clientset from kubeConfigPath
|
||||
type getClientsetForPathFn func(kubeConfigPath string) (*clientset.Clientset, error)
|
||||
|
||||
// getKubeConfigFn is a typed function that
|
||||
// abstracts fetching of config
|
||||
type getKubeConfigFn func() (*rest.Config, error)
|
||||
|
||||
// getKubeConfigForPathFn is a typed function that
|
||||
// abstracts fetching of config from kubeConfigPath
|
||||
type getKubeConfigForPathFn func(kubeConfigPath string) (*rest.Config, error)
|
||||
|
||||
// createFn is a typed function that abstracts
|
||||
// creation of pod
|
||||
type createFn func(cli *clientset.Clientset, namespace string, pod *corev1.Pod) (*corev1.Pod, error)
|
||||
|
||||
// listFn is a typed function that abstracts
|
||||
// listing of pods
|
||||
type listFn func(cli *clientset.Clientset, namespace string, opts metav1.ListOptions) (*corev1.PodList, error)
|
||||
|
||||
// deleteFn is a typed function that abstracts
|
||||
// deleting of pod
|
||||
type deleteFn func(cli *clientset.Clientset, namespace, name string, opts *metav1.DeleteOptions) error
|
||||
|
||||
// deleteFn is a typed function that abstracts
|
||||
// deletion of pod's collection
|
||||
type deleteCollectionFn func(cli *clientset.Clientset, namespace string, listOpts metav1.ListOptions, deleteOpts *metav1.DeleteOptions) error
|
||||
|
||||
// getFn is a typed function that abstracts
|
||||
// to get pod
|
||||
type getFn func(cli *clientset.Clientset, namespace, name string, opts metav1.GetOptions) (*corev1.Pod, error)
|
||||
|
||||
// execFn is a typed function that abstracts
|
||||
// pod exec
|
||||
type execFn func(cli *clientset.Clientset, config *rest.Config, name, namespace string, opts *corev1.PodExecOptions) (*ExecOutput, error)
|
||||
|
||||
// defaultExec is the default implementation of execFn
|
||||
func defaultExec(
|
||||
cli *clientset.Clientset,
|
||||
config *rest.Config,
|
||||
name string,
|
||||
namespace string,
|
||||
opts *corev1.PodExecOptions,
|
||||
) (*ExecOutput, error) {
|
||||
var stdout, stderr bytes.Buffer
|
||||
|
||||
req := cli.CoreV1().RESTClient().Post().
|
||||
Resource("pods").
|
||||
Name(name).
|
||||
Namespace(namespace).
|
||||
SubResource("exec").
|
||||
VersionedParams(opts, scheme.ParameterCodec)
|
||||
|
||||
// create exec executor which is an interface
|
||||
// for transporting shell-style streams
|
||||
exec, err := remotecommand.NewSPDYExecutor(config, "POST", req.URL())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Stream initiates transport of standard shell streams
|
||||
// It will transport any non-nil stream to a remote system,
|
||||
// and return an error if a problem occurs
|
||||
err = exec.Stream(remotecommand.StreamOptions{
|
||||
Stdin: nil,
|
||||
Stdout: &stdout,
|
||||
Stderr: &stderr,
|
||||
Tty: opts.TTY,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
execOutput := &ExecOutput{
|
||||
Stdout: stdout.String(),
|
||||
Stderr: stderr.String(),
|
||||
}
|
||||
return execOutput, nil
|
||||
}
|
||||
|
||||
// KubeClient enables kubernetes API operations
|
||||
// on pod instance
|
||||
type KubeClient struct {
|
||||
// clientset refers to pod clientset
|
||||
// that will be responsible to
|
||||
// make kubernetes API calls
|
||||
clientset *clientset.Clientset
|
||||
|
||||
// namespace holds the namespace on which
|
||||
// KubeClient has to operate
|
||||
namespace string
|
||||
|
||||
// kubeConfig represents kubernetes config
|
||||
kubeConfig *rest.Config
|
||||
|
||||
// kubeconfig path to get kubernetes clientset
|
||||
kubeConfigPath string
|
||||
|
||||
// functions useful during mocking
|
||||
getKubeConfig getKubeConfigFn
|
||||
getKubeConfigForPath getKubeConfigForPathFn
|
||||
getClientset getClientsetFn
|
||||
getClientsetForPath getClientsetForPathFn
|
||||
create createFn
|
||||
list listFn
|
||||
del deleteFn
|
||||
delCollection deleteCollectionFn
|
||||
get getFn
|
||||
exec execFn
|
||||
}
|
||||
|
||||
// ExecOutput struct contains stdout and stderr
|
||||
type ExecOutput struct {
|
||||
Stdout string `json:"stdout"`
|
||||
Stderr string `json:"stderr"`
|
||||
}
|
||||
|
||||
// KubeClientBuildOption defines the abstraction
|
||||
// to build a KubeClient instance
|
||||
type KubeClientBuildOption func(*KubeClient)
|
||||
|
||||
// withDefaults sets the default options
|
||||
// of KubeClient instance
|
||||
func (k *KubeClient) withDefaults() {
|
||||
if k.getKubeConfig == nil {
|
||||
k.getKubeConfig = func() (config *rest.Config, err error) {
|
||||
return client.New().Config()
|
||||
}
|
||||
}
|
||||
if k.getKubeConfigForPath == nil {
|
||||
k.getKubeConfigForPath = func(kubeConfigPath string) (
|
||||
config *rest.Config, err error) {
|
||||
return client.New(client.WithKubeConfigPath(kubeConfigPath)).
|
||||
GetConfigForPathOrDirect()
|
||||
}
|
||||
}
|
||||
if k.getClientset == nil {
|
||||
k.getClientset = func() (clients *clientset.Clientset, err error) {
|
||||
return client.New().Clientset()
|
||||
}
|
||||
}
|
||||
if k.getClientsetForPath == nil {
|
||||
k.getClientsetForPath = func(kubeConfigPath string) (
|
||||
clients *clientset.Clientset, err error) {
|
||||
return client.New(client.WithKubeConfigPath(kubeConfigPath)).Clientset()
|
||||
}
|
||||
}
|
||||
if k.create == nil {
|
||||
k.create = func(cli *clientset.Clientset,
|
||||
namespace string, pod *corev1.Pod) (*corev1.Pod, error) {
|
||||
return cli.CoreV1().Pods(namespace).Create(pod)
|
||||
}
|
||||
}
|
||||
if k.list == nil {
|
||||
k.list = func(cli *clientset.Clientset,
|
||||
namespace string, opts metav1.ListOptions) (*corev1.PodList, error) {
|
||||
return cli.CoreV1().Pods(namespace).List(opts)
|
||||
}
|
||||
}
|
||||
if k.del == nil {
|
||||
k.del = func(cli *clientset.Clientset, namespace,
|
||||
name string, opts *metav1.DeleteOptions) error {
|
||||
return cli.CoreV1().Pods(namespace).Delete(name, opts)
|
||||
}
|
||||
}
|
||||
if k.get == nil {
|
||||
k.get = func(cli *clientset.Clientset, namespace,
|
||||
name string, opts metav1.GetOptions) (*corev1.Pod, error) {
|
||||
return cli.CoreV1().Pods(namespace).Get(name, opts)
|
||||
}
|
||||
}
|
||||
if k.delCollection == nil {
|
||||
k.delCollection = func(cli *clientset.Clientset, namespace string,
|
||||
listOpts metav1.ListOptions, deleteOpts *metav1.DeleteOptions) error {
|
||||
return cli.CoreV1().Pods(namespace).DeleteCollection(deleteOpts, listOpts)
|
||||
}
|
||||
}
|
||||
if k.exec == nil {
|
||||
k.exec = defaultExec
|
||||
}
|
||||
}
|
||||
|
||||
// WithClientSet sets the kubernetes client against
|
||||
// the KubeClient instance
|
||||
func WithClientSet(c *clientset.Clientset) KubeClientBuildOption {
|
||||
return func(k *KubeClient) {
|
||||
k.clientset = c
|
||||
}
|
||||
}
|
||||
|
||||
// WithKubeConfigPath sets the kubeConfig path
|
||||
// against client instance
|
||||
func WithKubeConfigPath(path string) KubeClientBuildOption {
|
||||
return func(k *KubeClient) {
|
||||
k.kubeConfigPath = path
|
||||
}
|
||||
}
|
||||
|
||||
// NewKubeClient returns a new instance of KubeClient meant for
|
||||
// zfs volume replica operations
|
||||
func NewKubeClient(opts ...KubeClientBuildOption) *KubeClient {
|
||||
k := &KubeClient{}
|
||||
for _, o := range opts {
|
||||
o(k)
|
||||
}
|
||||
k.withDefaults()
|
||||
return k
|
||||
}
|
||||
|
||||
// WithNamespace sets the kubernetes namespace against
|
||||
// the provided namespace
|
||||
func (k *KubeClient) WithNamespace(namespace string) *KubeClient {
|
||||
k.namespace = namespace
|
||||
return k
|
||||
}
|
||||
|
||||
// WithKubeConfig sets the kubernetes config against
|
||||
// the KubeClient instance
|
||||
func (k *KubeClient) WithKubeConfig(config *rest.Config) *KubeClient {
|
||||
k.kubeConfig = config
|
||||
return k
|
||||
}
|
||||
|
||||
func (k *KubeClient) getClientsetForPathOrDirect() (
|
||||
*clientset.Clientset, error) {
|
||||
if k.kubeConfigPath != "" {
|
||||
return k.getClientsetForPath(k.kubeConfigPath)
|
||||
}
|
||||
return k.getClientset()
|
||||
}
|
||||
|
||||
// getClientsetOrCached returns either a new instance
|
||||
// of kubernetes client or its cached copy
|
||||
func (k *KubeClient) getClientsetOrCached() (*clientset.Clientset, error) {
|
||||
if k.clientset != nil {
|
||||
return k.clientset, nil
|
||||
}
|
||||
|
||||
cs, err := k.getClientsetForPathOrDirect()
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to get clientset")
|
||||
}
|
||||
k.clientset = cs
|
||||
return k.clientset, nil
|
||||
}
|
||||
|
||||
func (k *KubeClient) getKubeConfigForPathOrDirect() (*rest.Config, error) {
|
||||
if k.kubeConfigPath != "" {
|
||||
return k.getKubeConfigForPath(k.kubeConfigPath)
|
||||
}
|
||||
return k.getKubeConfig()
|
||||
}
|
||||
|
||||
// getKubeConfigOrCached returns either a new instance
|
||||
// of kubernetes config or its cached copy
|
||||
func (k *KubeClient) getKubeConfigOrCached() (*rest.Config, error) {
|
||||
if k.kubeConfig != nil {
|
||||
return k.kubeConfig, nil
|
||||
}
|
||||
|
||||
kc, err := k.getKubeConfigForPathOrDirect()
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to get kube config")
|
||||
}
|
||||
k.kubeConfig = kc
|
||||
return k.kubeConfig, nil
|
||||
}
|
||||
|
||||
// List returns a list of pod
|
||||
// instances present in kubernetes cluster
|
||||
func (k *KubeClient) List(opts metav1.ListOptions) (*corev1.PodList, error) {
|
||||
cli, err := k.getClientsetOrCached()
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to list pods")
|
||||
}
|
||||
return k.list(cli, k.namespace, opts)
|
||||
}
|
||||
|
||||
// Delete deletes a pod instance present in kubernetes cluster
|
||||
func (k *KubeClient) Delete(name string, opts *metav1.DeleteOptions) error {
|
||||
if len(name) == 0 {
|
||||
return errors.New("failed to delete pod: missing pod name")
|
||||
}
|
||||
cli, err := k.getClientsetOrCached()
|
||||
if err != nil {
|
||||
return errors.Wrapf(
|
||||
err,
|
||||
"failed to delete pod {%s}: failed to get clientset",
|
||||
name,
|
||||
)
|
||||
}
|
||||
return k.del(cli, k.namespace, name, opts)
|
||||
}
|
||||
|
||||
// Create creates a pod in specified namespace in kubernetes cluster
|
||||
func (k *KubeClient) Create(pod *corev1.Pod) (*corev1.Pod, error) {
|
||||
if pod == nil {
|
||||
return nil, errors.New("failed to create pod: nil pod object")
|
||||
}
|
||||
cli, err := k.getClientsetOrCached()
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(
|
||||
err,
|
||||
"failed to create pod {%s} in namespace {%s}",
|
||||
pod.Name,
|
||||
pod.Namespace,
|
||||
)
|
||||
}
|
||||
return k.create(cli, k.namespace, pod)
|
||||
}
|
||||
|
||||
// Get gets a pod object present in kubernetes cluster
|
||||
func (k *KubeClient) Get(name string,
|
||||
opts metav1.GetOptions) (*corev1.Pod, error) {
|
||||
if len(name) == 0 {
|
||||
return nil, errors.New("failed to get pod: missing pod name")
|
||||
}
|
||||
cli, err := k.getClientsetOrCached()
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(
|
||||
err,
|
||||
"failed to get pod {%s}: failed to get clientset",
|
||||
name,
|
||||
)
|
||||
}
|
||||
return k.get(cli, k.namespace, name, opts)
|
||||
}
|
||||
|
||||
// GetRaw gets pod object for a given name and namespace present
|
||||
// in kubernetes cluster and returns result in raw byte.
|
||||
func (k *KubeClient) GetRaw(name string,
|
||||
opts metav1.GetOptions) ([]byte, error) {
|
||||
p, err := k.Get(name, opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return json.Marshal(p)
|
||||
}
|
||||
|
||||
// Exec runs a command remotely in a container of a pod
|
||||
func (k *KubeClient) Exec(name string,
|
||||
opts *corev1.PodExecOptions) (*ExecOutput, error) {
|
||||
cli, err := k.getClientsetOrCached()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
config, err := k.getKubeConfigOrCached()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return k.exec(cli, config, name, k.namespace, opts)
|
||||
}
|
||||
|
||||
// ExecRaw runs a command remotely in a container of a pod
|
||||
// and returns raw output
|
||||
func (k *KubeClient) ExecRaw(name string,
|
||||
opts *corev1.PodExecOptions) ([]byte, error) {
|
||||
execOutput, err := k.Exec(name, opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return json.Marshal(execOutput)
|
||||
}
|
||||
|
||||
// DeleteCollection deletes a collection of pod objects.
|
||||
func (k *KubeClient) DeleteCollection(listOpts metav1.ListOptions, deleteOpts *metav1.DeleteOptions) error {
|
||||
cli, err := k.getClientsetOrCached()
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "failed to delete the collection of pods")
|
||||
}
|
||||
return k.delCollection(cli, k.namespace, listOpts, deleteOpts)
|
||||
}
|
||||
183
tests/pod/pod.go
Normal file
183
tests/pod/pod.go
Normal file
|
|
@ -0,0 +1,183 @@
|
|||
// Copyright 2019 The OpenEBS Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package pod
|
||||
|
||||
import (
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
)
|
||||
|
||||
// Pod holds the api's pod objects
|
||||
type Pod struct {
|
||||
object *corev1.Pod
|
||||
}
|
||||
|
||||
// PodList holds the list of API pod instances
|
||||
type PodList struct {
|
||||
items []*Pod
|
||||
}
|
||||
|
||||
// PredicateList holds a list of predicate
|
||||
type predicateList []Predicate
|
||||
|
||||
// Predicate defines an abstraction
|
||||
// to determine conditional checks
|
||||
// against the provided pod instance
|
||||
type Predicate func(*Pod) bool
|
||||
|
||||
// ToAPIList converts PodList to API PodList
|
||||
func (pl *PodList) ToAPIList() *corev1.PodList {
|
||||
plist := &corev1.PodList{}
|
||||
for _, pod := range pl.items {
|
||||
plist.Items = append(plist.Items, *pod.object)
|
||||
}
|
||||
return plist
|
||||
}
|
||||
|
||||
type podBuildOption func(*Pod)
|
||||
|
||||
// NewForAPIObject returns a new instance of Pod
|
||||
func NewForAPIObject(obj *corev1.Pod, opts ...podBuildOption) *Pod {
|
||||
p := &Pod{object: obj}
|
||||
for _, o := range opts {
|
||||
o(p)
|
||||
}
|
||||
return p
|
||||
}
|
||||
|
||||
// Len returns the number of items present in the PodList
|
||||
func (pl *PodList) Len() int {
|
||||
return len(pl.items)
|
||||
}
|
||||
|
||||
// all returns true if all the predicates
|
||||
// succeed against the provided pod
|
||||
// instance
|
||||
func (l predicateList) all(p *Pod) bool {
|
||||
for _, pred := range l {
|
||||
if !pred(p) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// IsRunning retuns true if the pod is in running
|
||||
// state
|
||||
func (p *Pod) IsRunning() bool {
|
||||
return p.object.Status.Phase == "Running"
|
||||
}
|
||||
|
||||
// IsRunning is a predicate to filter out pods
|
||||
// which in running state
|
||||
func IsRunning() Predicate {
|
||||
return func(p *Pod) bool {
|
||||
return p.IsRunning()
|
||||
}
|
||||
}
|
||||
|
||||
// IsCompleted retuns true if the pod is in completed
|
||||
// state
|
||||
func (p *Pod) IsCompleted() bool {
|
||||
return p.object.Status.Phase == "Succeeded"
|
||||
}
|
||||
|
||||
// IsCompleted is a predicate to filter out pods
|
||||
// which in completed state
|
||||
func IsCompleted() Predicate {
|
||||
return func(p *Pod) bool {
|
||||
return p.IsCompleted()
|
||||
}
|
||||
}
|
||||
|
||||
// HasLabels returns true if provided labels
|
||||
// map[key]value are present in the provided PodList
|
||||
// instance
|
||||
func HasLabels(keyValuePair map[string]string) Predicate {
|
||||
return func(p *Pod) bool {
|
||||
// objKeyValues := p.object.GetLabels()
|
||||
for key, value := range keyValuePair {
|
||||
if !p.HasLabel(key, value) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// HasLabel return true if provided lable
|
||||
// key and value are present in the the provided PodList
|
||||
// instance
|
||||
func (p *Pod) HasLabel(key, value string) bool {
|
||||
val, ok := p.object.GetLabels()[key]
|
||||
if ok {
|
||||
return val == value
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// HasLabel is predicate to filter out labeled
|
||||
// pod instances
|
||||
func HasLabel(key, value string) Predicate {
|
||||
return func(p *Pod) bool {
|
||||
return p.HasLabel(key, value)
|
||||
}
|
||||
}
|
||||
|
||||
// IsNil returns true if the pod instance
|
||||
// is nil
|
||||
func (p *Pod) IsNil() bool {
|
||||
return p.object == nil
|
||||
}
|
||||
|
||||
// IsNil is predicate to filter out nil pod
|
||||
// instances
|
||||
func IsNil() Predicate {
|
||||
return func(p *Pod) bool {
|
||||
return p.IsNil()
|
||||
}
|
||||
}
|
||||
|
||||
// GetAPIObject returns a API's Pod
|
||||
func (p *Pod) GetAPIObject() *corev1.Pod {
|
||||
return p.object
|
||||
}
|
||||
|
||||
// FromList created a PodList with provided api podlist
|
||||
func FromList(pods *corev1.PodList) *PodList {
|
||||
pl := ListBuilderForAPIList(pods).
|
||||
List()
|
||||
return pl
|
||||
}
|
||||
|
||||
// GetScheduledNodes returns the nodes on which pods are scheduled
|
||||
func (pl *PodList) GetScheduledNodes() map[string]int {
|
||||
nodeNames := make(map[string]int)
|
||||
for _, p := range pl.items {
|
||||
p := p // pin it
|
||||
nodeNames[p.object.Spec.NodeName]++
|
||||
}
|
||||
return nodeNames
|
||||
}
|
||||
|
||||
// IsMatchNodeAny checks the PodList is running on the provided nodes
|
||||
func (pl *PodList) IsMatchNodeAny(nodes map[string]int) bool {
|
||||
for _, p := range pl.items {
|
||||
p := p // pin it
|
||||
if nodes[p.object.Spec.NodeName] == 0 {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
60
tests/provision_test.go
Normal file
60
tests/provision_test.go
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
/*
|
||||
Copyright 2019 The OpenEBS Authors
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package tests
|
||||
|
||||
import (
|
||||
. "github.com/onsi/ginkgo"
|
||||
)
|
||||
|
||||
var _ = Describe("[zfspv] TEST VOLUME PROVISIONING", func() {
|
||||
Context("App is deployed with zfs driver", func() {
|
||||
It("Running zfs volume Creation Test", volumeCreationTest)
|
||||
})
|
||||
})
|
||||
|
||||
func datasetCreationTest() {
|
||||
By("Creating zfs storage class", createZfsStorageClass)
|
||||
By("creating and verifying PVC bound status", createAndVerifyPVC)
|
||||
By("Creating and deploying app pod", createDeployVerifyApp)
|
||||
By("verifying ZFSVolume object", VerifyZFSVolume)
|
||||
By("Deleting application deployment", deleteAppDeployment)
|
||||
By("Deleting pvc", deletePVC)
|
||||
By("Deleting storage class", deleteStorageClass)
|
||||
}
|
||||
|
||||
func zvolCreationTest() {
|
||||
By("Creating ext4 storage class", createExt4StorageClass)
|
||||
By("creating and verifying PVC bound status", createAndVerifyPVC)
|
||||
|
||||
/*
|
||||
* commenting app deployment as provisioning is taking time
|
||||
* since we are creating a zfs pool on a sparse file and mkfs
|
||||
* is taking forever for zvol.
|
||||
* Should create the zfs pool on the disk. Need to check if travis
|
||||
* has that functionality.
|
||||
*/
|
||||
//By("Creating and deploying app pod", createDeployVerifyApp)
|
||||
By("verifying ZFSVolume object", VerifyZFSVolume)
|
||||
//By("Deleting application deployment", deleteAppDeployment)
|
||||
By("Deleting pvc", deletePVC)
|
||||
By("Deleting storage class", deleteStorageClass)
|
||||
}
|
||||
|
||||
func volumeCreationTest() {
|
||||
By("Running dataset creation test", datasetCreationTest)
|
||||
By("Running zvol creation test", zvolCreationTest)
|
||||
}
|
||||
473
tests/pts/pts.go
Normal file
473
tests/pts/pts.go
Normal file
|
|
@ -0,0 +1,473 @@
|
|||
// Copyright 2019 The OpenEBS Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package pts
|
||||
|
||||
import (
|
||||
"github.com/openebs/zfs-localpv/pkg/common/errors"
|
||||
"github.com/openebs/zfs-localpv/tests/container"
|
||||
volume "github.com/openebs/zfs-localpv/tests/k8svolume"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
)
|
||||
|
||||
// PodTemplateSpec holds the api's podtemplatespec objects
|
||||
type PodTemplateSpec struct {
|
||||
Object *corev1.PodTemplateSpec
|
||||
}
|
||||
|
||||
// Builder is the builder object for Pod
|
||||
type Builder struct {
|
||||
podtemplatespec *PodTemplateSpec
|
||||
errs []error
|
||||
}
|
||||
|
||||
// NewBuilder returns new instance of Builder
|
||||
func NewBuilder() *Builder {
|
||||
return &Builder{
|
||||
podtemplatespec: &PodTemplateSpec{
|
||||
Object: &corev1.PodTemplateSpec{},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// WithName sets the Name field of podtemplatespec with provided value.
|
||||
func (b *Builder) WithName(name string) *Builder {
|
||||
if len(name) == 0 {
|
||||
b.errs = append(
|
||||
b.errs,
|
||||
errors.New("failed to build podtemplatespec object: missing name"),
|
||||
)
|
||||
return b
|
||||
}
|
||||
b.podtemplatespec.Object.Name = name
|
||||
return b
|
||||
}
|
||||
|
||||
// WithNamespace sets the Namespace field of PodTemplateSpec with provided value.
|
||||
func (b *Builder) WithNamespace(namespace string) *Builder {
|
||||
if len(namespace) == 0 {
|
||||
b.errs = append(
|
||||
b.errs,
|
||||
errors.New(
|
||||
"failed to build podtemplatespec object: missing namespace",
|
||||
),
|
||||
)
|
||||
return b
|
||||
}
|
||||
b.podtemplatespec.Object.Namespace = namespace
|
||||
return b
|
||||
}
|
||||
|
||||
// WithAnnotations merges existing annotations if any
|
||||
// with the ones that are provided here
|
||||
func (b *Builder) WithAnnotations(annotations map[string]string) *Builder {
|
||||
if len(annotations) == 0 {
|
||||
b.errs = append(
|
||||
b.errs,
|
||||
errors.New("failed to build deployment object: missing annotations"),
|
||||
)
|
||||
return b
|
||||
}
|
||||
|
||||
if b.podtemplatespec.Object.Annotations == nil {
|
||||
return b.WithAnnotationsNew(annotations)
|
||||
}
|
||||
|
||||
for key, value := range annotations {
|
||||
b.podtemplatespec.Object.Annotations[key] = value
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
// WithAnnotationsNew resets the annotation field of podtemplatespec
|
||||
// with provided arguments
|
||||
func (b *Builder) WithAnnotationsNew(annotations map[string]string) *Builder {
|
||||
if len(annotations) == 0 {
|
||||
b.errs = append(
|
||||
b.errs,
|
||||
errors.New(
|
||||
"failed to build podtemplatespec object: missing annotations",
|
||||
),
|
||||
)
|
||||
return b
|
||||
}
|
||||
|
||||
// copy of original map
|
||||
newannotations := map[string]string{}
|
||||
for key, value := range annotations {
|
||||
newannotations[key] = value
|
||||
}
|
||||
|
||||
// override
|
||||
b.podtemplatespec.Object.Annotations = newannotations
|
||||
return b
|
||||
}
|
||||
|
||||
// WithLabels merges existing labels if any
|
||||
// with the ones that are provided here
|
||||
func (b *Builder) WithLabels(labels map[string]string) *Builder {
|
||||
if len(labels) == 0 {
|
||||
b.errs = append(
|
||||
b.errs,
|
||||
errors.New(
|
||||
"failed to build podtemplatespec object: missing labels",
|
||||
),
|
||||
)
|
||||
return b
|
||||
}
|
||||
|
||||
if b.podtemplatespec.Object.Labels == nil {
|
||||
return b.WithLabelsNew(labels)
|
||||
}
|
||||
|
||||
for key, value := range labels {
|
||||
b.podtemplatespec.Object.Labels[key] = value
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
// WithLabelsNew resets the labels field of podtemplatespec
|
||||
// with provided arguments
|
||||
func (b *Builder) WithLabelsNew(labels map[string]string) *Builder {
|
||||
if len(labels) == 0 {
|
||||
b.errs = append(
|
||||
b.errs,
|
||||
errors.New(
|
||||
"failed to build podtemplatespec object: missing labels",
|
||||
),
|
||||
)
|
||||
return b
|
||||
}
|
||||
|
||||
// copy of original map
|
||||
newlbls := map[string]string{}
|
||||
for key, value := range labels {
|
||||
newlbls[key] = value
|
||||
}
|
||||
|
||||
// override
|
||||
b.podtemplatespec.Object.Labels = newlbls
|
||||
return b
|
||||
}
|
||||
|
||||
// WithNodeSelector merges the nodeselectors if present
|
||||
// with the provided arguments
|
||||
func (b *Builder) WithNodeSelector(nodeselectors map[string]string) *Builder {
|
||||
if len(nodeselectors) == 0 {
|
||||
b.errs = append(
|
||||
b.errs,
|
||||
errors.New(
|
||||
"failed to build podtemplatespec object: missing nodeselectors",
|
||||
),
|
||||
)
|
||||
return b
|
||||
}
|
||||
|
||||
if b.podtemplatespec.Object.Spec.NodeSelector == nil {
|
||||
return b.WithNodeSelectorNew(nodeselectors)
|
||||
}
|
||||
|
||||
for key, value := range nodeselectors {
|
||||
b.podtemplatespec.Object.Spec.NodeSelector[key] = value
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
// WithNodeSelectorNew resets the nodeselector field of podtemplatespec
|
||||
// with provided arguments
|
||||
func (b *Builder) WithNodeSelectorNew(nodeselectors map[string]string) *Builder {
|
||||
if len(nodeselectors) == 0 {
|
||||
b.errs = append(
|
||||
b.errs,
|
||||
errors.New(
|
||||
"failed to build podtemplatespec object: missing nodeselectors",
|
||||
),
|
||||
)
|
||||
return b
|
||||
}
|
||||
|
||||
// copy of original map
|
||||
newnodeselectors := map[string]string{}
|
||||
for key, value := range nodeselectors {
|
||||
newnodeselectors[key] = value
|
||||
}
|
||||
|
||||
// override
|
||||
b.podtemplatespec.Object.Spec.NodeSelector = newnodeselectors
|
||||
return b
|
||||
}
|
||||
|
||||
// WithServiceAccountName sets the ServiceAccountnNme field of podtemplatespec
|
||||
func (b *Builder) WithServiceAccountName(serviceAccountnNme string) *Builder {
|
||||
if len(serviceAccountnNme) == 0 {
|
||||
b.errs = append(
|
||||
b.errs,
|
||||
errors.New(
|
||||
"failed to build podtemplatespec object: missing serviceaccountname",
|
||||
),
|
||||
)
|
||||
return b
|
||||
}
|
||||
|
||||
b.podtemplatespec.Object.Spec.ServiceAccountName = serviceAccountnNme
|
||||
return b
|
||||
}
|
||||
|
||||
// WithAffinity sets the affinity field of podtemplatespec
|
||||
func (b *Builder) WithAffinity(affinity *corev1.Affinity) *Builder {
|
||||
if affinity == nil {
|
||||
b.errs = append(
|
||||
b.errs,
|
||||
errors.New(
|
||||
"failed to build podtemplatespec object: missing affinity",
|
||||
),
|
||||
)
|
||||
return b
|
||||
}
|
||||
|
||||
// copy of original pointer
|
||||
newaffinitylist := *affinity
|
||||
|
||||
b.podtemplatespec.Object.Spec.Affinity = &newaffinitylist
|
||||
return b
|
||||
}
|
||||
|
||||
// WithTolerations merges the existing tolerations
|
||||
// with the provided arguments
|
||||
func (b *Builder) WithTolerations(tolerations ...corev1.Toleration) *Builder {
|
||||
if tolerations == nil {
|
||||
b.errs = append(
|
||||
b.errs,
|
||||
errors.New(
|
||||
"failed to build podtemplatespec object: nil tolerations",
|
||||
),
|
||||
)
|
||||
return b
|
||||
}
|
||||
if len(tolerations) == 0 {
|
||||
b.errs = append(
|
||||
b.errs,
|
||||
errors.New(
|
||||
"failed to build podtemplatespec object: missing tolerations",
|
||||
),
|
||||
)
|
||||
return b
|
||||
}
|
||||
|
||||
if len(b.podtemplatespec.Object.Spec.Tolerations) == 0 {
|
||||
return b.WithTolerationsNew(tolerations...)
|
||||
}
|
||||
|
||||
b.podtemplatespec.Object.Spec.Tolerations = append(
|
||||
b.podtemplatespec.Object.Spec.Tolerations,
|
||||
tolerations...,
|
||||
)
|
||||
|
||||
return b
|
||||
}
|
||||
|
||||
// WithTolerationsNew sets the tolerations field of podtemplatespec
|
||||
func (b *Builder) WithTolerationsNew(tolerations ...corev1.Toleration) *Builder {
|
||||
if tolerations == nil {
|
||||
b.errs = append(
|
||||
b.errs,
|
||||
errors.New(
|
||||
"failed to build podtemplatespec object: nil tolerations",
|
||||
),
|
||||
)
|
||||
return b
|
||||
}
|
||||
if len(tolerations) == 0 {
|
||||
b.errs = append(
|
||||
b.errs,
|
||||
errors.New(
|
||||
"failed to build podtemplatespec object: missing tolerations",
|
||||
),
|
||||
)
|
||||
return b
|
||||
}
|
||||
|
||||
// copy of original slice
|
||||
newtolerations := []corev1.Toleration{}
|
||||
newtolerations = append(newtolerations, tolerations...)
|
||||
|
||||
b.podtemplatespec.Object.Spec.Tolerations = newtolerations
|
||||
|
||||
return b
|
||||
}
|
||||
|
||||
// WithContainerBuilders builds the list of containerbuilder
|
||||
// provided and merges it to the containers field of the podtemplatespec
|
||||
func (b *Builder) WithContainerBuilders(
|
||||
containerBuilderList ...*container.Builder,
|
||||
) *Builder {
|
||||
if containerBuilderList == nil {
|
||||
b.errs = append(
|
||||
b.errs,
|
||||
errors.New("failed to build podtemplatespec: nil containerbuilder"),
|
||||
)
|
||||
return b
|
||||
}
|
||||
for _, containerBuilder := range containerBuilderList {
|
||||
containerObj, err := containerBuilder.Build()
|
||||
if err != nil {
|
||||
b.errs = append(
|
||||
b.errs,
|
||||
errors.Wrap(
|
||||
err,
|
||||
"failed to build podtemplatespec",
|
||||
),
|
||||
)
|
||||
return b
|
||||
}
|
||||
b.podtemplatespec.Object.Spec.Containers = append(
|
||||
b.podtemplatespec.Object.Spec.Containers,
|
||||
containerObj,
|
||||
)
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
// WithVolumeBuilders builds the list of volumebuilders provided
|
||||
// and merges it to the volumes field of podtemplatespec.
|
||||
func (b *Builder) WithVolumeBuilders(
|
||||
volumeBuilderList ...*volume.Builder,
|
||||
) *Builder {
|
||||
if volumeBuilderList == nil {
|
||||
b.errs = append(
|
||||
b.errs,
|
||||
errors.New("failed to build podtemplatespec: nil volumeBuilderList"),
|
||||
)
|
||||
return b
|
||||
}
|
||||
for _, volumeBuilder := range volumeBuilderList {
|
||||
vol, err := volumeBuilder.Build()
|
||||
if err != nil {
|
||||
b.errs = append(
|
||||
b.errs,
|
||||
errors.Wrap(err, "failed to build podtemplatespec"),
|
||||
)
|
||||
return b
|
||||
}
|
||||
newvol := *vol
|
||||
b.podtemplatespec.Object.Spec.Volumes = append(
|
||||
b.podtemplatespec.Object.Spec.Volumes,
|
||||
newvol,
|
||||
)
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
// WithContainerBuildersNew builds the list of containerbuilder
|
||||
// provided and sets the containers field of the podtemplatespec
|
||||
func (b *Builder) WithContainerBuildersNew(
|
||||
containerBuilderList ...*container.Builder,
|
||||
) *Builder {
|
||||
if containerBuilderList == nil {
|
||||
b.errs = append(
|
||||
b.errs,
|
||||
errors.New("failed to build podtemplatespec: nil containerbuilder"),
|
||||
)
|
||||
return b
|
||||
}
|
||||
if len(containerBuilderList) == 0 {
|
||||
b.errs = append(
|
||||
b.errs,
|
||||
errors.New("failed to build podtemplatespec: missing containerbuilder"),
|
||||
)
|
||||
return b
|
||||
}
|
||||
containerList := []corev1.Container{}
|
||||
for _, containerBuilder := range containerBuilderList {
|
||||
containerObj, err := containerBuilder.Build()
|
||||
if err != nil {
|
||||
b.errs = append(
|
||||
b.errs,
|
||||
errors.Wrap(
|
||||
err,
|
||||
"failed to build podtemplatespec",
|
||||
),
|
||||
)
|
||||
return b
|
||||
}
|
||||
containerList = append(
|
||||
containerList,
|
||||
containerObj,
|
||||
)
|
||||
}
|
||||
b.podtemplatespec.Object.Spec.Containers = containerList
|
||||
return b
|
||||
}
|
||||
|
||||
// WithVolumeBuildersNew builds the list of volumebuilders provided
|
||||
// and sets Volumes field of podtemplatespec.
|
||||
func (b *Builder) WithVolumeBuildersNew(
|
||||
volumeBuilderList ...*volume.Builder,
|
||||
) *Builder {
|
||||
if volumeBuilderList == nil {
|
||||
b.errs = append(
|
||||
b.errs,
|
||||
errors.New("failed to build podtemplatespec: nil volumeBuilderList"),
|
||||
)
|
||||
return b
|
||||
}
|
||||
if len(volumeBuilderList) == 0 {
|
||||
b.errs = append(
|
||||
b.errs,
|
||||
errors.New("failed to build podtemplatespec: missing volumeBuilderList"),
|
||||
)
|
||||
return b
|
||||
}
|
||||
volList := []corev1.Volume{}
|
||||
for _, volumeBuilder := range volumeBuilderList {
|
||||
vol, err := volumeBuilder.Build()
|
||||
if err != nil {
|
||||
b.errs = append(
|
||||
b.errs,
|
||||
errors.Wrap(err, "failed to build podtemplatespec"),
|
||||
)
|
||||
return b
|
||||
}
|
||||
newvol := *vol
|
||||
volList = append(
|
||||
volList,
|
||||
newvol,
|
||||
)
|
||||
}
|
||||
b.podtemplatespec.Object.Spec.Volumes = volList
|
||||
return b
|
||||
}
|
||||
|
||||
// Build returns a deployment instance
|
||||
func (b *Builder) Build() (*PodTemplateSpec, error) {
|
||||
err := b.validate()
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(
|
||||
err,
|
||||
"failed to build a podtemplatespec: %s",
|
||||
b.podtemplatespec.Object,
|
||||
)
|
||||
}
|
||||
return b.podtemplatespec, nil
|
||||
}
|
||||
|
||||
func (b *Builder) validate() error {
|
||||
if len(b.errs) != 0 {
|
||||
return errors.Errorf(
|
||||
"failed to validate: build errors were found: %v",
|
||||
b.errs,
|
||||
)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
181
tests/pvc/build.go
Normal file
181
tests/pvc/build.go
Normal file
|
|
@ -0,0 +1,181 @@
|
|||
/*
|
||||
Copyright 2019 The OpenEBS Authors
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package pvc
|
||||
|
||||
import (
|
||||
"github.com/openebs/zfs-localpv/pkg/common/errors"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/api/resource"
|
||||
)
|
||||
|
||||
// Builder is the builder object for PVC
|
||||
type Builder struct {
|
||||
pvc *PVC
|
||||
errs []error
|
||||
}
|
||||
|
||||
// NewBuilder returns new instance of Builder
|
||||
func NewBuilder() *Builder {
|
||||
return &Builder{pvc: &PVC{object: &corev1.PersistentVolumeClaim{}}}
|
||||
}
|
||||
|
||||
// BuildFrom returns new instance of Builder
|
||||
// from the provided api instance
|
||||
func BuildFrom(pvc *corev1.PersistentVolumeClaim) *Builder {
|
||||
if pvc == nil {
|
||||
b := NewBuilder()
|
||||
b.errs = append(
|
||||
b.errs,
|
||||
errors.New("failed to build pvc object: nil pvc"),
|
||||
)
|
||||
return b
|
||||
}
|
||||
return &Builder{
|
||||
pvc: &PVC{
|
||||
object: pvc,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// WithName sets the Name field of PVC with provided value.
|
||||
func (b *Builder) WithName(name string) *Builder {
|
||||
if len(name) == 0 {
|
||||
b.errs = append(b.errs, errors.New("failed to build PVC object: missing PVC name"))
|
||||
return b
|
||||
}
|
||||
b.pvc.object.Name = name
|
||||
return b
|
||||
}
|
||||
|
||||
// WithGenerateName sets the GenerateName field of
|
||||
// PVC with provided value
|
||||
func (b *Builder) WithGenerateName(name string) *Builder {
|
||||
if len(name) == 0 {
|
||||
b.errs = append(
|
||||
b.errs,
|
||||
errors.New("failed to build PVC object: missing PVC generateName"),
|
||||
)
|
||||
return b
|
||||
}
|
||||
|
||||
b.pvc.object.GenerateName = name
|
||||
return b
|
||||
}
|
||||
|
||||
// WithNamespace sets the Namespace field of PVC provided arguments
|
||||
func (b *Builder) WithNamespace(namespace string) *Builder {
|
||||
if len(namespace) == 0 {
|
||||
namespace = "default"
|
||||
}
|
||||
b.pvc.object.Namespace = namespace
|
||||
return b
|
||||
}
|
||||
|
||||
// WithAnnotations sets the Annotations field of PVC with provided arguments
|
||||
func (b *Builder) WithAnnotations(annotations map[string]string) *Builder {
|
||||
if len(annotations) == 0 {
|
||||
b.errs = append(b.errs, errors.New("failed to build PVC object: missing annotations"))
|
||||
return b
|
||||
}
|
||||
b.pvc.object.Annotations = annotations
|
||||
return b
|
||||
}
|
||||
|
||||
// WithLabels merges existing labels if any
|
||||
// with the ones that are provided here
|
||||
func (b *Builder) WithLabels(labels map[string]string) *Builder {
|
||||
if len(labels) == 0 {
|
||||
b.errs = append(
|
||||
b.errs,
|
||||
errors.New("failed to build PVC object: missing labels"),
|
||||
)
|
||||
return b
|
||||
}
|
||||
|
||||
if b.pvc.object.Labels == nil {
|
||||
b.pvc.object.Labels = map[string]string{}
|
||||
}
|
||||
|
||||
for key, value := range labels {
|
||||
b.pvc.object.Labels[key] = value
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
// WithLabelsNew resets existing labels if any with
|
||||
// ones that are provided here
|
||||
func (b *Builder) WithLabelsNew(labels map[string]string) *Builder {
|
||||
if len(labels) == 0 {
|
||||
b.errs = append(
|
||||
b.errs,
|
||||
errors.New("failed to build PVC object: missing labels"),
|
||||
)
|
||||
return b
|
||||
}
|
||||
|
||||
// copy of original map
|
||||
newlbls := map[string]string{}
|
||||
for key, value := range labels {
|
||||
newlbls[key] = value
|
||||
}
|
||||
|
||||
// override
|
||||
b.pvc.object.Labels = newlbls
|
||||
return b
|
||||
}
|
||||
|
||||
// WithStorageClass sets the StorageClass field of PVC with provided arguments
|
||||
func (b *Builder) WithStorageClass(scName string) *Builder {
|
||||
if len(scName) == 0 {
|
||||
b.errs = append(b.errs, errors.New("failed to build PVC object: missing storageclass name"))
|
||||
return b
|
||||
}
|
||||
b.pvc.object.Spec.StorageClassName = &scName
|
||||
return b
|
||||
}
|
||||
|
||||
// WithAccessModes sets the AccessMode field in PVC with provided arguments
|
||||
func (b *Builder) WithAccessModes(accessMode []corev1.PersistentVolumeAccessMode) *Builder {
|
||||
if len(accessMode) == 0 {
|
||||
b.errs = append(b.errs, errors.New("failed to build PVC object: missing accessmodes"))
|
||||
return b
|
||||
}
|
||||
b.pvc.object.Spec.AccessModes = accessMode
|
||||
return b
|
||||
}
|
||||
|
||||
// WithCapacity sets the Capacity field in PVC with provided arguments
|
||||
func (b *Builder) WithCapacity(capacity string) *Builder {
|
||||
resCapacity, err := resource.ParseQuantity(capacity)
|
||||
if err != nil {
|
||||
b.errs = append(b.errs, errors.Wrapf(err, "failed to build PVC object: failed to parse capacity {%s}", capacity))
|
||||
return b
|
||||
}
|
||||
resourceList := corev1.ResourceList{
|
||||
corev1.ResourceName(corev1.ResourceStorage): resCapacity,
|
||||
}
|
||||
b.pvc.object.Spec.Resources.Requests = resourceList
|
||||
return b
|
||||
}
|
||||
|
||||
// Build returns the PVC API instance
|
||||
func (b *Builder) Build() (*corev1.PersistentVolumeClaim, error) {
|
||||
if len(b.errs) > 0 {
|
||||
return nil, errors.Errorf("%+v", b.errs)
|
||||
}
|
||||
return b.pvc.object, nil
|
||||
}
|
||||
165
tests/pvc/buildlist.go
Normal file
165
tests/pvc/buildlist.go
Normal file
|
|
@ -0,0 +1,165 @@
|
|||
/*
|
||||
Copyright 2019 The OpenEBS Authors
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package pvc
|
||||
|
||||
import (
|
||||
"github.com/openebs/zfs-localpv/pkg/common/errors"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
)
|
||||
|
||||
// ListBuilder enables building an instance of
|
||||
// PVCList
|
||||
type ListBuilder struct {
|
||||
// template to build a list of pvcs
|
||||
template *corev1.PersistentVolumeClaim
|
||||
|
||||
// count determines the number of
|
||||
// pvcs to be built using the provided
|
||||
// template
|
||||
count int
|
||||
|
||||
list *PVCList
|
||||
filters PredicateList
|
||||
errs []error
|
||||
}
|
||||
|
||||
// NewListBuilder returns an instance of ListBuilder
|
||||
func NewListBuilder() *ListBuilder {
|
||||
return &ListBuilder{list: &PVCList{}}
|
||||
}
|
||||
|
||||
// ListBuilderFromTemplate returns a new instance of
|
||||
// ListBuilder based on the provided pvc template
|
||||
func ListBuilderFromTemplate(pvc *corev1.PersistentVolumeClaim) *ListBuilder {
|
||||
b := NewListBuilder()
|
||||
if pvc == nil {
|
||||
b.errs = append(
|
||||
b.errs,
|
||||
errors.New("failed to build pvc list: nil pvc template"),
|
||||
)
|
||||
return b
|
||||
}
|
||||
|
||||
b.template = pvc
|
||||
b.count = 1
|
||||
return b
|
||||
}
|
||||
|
||||
// ListBuilderForAPIObjects returns a new instance of
|
||||
// ListBuilder based on provided api pvc list
|
||||
func ListBuilderForAPIObjects(pvcs *corev1.PersistentVolumeClaimList) *ListBuilder {
|
||||
b := &ListBuilder{list: &PVCList{}}
|
||||
|
||||
if pvcs == nil {
|
||||
b.errs = append(
|
||||
b.errs,
|
||||
errors.New("failed to build pvc list: missing api list"),
|
||||
)
|
||||
return b
|
||||
}
|
||||
|
||||
for _, pvc := range pvcs.Items {
|
||||
pvc := pvc
|
||||
b.list.items = append(b.list.items, &PVC{object: &pvc})
|
||||
}
|
||||
|
||||
return b
|
||||
}
|
||||
|
||||
// ListBuilderForObjects returns a new instance of
|
||||
// ListBuilder based on provided pvc list
|
||||
func ListBuilderForObjects(pvcs *PVCList) *ListBuilder {
|
||||
b := &ListBuilder{}
|
||||
if pvcs == nil {
|
||||
b.errs = append(
|
||||
b.errs,
|
||||
errors.New("failed to build pvc list: missing object list"),
|
||||
)
|
||||
return b
|
||||
}
|
||||
|
||||
b.list = pvcs
|
||||
return b
|
||||
}
|
||||
|
||||
// WithFilter adds filters on which the pvcs
|
||||
// are filtered
|
||||
func (b *ListBuilder) WithFilter(pred ...Predicate) *ListBuilder {
|
||||
b.filters = append(b.filters, pred...)
|
||||
return b
|
||||
}
|
||||
|
||||
// WithCount sets the count that determines
|
||||
// the number of pvcs to be built
|
||||
func (b *ListBuilder) WithCount(count int) *ListBuilder {
|
||||
b.count = count
|
||||
return b
|
||||
}
|
||||
|
||||
func (b *ListBuilder) buildFromTemplateIfNilList() {
|
||||
if len(b.list.items) != 0 || b.template == nil {
|
||||
return
|
||||
}
|
||||
|
||||
for i := 0; i < b.count; i++ {
|
||||
b.list.items = append(b.list.items, &PVC{object: b.template})
|
||||
}
|
||||
}
|
||||
|
||||
// List returns the list of pvc instances
|
||||
// that was built by this builder
|
||||
func (b *ListBuilder) List() (*PVCList, error) {
|
||||
if len(b.errs) > 0 {
|
||||
return nil, errors.Errorf("failed to build pvc list: %+v", b.errs)
|
||||
}
|
||||
|
||||
b.buildFromTemplateIfNilList()
|
||||
|
||||
if b.filters == nil || len(b.filters) == 0 {
|
||||
return b.list, nil
|
||||
}
|
||||
|
||||
filteredList := &PVCList{}
|
||||
for _, pvc := range b.list.items {
|
||||
if b.filters.all(pvc) {
|
||||
filteredList.items = append(filteredList.items, pvc)
|
||||
}
|
||||
}
|
||||
|
||||
return filteredList, nil
|
||||
}
|
||||
|
||||
// Len returns the number of items present
|
||||
// in the PVCList of a builder
|
||||
func (b *ListBuilder) Len() (int, error) {
|
||||
l, err := b.List()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return l.Len(), nil
|
||||
}
|
||||
|
||||
// APIList builds core API PVC list using listbuilder
|
||||
func (b *ListBuilder) APIList() (*corev1.PersistentVolumeClaimList, error) {
|
||||
l, err := b.List()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return l.ToAPIList(), nil
|
||||
}
|
||||
288
tests/pvc/kubernetes.go
Normal file
288
tests/pvc/kubernetes.go
Normal file
|
|
@ -0,0 +1,288 @@
|
|||
// Copyright 2019 The OpenEBS Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package pvc
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/openebs/zfs-localpv/pkg/common/errors"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
client "github.com/openebs/zfs-localpv/pkg/common/kubernetes/client"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
)
|
||||
|
||||
// getClientsetFn is a typed function that
|
||||
// abstracts fetching of clientset
|
||||
type getClientsetFn func() (clientset *kubernetes.Clientset, err error)
|
||||
|
||||
// getClientsetFromPathFn is a typed function that
|
||||
// abstracts fetching of clientset from kubeConfigPath
|
||||
type getClientsetForPathFn func(kubeConfigPath string) (clientset *kubernetes.Clientset, err error)
|
||||
|
||||
// getpvcFn is a typed function that
|
||||
// abstracts fetching of pvc
|
||||
type getFn func(cli *kubernetes.Clientset, name string, namespace string, opts metav1.GetOptions) (*corev1.PersistentVolumeClaim, error)
|
||||
|
||||
// listFn is a typed function that abstracts
|
||||
// listing of pvcs
|
||||
type listFn func(cli *kubernetes.Clientset, namespace string, opts metav1.ListOptions) (*corev1.PersistentVolumeClaimList, error)
|
||||
|
||||
// deleteFn is a typed function that abstracts
|
||||
// deletion of pvcs
|
||||
type deleteFn func(cli *kubernetes.Clientset, namespace string, name string, deleteOpts *metav1.DeleteOptions) error
|
||||
|
||||
// deleteFn is a typed function that abstracts
|
||||
// deletion of pvc's collection
|
||||
type deleteCollectionFn func(cli *kubernetes.Clientset, namespace string, listOpts metav1.ListOptions, deleteOpts *metav1.DeleteOptions) error
|
||||
|
||||
// createFn is a typed function that abstracts
|
||||
// creation of pvc
|
||||
type createFn func(cli *kubernetes.Clientset, namespace string, pvc *corev1.PersistentVolumeClaim) (*corev1.PersistentVolumeClaim, error)
|
||||
|
||||
// updateFn is a typed function that abstracts
|
||||
// updation of pvc
|
||||
type updateFn func(cli *kubernetes.Clientset, namespace string, pvc *corev1.PersistentVolumeClaim) (*corev1.PersistentVolumeClaim, error)
|
||||
|
||||
// Kubeclient enables kubernetes API operations
|
||||
// on pvc instance
|
||||
type Kubeclient struct {
|
||||
// clientset refers to pvc clientset
|
||||
// that will be responsible to
|
||||
// make kubernetes API calls
|
||||
clientset *kubernetes.Clientset
|
||||
|
||||
// namespace holds the namespace on which
|
||||
// kubeclient has to operate
|
||||
namespace string
|
||||
|
||||
// kubeconfig path to get kubernetes clientset
|
||||
kubeConfigPath string
|
||||
|
||||
// functions useful during mocking
|
||||
getClientset getClientsetFn
|
||||
getClientsetForPath getClientsetForPathFn
|
||||
list listFn
|
||||
get getFn
|
||||
create createFn
|
||||
update updateFn
|
||||
del deleteFn
|
||||
delCollection deleteCollectionFn
|
||||
}
|
||||
|
||||
// KubeclientBuildOption abstracts creating an
|
||||
// instance of kubeclient
|
||||
type KubeclientBuildOption func(*Kubeclient)
|
||||
|
||||
// withDefaults sets the default options
|
||||
// of kubeclient instance
|
||||
func (k *Kubeclient) withDefaults() {
|
||||
if k.getClientset == nil {
|
||||
k.getClientset = func() (clients *kubernetes.Clientset, err error) {
|
||||
return client.New().Clientset()
|
||||
}
|
||||
}
|
||||
|
||||
if k.getClientsetForPath == nil {
|
||||
k.getClientsetForPath = func(kubeConfigPath string) (clients *kubernetes.Clientset, err error) {
|
||||
return client.New(client.WithKubeConfigPath(kubeConfigPath)).Clientset()
|
||||
}
|
||||
}
|
||||
|
||||
if k.get == nil {
|
||||
k.get = func(cli *kubernetes.Clientset, name string, namespace string, opts metav1.GetOptions) (*corev1.PersistentVolumeClaim, error) {
|
||||
return cli.CoreV1().PersistentVolumeClaims(namespace).Get(name, opts)
|
||||
}
|
||||
}
|
||||
|
||||
if k.list == nil {
|
||||
k.list = func(cli *kubernetes.Clientset, namespace string, opts metav1.ListOptions) (*corev1.PersistentVolumeClaimList, error) {
|
||||
return cli.CoreV1().PersistentVolumeClaims(namespace).List(opts)
|
||||
}
|
||||
}
|
||||
|
||||
if k.del == nil {
|
||||
k.del = func(cli *kubernetes.Clientset, namespace string, name string, deleteOpts *metav1.DeleteOptions) error {
|
||||
return cli.CoreV1().PersistentVolumeClaims(namespace).Delete(name, deleteOpts)
|
||||
}
|
||||
}
|
||||
|
||||
if k.delCollection == nil {
|
||||
k.delCollection = func(cli *kubernetes.Clientset, namespace string, listOpts metav1.ListOptions, deleteOpts *metav1.DeleteOptions) error {
|
||||
return cli.CoreV1().PersistentVolumeClaims(namespace).DeleteCollection(deleteOpts, listOpts)
|
||||
}
|
||||
}
|
||||
|
||||
if k.create == nil {
|
||||
k.create = func(cli *kubernetes.Clientset, namespace string, pvc *corev1.PersistentVolumeClaim) (*corev1.PersistentVolumeClaim, error) {
|
||||
return cli.CoreV1().PersistentVolumeClaims(namespace).Create(pvc)
|
||||
}
|
||||
}
|
||||
|
||||
if k.update == nil {
|
||||
k.update = func(cli *kubernetes.Clientset, namespace string, pvc *corev1.PersistentVolumeClaim) (*corev1.PersistentVolumeClaim, error) {
|
||||
return cli.CoreV1().PersistentVolumeClaims(namespace).Update(pvc)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// WithClientSet sets the kubernetes client against
|
||||
// the kubeclient instance
|
||||
func WithClientSet(c *kubernetes.Clientset) KubeclientBuildOption {
|
||||
return func(k *Kubeclient) {
|
||||
k.clientset = c
|
||||
}
|
||||
}
|
||||
|
||||
// WithKubeConfigPath sets the kubeConfig path
|
||||
// against client instance
|
||||
func WithKubeConfigPath(path string) KubeclientBuildOption {
|
||||
return func(k *Kubeclient) {
|
||||
k.kubeConfigPath = path
|
||||
}
|
||||
}
|
||||
|
||||
// NewKubeClient returns a new instance of kubeclient meant for
|
||||
// pvc operations
|
||||
func NewKubeClient(opts ...KubeclientBuildOption) *Kubeclient {
|
||||
k := &Kubeclient{}
|
||||
for _, o := range opts {
|
||||
o(k)
|
||||
}
|
||||
k.withDefaults()
|
||||
return k
|
||||
}
|
||||
|
||||
// WithNamespace sets the kubernetes client against
|
||||
// the provided namespace
|
||||
func (k *Kubeclient) WithNamespace(namespace string) *Kubeclient {
|
||||
k.namespace = namespace
|
||||
return k
|
||||
}
|
||||
|
||||
func (k *Kubeclient) getClientsetForPathOrDirect() (*kubernetes.Clientset, error) {
|
||||
if k.kubeConfigPath != "" {
|
||||
return k.getClientsetForPath(k.kubeConfigPath)
|
||||
}
|
||||
return k.getClientset()
|
||||
}
|
||||
|
||||
// getClientsetOrCached returns either a new instance
|
||||
// of kubernetes client or its cached copy
|
||||
func (k *Kubeclient) getClientsetOrCached() (*kubernetes.Clientset, error) {
|
||||
if k.clientset != nil {
|
||||
return k.clientset, nil
|
||||
}
|
||||
|
||||
cs, err := k.getClientsetForPathOrDirect()
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to get clientset")
|
||||
}
|
||||
k.clientset = cs
|
||||
return k.clientset, nil
|
||||
}
|
||||
|
||||
// Get returns a pvc resource
|
||||
// instances present in kubernetes cluster
|
||||
func (k *Kubeclient) Get(name string, opts metav1.GetOptions) (*corev1.PersistentVolumeClaim, error) {
|
||||
if strings.TrimSpace(name) == "" {
|
||||
return nil, errors.New("failed to get pvc: missing pvc name")
|
||||
}
|
||||
cli, err := k.getClientsetOrCached()
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to get pvc {%s}", name)
|
||||
}
|
||||
return k.get(cli, name, k.namespace, opts)
|
||||
}
|
||||
|
||||
// List returns a list of pvc
|
||||
// instances present in kubernetes cluster
|
||||
func (k *Kubeclient) List(opts metav1.ListOptions) (*corev1.PersistentVolumeClaimList, error) {
|
||||
cli, err := k.getClientsetOrCached()
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to list pvc listoptions: '%v'", opts)
|
||||
}
|
||||
return k.list(cli, k.namespace, opts)
|
||||
}
|
||||
|
||||
// Delete deletes a pvc instance from the
|
||||
// kubecrnetes cluster
|
||||
func (k *Kubeclient) Delete(name string, deleteOpts *metav1.DeleteOptions) error {
|
||||
if strings.TrimSpace(name) == "" {
|
||||
return errors.New("failed to delete pvc: missing pvc name")
|
||||
}
|
||||
cli, err := k.getClientsetOrCached()
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "failed to delete pvc {%s}", name)
|
||||
}
|
||||
return k.del(cli, k.namespace, name, deleteOpts)
|
||||
}
|
||||
|
||||
// Create creates a pvc in specified namespace in kubernetes cluster
|
||||
func (k *Kubeclient) Create(pvc *corev1.PersistentVolumeClaim) (*corev1.PersistentVolumeClaim, error) {
|
||||
if pvc == nil {
|
||||
return nil, errors.New("failed to create pvc: nil pvc object")
|
||||
}
|
||||
cli, err := k.getClientsetOrCached()
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to create pvc {%s} in namespace {%s}", pvc.Name, pvc.Namespace)
|
||||
}
|
||||
return k.create(cli, k.namespace, pvc)
|
||||
}
|
||||
|
||||
// Update updates a pvc in specified namespace in kubernetes cluster
|
||||
func (k *Kubeclient) Update(pvc *corev1.PersistentVolumeClaim) (*corev1.PersistentVolumeClaim, error) {
|
||||
if pvc == nil {
|
||||
return nil, errors.New("failed to update pvc: nil pvc object")
|
||||
}
|
||||
cli, err := k.getClientsetOrCached()
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to update pvc {%s} in namespace {%s}", pvc.Name, pvc.Namespace)
|
||||
}
|
||||
return k.update(cli, k.namespace, pvc)
|
||||
}
|
||||
|
||||
// CreateCollection creates a list of pvcs
|
||||
// in specified namespace in kubernetes cluster
|
||||
func (k *Kubeclient) CreateCollection(
|
||||
list *corev1.PersistentVolumeClaimList,
|
||||
) (*corev1.PersistentVolumeClaimList, error) {
|
||||
if list == nil || len(list.Items) == 0 {
|
||||
return nil, errors.New("failed to create list of pvcs: nil pvc list provided")
|
||||
}
|
||||
|
||||
newlist := &corev1.PersistentVolumeClaimList{}
|
||||
for _, item := range list.Items {
|
||||
item := item
|
||||
obj, err := k.Create(&item)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
newlist.Items = append(newlist.Items, *obj)
|
||||
}
|
||||
|
||||
return newlist, nil
|
||||
}
|
||||
|
||||
// DeleteCollection deletes a collection of pvc objects.
|
||||
func (k *Kubeclient) DeleteCollection(listOpts metav1.ListOptions, deleteOpts *metav1.DeleteOptions) error {
|
||||
cli, err := k.getClientsetOrCached()
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "failed to delete the collection of pvcs")
|
||||
}
|
||||
return k.delCollection(cli, k.namespace, listOpts, deleteOpts)
|
||||
}
|
||||
116
tests/pvc/persistentvolumeclaim.go
Normal file
116
tests/pvc/persistentvolumeclaim.go
Normal file
|
|
@ -0,0 +1,116 @@
|
|||
// Copyright 2019 The OpenEBS Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package pvc
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
)
|
||||
|
||||
// PVC is a wrapper over persistentvolumeclaim api
|
||||
// object. It provides build, validations and other common
|
||||
// logic to be used by various feature specific callers.
|
||||
type PVC struct {
|
||||
object *corev1.PersistentVolumeClaim
|
||||
}
|
||||
|
||||
// PVCList is a wrapper over persistentvolumeclaim api
|
||||
// object. It provides build, validations and other common
|
||||
// logic to be used by various feature specific callers.
|
||||
type PVCList struct {
|
||||
items []*PVC
|
||||
}
|
||||
|
||||
// Len returns the number of items present
|
||||
// in the PVCList
|
||||
func (p *PVCList) Len() int {
|
||||
return len(p.items)
|
||||
}
|
||||
|
||||
// ToAPIList converts PVCList to API PVCList
|
||||
func (p *PVCList) ToAPIList() *corev1.PersistentVolumeClaimList {
|
||||
plist := &corev1.PersistentVolumeClaimList{}
|
||||
for _, pvc := range p.items {
|
||||
plist.Items = append(plist.Items, *pvc.object)
|
||||
}
|
||||
return plist
|
||||
}
|
||||
|
||||
type pvcBuildOption func(*PVC)
|
||||
|
||||
// NewForAPIObject returns a new instance of PVC
|
||||
func NewForAPIObject(obj *corev1.PersistentVolumeClaim, opts ...pvcBuildOption) *PVC {
|
||||
p := &PVC{object: obj}
|
||||
for _, o := range opts {
|
||||
o(p)
|
||||
}
|
||||
return p
|
||||
}
|
||||
|
||||
// Predicate defines an abstraction
|
||||
// to determine conditional checks
|
||||
// against the provided pvc instance
|
||||
type Predicate func(*PVC) bool
|
||||
|
||||
// IsBound returns true if the pvc is bounded
|
||||
func (p *PVC) IsBound() bool {
|
||||
return p.object.Status.Phase == corev1.ClaimBound
|
||||
}
|
||||
|
||||
// IsBound is a predicate to filter out pvcs
|
||||
// which is bounded
|
||||
func IsBound() Predicate {
|
||||
return func(p *PVC) bool {
|
||||
return p.IsBound()
|
||||
}
|
||||
}
|
||||
|
||||
// IsNil returns true if the PVC instance
|
||||
// is nil
|
||||
func (p *PVC) IsNil() bool {
|
||||
return p.object == nil
|
||||
}
|
||||
|
||||
// IsNil is predicate to filter out nil PVC
|
||||
// instances
|
||||
func IsNil() Predicate {
|
||||
return func(p *PVC) bool {
|
||||
return p.IsNil()
|
||||
}
|
||||
}
|
||||
|
||||
// ContainsName is filter function to filter pvc's
|
||||
// based on the name
|
||||
func ContainsName(name string) Predicate {
|
||||
return func(p *PVC) bool {
|
||||
return strings.Contains(p.object.GetName(), name)
|
||||
}
|
||||
}
|
||||
|
||||
// PredicateList holds a list of predicate
|
||||
type PredicateList []Predicate
|
||||
|
||||
// all returns true if all the predicates
|
||||
// succeed against the provided pvc
|
||||
// instance
|
||||
func (l PredicateList) all(p *PVC) bool {
|
||||
for _, pred := range l {
|
||||
if !pred(p) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
115
tests/sc/build.go
Normal file
115
tests/sc/build.go
Normal file
|
|
@ -0,0 +1,115 @@
|
|||
/*
|
||||
Copyright 2019 The OpenEBS Authors
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package sc
|
||||
|
||||
import (
|
||||
"github.com/openebs/zfs-localpv/pkg/common/errors"
|
||||
storagev1 "k8s.io/api/storage/v1"
|
||||
)
|
||||
|
||||
// Builder enables building an instance of StorageClass
|
||||
type Builder struct {
|
||||
sc *StorageClass
|
||||
errs []error
|
||||
}
|
||||
|
||||
// NewBuilder returns new instance of Builder
|
||||
func NewBuilder() *Builder {
|
||||
return &Builder{sc: &StorageClass{object: &storagev1.StorageClass{}}}
|
||||
}
|
||||
|
||||
// WithName sets the Name field of storageclass with provided argument.
|
||||
func (b *Builder) WithName(name string) *Builder {
|
||||
if len(name) == 0 {
|
||||
b.errs = append(b.errs, errors.New("failed to build storageclass: missing storageclass name"))
|
||||
return b
|
||||
}
|
||||
b.sc.object.Name = name
|
||||
return b
|
||||
}
|
||||
|
||||
// WithGenerateName appends a random string after the name
|
||||
func (b *Builder) WithGenerateName(name string) *Builder {
|
||||
b.sc.object.GenerateName = name + "-"
|
||||
return b
|
||||
}
|
||||
|
||||
// WithAnnotations sets the Annotations field of storageclass with provided value.
|
||||
func (b *Builder) WithAnnotations(annotations map[string]string) *Builder {
|
||||
if len(annotations) == 0 {
|
||||
b.errs = append(b.errs, errors.New("failed to build storageclass: missing annotations"))
|
||||
}
|
||||
b.sc.object.Annotations = annotations
|
||||
return b
|
||||
}
|
||||
|
||||
// WithParametersNew resets existing parameters if any with
|
||||
// ones that are provided here
|
||||
func (b *Builder) WithParametersNew(parameters map[string]string) *Builder {
|
||||
if len(parameters) == 0 {
|
||||
b.errs = append(
|
||||
b.errs,
|
||||
errors.New("failed to build storageclass object: no new parameters"),
|
||||
)
|
||||
return b
|
||||
}
|
||||
|
||||
// copy of original map
|
||||
newparameters := map[string]string{}
|
||||
for key, value := range parameters {
|
||||
newparameters[key] = value
|
||||
}
|
||||
|
||||
// override
|
||||
b.sc.object.Parameters = newparameters
|
||||
return b
|
||||
}
|
||||
|
||||
// WithProvisioner sets the Provisioner field of storageclass with provided argument.
|
||||
func (b *Builder) WithProvisioner(provisioner string) *Builder {
|
||||
if len(provisioner) == 0 {
|
||||
b.errs = append(b.errs, errors.New("failed to build storageclass: missing provisioner name"))
|
||||
return b
|
||||
}
|
||||
b.sc.object.Provisioner = provisioner
|
||||
return b
|
||||
}
|
||||
|
||||
// WithVolumeExpansion sets the AllowedVolumeExpansion field of storageclass with provided argument.
|
||||
func (b *Builder) WithVolumeExpansion(expansionAllowed bool) *Builder {
|
||||
b.sc.object.AllowVolumeExpansion = &expansionAllowed
|
||||
return b
|
||||
}
|
||||
|
||||
// Build returns the StorageClass API instance
|
||||
func (b *Builder) Build() (*storagev1.StorageClass, error) {
|
||||
if len(b.errs) > 0 {
|
||||
return nil, errors.Errorf("%+v", b.errs)
|
||||
}
|
||||
return b.sc.object, nil
|
||||
}
|
||||
|
||||
// WithVolumeBindingMode sets the volume binding mode of storageclass with
|
||||
// provided argument.
|
||||
func (b *Builder) WithVolumeBindingMode(bindingMode storagev1.VolumeBindingMode) *Builder {
|
||||
if bindingMode == "" {
|
||||
b.errs = append(b.errs, errors.New("failed to build storageclass: missing volume binding mode"))
|
||||
return b
|
||||
}
|
||||
b.sc.object.VolumeBindingMode = &bindingMode
|
||||
return b
|
||||
}
|
||||
102
tests/sc/buildlist.go
Normal file
102
tests/sc/buildlist.go
Normal file
|
|
@ -0,0 +1,102 @@
|
|||
/*
|
||||
Copyright 2019 The OpenEBS Authors
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package sc
|
||||
|
||||
import (
|
||||
"github.com/openebs/zfs-localpv/pkg/common/errors"
|
||||
storagev1 "k8s.io/api/storage/v1"
|
||||
)
|
||||
|
||||
// ListBuilder enables building an instance of StorageClassList
|
||||
type ListBuilder struct {
|
||||
list *StorageClassList
|
||||
filters predicateList
|
||||
errs []error
|
||||
}
|
||||
|
||||
// NewListBuilder returns a instance of ListBuilder
|
||||
func NewListBuilder() *ListBuilder {
|
||||
return &ListBuilder{list: &StorageClassList{items: []*StorageClass{}}}
|
||||
}
|
||||
|
||||
// ListBuilderForAPIList builds the ListBuilder object based on SC API list
|
||||
func ListBuilderForAPIList(scl *storagev1.StorageClassList) *ListBuilder {
|
||||
b := &ListBuilder{list: &StorageClassList{}}
|
||||
if scl == nil {
|
||||
b.errs = append(b.errs, errors.New("failed to build storageclass list: missing api list"))
|
||||
return b
|
||||
}
|
||||
for _, sc := range scl.Items {
|
||||
sc := sc
|
||||
b.list.items = append(b.list.items, &StorageClass{object: &sc})
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
// ListBuilderForObjects returns a instance of ListBuilder from SC instances
|
||||
func ListBuilderForObjects(scl *StorageClassList) *ListBuilder {
|
||||
b := &ListBuilder{list: &StorageClassList{}}
|
||||
if scl == nil {
|
||||
b.errs = append(b.errs, errors.New("failed to build storageclass list: missing object list"))
|
||||
return b
|
||||
}
|
||||
b.list = scl
|
||||
return b
|
||||
}
|
||||
|
||||
// List returns the list of StorageClass instances that was built by this builder
|
||||
func (b *ListBuilder) List() (*StorageClassList, error) {
|
||||
if len(b.errs) > 0 {
|
||||
return nil, errors.Errorf("failed to list storageclass: %+v", b.errs)
|
||||
}
|
||||
if b.filters == nil || len(b.filters) == 0 {
|
||||
return b.list, nil
|
||||
}
|
||||
filtered := &StorageClassList{}
|
||||
for _, sc := range b.list.items {
|
||||
if b.filters.all(sc) {
|
||||
sc := sc // Pin it
|
||||
filtered.items = append(filtered.items, sc)
|
||||
}
|
||||
}
|
||||
return filtered, nil
|
||||
}
|
||||
|
||||
// WithFilter add filters on which the StorageClass has to be filtered
|
||||
func (b *ListBuilder) WithFilter(pred ...Predicate) *ListBuilder {
|
||||
b.filters = append(b.filters, pred...)
|
||||
return b
|
||||
}
|
||||
|
||||
// APIList builds core API PVC list using listbuilder
|
||||
func (b *ListBuilder) APIList() (*storagev1.StorageClassList, error) {
|
||||
l, err := b.List()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return l.ToAPIList(), nil
|
||||
}
|
||||
|
||||
// Len returns the number of items present
|
||||
// in the PVCList of a builder
|
||||
func (b *ListBuilder) Len() (int, error) {
|
||||
l, err := b.List()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return l.Len(), nil
|
||||
}
|
||||
193
tests/sc/kubernetes.go
Normal file
193
tests/sc/kubernetes.go
Normal file
|
|
@ -0,0 +1,193 @@
|
|||
/*
|
||||
Copyright 2019 The OpenEBS Authors
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package sc
|
||||
|
||||
import (
|
||||
"github.com/openebs/zfs-localpv/pkg/common/errors"
|
||||
client "github.com/openebs/zfs-localpv/pkg/common/kubernetes/client"
|
||||
storagev1 "k8s.io/api/storage/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
)
|
||||
|
||||
// getClientsetFn is a typed function that abstracts
|
||||
// fetching an instance of kubernetes clientset
|
||||
type getClientsetFn func() (clientset *kubernetes.Clientset, err error)
|
||||
|
||||
// getClientsetFromPathFn is a typed function that
|
||||
// abstracts fetching of clientset from kubeConfigPath
|
||||
type getClientsetForPathFn func(kubeConfigPath string) (clientset *kubernetes.Clientset, err error)
|
||||
|
||||
// listFn is a typed function that abstracts
|
||||
// listing of storageclasses
|
||||
type listFn func(cli *kubernetes.Clientset, opts metav1.ListOptions) (*storagev1.StorageClassList, error)
|
||||
|
||||
// getFn is a typed function that abstracts
|
||||
// fetching an instance of storageclass
|
||||
type getFn func(cli *kubernetes.Clientset, name string, opts metav1.GetOptions) (*storagev1.StorageClass, error)
|
||||
|
||||
// createFn is a typed function that abstracts
|
||||
// to create storage class
|
||||
type createFn func(cli *kubernetes.Clientset, sc *storagev1.StorageClass) (*storagev1.StorageClass, error)
|
||||
|
||||
// deleteFn is a typed function that abstracts
|
||||
// to delete storageclass
|
||||
type deleteFn func(cli *kubernetes.Clientset, name string, opts *metav1.DeleteOptions) error
|
||||
|
||||
// Kubeclient enables kubernetes API operations on storageclass instance
|
||||
type Kubeclient struct {
|
||||
// clientset refers to storageclass clientset
|
||||
// that will be responsible to
|
||||
// make kubernetes API calls
|
||||
clientset *kubernetes.Clientset
|
||||
|
||||
// kubeconfig path to get kubernetes clientset
|
||||
kubeConfigPath string
|
||||
|
||||
// functions useful during mocking
|
||||
getClientset getClientsetFn
|
||||
getClientsetForPath getClientsetForPathFn
|
||||
list listFn
|
||||
get getFn
|
||||
create createFn
|
||||
del deleteFn
|
||||
}
|
||||
|
||||
// KubeClientBuildOption defines the abstraction
|
||||
// to build a kubeclient instance
|
||||
type KubeClientBuildOption func(*Kubeclient)
|
||||
|
||||
func (k *Kubeclient) withDefaults() {
|
||||
if k.getClientset == nil {
|
||||
k.getClientset = func() (clients *kubernetes.Clientset, err error) {
|
||||
return client.New().Clientset()
|
||||
}
|
||||
}
|
||||
if k.getClientsetForPath == nil {
|
||||
k.getClientsetForPath = func(kubeConfigPath string) (clients *kubernetes.Clientset, err error) {
|
||||
return client.New(client.WithKubeConfigPath(kubeConfigPath)).Clientset()
|
||||
}
|
||||
}
|
||||
if k.list == nil {
|
||||
k.list = func(cli *kubernetes.Clientset, opts metav1.ListOptions) (*storagev1.StorageClassList, error) {
|
||||
return cli.StorageV1().StorageClasses().List(opts)
|
||||
}
|
||||
}
|
||||
if k.get == nil {
|
||||
k.get = func(cli *kubernetes.Clientset, name string, opts metav1.GetOptions) (*storagev1.StorageClass, error) {
|
||||
return cli.StorageV1().StorageClasses().Get(name, opts)
|
||||
}
|
||||
}
|
||||
if k.create == nil {
|
||||
k.create = func(cli *kubernetes.Clientset, sc *storagev1.StorageClass) (*storagev1.StorageClass, error) {
|
||||
return cli.StorageV1().StorageClasses().Create(sc)
|
||||
}
|
||||
}
|
||||
if k.del == nil {
|
||||
k.del = func(cli *kubernetes.Clientset, name string, opts *metav1.DeleteOptions) error {
|
||||
return cli.StorageV1().StorageClasses().Delete(name, opts)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// NewKubeClient returns a new instance of kubeclient meant for storageclass
|
||||
func NewKubeClient(opts ...KubeClientBuildOption) *Kubeclient {
|
||||
k := &Kubeclient{}
|
||||
for _, o := range opts {
|
||||
o(k)
|
||||
}
|
||||
k.withDefaults()
|
||||
return k
|
||||
}
|
||||
|
||||
// WithClientSet sets the kubernetes client against
|
||||
// the kubeclient instance
|
||||
func WithClientSet(c *kubernetes.Clientset) KubeClientBuildOption {
|
||||
return func(k *Kubeclient) {
|
||||
k.clientset = c
|
||||
}
|
||||
}
|
||||
|
||||
// WithKubeConfigPath sets the kubeConfig path
|
||||
// against client instance
|
||||
func WithKubeConfigPath(path string) KubeClientBuildOption {
|
||||
return func(k *Kubeclient) {
|
||||
k.kubeConfigPath = path
|
||||
}
|
||||
}
|
||||
|
||||
func (k *Kubeclient) getClientsetForPathOrDirect() (*kubernetes.Clientset, error) {
|
||||
if k.kubeConfigPath != "" {
|
||||
return k.getClientsetForPath(k.kubeConfigPath)
|
||||
}
|
||||
return k.getClientset()
|
||||
}
|
||||
|
||||
// getClientsetOrCached returns either a new
|
||||
// instance of kubernetes clientset or its
|
||||
// cached copy cached copy
|
||||
func (k *Kubeclient) getClientsetOrCached() (*kubernetes.Clientset, error) {
|
||||
if k.clientset != nil {
|
||||
return k.clientset, nil
|
||||
}
|
||||
|
||||
cs, err := k.getClientsetForPathOrDirect()
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to get clientset")
|
||||
}
|
||||
k.clientset = cs
|
||||
return k.clientset, nil
|
||||
}
|
||||
|
||||
// List returns a list of storageclass instances present in kubernetes cluster
|
||||
func (k *Kubeclient) List(opts metav1.ListOptions) (*storagev1.StorageClassList, error) {
|
||||
cli, err := k.getClientsetOrCached()
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to list storageclasses")
|
||||
}
|
||||
return k.list(cli, opts)
|
||||
}
|
||||
|
||||
// Get return a storageclass instance present in kubernetes cluster
|
||||
func (k *Kubeclient) Get(name string, opts metav1.GetOptions) (*storagev1.StorageClass, error) {
|
||||
cli, err := k.getClientsetOrCached()
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to get storageclass {%s}", name)
|
||||
}
|
||||
return k.get(cli, name, opts)
|
||||
}
|
||||
|
||||
// Create creates and returns a storageclass instance
|
||||
func (k *Kubeclient) Create(sc *storagev1.StorageClass) (*storagev1.StorageClass, error) {
|
||||
if sc == nil {
|
||||
return nil, errors.New("failed to create storageclass: nil storageclass object")
|
||||
}
|
||||
cli, err := k.getClientsetOrCached()
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to create storageclass")
|
||||
}
|
||||
return k.create(cli, sc)
|
||||
}
|
||||
|
||||
// Delete deletes the storageclass if present in kubernetes cluster
|
||||
func (k *Kubeclient) Delete(name string, opts *metav1.DeleteOptions) error {
|
||||
cli, err := k.getClientsetOrCached()
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "failed to delete storageclass: {%s}", name)
|
||||
}
|
||||
return k.del(cli, name, opts)
|
||||
}
|
||||
73
tests/sc/storageclass.go
Normal file
73
tests/sc/storageclass.go
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
/*
|
||||
Copyright 2019 The OpenEBS Authors
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package sc
|
||||
|
||||
import (
|
||||
storagev1 "k8s.io/api/storage/v1"
|
||||
)
|
||||
|
||||
// StorageClass is a wrapper over API based
|
||||
// storage class instance
|
||||
type StorageClass struct {
|
||||
object *storagev1.StorageClass
|
||||
}
|
||||
|
||||
// StorageClassList holds the list of StorageClass instances
|
||||
type StorageClassList struct {
|
||||
items []*StorageClass
|
||||
}
|
||||
|
||||
// Predicate defines an abstraction
|
||||
// to determine conditional checks
|
||||
// against the provided StorageClass instance
|
||||
type Predicate func(*StorageClass) bool
|
||||
|
||||
// predicateList holds the list of predicates
|
||||
type predicateList []Predicate
|
||||
|
||||
// ToAPIList converts StorageClassList to API StorageClassList
|
||||
func (scl *StorageClassList) ToAPIList() *storagev1.StorageClassList {
|
||||
sclist := &storagev1.StorageClassList{}
|
||||
for _, sc := range scl.items {
|
||||
sc := sc // Pin it
|
||||
sclist.Items = append(sclist.Items, *sc.object)
|
||||
}
|
||||
return sclist
|
||||
}
|
||||
|
||||
// all returns true if all the predicateList
|
||||
// succeed against the provided StorageClass
|
||||
// instance
|
||||
func (l predicateList) all(sc *StorageClass) bool {
|
||||
for _, pred := range l {
|
||||
if !pred(sc) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// Len returns the number of items present in the StorageClassList
|
||||
func (scl *StorageClassList) Len() int {
|
||||
return len(scl.items)
|
||||
}
|
||||
|
||||
// NewForAPIObject returns a new instance of StorageClass
|
||||
func NewForAPIObject(obj *storagev1.StorageClass) *StorageClass {
|
||||
sc := &StorageClass{object: obj}
|
||||
return sc
|
||||
}
|
||||
63
tests/stringer/stringer.go
Normal file
63
tests/stringer/stringer.go
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
/*
|
||||
Copyright 2019 The OpenEBS Authors
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package stringer
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/ghodss/yaml"
|
||||
)
|
||||
|
||||
// Yaml returns the provided object
|
||||
// as a yaml formatted string
|
||||
func Yaml(ctx string, obj interface{}) string {
|
||||
if obj == nil {
|
||||
return fmt.Sprintf("\n%s {nil}", ctx)
|
||||
}
|
||||
|
||||
str, ok := obj.(string)
|
||||
if ok {
|
||||
return fmt.Sprintf("\n%s {%s}", ctx, str)
|
||||
}
|
||||
|
||||
b, err := yaml.Marshal(obj)
|
||||
if err != nil {
|
||||
return fmt.Sprintf("\n%s {nil}", ctx)
|
||||
}
|
||||
|
||||
return fmt.Sprintf("\n%s {%s}", ctx, string(b))
|
||||
}
|
||||
|
||||
// JSONIndent returns the provided object
|
||||
// as a json indent string
|
||||
func JSONIndent(ctx string, obj interface{}) string {
|
||||
if obj == nil {
|
||||
return fmt.Sprintf("\n%s {nil}", ctx)
|
||||
}
|
||||
|
||||
str, ok := obj.(string)
|
||||
if ok {
|
||||
return fmt.Sprintf("\n%s {%s}", ctx, str)
|
||||
}
|
||||
|
||||
b, err := json.MarshalIndent(obj, "", ".")
|
||||
if err != nil {
|
||||
return fmt.Sprintf("\n%s {nil}", ctx)
|
||||
}
|
||||
|
||||
return fmt.Sprintf("\n%s %s", ctx, string(b))
|
||||
}
|
||||
78
tests/suite_test.go
Normal file
78
tests/suite_test.go
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
/*
|
||||
Copyright 2019 The OpenEBS Authors
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package tests
|
||||
|
||||
import (
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
"github.com/openebs/zfs-localpv/pkg/builder"
|
||||
"github.com/openebs/zfs-localpv/tests/deploy"
|
||||
"github.com/openebs/zfs-localpv/tests/pod"
|
||||
"github.com/openebs/zfs-localpv/tests/pvc"
|
||||
"github.com/openebs/zfs-localpv/tests/sc"
|
||||
appsv1 "k8s.io/api/apps/v1"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
storagev1 "k8s.io/api/storage/v1"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
// auth plugins
|
||||
_ "k8s.io/client-go/plugin/pkg/client/auth/gcp"
|
||||
)
|
||||
|
||||
const (
|
||||
// zfs pool name where volume provisioning will happen
|
||||
POOLNAME = "zfspv-pool"
|
||||
)
|
||||
|
||||
var (
|
||||
ZFSClient *builder.Kubeclient
|
||||
SCClient *sc.Kubeclient
|
||||
PVCClient *pvc.Kubeclient
|
||||
DeployClient *deploy.Kubeclient
|
||||
PodClient *pod.KubeClient
|
||||
openebsNamespace = "openebs"
|
||||
nsName = "zfspv-provision"
|
||||
scName = "zfspv-sc"
|
||||
ZFSProvisioner = "zfs.csi.openebs.io"
|
||||
pvcName = "zfspv-pvc"
|
||||
appName = "busybox-zfspv"
|
||||
|
||||
nsObj *corev1.Namespace
|
||||
scObj *storagev1.StorageClass
|
||||
deployObj *appsv1.Deployment
|
||||
pvcObj *corev1.PersistentVolumeClaim
|
||||
appPod *corev1.PodList
|
||||
accessModes = []corev1.PersistentVolumeAccessMode{corev1.ReadWriteOnce}
|
||||
capacity = "5368709120" // 5Gi
|
||||
KubeConfigPath string
|
||||
)
|
||||
|
||||
func init() {
|
||||
KubeConfigPath = os.Getenv("KUBECONFIG")
|
||||
|
||||
SCClient = sc.NewKubeClient(sc.WithKubeConfigPath(KubeConfigPath))
|
||||
PVCClient = pvc.NewKubeClient(pvc.WithKubeConfigPath(KubeConfigPath))
|
||||
DeployClient = deploy.NewKubeClient(deploy.WithKubeConfigPath(KubeConfigPath))
|
||||
PodClient = pod.NewKubeClient(pod.WithKubeConfigPath(KubeConfigPath))
|
||||
ZFSClient = builder.NewKubeclient(builder.WithKubeConfigPath(KubeConfigPath))
|
||||
}
|
||||
|
||||
func TestSource(t *testing.T) {
|
||||
RegisterFailHandler(Fail)
|
||||
RunSpecs(t, "Test ZFSPV volume provisioning")
|
||||
}
|
||||
294
tests/utils.go
Normal file
294
tests/utils.go
Normal file
|
|
@ -0,0 +1,294 @@
|
|||
/*
|
||||
Copyright 2019 The OpenEBS Authors
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package tests
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
"github.com/openebs/zfs-localpv/pkg/zfs"
|
||||
"github.com/openebs/zfs-localpv/tests/container"
|
||||
"github.com/openebs/zfs-localpv/tests/deploy"
|
||||
"github.com/openebs/zfs-localpv/tests/k8svolume"
|
||||
"github.com/openebs/zfs-localpv/tests/pod"
|
||||
"github.com/openebs/zfs-localpv/tests/pts"
|
||||
"github.com/openebs/zfs-localpv/tests/pvc"
|
||||
"github.com/openebs/zfs-localpv/tests/sc"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
k8serrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
// IsPVCBoundEventually checks if the pvc is bound or not eventually
|
||||
func IsPVCBoundEventually(pvcName string) bool {
|
||||
return Eventually(func() bool {
|
||||
volume, err := PVCClient.
|
||||
Get(pvcName, metav1.GetOptions{})
|
||||
Expect(err).ShouldNot(HaveOccurred())
|
||||
return pvc.NewForAPIObject(volume).IsBound()
|
||||
},
|
||||
120, 10).
|
||||
Should(BeTrue())
|
||||
}
|
||||
|
||||
// IsPodRunningEventually return true if the pod comes to running state
|
||||
func IsPodRunningEventually(namespace, podName string) bool {
|
||||
return Eventually(func() bool {
|
||||
p, err := PodClient.
|
||||
WithNamespace(namespace).
|
||||
Get(podName, metav1.GetOptions{})
|
||||
Expect(err).ShouldNot(HaveOccurred())
|
||||
return pod.NewForAPIObject(p).
|
||||
IsRunning()
|
||||
},
|
||||
150, 10).
|
||||
Should(BeTrue())
|
||||
}
|
||||
|
||||
// IsPVCDeletedEventually tries to get the deleted pvc
|
||||
// and returns true if pvc is not found
|
||||
// else returns false
|
||||
func IsPVCDeletedEventually(pvcName string) bool {
|
||||
return Eventually(func() bool {
|
||||
_, err := PVCClient.
|
||||
Get(pvcName, metav1.GetOptions{})
|
||||
return k8serrors.IsNotFound(err)
|
||||
},
|
||||
120, 10).
|
||||
Should(BeTrue())
|
||||
}
|
||||
|
||||
func createExt4StorageClass() {
|
||||
var (
|
||||
err error
|
||||
)
|
||||
|
||||
parameters := map[string]string{
|
||||
"poolname": POOLNAME,
|
||||
"fstype": "ext4",
|
||||
}
|
||||
|
||||
By("building a ext4 storage class")
|
||||
scObj, err = sc.NewBuilder().
|
||||
WithGenerateName(scName).
|
||||
WithParametersNew(parameters).
|
||||
WithProvisioner(ZFSProvisioner).Build()
|
||||
Expect(err).ShouldNot(HaveOccurred(),
|
||||
"while building ext4 storageclass obj with prefix {%s}", scName)
|
||||
|
||||
scObj, err = SCClient.Create(scObj)
|
||||
Expect(err).To(BeNil(), "while creating a ext4 storageclass {%s}", scName)
|
||||
}
|
||||
|
||||
func createZfsStorageClass() {
|
||||
var (
|
||||
err error
|
||||
)
|
||||
|
||||
parameters := map[string]string{
|
||||
"poolname": POOLNAME,
|
||||
"fstype": "zfs",
|
||||
}
|
||||
|
||||
By("building a zfs storage class")
|
||||
scObj, err = sc.NewBuilder().
|
||||
WithGenerateName(scName).
|
||||
WithParametersNew(parameters).
|
||||
WithProvisioner(ZFSProvisioner).Build()
|
||||
Expect(err).ShouldNot(HaveOccurred(),
|
||||
"while building zfs storageclass obj with prefix {%s}", scName)
|
||||
|
||||
scObj, err = SCClient.Create(scObj)
|
||||
Expect(err).To(BeNil(), "while creating a zfs storageclass {%s}", scName)
|
||||
}
|
||||
|
||||
func VerifyZFSVolume() {
|
||||
By("fetching zfs volume")
|
||||
vol, err := ZFSClient.WithNamespace(openebsNamespace).
|
||||
Get(pvcObj.Spec.VolumeName, metav1.GetOptions{})
|
||||
Expect(err).To(BeNil(), "while fetching the zfs volume {%s}", pvcObj.Spec.VolumeName)
|
||||
|
||||
volType := zfs.VOLTYPE_ZVOL
|
||||
if scObj.Parameters["fstype"] == zfs.FSTYPE_ZFS {
|
||||
volType = zfs.VOLTYPE_DATASET
|
||||
}
|
||||
|
||||
By("verifying zfs volume")
|
||||
Expect(vol.Spec.PoolName).To(Equal(scObj.Parameters["poolname"]),
|
||||
"while checking poolname of zfs volume", pvcObj.Spec.VolumeName)
|
||||
Expect(vol.Spec.FsType).To(Equal(scObj.Parameters["fstype"]),
|
||||
"while checking fstype of zfs volume", pvcObj.Spec.VolumeName)
|
||||
Expect(vol.Spec.VolumeType).To(Equal(volType),
|
||||
"while checking Volume type as dataset", pvcObj.Spec.VolumeName)
|
||||
Expect(vol.Spec.Capacity).To(Equal(capacity),
|
||||
"while checking capacity of zfs volume", pvcObj.Spec.VolumeName)
|
||||
|
||||
// it might fail if we are checking finializer before event is processed by node agent
|
||||
Expect(vol.Finalizers[0]).To(Equal(zfs.ZFSFinalizer), "while checking finializer to be set {%s}", pvcObj.Spec.VolumeName)
|
||||
}
|
||||
|
||||
func deleteStorageClass() {
|
||||
err := SCClient.Delete(scObj.Name, &metav1.DeleteOptions{})
|
||||
Expect(err).To(BeNil(),
|
||||
"while deleting zfs storageclass {%s}", scObj.Name)
|
||||
}
|
||||
|
||||
func createAndVerifyPVC() {
|
||||
var (
|
||||
err error
|
||||
pvcName = "zfspv-pvc"
|
||||
)
|
||||
By("building a pvc")
|
||||
pvcObj, err = pvc.NewBuilder().
|
||||
WithName(pvcName).
|
||||
WithNamespace(openebsNamespace).
|
||||
WithStorageClass(scObj.Name).
|
||||
WithAccessModes(accessModes).
|
||||
WithCapacity(capacity).Build()
|
||||
Expect(err).ShouldNot(
|
||||
HaveOccurred(),
|
||||
"while building pvc {%s} in namespace {%s}",
|
||||
pvcName,
|
||||
openebsNamespace,
|
||||
)
|
||||
|
||||
By("creating above pvc")
|
||||
pvcObj, err = PVCClient.WithNamespace(openebsNamespace).Create(pvcObj)
|
||||
Expect(err).To(
|
||||
BeNil(),
|
||||
"while creating pvc {%s} in namespace {%s}",
|
||||
pvcName,
|
||||
openebsNamespace,
|
||||
)
|
||||
|
||||
By("verifying pvc status as bound")
|
||||
|
||||
status := IsPVCBoundEventually(pvcName)
|
||||
Expect(status).To(Equal(true),
|
||||
"while checking status equal to bound")
|
||||
|
||||
pvcObj, err = PVCClient.WithNamespace(openebsNamespace).Get(pvcObj.Name, metav1.GetOptions{})
|
||||
Expect(err).To(
|
||||
BeNil(),
|
||||
"while retrieving pvc {%s} in namespace {%s}",
|
||||
pvcName,
|
||||
openebsNamespace,
|
||||
)
|
||||
}
|
||||
|
||||
func createDeployVerifyApp() {
|
||||
By("creating and deploying app pod", createAndDeployAppPod)
|
||||
time.Sleep(30 * time.Second)
|
||||
By("verifying app pod is running", verifyAppPodRunning)
|
||||
}
|
||||
|
||||
func createAndDeployAppPod() {
|
||||
var err error
|
||||
By("building a busybox app pod deployment using above zfs volume")
|
||||
deployObj, err = deploy.NewBuilder().
|
||||
WithName(appName).
|
||||
WithNamespace(openebsNamespace).
|
||||
WithLabelsNew(
|
||||
map[string]string{
|
||||
"app": "busybox",
|
||||
},
|
||||
).
|
||||
WithSelectorMatchLabelsNew(
|
||||
map[string]string{
|
||||
"app": "busybox",
|
||||
},
|
||||
).
|
||||
WithPodTemplateSpecBuilder(
|
||||
pts.NewBuilder().
|
||||
WithLabelsNew(
|
||||
map[string]string{
|
||||
"app": "busybox",
|
||||
},
|
||||
).
|
||||
WithContainerBuilders(
|
||||
container.NewBuilder().
|
||||
WithImage("busybox").
|
||||
WithName("busybox").
|
||||
WithImagePullPolicy(corev1.PullIfNotPresent).
|
||||
WithCommandNew(
|
||||
[]string{
|
||||
"sh",
|
||||
"-c",
|
||||
"date > /mnt/datadir/date.txt; sync; sleep 5; sync; tail -f /dev/null;",
|
||||
},
|
||||
).
|
||||
WithVolumeMountsNew(
|
||||
[]corev1.VolumeMount{
|
||||
corev1.VolumeMount{
|
||||
Name: "datavol1",
|
||||
MountPath: "/mnt/datadir",
|
||||
},
|
||||
},
|
||||
),
|
||||
).
|
||||
WithVolumeBuilders(
|
||||
k8svolume.NewBuilder().
|
||||
WithName("datavol1").
|
||||
WithPVCSource(pvcObj.Name),
|
||||
),
|
||||
).
|
||||
Build()
|
||||
|
||||
Expect(err).ShouldNot(HaveOccurred(), "while building app deployement {%s}", appName)
|
||||
|
||||
deployObj, err = DeployClient.WithNamespace(openebsNamespace).Create(deployObj)
|
||||
Expect(err).ShouldNot(
|
||||
HaveOccurred(),
|
||||
"while creating pod {%s} in namespace {%s}",
|
||||
appName,
|
||||
openebsNamespace,
|
||||
)
|
||||
}
|
||||
|
||||
func verifyAppPodRunning() {
|
||||
var err error
|
||||
appPod, err = PodClient.WithNamespace(openebsNamespace).
|
||||
List(metav1.ListOptions{
|
||||
LabelSelector: "app=busybox",
|
||||
},
|
||||
)
|
||||
Expect(err).ShouldNot(HaveOccurred(), "while verifying application pod")
|
||||
|
||||
status := IsPodRunningEventually(openebsNamespace, appPod.Items[0].Name)
|
||||
Expect(status).To(Equal(true), "while checking status of pod {%s}", appPod.Items[0].Name)
|
||||
}
|
||||
|
||||
func deleteAppDeployment() {
|
||||
err := DeployClient.WithNamespace(openebsNamespace).
|
||||
Delete(deployObj.Name, &metav1.DeleteOptions{})
|
||||
Expect(err).ShouldNot(HaveOccurred(), "while deleting application pod")
|
||||
}
|
||||
|
||||
func deletePVC() {
|
||||
err := PVCClient.WithNamespace(openebsNamespace).Delete(pvcName, &metav1.DeleteOptions{})
|
||||
Expect(err).To(
|
||||
BeNil(),
|
||||
"while deleting pvc {%s} in namespace {%s}",
|
||||
pvcName,
|
||||
openebsNamespace,
|
||||
)
|
||||
By("verifying deleted pvc")
|
||||
status := IsPVCDeletedEventually(pvcName)
|
||||
Expect(status).To(Equal(true), "while trying to get deleted pvc")
|
||||
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue