test(zfspv): minikube setup for travis

to run integration test cases

Signed-off-by: Pawan <pawan@mayadata.io>
This commit is contained in:
Pawan 2019-11-26 17:09:07 +05:30 committed by Kiran Mova
parent 7ab6156b98
commit d933b47c75
27 changed files with 4933 additions and 5 deletions

View file

@ -1,6 +1,13 @@
sudo: required
dist: xenial
env:
global:
- CHANGE_MINIKUBE_NONE_USER=true
- MINIKUBE_WANTUPDATENOTIFICATION=false
- MINIKUBE_WANTREPORTERRORPROMPT=false
- MINIKUBE_HOME=$HOME
- CHANGE_MINIKUBE_NONE_USER=true
- KUBECONFIG=$HOME/.kube/config
services:
- docker
language: go
@ -10,12 +17,31 @@ go:
addons:
apt:
update: true
before_install:
- sudo apt-get update -qq
- sudo apt-get install -y zfsutils-linux
- truncate -s 100G /tmp/disk.img
- sudo zpool create -f zfspv-pool /tmp/disk.img
install:
- make bootstrap
- make format
- curl -Lo kubectl https://storage.googleapis.com/kubernetes-release/release/v1.16.0/bin/linux/amd64/kubectl
&& chmod +x kubectl && sudo mv kubectl /usr/local/bin/
- curl -Lo minikube https://storage.googleapis.com/minikube/releases/v1.4.0/minikube-linux-amd64
&& chmod +x minikube && sudo mv minikube /usr/local/bin/
- mkdir -p $HOME/.kube $HOME/.minikube
- touch $KUBECONFIG
- sudo minikube start --vm-driver=none --kubernetes-version=v1.16.0
- sudo chown -R $USER $HOME/.minikube
- sudo chown -R $USER $HOME/.kube
- JSONPATH='{range .items[*]}{@.metadata.name}:{range @.status.conditions[*]}{@.type}={@.status};{end}{end}';
until kubectl get nodes -o jsonpath="$JSONPATH" 2>&1 | grep -q "Ready=True"; do
sleep 1; done
- kubectl cluster-info
before_script:
- "./buildscripts/travis-build.sh"
script:
- ./buildscripts/travis-build.sh
- make ci
after_success:
- make deploy-images
- bash <(curl -s https://codecov.io/bash)

View file

@ -36,10 +36,11 @@ CSI_DRIVER=zfs-driver
BUILD_DATE = $(shell date +'%Y%m%d%H%M%S')
.PHONY: all
all: test zfs-driver-image
all: zfs-driver-image
.PHONY: clean
clean:
@echo "--> Cleaning Directory" ;
go clean -testcache
rm -rf bin
rm -rf ${GOPATH}/bin/${CSI_DRIVER}
@ -132,9 +133,9 @@ informer:
--go-header-file ./buildscripts/custom-boilerplate.go.txt
.PHONY: zfs-driver
zfs-driver:
zfs-driver: format
@echo "--------------------------------"
@echo "+ Building ${CSI_DRIVER} "
@echo "--> Building ${CSI_DRIVER} "
@echo "--------------------------------"
@PNAME=${CSI_DRIVER} CTLNAME=${CSI_DRIVER} sh -c "'$(PWD)/buildscripts/build.sh'"
@ -147,6 +148,10 @@ zfs-driver-image: zfs-driver
cd buildscripts/${CSI_DRIVER} && sudo docker build -t openebs/${CSI_DRIVER}:${IMAGE_TAG} --build-arg BUILD_DATE=${BUILD_DATE} . && sudo docker tag openebs/${CSI_DRIVER}:${IMAGE_TAG} quay.io/openebs/${CSI_DRIVER}:${IMAGE_TAG}
@rm buildscripts/${CSI_DRIVER}/${CSI_DRIVER}
.PHONY: ci
ci:
@echo "--> Running ci test";
$(PWD)/ci/ci-test.sh
# Push images
deploy-images:
@DIMAGE="openebs/zfs-driver" ./buildscripts/push

87
ci/ci-test.sh Executable file
View 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"

View file

@ -18,6 +18,7 @@ package v1alpha1
import (
"strings"
"sync"
env "github.com/openebs/zfs-localpv/pkg/common/env"
"github.com/pkg/errors"
@ -140,6 +141,21 @@ func New(opts ...OptionFunc) *Client {
return c
}
var (
instance *Client
once sync.Once
)
// Instance returns a singleton instance of
// this client
func Instance(opts ...OptionFunc) *Client {
once.Do(func() {
instance = New(opts...)
})
return instance
}
func withDefaults(c *Client) {
if c.getInClusterConfig == nil {
c.getInClusterConfig = rest.InClusterConfig

View 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
View 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
View 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)
}

View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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)
}

View 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
View 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
View 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
View 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
View 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
}

View 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
View 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
View 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")
}