Use bundle mock for k8s applier tests

Change-Id: Id00e5c493bf9cca2c03ea30f51c1ed978df74c15
Relates-To: #464
Relates-To: #465
This commit is contained in:
Vladimir Kozhukalov 2021-03-30 22:17:27 +03:00
parent ab98b63bf3
commit ebb03b504b
5 changed files with 147 additions and 100 deletions

View File

@ -68,7 +68,7 @@ func TestNewBMHExecutor(t *testing.T) {
execDoc := executorDoc(t, fmt.Sprintf(bmhExecutorTemplate, "reboot", "/home/iso-url")) execDoc := executorDoc(t, fmt.Sprintf(bmhExecutorTemplate, "reboot", "/home/iso-url"))
executor, err := executors.NewBaremetalExecutor(ifc.ExecutorConfig{ executor, err := executors.NewBaremetalExecutor(ifc.ExecutorConfig{
ExecutorDocument: execDoc, ExecutorDocument: execDoc,
BundleFactory: testBundleFactory(singleExecutorBundlePath), BundleFactory: testBundleFactory(),
}) })
assert.NoError(t, err) assert.NoError(t, err)
assert.NotNil(t, executor) assert.NotNil(t, executor)
@ -81,7 +81,7 @@ func TestNewBMHExecutor(t *testing.T) {
} }
executor, actualErr := executors.NewBaremetalExecutor(ifc.ExecutorConfig{ executor, actualErr := executors.NewBaremetalExecutor(ifc.ExecutorConfig{
ExecutorDocument: execDoc, ExecutorDocument: execDoc,
BundleFactory: testBundleFactory(singleExecutorBundlePath), BundleFactory: testBundleFactory(),
}) })
assert.Equal(t, exepectedErr, actualErr) assert.Equal(t, exepectedErr, actualErr)
assert.Nil(t, executor) assert.Nil(t, executor)

View File

@ -96,9 +96,10 @@ func executorDoc(t *testing.T, s string) document.Document {
return doc return doc
} }
func testBundleFactory(path string) document.BundleFactoryFunc { // TODO replace this test bundle factory with one that uses bundle mock
func testBundleFactory() document.BundleFactoryFunc {
return func() (document.Bundle, error) { return func() (document.Bundle, error) {
return document.NewBundleByPath(path) return document.NewBundleByPath(singleExecutorBundlePath)
} }
} }

View File

@ -91,7 +91,7 @@ func TestNewContainerExecutor(t *testing.T) {
t.Run("success new container executor", func(t *testing.T) { t.Run("success new container executor", func(t *testing.T) {
e, err := executors.NewContainerExecutor(ifc.ExecutorConfig{ e, err := executors.NewContainerExecutor(ifc.ExecutorConfig{
ExecutorDocument: execDoc, ExecutorDocument: execDoc,
BundleFactory: testBundleFactory(singleExecutorBundlePath), BundleFactory: testBundleFactory(),
}) })
assert.NoError(t, err) assert.NoError(t, err)
assert.NotNil(t, e) assert.NotNil(t, e)

View File

@ -70,18 +70,23 @@ func NewKubeApplierExecutor(cfg ifc.ExecutorConfig) (ifc.Executor, error) {
clusterMap: cfg.ClusterMap, clusterMap: cfg.ClusterMap,
clusterName: cfg.ClusterName, clusterName: cfg.ClusterName,
kubeconfig: cfg.KubeConfig, kubeconfig: cfg.KubeConfig,
// default cleanup that does nothing
// replaced with a meaningful cleanup while preparing kubeconfig
cleanup: func() {},
}, nil }, nil
} }
// Run executor, should be performed in separate go routine // Run executor, should be performed in separate go routine
func (e *KubeApplierExecutor) Run(ch chan events.Event, runOpts ifc.RunOptions) { func (e *KubeApplierExecutor) Run(ch chan events.Event, runOpts ifc.RunOptions) {
defer close(ch) defer close(ch)
applier, filteredBundle, err := e.prepareApplier(ch) applier, filteredBundle, err := e.prepareApplier(ch)
if err != nil { if err != nil {
handleError(ch, err) handleError(ch, err)
return return
} }
defer e.cleanup() defer e.cleanup()
dryRunStrategy := common.DryRunNone dryRunStrategy := common.DryRunNone
if runOpts.DryRun { if runOpts.DryRun {
dryRunStrategy = common.DryRunClient dryRunStrategy = common.DryRunClient
@ -102,6 +107,11 @@ func (e *KubeApplierExecutor) Run(ch chan events.Event, runOpts ifc.RunOptions)
} }
func (e *KubeApplierExecutor) prepareApplier(ch chan events.Event) (*k8sapplier.Applier, document.Bundle, error) { func (e *KubeApplierExecutor) prepareApplier(ch chan events.Event) (*k8sapplier.Applier, document.Bundle, error) {
log.Debug("Filtering out documents that shouldn't be applied to kubernetes from document bundle")
bundle, err := e.ExecutorBundle.SelectBundle(document.NewDeployToK8sSelector())
if err != nil {
return nil, nil, err
}
log.Debug("Getting kubeconfig context name from cluster map") log.Debug("Getting kubeconfig context name from cluster map")
context, err := e.clusterMap.ClusterKubeconfigContext(e.clusterName) context, err := e.clusterMap.ClusterKubeconfigContext(e.clusterName)
if err != nil { if err != nil {
@ -112,12 +122,6 @@ func (e *KubeApplierExecutor) prepareApplier(ch chan events.Event) (*k8sapplier.
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
log.Debug("Filtering out documents that shouldn't be applied to kubernetes from document bundle")
bundle, err := e.ExecutorBundle.SelectBundle(document.NewDeployToK8sSelector())
if err != nil {
cleanup()
return nil, nil, err
}
// set up cleanup only if all calls up to here were successful // set up cleanup only if all calls up to here were successful
e.cleanup = cleanup e.cleanup = cleanup
log.Printf("Using kubeconfig at '%s' and context '%s'", path, context) log.Printf("Using kubeconfig at '%s' and context '%s'", path, context)

View File

@ -16,9 +16,12 @@ package executors_test
import ( import (
"bytes" "bytes"
"errors"
"io"
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"opendev.org/airship/airshipctl/pkg/api/v1alpha1" "opendev.org/airship/airshipctl/pkg/api/v1alpha1"
@ -30,6 +33,7 @@ import (
"opendev.org/airship/airshipctl/pkg/k8s/utils" "opendev.org/airship/airshipctl/pkg/k8s/utils"
"opendev.org/airship/airshipctl/pkg/phase/executors" "opendev.org/airship/airshipctl/pkg/phase/executors"
"opendev.org/airship/airshipctl/pkg/phase/ifc" "opendev.org/airship/airshipctl/pkg/phase/ifc"
testdoc "opendev.org/airship/airshipctl/testutil/document"
testfs "opendev.org/airship/airshipctl/testutil/fs" testfs "opendev.org/airship/airshipctl/testutil/fs"
) )
@ -79,58 +83,108 @@ users:
client-certificate-data: cert-data client-certificate-data: cert-data
client-key-data: client-keydata client-key-data: client-keydata
` `
) WrongExecutorDoc = `apiVersion: v1
func TestNewKubeApplierExecutor(t *testing.T) {
tests := []struct {
name string
cfgDoc string
expectedErr string
kubeconf kubeconfig.Interface
bundleFactory document.BundleFactoryFunc
}{
{
name: "valid executor",
cfgDoc: ValidExecutorDoc,
kubeconf: testKubeconfig(testValidKubeconfig),
bundleFactory: testBundleFactory("../../k8s/applier/testdata/source_bundle"),
},
{
name: "wrong config document",
cfgDoc: `apiVersion: v1
kind: ConfigMap kind: ConfigMap
metadata: metadata:
name: first-map name: first-map
namespace: default namespace: default
labels: labels:
cli-utils.sigs.k8s.io/inventory-id: "some id"`, cli-utils.sigs.k8s.io/inventory-id: "some id"
expectedErr: "wrong config document", `
bundleFactory: testBundleFactory("../../k8s/applier/testdata/source_bundle"), )
func testApplierBundleFactory(t *testing.T, filteredContent string, writer io.Writer) document.BundleFactoryFunc {
return func() (document.Bundle, error) {
// When the k8s applier executor Run method is called, the executor bundle is filtered
// using the label selector "airshipit.org/deploy-k8s notin (False, false)".
// Render method just filters out document with a given selector.
// That is why we need "SelectBundle" method mocked and return a filtered bundle.
filteredBundle := &testdoc.MockBundle{}
// Filtered bundle is passed to the k8s applier, which looks it up
// for an inventory document using label "cli-utils.sigs.k8s.io/inventory-id"
// and kind "ConfigMap". Therefore "SelectOne" method is mocked in the filtered bundle.
// This mock is used to get inventory document. Empty document is ok for k8s applier.
filteredBundle.On("SelectOne", mock.Anything).Return(&testdoc.MockDocument{}, nil)
// This mock is used to get the contents of the applier filtered bundle both for
// rendering and for applying it to a k8s cluster.
filteredBundle.On("Write", writer).
Return(nil).
Run(func(args mock.Arguments) {
arg, ok := args.Get(0).(io.Writer)
if ok {
_, err := arg.Write([]byte(filteredContent))
require.NoError(t, err)
}
})
// This is the applier executor bundle.
bundle := &testdoc.MockBundle{}
// This mock is used to filter out documents labeled with
// "airshipit.org/deploy-k8s notin (False, false)"
bundle.On("SelectBundle", mock.Anything).Return(filteredBundle, nil)
return bundle, nil
}
}
func testApplierBundleFactoryFilterError() document.BundleFactoryFunc {
return func() (document.Bundle, error) {
bundle := &testdoc.MockBundle{}
bundle.On("SelectBundle", mock.Anything).
Return(nil, errors.New("error"))
return bundle, nil
}
}
func testApplierBundleFactoryEmptyAllDocuments() document.BundleFactoryFunc {
return func() (document.Bundle, error) {
bundle := &testdoc.MockBundle{}
bundle.On("GetAllDocuments").Return([]document.Document{}, nil)
return bundle, nil
}
}
func testApplierBundleFactoryAllDocuments() document.BundleFactoryFunc {
return func() (document.Bundle, error) {
bundle := &testdoc.MockBundle{}
bundle.On("GetAllDocuments").Return([]document.Document{&testdoc.MockDocument{}}, nil)
return bundle, nil
}
}
func TestNewKubeApplierExecutor(t *testing.T) {
tests := []struct {
name string
execDoc document.Document
expectedErr bool
bundleFactory document.BundleFactoryFunc
}{
{
name: "valid executor",
execDoc: executorDoc(t, ValidExecutorDoc),
bundleFactory: testdoc.EmptyBundleFactory,
},
{
name: "wrong executor document",
execDoc: executorDoc(t, WrongExecutorDoc),
expectedErr: true,
}, },
{ {
name: "path to bundle does not exist", name: "bundle factory returns an error",
cfgDoc: ValidExecutorDoc, execDoc: executorDoc(t, ValidExecutorDoc),
expectedErr: "no such file or directory", expectedErr: true,
kubeconf: testKubeconfig(testValidKubeconfig), bundleFactory: testdoc.ErrorBundleFactory,
bundleFactory: testBundleFactory("does not exist"),
}, },
} }
for _, tt := range tests { for _, tt := range tests {
tt := tt tt := tt
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
doc, err := document.NewDocumentFromBytes([]byte(tt.cfgDoc))
require.NoError(t, err)
require.NotNil(t, doc)
exec, err := executors.NewKubeApplierExecutor( exec, err := executors.NewKubeApplierExecutor(
ifc.ExecutorConfig{ ifc.ExecutorConfig{
ExecutorDocument: doc, ExecutorDocument: tt.execDoc,
BundleFactory: tt.bundleFactory, BundleFactory: tt.bundleFactory,
KubeConfig: tt.kubeconf,
}) })
if tt.expectedErr != "" { if tt.expectedErr {
require.Error(t, err) require.Error(t, err)
assert.Contains(t, err.Error(), "") assert.Contains(t, err.Error(), "")
assert.Nil(t, exec) assert.Nil(t, exec)
@ -159,9 +213,9 @@ func TestKubeApplierExecutorRun(t *testing.T) {
{ {
name: "cant read kubeconfig error", name: "cant read kubeconfig error",
containsErr: "no such file or directory", containsErr: "no such file or directory",
bundleFactory: testBundleFactory("../../k8s/applier/testdata/source_bundle"), bundleFactory: testApplierBundleFactory(t, "", nil),
kubeconf: testKubeconfig(`invalid kubeconfig`), kubeconf: testKubeconfig(`invalid kubeconfig`),
execDoc: executorDoc(t, ValidExecutorDocNamespaced), execDoc: executorDoc(t, ValidExecutorDoc),
clusterName: "ephemeral-cluster", clusterName: "ephemeral-cluster",
clusterMap: clustermap.NewClusterMap(&v1alpha1.ClusterMap{ clusterMap: clustermap.NewClusterMap(&v1alpha1.ClusterMap{
Map: map[string]*v1alpha1.Cluster{ Map: map[string]*v1alpha1.Cluster{
@ -172,11 +226,17 @@ func TestKubeApplierExecutorRun(t *testing.T) {
{ {
name: "error cluster not defined", name: "error cluster not defined",
containsErr: "is not defined in cluster map", containsErr: "is not defined in cluster map",
bundleFactory: testBundleFactory("../../k8s/applier/testdata/source_bundle"), bundleFactory: testApplierBundleFactory(t, "", nil),
kubeconf: testKubeconfig(testValidKubeconfig), kubeconf: testKubeconfig(testValidKubeconfig),
execDoc: executorDoc(t, ValidExecutorDocNamespaced), execDoc: executorDoc(t, ValidExecutorDoc),
clusterMap: clustermap.NewClusterMap(v1alpha1.DefaultClusterMap()), clusterMap: clustermap.NewClusterMap(v1alpha1.DefaultClusterMap()),
}, },
{
name: "error during executor bundle filtering",
containsErr: "error",
execDoc: executorDoc(t, ValidExecutorDoc),
bundleFactory: testApplierBundleFactoryFilterError(),
},
} }
for _, tt := range tests { for _, tt := range tests {
tt := tt tt := tt
@ -189,10 +249,6 @@ func TestKubeApplierExecutorRun(t *testing.T) {
ClusterMap: tt.clusterMap, ClusterMap: tt.clusterMap,
ClusterName: tt.clusterName, ClusterName: tt.clusterName,
}) })
if tt.name == "Nil bundle provided" {
require.Error(t, err)
assert.Contains(t, err.Error(), tt.containsErr)
} else {
require.NoError(t, err) require.NoError(t, err)
require.NotNil(t, exec) require.NotNil(t, exec)
ch := make(chan events.Event) ch := make(chan events.Event)
@ -205,28 +261,25 @@ func TestKubeApplierExecutorRun(t *testing.T) {
} else { } else {
assert.NoError(t, err) assert.NoError(t, err)
} }
}
}) })
} }
} }
func TestRender(t *testing.T) { func TestRender(t *testing.T) {
execDoc, err := document.NewDocumentFromBytes([]byte(ValidExecutorDoc)) writer := bytes.NewBuffer([]byte{})
require.NoError(t, err) content := "Some content"
require.NotNil(t, execDoc)
exec, err := executors.NewKubeApplierExecutor(ifc.ExecutorConfig{ exec, err := executors.NewKubeApplierExecutor(ifc.ExecutorConfig{
BundleFactory: testBundleFactory("../../k8s/applier/testdata/source_bundle"), BundleFactory: testApplierBundleFactory(t, content, writer),
ExecutorDocument: execDoc, ExecutorDocument: executorDoc(t, ValidExecutorDoc),
}) })
require.NoError(t, err) require.NoError(t, err)
require.NotNil(t, exec) require.NotNil(t, exec)
writerReader := bytes.NewBuffer([]byte{}) err = exec.Render(writer, ifc.RenderOptions{})
err = exec.Render(writerReader, ifc.RenderOptions{})
require.NoError(t, err) require.NoError(t, err)
result := writerReader.String() result := writer.String()
assert.Contains(t, result, "ReplicationController") assert.Equal(t, content, result)
} }
func testKubeconfig(stringData string) kubeconfig.Interface { func testKubeconfig(stringData string) kubeconfig.Interface {
@ -247,48 +300,37 @@ func testKubeconfig(stringData string) kubeconfig.Interface {
} }
func TestKubeApplierExecutor_Validate(t *testing.T) { func TestKubeApplierExecutor_Validate(t *testing.T) {
type fields struct {
BundleName string
path string
}
tests := []struct { tests := []struct {
name string name string
fields fields bundleFactory document.BundleFactoryFunc
bundleName string
wantErr bool wantErr bool
}{ }{
{ {
name: "Error empty BundleName", name: "Error empty BundleName",
fields: fields{ bundleFactory: testdoc.EmptyBundleFactory,
path: "../../k8s/applier/testdata/source_bundle",
},
wantErr: true, wantErr: true,
}, },
{ {
name: "Error no documents", name: "Error no documents",
fields: fields{BundleName: "some name", bundleName: "some name",
path: "../../k8s/applier/testdata/no_bundle", bundleFactory: testApplierBundleFactoryEmptyAllDocuments(),
},
wantErr: true, wantErr: true,
}, },
{ {
name: "Success case", name: "Success case",
fields: fields{BundleName: "some name", bundleName: "some name",
path: "../../k8s/applier/testdata/source_bundle", bundleFactory: testApplierBundleFactoryAllDocuments(),
},
wantErr: false, wantErr: false,
}, },
} }
for _, test := range tests { for _, test := range tests {
tt := test tt := test
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
execDoc, err := document.NewDocumentFromBytes([]byte(ValidExecutorDoc))
require.NoError(t, err)
require.NotNil(t, execDoc)
e, err := executors.NewKubeApplierExecutor(ifc.ExecutorConfig{ e, err := executors.NewKubeApplierExecutor(ifc.ExecutorConfig{
BundleFactory: testBundleFactory(tt.fields.path), BundleFactory: tt.bundleFactory,
PhaseName: tt.fields.BundleName, PhaseName: tt.bundleName,
ExecutorDocument: execDoc, ExecutorDocument: executorDoc(t, ValidExecutorDoc),
}) })
require.NoError(t, err) require.NoError(t, err)
require.NotNil(t, e) require.NotNil(t, e)