mirror of
https://github.com/TECHNOFAB11/zfs-localpv.git
synced 2025-12-12 14:30:12 +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
|
sudo: required
|
||||||
dist: xenial
|
dist: xenial
|
||||||
env:
|
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:
|
services:
|
||||||
- docker
|
- docker
|
||||||
language: go
|
language: go
|
||||||
|
|
@ -10,12 +17,31 @@ go:
|
||||||
addons:
|
addons:
|
||||||
apt:
|
apt:
|
||||||
update: true
|
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:
|
install:
|
||||||
- make bootstrap
|
- make bootstrap
|
||||||
- make format
|
- 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:
|
script:
|
||||||
- ./buildscripts/travis-build.sh
|
- make ci
|
||||||
after_success:
|
after_success:
|
||||||
- make deploy-images
|
- make deploy-images
|
||||||
- bash <(curl -s https://codecov.io/bash)
|
- 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')
|
BUILD_DATE = $(shell date +'%Y%m%d%H%M%S')
|
||||||
|
|
||||||
.PHONY: all
|
.PHONY: all
|
||||||
all: test zfs-driver-image
|
all: zfs-driver-image
|
||||||
|
|
||||||
.PHONY: clean
|
.PHONY: clean
|
||||||
clean:
|
clean:
|
||||||
|
@echo "--> Cleaning Directory" ;
|
||||||
go clean -testcache
|
go clean -testcache
|
||||||
rm -rf bin
|
rm -rf bin
|
||||||
rm -rf ${GOPATH}/bin/${CSI_DRIVER}
|
rm -rf ${GOPATH}/bin/${CSI_DRIVER}
|
||||||
|
|
@ -132,9 +133,9 @@ informer:
|
||||||
--go-header-file ./buildscripts/custom-boilerplate.go.txt
|
--go-header-file ./buildscripts/custom-boilerplate.go.txt
|
||||||
|
|
||||||
.PHONY: zfs-driver
|
.PHONY: zfs-driver
|
||||||
zfs-driver:
|
zfs-driver: format
|
||||||
@echo "--------------------------------"
|
@echo "--------------------------------"
|
||||||
@echo "+ Building ${CSI_DRIVER} "
|
@echo "--> Building ${CSI_DRIVER} "
|
||||||
@echo "--------------------------------"
|
@echo "--------------------------------"
|
||||||
@PNAME=${CSI_DRIVER} CTLNAME=${CSI_DRIVER} sh -c "'$(PWD)/buildscripts/build.sh'"
|
@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}
|
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}
|
@rm buildscripts/${CSI_DRIVER}/${CSI_DRIVER}
|
||||||
|
|
||||||
|
.PHONY: ci
|
||||||
|
ci:
|
||||||
|
@echo "--> Running ci test";
|
||||||
|
$(PWD)/ci/ci-test.sh
|
||||||
# Push images
|
# Push images
|
||||||
deploy-images:
|
deploy-images:
|
||||||
@DIMAGE="openebs/zfs-driver" ./buildscripts/push
|
@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 (
|
import (
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
|
|
||||||
env "github.com/openebs/zfs-localpv/pkg/common/env"
|
env "github.com/openebs/zfs-localpv/pkg/common/env"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
|
|
@ -140,6 +141,21 @@ func New(opts ...OptionFunc) *Client {
|
||||||
return c
|
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) {
|
func withDefaults(c *Client) {
|
||||||
if c.getInClusterConfig == nil {
|
if c.getInClusterConfig == nil {
|
||||||
c.getInClusterConfig = rest.InClusterConfig
|
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