From dd30a194ab8754af48ece8d1a5cbf69b8dec290e Mon Sep 17 00:00:00 2001 From: Stan Lagun Date: Fri, 19 May 2017 17:25:31 -0700 Subject: [PATCH] New 'void' pseudo resource type void resource type is a resource that does nothing and is always ready. There is no Definition for void resources, but they can be used in dependencies by specifying "void/something" (i.e. the syntax to refer to external resources without Definition). Here is why it is needed: * void resources can be used to create sync points between flow replicas (see unit test in this commit. Similar technique is used in etcd app, but in absence of void, service resource was used instead) * any vertex in the graph can be replaced by void/* without breaking dependencies. It can be used to exclude resource from the graph and keeping it structure * can be used in functional tests similar to how mock resources are used in unit tests --- pkg/resources/common.go | 1 + pkg/resources/void.go | 69 +++++++++++++++++++++++++++++++++++++ pkg/scheduler/flows_test.go | 40 +++++++++++++++++++++ pkg/scheduler/scheduler.go | 1 - 4 files changed, 110 insertions(+), 1 deletion(-) create mode 100644 pkg/resources/void.go diff --git a/pkg/resources/common.go b/pkg/resources/common.go index 796d81b..bcbae07 100644 --- a/pkg/resources/common.go +++ b/pkg/resources/common.go @@ -44,6 +44,7 @@ func init() { serviceTemplateFactory{}, serviceAccountTemplateFactory{}, statefulSetTemplateFactory{}, + voidTemplateFactory{}, } for _, factory := range factories { KindToResourceTemplate[factory.Kind()] = factory diff --git a/pkg/resources/void.go b/pkg/resources/void.go new file mode 100644 index 0000000..3459fe4 --- /dev/null +++ b/pkg/resources/void.go @@ -0,0 +1,69 @@ +// Copyright 2017 Mirantis +// +// 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 resources + +import ( + "github.com/Mirantis/k8s-AppController/pkg/client" + "github.com/Mirantis/k8s-AppController/pkg/interfaces" + "github.com/Mirantis/k8s-AppController/pkg/report" +) + +type void struct { + Base + name string +} + +type voidTemplateFactory struct{} + +// Returns wrapped resource name. Since void resource cannot be wrapped it is always empty string +func (voidTemplateFactory) ShortName(_ client.ResourceDefinition) string { + return "" +} + +// k8s resource kind that this fabric supports +func (voidTemplateFactory) Kind() string { + return "void" +} + +// New returns new void based on resource definition. Since void cannot be wrapped it is always nil +func (voidTemplateFactory) New(_ client.ResourceDefinition, _ client.Interface, _ interfaces.GraphContext) interfaces.Resource { + return nil +} + +// NewExisting returns new void with specified name +func (voidTemplateFactory) NewExisting(name string, _ client.Interface, gc interfaces.GraphContext) interfaces.Resource { + name = parametrizeResource(name, gc, []string{"*"}).(string) + return report.SimpleReporter{BaseResource: void{name: name}} +} + +// Key returns void name +func (v void) Key() string { + return "void/" + v.name +} + +// Status always returns "ready" +func (void) Status(_ map[string]string) (interfaces.ResourceStatus, error) { + return interfaces.ResourceReady, nil +} + +// Delete is a no-op method to create resource +func (void) Create() error { + return nil +} + +// Delete is a no-op method to delete resource +func (void) Delete() error { + return nil +} diff --git a/pkg/scheduler/flows_test.go b/pkg/scheduler/flows_test.go index b871e0f..464865c 100644 --- a/pkg/scheduler/flows_test.go +++ b/pkg/scheduler/flows_test.go @@ -1327,3 +1327,43 @@ func TestMultipathParameterPassingWithSuffix(t *testing.T) { t.Errorf("not all jobs were created: %d jobs were not found", len(jobNames)) } } + +// TestSyncOnVoidResource tests replica deployment synchronization on void resource - pods from all replicas must be +// created before the first job deployment begins. Void acts here as a sync point because it dose nothing but is shared +// by all replicas and thus depends on parents (pods) from all replicas +func TestSyncOnVoidResource(t *testing.T) { + replicaCount := 5 + c, fake := mocks.NewClientWithFake( + mocks.MakeFlow("test"), + mocks.MakeResourceDefinition("pod/ready-$AC_NAME"), + mocks.MakeResourceDefinition("job/ready-$AC_NAME"), + mocks.MakeDependency("flow/test", "pod/ready-$AC_NAME", "flow=test"), + mocks.MakeDependency("pod/ready-$AC_NAME", "void/checkpoint", "flow=test"), + mocks.MakeDependency("void/checkpoint", "job/ready-$AC_NAME", "flow=test"), + ) + + stopChan := make(chan struct{}) + podCount := 0 + + fake.PrependReactor("create", "*", + func(action k8stesting.Action) (handled bool, ret runtime.Object, err error) { + switch action.GetResource().Resource { + case "pods": + podCount++ + case "jobs": + if podCount != replicaCount { + t.Errorf("expected %d pods to exist, found %d", replicaCount, podCount) + } + } + return false, nil, nil + }) + + depGraph, err := New(c, nil, 0).BuildDependencyGraph( + interfaces.DependencyGraphOptions{ReplicaCount: replicaCount, FlowName: "test"}) + if err != nil { + t.Fatal(err) + } + + depGraph.Deploy(stopChan) + ensureReplicas(c, t, replicaCount, replicaCount) +} diff --git a/pkg/scheduler/scheduler.go b/pkg/scheduler/scheduler.go index 073fbb7..09d35b9 100644 --- a/pkg/scheduler/scheduler.go +++ b/pkg/scheduler/scheduler.go @@ -225,7 +225,6 @@ func createResources(toCreate chan *ScheduledResource, finished chan string, ccL req := r.context.graph.graph[reqKey] go func(req *ScheduledResource, toCreate chan *ScheduledResource) { ticker := time.NewTicker(CheckInterval) - log.Printf("Requesting creation of dependency %v", req.Key()) for { select { case <-stopChan: