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

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
}