airshipctl/pkg/k8s/poller/poller_test.go

319 lines
9.2 KiB
Go
Executable File

/*
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
https://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 poller_test
import (
"context"
"errors"
"testing"
"time"
"github.com/stretchr/testify/require"
appsv1 "k8s.io/api/apps/v1"
v1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/meta"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/cli-runtime/pkg/resource"
"k8s.io/client-go/discovery"
"k8s.io/client-go/dynamic"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/rest"
"k8s.io/client-go/tools/clientcmd"
cmdutil "k8s.io/kubectl/pkg/cmd/util"
"k8s.io/kubectl/pkg/util/openapi"
"k8s.io/kubectl/pkg/validation"
"sigs.k8s.io/cli-utils/pkg/kstatus/polling"
"sigs.k8s.io/cli-utils/pkg/kstatus/polling/engine"
"sigs.k8s.io/cli-utils/pkg/kstatus/polling/event"
kstatustestutil "sigs.k8s.io/cli-utils/pkg/kstatus/polling/testutil"
"sigs.k8s.io/cli-utils/pkg/kstatus/status"
"sigs.k8s.io/cli-utils/pkg/object"
"sigs.k8s.io/cli-utils/pkg/testutil"
"sigs.k8s.io/controller-runtime/pkg/client"
"opendev.org/airship/airshipctl/pkg/k8s/poller"
)
func TestNewStatusPoller(t *testing.T) {
testCases := map[string]struct {
factory cmdutil.Factory
expectedError bool
}{
"failed rest config": {
factory: &MockCmdUtilFactory{MockToRESTConfig: func() (*rest.Config, error) {
return nil, errors.New("rest config error")
}},
expectedError: true,
},
"failed rest mapper": {
factory: &MockCmdUtilFactory{MockToRESTConfig: func() (*rest.Config, error) {
return nil, nil
},
MockToRESTMapper: func() (meta.RESTMapper, error) {
return nil, errors.New("rest mapper error")
}},
expectedError: true,
},
"failed new client": {
factory: &MockCmdUtilFactory{MockToRESTConfig: func() (*rest.Config, error) {
return nil, nil
},
MockToRESTMapper: func() (meta.RESTMapper, error) {
return nil, nil
}},
expectedError: true,
},
"success new poller": {
factory: &MockCmdUtilFactory{MockToRESTConfig: func() (*rest.Config, error) {
return &rest.Config{}, nil
},
MockToRESTMapper: func() (meta.RESTMapper, error) {
return testutil.NewFakeRESTMapper(), nil
}},
expectedError: false,
},
}
for tn, tc := range testCases {
t.Run(tn, func(t *testing.T) {
p, err := poller.NewStatusPoller(tc.factory)
if tc.expectedError {
require.Error(t, err)
} else {
require.NoError(t, err)
require.NotNil(t, p)
}
})
}
}
func TestStatusPollerRun(t *testing.T) {
testCases := map[string]struct {
identifiers []object.ObjMetadata
ClusterReaderFactoryFunc engine.ClusterReaderFactoryFunc
StatusReadersFactoryFunc engine.StatusReadersFactoryFunc
defaultStatusReader engine.StatusReader
expectedEventTypes []event.EventType
}{
"single resource": {
identifiers: []object.ObjMetadata{
{
GroupKind: schema.GroupKind{
Group: "apps",
Kind: "Deployment",
},
Name: "foo",
Namespace: "bar",
},
},
defaultStatusReader: &fakeStatusReader{
resourceStatuses: map[schema.GroupKind][]status.Status{
schema.GroupKind{Group: "apps", Kind: "Deployment"}: { //nolint:gofmt
status.InProgressStatus,
status.CurrentStatus,
},
},
resourceStatusCount: make(map[schema.GroupKind]int),
},
expectedEventTypes: []event.EventType{
event.ResourceUpdateEvent,
event.ResourceUpdateEvent,
},
ClusterReaderFactoryFunc: func(_ client.Reader, _ meta.RESTMapper, _ []object.ObjMetadata) (
engine.ClusterReader, error) {
return kstatustestutil.NewNoopClusterReader(), nil
},
StatusReadersFactoryFunc: func(_ engine.ClusterReader, _ meta.RESTMapper) (
statusReaders map[schema.GroupKind]engine.StatusReader, defaultStatusReader engine.StatusReader) {
return make(map[schema.GroupKind]engine.StatusReader), &fakeStatusReader{
resourceStatuses: map[schema.GroupKind][]status.Status{
schema.GroupKind{Group: "apps", Kind: "Deployment"}: { //nolint:gofmt
status.InProgressStatus,
status.CurrentStatus,
},
},
resourceStatusCount: make(map[schema.GroupKind]int),
}
},
},
"multiple resources": {
identifiers: []object.ObjMetadata{
{
GroupKind: schema.GroupKind{
Group: "apps",
Kind: "Deployment",
},
Name: "foo",
Namespace: "default",
},
{
GroupKind: schema.GroupKind{
Group: "",
Kind: "Service",
},
Name: "bar",
Namespace: "default",
},
},
ClusterReaderFactoryFunc: func(_ client.Reader, _ meta.RESTMapper, _ []object.ObjMetadata) (
engine.ClusterReader, error) {
return kstatustestutil.NewNoopClusterReader(), nil
},
StatusReadersFactoryFunc: func(_ engine.ClusterReader, _ meta.RESTMapper) (
statusReaders map[schema.GroupKind]engine.StatusReader, defaultStatusReader engine.StatusReader) {
return make(map[schema.GroupKind]engine.StatusReader), &fakeStatusReader{
resourceStatuses: map[schema.GroupKind][]status.Status{
schema.GroupKind{Group: "apps", Kind: "Deployment"}: { //nolint:gofmt
status.InProgressStatus,
status.CurrentStatus,
},
schema.GroupKind{Group: "", Kind: "Service"}: { //nolint:gofmt
status.InProgressStatus,
status.InProgressStatus,
status.CurrentStatus,
},
},
resourceStatusCount: make(map[schema.GroupKind]int),
}
},
expectedEventTypes: []event.EventType{
event.ResourceUpdateEvent,
event.ResourceUpdateEvent,
event.ResourceUpdateEvent,
event.ResourceUpdateEvent,
},
},
}
for tn, tc := range testCases {
t.Run(tn, func(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
identifiers := tc.identifiers
fakeMapper := testutil.NewFakeRESTMapper(
appsv1.SchemeGroupVersion.WithKind("Deployment"),
v1.SchemeGroupVersion.WithKind("Service"),
)
e := poller.StatusPoller{
ClusterReaderFactoryFunc: tc.ClusterReaderFactoryFunc,
StatusReadersFactoryFunc: tc.StatusReadersFactoryFunc,
Engine: &engine.PollerEngine{Mapper: fakeMapper},
}
options := polling.Options{PollInterval: time.Second, UseCache: true}
eventChannel := e.Poll(ctx, identifiers, options)
var eventTypes []event.EventType
for ch := range eventChannel {
eventTypes = append(eventTypes, ch.EventType)
if len(eventTypes) == len(tc.expectedEventTypes) {
cancel()
}
}
require.Equal(t, tc.expectedEventTypes, eventTypes)
})
}
}
type fakeStatusReader struct {
resourceStatuses map[schema.GroupKind][]status.Status
resourceStatusCount map[schema.GroupKind]int
}
func (f *fakeStatusReader) ReadStatus(_ context.Context, identifier object.ObjMetadata) *event.ResourceStatus {
count := f.resourceStatusCount[identifier.GroupKind]
resourceStatusSlice := f.resourceStatuses[identifier.GroupKind]
var resourceStatus status.Status
if len(resourceStatusSlice) > count {
resourceStatus = resourceStatusSlice[count]
} else {
resourceStatus = resourceStatusSlice[len(resourceStatusSlice)-1]
}
f.resourceStatusCount[identifier.GroupKind] = count + 1
return &event.ResourceStatus{
Identifier: identifier,
Status: resourceStatus,
}
}
func (f *fakeStatusReader) ReadStatusForObject(_ context.Context, _ *unstructured.Unstructured) *event.ResourceStatus {
return nil
}
var _ cmdutil.Factory = &MockCmdUtilFactory{}
type MockCmdUtilFactory struct {
MockToRESTConfig func() (*rest.Config, error)
MockToRESTMapper func() (meta.RESTMapper, error)
}
func (n *MockCmdUtilFactory) ToRESTConfig() (*rest.Config, error) {
return n.MockToRESTConfig()
}
func (n *MockCmdUtilFactory) ToDiscoveryClient() (discovery.CachedDiscoveryInterface, error) {
return nil, nil
}
func (n *MockCmdUtilFactory) ToRESTMapper() (meta.RESTMapper, error) {
return n.MockToRESTMapper()
}
func (n *MockCmdUtilFactory) ToRawKubeConfigLoader() clientcmd.ClientConfig {
return nil
}
func (n *MockCmdUtilFactory) DynamicClient() (dynamic.Interface, error) {
return nil, nil
}
func (n *MockCmdUtilFactory) KubernetesClientSet() (*kubernetes.Clientset, error) {
return nil, nil
}
func (n *MockCmdUtilFactory) RESTClient() (*rest.RESTClient, error) {
return nil, nil
}
func (n *MockCmdUtilFactory) NewBuilder() *resource.Builder {
return nil
}
func (n *MockCmdUtilFactory) ClientForMapping(_ *meta.RESTMapping) (resource.RESTClient, error) {
return nil, nil
}
func (n *MockCmdUtilFactory) UnstructuredClientForMapping(_ *meta.RESTMapping) (resource.RESTClient, error) {
return nil, nil
}
func (n *MockCmdUtilFactory) Validator(_ bool) (validation.Schema, error) {
return nil, nil
}
func (n *MockCmdUtilFactory) OpenAPISchema() (openapi.Resources, error) {
return nil, nil
}
func (n *MockCmdUtilFactory) OpenAPIGetter() discovery.OpenAPISchemaInterface {
return nil
}