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 9438257..43e0031 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: