mirror of
https://github.com/TECHNOFAB11/zfs-localpv.git
synced 2025-12-12 06:20:11 +01:00
test(zfspv): minikube setup for travis
to run integration test cases Signed-off-by: Pawan <pawan@mayadata.io>
This commit is contained in:
parent
7ab6156b98
commit
d933b47c75
27 changed files with 4933 additions and 5 deletions
556
tests/deploy/deployment.go
Normal file
556
tests/deploy/deployment.go
Normal file
|
|
@ -0,0 +1,556 @@
|
|||
/*
|
||||
Copyright 2019 The OpenEBS Authors
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package deploy
|
||||
|
||||
import (
|
||||
"github.com/openebs/zfs-localpv/pkg/common/errors"
|
||||
templatespec "github.com/openebs/zfs-localpv/tests/pts"
|
||||
"github.com/openebs/zfs-localpv/tests/stringer"
|
||||
appsv1 "k8s.io/api/apps/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
// Predicate abstracts conditional logic w.r.t the deployment instance
|
||||
//
|
||||
// NOTE:
|
||||
// predicate is a functional approach versus traditional approach to mix
|
||||
// conditions such as *if-else* within blocks of business logic
|
||||
//
|
||||
// NOTE:
|
||||
// predicate approach enables clear separation of conditionals from
|
||||
// imperatives i.e. actions that form the business logic
|
||||
type Predicate func(*Deploy) bool
|
||||
|
||||
// Deploy is the wrapper over k8s deployment Object
|
||||
type Deploy struct {
|
||||
// kubernetes deployment instance
|
||||
object *appsv1.Deployment
|
||||
}
|
||||
|
||||
// Builder enables building an instance of
|
||||
// deployment
|
||||
type Builder struct {
|
||||
deployment *Deploy // kubernetes deployment instance
|
||||
checks []Predicate // predicate list for deploy
|
||||
errors []error
|
||||
}
|
||||
|
||||
// PredicateName type is wrapper over string.
|
||||
// It is used to refer predicate and status msg.
|
||||
type PredicateName string
|
||||
|
||||
const (
|
||||
// PredicateProgressDeadlineExceeded refer to
|
||||
// predicate IsProgressDeadlineExceeded.
|
||||
PredicateProgressDeadlineExceeded PredicateName = "ProgressDeadlineExceeded"
|
||||
// PredicateNotSpecSynced refer to predicate IsNotSpecSynced
|
||||
PredicateNotSpecSynced PredicateName = "NotSpecSynced"
|
||||
// PredicateOlderReplicaActive refer to predicate IsOlderReplicaActive
|
||||
PredicateOlderReplicaActive PredicateName = "OlderReplicaActive"
|
||||
// PredicateTerminationInProgress refer to predicate IsTerminationInProgress
|
||||
PredicateTerminationInProgress PredicateName = "TerminationInProgress"
|
||||
// PredicateUpdateInProgress refer to predicate IsUpdateInProgress.
|
||||
PredicateUpdateInProgress PredicateName = "UpdateInProgress"
|
||||
)
|
||||
|
||||
// String implements the stringer interface
|
||||
func (d *Deploy) String() string {
|
||||
return stringer.Yaml("deployment", d.object)
|
||||
}
|
||||
|
||||
// GoString implements the goStringer interface
|
||||
func (d *Deploy) GoString() string {
|
||||
return d.String()
|
||||
}
|
||||
|
||||
// NewBuilder returns a new instance of builder meant for deployment
|
||||
func NewBuilder() *Builder {
|
||||
return &Builder{
|
||||
deployment: &Deploy{
|
||||
object: &appsv1.Deployment{},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// WithName sets the Name field of deployment with provided value.
|
||||
func (b *Builder) WithName(name string) *Builder {
|
||||
if len(name) == 0 {
|
||||
b.errors = append(
|
||||
b.errors,
|
||||
errors.New("failed to build deployment: missing name"),
|
||||
)
|
||||
return b
|
||||
}
|
||||
b.deployment.object.Name = name
|
||||
return b
|
||||
}
|
||||
|
||||
// WithNamespace sets the Namespace field of deployment with provided value.
|
||||
func (b *Builder) WithNamespace(namespace string) *Builder {
|
||||
if len(namespace) == 0 {
|
||||
b.errors = append(
|
||||
b.errors,
|
||||
errors.New("failed to build deployment: missing namespace"),
|
||||
)
|
||||
return b
|
||||
}
|
||||
b.deployment.object.Namespace = namespace
|
||||
return b
|
||||
}
|
||||
|
||||
// WithAnnotations merges existing annotations if any
|
||||
// with the ones that are provided here
|
||||
func (b *Builder) WithAnnotations(annotations map[string]string) *Builder {
|
||||
if len(annotations) == 0 {
|
||||
b.errors = append(
|
||||
b.errors,
|
||||
errors.New("failed to build deployment object: missing annotations"),
|
||||
)
|
||||
return b
|
||||
}
|
||||
|
||||
if b.deployment.object.Annotations == nil {
|
||||
return b.WithAnnotationsNew(annotations)
|
||||
}
|
||||
|
||||
for key, value := range annotations {
|
||||
b.deployment.object.Annotations[key] = value
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
// WithAnnotationsNew resets existing annotaions if any with
|
||||
// ones that are provided here
|
||||
func (b *Builder) WithAnnotationsNew(annotations map[string]string) *Builder {
|
||||
if len(annotations) == 0 {
|
||||
b.errors = append(
|
||||
b.errors,
|
||||
errors.New("failed to build deployment object: no new annotations"),
|
||||
)
|
||||
return b
|
||||
}
|
||||
|
||||
// copy of original map
|
||||
newannotations := map[string]string{}
|
||||
for key, value := range annotations {
|
||||
newannotations[key] = value
|
||||
}
|
||||
|
||||
// override
|
||||
b.deployment.object.Annotations = newannotations
|
||||
return b
|
||||
}
|
||||
|
||||
// WithNodeSelector Sets the node selector with the provided argument.
|
||||
func (b *Builder) WithNodeSelector(selector map[string]string) *Builder {
|
||||
if len(selector) == 0 {
|
||||
b.errors = append(
|
||||
b.errors,
|
||||
errors.New("failed to build deployment object: no node selector"),
|
||||
)
|
||||
return b
|
||||
}
|
||||
if b.deployment.object.Spec.Template.Spec.NodeSelector == nil {
|
||||
return b.WithNodeSelectorNew(selector)
|
||||
}
|
||||
|
||||
for key, value := range selector {
|
||||
b.deployment.object.Spec.Template.Spec.NodeSelector[key] = value
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
// WithNodeSelector Sets the node selector with the provided argument.
|
||||
func (b *Builder) WithNodeSelectorNew(selector map[string]string) *Builder {
|
||||
if len(selector) == 0 {
|
||||
b.errors = append(
|
||||
b.errors,
|
||||
errors.New("failed to build deployment object: no new node selector"),
|
||||
)
|
||||
return b
|
||||
}
|
||||
|
||||
b.deployment.object.Spec.Template.Spec.NodeSelector = selector
|
||||
return b
|
||||
}
|
||||
|
||||
// WithOwnerReferenceNew sets ownerreference if any with
|
||||
// ones that are provided here
|
||||
func (b *Builder) WithOwnerReferenceNew(ownerRefernce []metav1.OwnerReference) *Builder {
|
||||
if len(ownerRefernce) == 0 {
|
||||
b.errors = append(
|
||||
b.errors,
|
||||
errors.New("failed to build deployment object: no new ownerRefernce"),
|
||||
)
|
||||
return b
|
||||
}
|
||||
|
||||
b.deployment.object.OwnerReferences = ownerRefernce
|
||||
return b
|
||||
}
|
||||
|
||||
// WithLabels merges existing labels if any
|
||||
// with the ones that are provided here
|
||||
func (b *Builder) WithLabels(labels map[string]string) *Builder {
|
||||
if len(labels) == 0 {
|
||||
b.errors = append(
|
||||
b.errors,
|
||||
errors.New("failed to build deployment object: missing labels"),
|
||||
)
|
||||
return b
|
||||
}
|
||||
|
||||
if b.deployment.object.Labels == nil {
|
||||
return b.WithLabelsNew(labels)
|
||||
}
|
||||
|
||||
for key, value := range labels {
|
||||
b.deployment.object.Labels[key] = value
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
// WithLabelsNew resets existing labels if any with
|
||||
// ones that are provided here
|
||||
func (b *Builder) WithLabelsNew(labels map[string]string) *Builder {
|
||||
if len(labels) == 0 {
|
||||
b.errors = append(
|
||||
b.errors,
|
||||
errors.New("failed to build deployment object: no new labels"),
|
||||
)
|
||||
return b
|
||||
}
|
||||
|
||||
// copy of original map
|
||||
newlbls := map[string]string{}
|
||||
for key, value := range labels {
|
||||
newlbls[key] = value
|
||||
}
|
||||
|
||||
// override
|
||||
b.deployment.object.Labels = newlbls
|
||||
return b
|
||||
}
|
||||
|
||||
// WithSelectorMatchLabels merges existing matchlabels if any
|
||||
// with the ones that are provided here
|
||||
func (b *Builder) WithSelectorMatchLabels(matchlabels map[string]string) *Builder {
|
||||
if len(matchlabels) == 0 {
|
||||
b.errors = append(
|
||||
b.errors,
|
||||
errors.New("failed to build deployment object: missing matchlabels"),
|
||||
)
|
||||
return b
|
||||
}
|
||||
|
||||
if b.deployment.object.Spec.Selector == nil {
|
||||
return b.WithSelectorMatchLabelsNew(matchlabels)
|
||||
}
|
||||
|
||||
for key, value := range matchlabels {
|
||||
b.deployment.object.Spec.Selector.MatchLabels[key] = value
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
// WithSelectorMatchLabelsNew resets existing matchlabels if any with
|
||||
// ones that are provided here
|
||||
func (b *Builder) WithSelectorMatchLabelsNew(matchlabels map[string]string) *Builder {
|
||||
if len(matchlabels) == 0 {
|
||||
b.errors = append(
|
||||
b.errors,
|
||||
errors.New("failed to build deployment object: no new matchlabels"),
|
||||
)
|
||||
return b
|
||||
}
|
||||
|
||||
// copy of original map
|
||||
newmatchlabels := map[string]string{}
|
||||
for key, value := range matchlabels {
|
||||
newmatchlabels[key] = value
|
||||
}
|
||||
|
||||
newselector := &metav1.LabelSelector{
|
||||
MatchLabels: newmatchlabels,
|
||||
}
|
||||
|
||||
// override
|
||||
b.deployment.object.Spec.Selector = newselector
|
||||
return b
|
||||
}
|
||||
|
||||
// WithReplicas sets the replica field of deployment
|
||||
func (b *Builder) WithReplicas(replicas *int32) *Builder {
|
||||
|
||||
if replicas == nil {
|
||||
b.errors = append(
|
||||
b.errors,
|
||||
errors.New("failed to build deployment object: nil replicas"),
|
||||
)
|
||||
return b
|
||||
}
|
||||
|
||||
newreplicas := *replicas
|
||||
|
||||
if newreplicas < 0 {
|
||||
b.errors = append(
|
||||
b.errors,
|
||||
errors.Errorf(
|
||||
"failed to build deployment object: invalid replicas {%d}",
|
||||
newreplicas,
|
||||
),
|
||||
)
|
||||
return b
|
||||
}
|
||||
|
||||
b.deployment.object.Spec.Replicas = &newreplicas
|
||||
return b
|
||||
}
|
||||
|
||||
//WithStrategyType sets the strategy field of the deployment
|
||||
func (b *Builder) WithStrategyType(
|
||||
strategytype appsv1.DeploymentStrategyType,
|
||||
) *Builder {
|
||||
if len(strategytype) == 0 {
|
||||
b.errors = append(
|
||||
b.errors,
|
||||
errors.New("failed to build deployment object: missing strategytype"),
|
||||
)
|
||||
return b
|
||||
}
|
||||
|
||||
b.deployment.object.Spec.Strategy.Type = strategytype
|
||||
return b
|
||||
}
|
||||
|
||||
// WithPodTemplateSpecBuilder sets the template field of the deployment
|
||||
func (b *Builder) WithPodTemplateSpecBuilder(
|
||||
tmplbuilder *templatespec.Builder,
|
||||
) *Builder {
|
||||
if tmplbuilder == nil {
|
||||
b.errors = append(
|
||||
b.errors,
|
||||
errors.New("failed to build deployment: nil templatespecbuilder"),
|
||||
)
|
||||
return b
|
||||
}
|
||||
|
||||
templatespecObj, err := tmplbuilder.Build()
|
||||
|
||||
if err != nil {
|
||||
b.errors = append(
|
||||
b.errors,
|
||||
errors.Wrap(
|
||||
err,
|
||||
"failed to build deployment",
|
||||
),
|
||||
)
|
||||
return b
|
||||
}
|
||||
|
||||
b.deployment.object.Spec.Template = *templatespecObj.Object
|
||||
return b
|
||||
}
|
||||
|
||||
type deployBuildOption func(*Deploy)
|
||||
|
||||
// NewForAPIObject returns a new instance of builder
|
||||
// for a given deployment Object
|
||||
func NewForAPIObject(
|
||||
obj *appsv1.Deployment,
|
||||
opts ...deployBuildOption,
|
||||
) *Deploy {
|
||||
d := &Deploy{object: obj}
|
||||
for _, o := range opts {
|
||||
o(d)
|
||||
}
|
||||
return d
|
||||
}
|
||||
|
||||
// Build returns a deployment instance
|
||||
func (b *Builder) Build() (*appsv1.Deployment, error) {
|
||||
err := b.validate()
|
||||
// TODO: err in Wrapf is not logged. Fix is required
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err,
|
||||
"failed to build a deployment: %s",
|
||||
b.deployment.object.Name)
|
||||
}
|
||||
return b.deployment.object, nil
|
||||
}
|
||||
|
||||
func (b *Builder) validate() error {
|
||||
if len(b.errors) != 0 {
|
||||
return errors.Errorf(
|
||||
"failed to validate: build errors were found: %+v",
|
||||
b.errors,
|
||||
)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// IsRollout range over rolloutChecks map and check status of each predicate
|
||||
// also it generates status message from rolloutStatuses using predicate key
|
||||
func (d *Deploy) IsRollout() (PredicateName, bool) {
|
||||
for pk, p := range rolloutChecks {
|
||||
if p(d) {
|
||||
return pk, false
|
||||
}
|
||||
}
|
||||
return "", true
|
||||
}
|
||||
|
||||
// FailedRollout returns rollout status message for fail condition
|
||||
func (d *Deploy) FailedRollout(name PredicateName) *RolloutOutput {
|
||||
return &RolloutOutput{
|
||||
Message: rolloutStatuses[name](d),
|
||||
IsRolledout: false,
|
||||
}
|
||||
}
|
||||
|
||||
// SuccessRollout returns rollout status message for success condition
|
||||
func (d *Deploy) SuccessRollout() *RolloutOutput {
|
||||
return &RolloutOutput{
|
||||
Message: "deployment successfully rolled out",
|
||||
IsRolledout: true,
|
||||
}
|
||||
}
|
||||
|
||||
// RolloutStatus returns rollout message of deployment instance
|
||||
func (d *Deploy) RolloutStatus() (op *RolloutOutput, err error) {
|
||||
pk, ok := d.IsRollout()
|
||||
if ok {
|
||||
return d.SuccessRollout(), nil
|
||||
}
|
||||
return d.FailedRollout(pk), nil
|
||||
}
|
||||
|
||||
// RolloutStatusRaw returns rollout message of deployment instance
|
||||
// in byte format
|
||||
func (d *Deploy) RolloutStatusRaw() (op []byte, err error) {
|
||||
message, err := d.RolloutStatus()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return NewRollout(
|
||||
withOutputObject(message)).
|
||||
Raw()
|
||||
}
|
||||
|
||||
// AddCheck adds the predicate as a condition to be validated
|
||||
// against the deployment instance
|
||||
func (b *Builder) AddCheck(p Predicate) *Builder {
|
||||
b.checks = append(b.checks, p)
|
||||
return b
|
||||
}
|
||||
|
||||
// AddChecks adds the provided predicates as conditions to be
|
||||
// validated against the deployment instance
|
||||
func (b *Builder) AddChecks(p []Predicate) *Builder {
|
||||
for _, check := range p {
|
||||
b.AddCheck(check)
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
// IsProgressDeadlineExceeded is used to check update is timed out or not.
|
||||
// If `Progressing` condition's reason is `ProgressDeadlineExceeded` then
|
||||
// it is not rolled out.
|
||||
func IsProgressDeadlineExceeded() Predicate {
|
||||
return func(d *Deploy) bool {
|
||||
return d.IsProgressDeadlineExceeded()
|
||||
}
|
||||
}
|
||||
|
||||
// IsProgressDeadlineExceeded is used to check update is timed out or not.
|
||||
// If `Progressing` condition's reason is `ProgressDeadlineExceeded` then
|
||||
// it is not rolled out.
|
||||
func (d *Deploy) IsProgressDeadlineExceeded() bool {
|
||||
for _, cond := range d.object.Status.Conditions {
|
||||
if cond.Type == appsv1.DeploymentProgressing &&
|
||||
cond.Reason == "ProgressDeadlineExceeded" {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// IsOlderReplicaActive check if older replica's are still active or not if
|
||||
// Status.UpdatedReplicas < *Spec.Replicas then some of the replicas are
|
||||
// updated and some of them are not.
|
||||
func IsOlderReplicaActive() Predicate {
|
||||
return func(d *Deploy) bool {
|
||||
return d.IsOlderReplicaActive()
|
||||
}
|
||||
}
|
||||
|
||||
// IsOlderReplicaActive check if older replica's are still active or not if
|
||||
// Status.UpdatedReplicas < *Spec.Replicas then some of the replicas are
|
||||
// updated and some of them are not.
|
||||
func (d *Deploy) IsOlderReplicaActive() bool {
|
||||
return d.object.Spec.Replicas != nil &&
|
||||
d.object.Status.UpdatedReplicas < *d.object.Spec.Replicas
|
||||
}
|
||||
|
||||
// IsTerminationInProgress checks for older replicas are waiting to
|
||||
// terminate or not. If Status.Replicas > Status.UpdatedReplicas then
|
||||
// some of the older replicas are in running state because newer
|
||||
// replicas are not in running state. It waits for newer replica to
|
||||
// come into running state then terminate.
|
||||
func IsTerminationInProgress() Predicate {
|
||||
return func(d *Deploy) bool {
|
||||
return d.IsTerminationInProgress()
|
||||
}
|
||||
}
|
||||
|
||||
// IsTerminationInProgress checks for older replicas are waiting to
|
||||
// terminate or not. If Status.Replicas > Status.UpdatedReplicas then
|
||||
// some of the older replicas are in running state because newer
|
||||
// replicas are not in running state. It waits for newer replica to
|
||||
// come into running state then terminate.
|
||||
func (d *Deploy) IsTerminationInProgress() bool {
|
||||
return d.object.Status.Replicas > d.object.Status.UpdatedReplicas
|
||||
}
|
||||
|
||||
// IsUpdateInProgress Checks if all the replicas are updated or not.
|
||||
// If Status.AvailableReplicas < Status.UpdatedReplicas then all the
|
||||
//older replicas are not there but there are less number of availableReplicas
|
||||
func IsUpdateInProgress() Predicate {
|
||||
return func(d *Deploy) bool {
|
||||
return d.IsUpdateInProgress()
|
||||
}
|
||||
}
|
||||
|
||||
// IsUpdateInProgress Checks if all the replicas are updated or not.
|
||||
// If Status.AvailableReplicas < Status.UpdatedReplicas then all the
|
||||
// older replicas are not there but there are less number of availableReplicas
|
||||
func (d *Deploy) IsUpdateInProgress() bool {
|
||||
return d.object.Status.AvailableReplicas < d.object.Status.UpdatedReplicas
|
||||
}
|
||||
|
||||
// IsNotSyncSpec compare generation in status and spec and check if
|
||||
// deployment spec is synced or not. If Generation <= Status.ObservedGeneration
|
||||
// then deployment spec is not updated yet.
|
||||
func IsNotSyncSpec() Predicate {
|
||||
return func(d *Deploy) bool {
|
||||
return d.IsNotSyncSpec()
|
||||
}
|
||||
}
|
||||
|
||||
// IsNotSyncSpec compare generation in status and spec and check if
|
||||
// deployment spec is synced or not. If Generation <= Status.ObservedGeneration
|
||||
// then deployment spec is not updated yet.
|
||||
func (d *Deploy) IsNotSyncSpec() bool {
|
||||
return d.object.Generation > d.object.Status.ObservedGeneration
|
||||
}
|
||||
417
tests/deploy/kubernetes.go
Normal file
417
tests/deploy/kubernetes.go
Normal file
|
|
@ -0,0 +1,417 @@
|
|||
/*
|
||||
Copyright 2019 The OpenEBS Authors
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
package deploy
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"strings"
|
||||
|
||||
client "github.com/openebs/zfs-localpv/pkg/common/kubernetes/client"
|
||||
"github.com/pkg/errors"
|
||||
appsv1 "k8s.io/api/apps/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
types "k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
)
|
||||
|
||||
// getClientsetFn is a typed function
|
||||
// that abstracts fetching of kubernetes clientset
|
||||
type getClientsetFn func() (*kubernetes.Clientset, error)
|
||||
|
||||
// getClientsetFromPathFn is a typed function that
|
||||
// abstracts fetching of clientset from kubeconfig path
|
||||
type getClientsetForPathFn func(path string) (*kubernetes.Clientset, error)
|
||||
|
||||
// getFn is a typed function that abstracts fetching a
|
||||
// deployment instance from kubernetes cluster
|
||||
type getFn func(
|
||||
cli *kubernetes.Clientset,
|
||||
name string,
|
||||
namespace string,
|
||||
opts *metav1.GetOptions,
|
||||
) (*appsv1.Deployment, error)
|
||||
|
||||
// listFn is a typed function that abstracts listing
|
||||
// deployment instances from kubernetes cluster
|
||||
type listFn func(
|
||||
cli *kubernetes.Clientset,
|
||||
namespace string,
|
||||
opts *metav1.ListOptions,
|
||||
) (*appsv1.DeploymentList, error)
|
||||
|
||||
// createFn is a typed function that abstracts
|
||||
// creating a deployment instance in kubernetes cluster
|
||||
type createFn func(
|
||||
cli *kubernetes.Clientset,
|
||||
namespace string,
|
||||
deploy *appsv1.Deployment,
|
||||
) (*appsv1.Deployment, error)
|
||||
|
||||
// deleteFn is a typed function that abstracts
|
||||
// deleting a deployment from kubernetes cluster
|
||||
type deleteFn func(
|
||||
cli *kubernetes.Clientset,
|
||||
namespace string,
|
||||
name string,
|
||||
opts *metav1.DeleteOptions,
|
||||
) error
|
||||
|
||||
// patchFn is a typed function that abstracts
|
||||
// patching a deployment from kubernetes cluster
|
||||
type patchFn func(
|
||||
cli *kubernetes.Clientset,
|
||||
name, namespace string,
|
||||
pt types.PatchType,
|
||||
data []byte,
|
||||
subresources ...string,
|
||||
) (*appsv1.Deployment, error)
|
||||
|
||||
// rolloutStatusFn is a typed function that abstracts
|
||||
// fetching rollout status of a deployment instance from
|
||||
// kubernetes cluster
|
||||
type rolloutStatusFn func(d *appsv1.Deployment) (*RolloutOutput, error)
|
||||
|
||||
// rolloutStatusfFn is a typed function that abstracts
|
||||
// fetching rollout status of a deployment instance from
|
||||
// kubernetes cluster
|
||||
type rolloutStatusfFn func(d *appsv1.Deployment) ([]byte, error)
|
||||
|
||||
// defaultGetClientset is the default implementation to
|
||||
// get kubernetes clientset instance
|
||||
func defaultGetClientset() (*kubernetes.Clientset, error) {
|
||||
return client.Instance().Clientset()
|
||||
}
|
||||
|
||||
// defaultGetClientsetForPath is the default implementation to
|
||||
// get kubernetes clientset instance based on the given
|
||||
// kubeconfig path
|
||||
func defaultGetClientsetForPath(path string) (*kubernetes.Clientset, error) {
|
||||
return client.New(client.WithKubeConfigPath(path)).Clientset()
|
||||
}
|
||||
|
||||
// defaultGet is the default implementation to get a
|
||||
// deployment instance from kubernetes cluster
|
||||
func defaultGet(
|
||||
cli *kubernetes.Clientset,
|
||||
name string,
|
||||
namespace string,
|
||||
opts *metav1.GetOptions,
|
||||
) (*appsv1.Deployment, error) {
|
||||
|
||||
return cli.AppsV1().Deployments(namespace).Get(name, *opts)
|
||||
}
|
||||
|
||||
// defaultList is the default implementation to list
|
||||
// deployment instances from kubernetes cluster
|
||||
func defaultList(
|
||||
cli *kubernetes.Clientset,
|
||||
namespace string,
|
||||
opts *metav1.ListOptions,
|
||||
) (*appsv1.DeploymentList, error) {
|
||||
|
||||
return cli.AppsV1().Deployments(namespace).List(*opts)
|
||||
}
|
||||
|
||||
// defaultCreate is the default implementation to create
|
||||
// a deployment instance in kubernetes cluster
|
||||
func defaultCreate(
|
||||
cli *kubernetes.Clientset,
|
||||
namespace string,
|
||||
deploy *appsv1.Deployment,
|
||||
) (*appsv1.Deployment, error) {
|
||||
|
||||
return cli.AppsV1().Deployments(namespace).Create(deploy)
|
||||
}
|
||||
|
||||
// defaultDel is the default implementation to delete a
|
||||
// deployment instance in kubernetes cluster
|
||||
func defaultDel(
|
||||
cli *kubernetes.Clientset,
|
||||
namespace string,
|
||||
name string,
|
||||
opts *metav1.DeleteOptions,
|
||||
) error {
|
||||
|
||||
return cli.AppsV1().Deployments(namespace).Delete(name, opts)
|
||||
}
|
||||
|
||||
func defaultPatch(
|
||||
cli *kubernetes.Clientset,
|
||||
name, namespace string,
|
||||
pt types.PatchType,
|
||||
data []byte,
|
||||
subresources ...string,
|
||||
) (*appsv1.Deployment, error) {
|
||||
return cli.AppsV1().Deployments(namespace).Patch(name, pt, data, subresources...)
|
||||
}
|
||||
|
||||
// defaultRolloutStatus is the default implementation to
|
||||
// fetch rollout status of a deployment instance from kubernetes
|
||||
// cluster
|
||||
func defaultRolloutStatus(d *appsv1.Deployment) (*RolloutOutput, error) {
|
||||
b := NewForAPIObject(d)
|
||||
return b.RolloutStatus()
|
||||
}
|
||||
|
||||
// defaultRolloutStatusf is the default implementation to fetch
|
||||
// rollout status of a deployment instance from kubernetes cluster
|
||||
func defaultRolloutStatusf(d *appsv1.Deployment) ([]byte, error) {
|
||||
b := NewForAPIObject(d)
|
||||
return b.RolloutStatusRaw()
|
||||
}
|
||||
|
||||
// Kubeclient enables kubernetes API operations on deployment instance
|
||||
type Kubeclient struct {
|
||||
// clientset refers to kubernetes clientset
|
||||
//
|
||||
// It enables CRUD operations of a deployment instance
|
||||
// against a kubernetes cluster
|
||||
clientset *kubernetes.Clientset
|
||||
|
||||
namespace string
|
||||
|
||||
// kubeconfig path to get kubernetes clientset
|
||||
kubeConfigPath string
|
||||
|
||||
// functions useful during mocking
|
||||
getClientset getClientsetFn
|
||||
getClientsetForPath getClientsetForPathFn
|
||||
get getFn
|
||||
list listFn
|
||||
create createFn
|
||||
del deleteFn
|
||||
patch patchFn
|
||||
rolloutStatus rolloutStatusFn
|
||||
rolloutStatusf rolloutStatusfFn
|
||||
}
|
||||
|
||||
// KubeclientBuildOption defines the abstraction to build a
|
||||
// kubeclient instance
|
||||
type KubeclientBuildOption func(*Kubeclient)
|
||||
|
||||
// withDefaults sets kubeclient instance's fields with defaults
|
||||
// if these fields are not set
|
||||
func (k *Kubeclient) withDefaults() {
|
||||
|
||||
if k.getClientset == nil {
|
||||
k.getClientset = defaultGetClientset
|
||||
}
|
||||
if k.getClientsetForPath == nil {
|
||||
k.getClientsetForPath = defaultGetClientsetForPath
|
||||
}
|
||||
if k.get == nil {
|
||||
k.get = defaultGet
|
||||
}
|
||||
if k.list == nil {
|
||||
k.list = defaultList
|
||||
}
|
||||
if k.create == nil {
|
||||
k.create = defaultCreate
|
||||
}
|
||||
if k.del == nil {
|
||||
k.del = defaultDel
|
||||
}
|
||||
if k.patch == nil {
|
||||
k.patch = defaultPatch
|
||||
}
|
||||
if k.rolloutStatus == nil {
|
||||
k.rolloutStatus = defaultRolloutStatus
|
||||
}
|
||||
if k.rolloutStatusf == nil {
|
||||
k.rolloutStatusf = defaultRolloutStatusf
|
||||
}
|
||||
}
|
||||
|
||||
// WithClientset sets the kubernetes client against the kubeclient instance
|
||||
func WithClientset(c *kubernetes.Clientset) KubeclientBuildOption {
|
||||
return func(k *Kubeclient) {
|
||||
k.clientset = c
|
||||
}
|
||||
}
|
||||
|
||||
// WithNamespace sets the kubernetes client against
|
||||
// the provided namespace
|
||||
func (k *Kubeclient) WithNamespace(namespace string) *Kubeclient {
|
||||
k.namespace = namespace
|
||||
return k
|
||||
}
|
||||
|
||||
// WithKubeConfigPath sets the kubeConfig path
|
||||
// against client instance
|
||||
func WithKubeConfigPath(path string) KubeclientBuildOption {
|
||||
return func(k *Kubeclient) {
|
||||
k.kubeConfigPath = path
|
||||
}
|
||||
}
|
||||
|
||||
// WithNamespace set namespace in kubeclient object
|
||||
func WithNamespace(namespace string) KubeclientBuildOption {
|
||||
return func(k *Kubeclient) {
|
||||
k.namespace = namespace
|
||||
}
|
||||
}
|
||||
|
||||
// NewKubeClient returns a new instance of kubeclient meant for deployment.
|
||||
// caller can configure it with different kubeclientBuildOption
|
||||
func NewKubeClient(opts ...KubeclientBuildOption) *Kubeclient {
|
||||
k := &Kubeclient{}
|
||||
for _, o := range opts {
|
||||
o(k)
|
||||
}
|
||||
|
||||
k.withDefaults()
|
||||
return k
|
||||
}
|
||||
|
||||
func (k *Kubeclient) getClientsetForPathOrDirect() (*kubernetes.Clientset, error) {
|
||||
if k.kubeConfigPath != "" {
|
||||
return k.getClientsetForPath(k.kubeConfigPath)
|
||||
}
|
||||
|
||||
return k.getClientset()
|
||||
}
|
||||
|
||||
// getClientOrCached returns either a new instance
|
||||
// of kubernetes client or its cached copy
|
||||
func (k *Kubeclient) getClientOrCached() (*kubernetes.Clientset, error) {
|
||||
if k.clientset != nil {
|
||||
return k.clientset, nil
|
||||
}
|
||||
|
||||
c, err := k.getClientsetForPathOrDirect()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
k.clientset = c
|
||||
return k.clientset, nil
|
||||
}
|
||||
|
||||
// Get returns deployment object for given name
|
||||
func (k *Kubeclient) Get(name string) (*appsv1.Deployment, error) {
|
||||
cli, err := k.getClientOrCached()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return k.get(cli, name, k.namespace, &metav1.GetOptions{})
|
||||
}
|
||||
|
||||
// List returns deployment object for given name
|
||||
func (k *Kubeclient) List(opts *metav1.ListOptions) (*appsv1.DeploymentList, error) {
|
||||
cli, err := k.getClientOrCached()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return k.list(cli, k.namespace, opts)
|
||||
}
|
||||
|
||||
// Patch patches deployment object for given name
|
||||
func (k *Kubeclient) Patch(
|
||||
name string,
|
||||
pt types.PatchType,
|
||||
data []byte,
|
||||
subresources ...string,
|
||||
) (*appsv1.Deployment, error) {
|
||||
cli, err := k.getClientOrCached()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return k.patch(cli, name, k.namespace, pt, data, subresources...)
|
||||
}
|
||||
|
||||
// GetRaw returns deployment object for given name
|
||||
func (k *Kubeclient) GetRaw(name string) ([]byte, error) {
|
||||
cli, err := k.getClientOrCached()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
d, err := k.get(cli, name, k.namespace, &metav1.GetOptions{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return json.Marshal(d)
|
||||
}
|
||||
|
||||
// Delete deletes a deployment instance from the
|
||||
// kubernetes cluster
|
||||
func (k *Kubeclient) Delete(name string, opts *metav1.DeleteOptions) error {
|
||||
|
||||
if strings.TrimSpace(name) == "" {
|
||||
return errors.New("failed to delete deployment: missing deployment name")
|
||||
}
|
||||
|
||||
cli, err := k.getClientOrCached()
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "failed to delete deployment {%s}", name)
|
||||
}
|
||||
|
||||
return k.del(cli, k.namespace, name, opts)
|
||||
}
|
||||
|
||||
// Create creates a deployment in specified namespace in kubernetes cluster
|
||||
func (k *Kubeclient) Create(deployment *appsv1.Deployment) (*appsv1.Deployment, error) {
|
||||
|
||||
if deployment == nil {
|
||||
return nil, errors.New("failed to create deployment: nil deployment object")
|
||||
}
|
||||
|
||||
cli, err := k.getClientOrCached()
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(
|
||||
err,
|
||||
"failed to create deployment {%s} in namespace {%s}",
|
||||
deployment.Name,
|
||||
deployment.Namespace,
|
||||
)
|
||||
}
|
||||
|
||||
return k.create(cli, k.namespace, deployment)
|
||||
}
|
||||
|
||||
// RolloutStatusf returns deployment's rollout status for given name
|
||||
// in raw bytes
|
||||
func (k *Kubeclient) RolloutStatusf(name string) (op []byte, err error) {
|
||||
cli, err := k.getClientOrCached()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
d, err := k.get(cli, name, k.namespace, &metav1.GetOptions{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return k.rolloutStatusf(d)
|
||||
}
|
||||
|
||||
// RolloutStatus returns deployment's rollout status for given name
|
||||
func (k *Kubeclient) RolloutStatus(name string) (*RolloutOutput, error) {
|
||||
cli, err := k.getClientOrCached()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
d, err := k.get(cli, name, k.namespace, &metav1.GetOptions{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return k.rolloutStatus(d)
|
||||
}
|
||||
135
tests/deploy/rollout_status.go
Normal file
135
tests/deploy/rollout_status.go
Normal file
|
|
@ -0,0 +1,135 @@
|
|||
/*
|
||||
Copyright 2019 The OpenEBS Authors
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package deploy
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// rolloutStatus is a typed function that
|
||||
// abstracts status message formation logic
|
||||
type rolloutStatus func(*Deploy) string
|
||||
|
||||
// rolloutStatuses contains a group of status message for
|
||||
// each predicate checks. It uses predicateName as key.
|
||||
var rolloutStatuses = map[PredicateName]rolloutStatus{
|
||||
// PredicateProgressDeadlineExceeded refer to rolloutStatus
|
||||
// for predicate IsProgressDeadlineExceeded.
|
||||
PredicateProgressDeadlineExceeded: func(d *Deploy) string {
|
||||
return "deployment exceeded its progress deadline"
|
||||
},
|
||||
// PredicateOlderReplicaActive refer to rolloutStatus for
|
||||
// predicate IsOlderReplicaActive.
|
||||
PredicateOlderReplicaActive: func(d *Deploy) string {
|
||||
if d.object.Spec.Replicas == nil {
|
||||
return "replica update in-progress: some older replicas were updated"
|
||||
}
|
||||
return fmt.Sprintf(
|
||||
"replica update in-progress: %d of %d new replicas were updated",
|
||||
d.object.Status.UpdatedReplicas, *d.object.Spec.Replicas)
|
||||
},
|
||||
// PredicateTerminationInProgress refer rolloutStatus
|
||||
// for predicate IsTerminationInProgress.
|
||||
PredicateTerminationInProgress: func(d *Deploy) string {
|
||||
return fmt.Sprintf(
|
||||
"replica termination in-progress: %d old replicas are pending termination",
|
||||
d.object.Status.Replicas-d.object.Status.UpdatedReplicas)
|
||||
},
|
||||
// PredicateUpdateInProgress refer to rolloutStatus for predicate IsUpdateInProgress.
|
||||
PredicateUpdateInProgress: func(d *Deploy) string {
|
||||
return fmt.Sprintf(
|
||||
"replica update in-progress: %d of %d updated replicas are available",
|
||||
d.object.Status.AvailableReplicas, d.object.Status.UpdatedReplicas)
|
||||
},
|
||||
// PredicateNotSpecSynced refer to status rolloutStatus for predicate IsNotSyncSpec.
|
||||
PredicateNotSpecSynced: func(d *Deploy) string {
|
||||
return "deployment rollout in-progress: waiting for deployment spec update"
|
||||
},
|
||||
}
|
||||
|
||||
// rolloutChecks contains a group of predicate it uses predicateName as key.
|
||||
var rolloutChecks = map[PredicateName]Predicate{
|
||||
// PredicateProgressDeadlineExceeded refer to predicate IsProgressDeadlineExceeded.
|
||||
PredicateProgressDeadlineExceeded: IsProgressDeadlineExceeded(),
|
||||
// PredicateOlderReplicaActive refer to predicate IsOlderReplicaActive.
|
||||
PredicateOlderReplicaActive: IsOlderReplicaActive(),
|
||||
// PredicateTerminationInProgress refer to predicate IsTerminationInProgress.
|
||||
PredicateTerminationInProgress: IsTerminationInProgress(),
|
||||
// PredicateUpdateInProgress refer to predicate IsUpdateInProgress.
|
||||
PredicateUpdateInProgress: IsUpdateInProgress(),
|
||||
// PredicateNotSpecSynced refer to predicate IsSyncSpec.
|
||||
PredicateNotSpecSynced: IsNotSyncSpec(),
|
||||
}
|
||||
|
||||
// RolloutOutput struct contains message and boolean value to show rolloutstatus
|
||||
type RolloutOutput struct {
|
||||
IsRolledout bool `json:"isRolledout"`
|
||||
Message string `json:"message"`
|
||||
}
|
||||
|
||||
// rawFn is a typed function that abstracts
|
||||
// conversion of rolloutOutput struct to raw byte
|
||||
type rawFn func(r *RolloutOutput) ([]byte, error)
|
||||
|
||||
// Rollout enables getting various output format of rolloutOutput
|
||||
type Rollout struct {
|
||||
output *RolloutOutput
|
||||
raw rawFn
|
||||
}
|
||||
|
||||
// rolloutBuildOption defines the
|
||||
// abstraction to build a rollout instance
|
||||
type rolloutBuildOption func(*Rollout)
|
||||
|
||||
// NewRollout returns new instance of rollout meant for
|
||||
// rolloutOutput. caller can configure it with different
|
||||
// rolloutOutputBuildOption
|
||||
func NewRollout(opts ...rolloutBuildOption) *Rollout {
|
||||
r := &Rollout{}
|
||||
for _, o := range opts {
|
||||
o(r)
|
||||
}
|
||||
r.withDefaults()
|
||||
return r
|
||||
}
|
||||
|
||||
// withOutputObject sets rolloutOutput in rollout instance
|
||||
func withOutputObject(o *RolloutOutput) rolloutBuildOption {
|
||||
return func(r *Rollout) {
|
||||
r.output = o
|
||||
}
|
||||
}
|
||||
|
||||
// withDefaults sets the default options of rolloutBuilder instance
|
||||
func (r *Rollout) withDefaults() {
|
||||
if r.raw == nil {
|
||||
r.raw = func(o *RolloutOutput) ([]byte, error) {
|
||||
return json.Marshal(o)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Raw returns raw bytes outpot of rollout
|
||||
func (r *Rollout) Raw() ([]byte, error) {
|
||||
if r.output == nil {
|
||||
return nil, errors.New("unable to get rollout status output")
|
||||
}
|
||||
return r.raw(r.output)
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue