basic tests for pkg/storage/kube.ConfigMaps

This commit is contained in:
fibonacci1729 2018-01-22 09:12:23 -07:00
Родитель e5cf83eb42
Коммит 1a711c491a
3 изменённых файлов: 239 добавлений и 41 удалений

Просмотреть файл

@ -0,0 +1,98 @@
package kube
import (
"testing"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
corev1 "k8s.io/client-go/kubernetes/typed/core/v1"
"k8s.io/client-go/pkg/api"
"k8s.io/client-go/pkg/api/v1"
"github.com/Azure/draft/pkg/storage"
)
// MockConfigMaps mocks a kubernetes ConfigMapsInterface.
//
// For use in testing only.
type MockConfigMaps struct {
corev1.ConfigMapInterface
cfgmaps map[string]*v1.ConfigMap
}
// NewConfigMapsWithMocks initializes a new ConfigMaps store initialized
// with kubernetes ConfigMap objects created from the provided entries.
func NewConfigMapsWithMocks(t *testing.T, entries ...struct {
appName string
objects []*storage.Object
}) *ConfigMaps {
var mock MockConfigMaps
mock.Init(t, entries...)
return NewConfigMaps(&mock)
}
// Init initializes the MockConfigMaps mock with the set of storage objects.
func (mock *MockConfigMaps) Init(t *testing.T, entries ...struct {
appName string
objects []*storage.Object
}) {
mock.cfgmaps = make(map[string]*v1.ConfigMap)
for _, entry := range entries {
var cfgmap *v1.ConfigMap
for _, object := range entry.objects {
if cfgmap != nil {
if _, ok := cfgmap.Data[object.BuildID]; !ok {
content, err := storage.EncodeToString(object)
if err != nil {
t.Fatalf("failed to encode storage object: %v", err)
}
cfgmap.Data[object.BuildID] = content
}
} else {
var err error
if cfgmap, err = newConfigMap(entry.appName, object); err != nil {
t.Fatalf("failed to create configmap: %v", err)
}
}
}
mock.cfgmaps[entry.appName] = cfgmap
}
}
// Get returns the ConfigMap by name.
func (mock *MockConfigMaps) Get(name string, options metav1.GetOptions) (*v1.ConfigMap, error) {
cfgmap, ok := mock.cfgmaps[name]
if !ok {
return nil, apierrors.NewNotFound(api.Resource("tests"), name)
}
return cfgmap, nil
}
// Create creates a new ConfigMap.
func (mock *MockConfigMaps) Create(cfgmap *v1.ConfigMap) (*v1.ConfigMap, error) {
name := cfgmap.ObjectMeta.Name
if object, ok := mock.cfgmaps[name]; ok {
return object, apierrors.NewAlreadyExists(api.Resource("tests"), name)
}
mock.cfgmaps[name] = cfgmap
return cfgmap, nil
}
// Update updates a ConfigMap.
func (mock *MockConfigMaps) Update(cfgmap *v1.ConfigMap) (*v1.ConfigMap, error) {
name := cfgmap.ObjectMeta.Name
if _, ok := mock.cfgmaps[name]; !ok {
return nil, apierrors.NewNotFound(api.Resource("tests"), name)
}
mock.cfgmaps[name] = cfgmap
return cfgmap, nil
}
// Delete deletes a ConfigMap by name.
func (mock *MockConfigMaps) Delete(name string, opts *metav1.DeleteOptions) error {
if _, ok := mock.cfgmaps[name]; !ok {
return apierrors.NewNotFound(api.Resource("tests"), name)
}
delete(mock.cfgmaps, name)
return nil
}

Просмотреть файл

@ -6,36 +6,36 @@ import (
"github.com/Azure/draft/pkg/storage"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
k8s "k8s.io/client-go/kubernetes"
"k8s.io/client-go/pkg/api/v1"
corev1 "k8s.io/client-go/kubernetes/typed/core/v1"
)
// Store represents a Kubernetes configmap storage engine for a storage.Object .
type Store struct {
client k8s.Interface
namespace string
// ConfigMaps represents a Kubernetes configmap storage engine for a storage.Object .
type ConfigMaps struct {
impl corev1.ConfigMapInterface
}
var _ storage.Store = (*Store)(nil)
var _ storage.Store = (*ConfigMaps)(nil)
func NewStore(c k8s.Interface, namespace string) *Store {
return &Store{c, namespace}
func NewConfigMaps(impl corev1.ConfigMapInterface) *ConfigMaps {
return &ConfigMaps{impl}
}
// DeleteBuilds deletes all draft builds for the application specified by appName.
func (s *Store) DeleteBuilds(ctx context.Context, appName string) ([]*storage.Object, error) {
builds, err := s.GetBuilds(ctx, appName)
func (this *ConfigMaps) DeleteBuilds(ctx context.Context, appName string) ([]*storage.Object, error) {
builds, err := this.GetBuilds(ctx, appName)
if err != nil {
return nil, err
}
err = s.client.CoreV1().ConfigMaps(s.namespace).Delete(appName, &metav1.DeleteOptions{})
err = this.impl.Delete(appName, &metav1.DeleteOptions{})
return builds, err
}
// DeleteBuild deletes the draft build given by buildID for the application specified by appName.
func (s *Store) DeleteBuild(ctx context.Context, appName, buildID string) (obj *storage.Object, err error) {
func (this *ConfigMaps) DeleteBuild(ctx context.Context, appName, buildID string) (obj *storage.Object, err error) {
var cfgmap *v1.ConfigMap
if cfgmap, err = s.client.CoreV1().ConfigMaps(s.namespace).Get(appName, metav1.GetOptions{}); err != nil {
if cfgmap, err = this.impl.Get(appName, metav1.GetOptions{}); err != nil {
return nil, err
}
if build, ok := cfgmap.Data[buildID]; ok {
@ -43,36 +43,26 @@ func (s *Store) DeleteBuild(ctx context.Context, appName, buildID string) (obj *
return nil, err
}
delete(cfgmap.Data, buildID)
_, err = s.client.CoreV1().ConfigMaps(s.namespace).Update(cfgmap)
_, err = this.impl.Update(cfgmap)
return obj, err
}
return nil, fmt.Errorf("application %q storage object %q not found", appName, buildID)
}
// CreateBuild stores a draft.Build for the application specified by appName.
func (s *Store) CreateBuild(ctx context.Context, appName string, build *storage.Object) error {
content, err := storage.EncodeToString(build)
func (this *ConfigMaps) CreateBuild(ctx context.Context, appName string, build *storage.Object) error {
cfgmap, err := newConfigMap(appName, build)
if err != nil {
return err
}
cfgmap := &v1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Name: appName,
Labels: map[string]string{
"heritage": "draft",
"appname": appName,
},
},
Data: map[string]string{build.BuildID: content},
}
_, err = s.client.CoreV1().ConfigMaps(s.namespace).Create(cfgmap)
_, err = this.impl.Create(cfgmap)
return err
}
// GetBuilds returns a slice of builds for the given app name.
func (s *Store) GetBuilds(ctx context.Context, appName string) (builds []*storage.Object, err error) {
func (this *ConfigMaps) GetBuilds(ctx context.Context, appName string) (builds []*storage.Object, err error) {
var cfgmap *v1.ConfigMap
if cfgmap, err = s.client.CoreV1().ConfigMaps(s.namespace).Get(appName, metav1.GetOptions{}); err != nil {
if cfgmap, err = this.impl.Get(appName, metav1.GetOptions{}); err != nil {
return nil, err
}
for _, obj := range cfgmap.Data {
@ -86,9 +76,9 @@ func (s *Store) GetBuilds(ctx context.Context, appName string) (builds []*storag
}
// GetBuild returns the build associated with buildID for the specified app name.
func (s *Store) GetBuild(ctx context.Context, appName, buildID string) (obj *storage.Object, err error) {
func (this *ConfigMaps) GetBuild(ctx context.Context, appName, buildID string) (obj *storage.Object, err error) {
var cfgmap *v1.ConfigMap
if cfgmap, err = s.client.CoreV1().ConfigMaps(s.namespace).Get(appName, metav1.GetOptions{}); err != nil {
if cfgmap, err = this.impl.Get(appName, metav1.GetOptions{}); err != nil {
return nil, err
}
if data, ok := cfgmap.Data[buildID]; ok {
@ -99,3 +89,25 @@ func (s *Store) GetBuild(ctx context.Context, appName, buildID string) (obj *sto
}
return nil, fmt.Errorf("application %q storage object %q not found", appName, buildID)
}
// newConfigMap constructs a kubernetes ConfigMap object to store a build.
//
// Each configmap data entry is the base64 encoded string of a *storage.Object
// binary protobuf encoding.
func newConfigMap(appName string, build *storage.Object) (*v1.ConfigMap, error) {
content, err := storage.EncodeToString(build)
if err != nil {
return nil, err
}
cfgmap := &v1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Name: appName,
Labels: map[string]string{
"heritage": "draft",
"appname": appName,
},
},
Data: map[string]string{build.BuildID: content},
}
return cfgmap, nil
}

Просмотреть файл

@ -1,25 +1,113 @@
package kube
import (
"context"
"reflect"
"testing"
"github.com/Azure/draft/pkg/storage"
)
func TestStore_DeleteBuilds(t *testing.T) {
t.Skip("TestStore_DeleteBuilds: TODO")
func TestStoreDeleteBuilds(t *testing.T) {
var (
store = newMockConfigMapsTestFixture(t)
ctx = context.Background()
)
switch objs, err := store.DeleteBuilds(ctx, "app1"); {
case err != nil:
t.Fatalf("failed to delete builds: %v", err)
case len(objs) != 4:
t.Fatalf("expected 4 deleted builds, got %d", len(objs))
}
}
func TestStore_DeleteBuild(t *testing.T) {
t.Skip("TestStore_DeleteBuild: TODO")
func TestStoreDeleteBuild(t *testing.T) {
var (
store = newMockConfigMapsTestFixture(t)
ctx = context.Background()
)
obj, err := store.DeleteBuild(ctx, "app1", "foo4")
if err != nil {
t.Fatalf("failed to delete build: %v", err)
}
assertEqual(t, "DeleteBuild", obj, objectStub("foo4", "bar4", []byte("foobar4")))
}
func TestStore_CreateBuild(t *testing.T) {
t.Skip("TestStore_CreateBuild: TODO")
func TestStoreCreateBuild(t *testing.T) {
var (
store = newMockConfigMapsTestFixture(t)
ctx = context.Background()
)
obj := objectStub("foo1", "bar1", []byte("foobar1"))
err := store.CreateBuild(ctx, "app2", obj)
if err != nil {
t.Fatalf("failed to created build: %v", err)
}
got, err := store.GetBuild(ctx, "app2", "foo1")
if err != nil {
t.Fatalf("failed to get storage object: %v", err)
}
assertEqual(t, "CreateBuild", got, obj)
}
func TestStore_GetBuilds(t *testing.T) {
t.Skip("TestStore_GetBuilds: TODO")
func TestStoreGetBuilds(t *testing.T) {
var (
store = newMockConfigMapsTestFixture(t)
ctx = context.Background()
)
switch got, err := store.GetBuilds(ctx, "app1"); {
case err != nil:
t.Fatalf("failed to get builds: %v", err)
case len(got) != 4:
t.Fatalf("expected 4 storage objects, got %d", len(got))
}
}
func TestStore_GetBuild(t *testing.T) {
t.Skip("TestStore_GetBuild: TODO")
func TestStoreGetBuild(t *testing.T) {
var (
store = newMockConfigMapsTestFixture(t)
ctx = context.Background()
want = objectStub("foo1", "bar1", []byte("foobar1"))
)
got, err := store.GetBuild(ctx, "app1", "foo1")
if err != nil {
t.Fatalf("failed to get storage object: %v", err)
}
assertEqual(t, "GetBuild", got, want)
}
//
// test fixtures / helpers
//
func newMockConfigMapsTestFixture(t *testing.T) *ConfigMaps {
var mocks = []struct {
appName string
objects []*storage.Object
}{
{
appName: "app1",
objects: []*storage.Object{
objectStub("foo1", "bar1", []byte("foobar1")),
objectStub("foo2", "bar2", []byte("foobar2")),
objectStub("foo3", "bar3", []byte("foobar3")),
objectStub("foo4", "bar4", []byte("foobar4")),
},
},
}
return NewConfigMapsWithMocks(t, mocks...)
}
func objectStub(buildID, release string, contextID []byte) *storage.Object {
return &storage.Object{
BuildID: buildID,
Release: release,
ContextID: contextID,
}
}
func assertEqual(t *testing.T, label string, a, b interface{}) {
if !reflect.DeepEqual(a, b) {
t.Errorf("failed equality for %s", label)
}
}