Commit e65d4270 authored by Geoff Simmons's avatar Geoff Simmons

Sync a changed ConfigMap only when VCL file modification times change.

Take no action for a deleted ConfigMap, other than resetting current
knowledge of VCL mtimes.
parent 3d600e0b
......@@ -84,6 +84,9 @@ var (
"k8s ConfigMap representing Varnish sources (required)")
versionKeyF = flag.String("versionkey", "",
"key in the ConfigMap for a VCL version string")
vclrootF = flag.String("vclroot", "/etc/varnish",
"root directory for VCL files\n"+
"(mount volume for the ConfigMap)")
logFormat = logrus.TextFormatter{
DisableColors: true,
......@@ -240,6 +243,7 @@ func main() {
CfgMapNamespace: cfgMapNamespace,
CfgMapName: cfgMapName,
VersionKey: *versionKeyF,
VCLRoot: *vclrootF,
}
vController, err := varnish.NewController(log, *homeF, tmplMap, *mainF,
......
......@@ -44,10 +44,13 @@ func (worker *NamespaceWorker) updateCfgMap(key string) error {
}
func (worker *NamespaceWorker) deleteCfgMap(obj interface{}) error {
worker.updater.ClearVCL()
cfgMap, ok := obj.(*api_v1.ConfigMap)
if !ok || cfgMap == nil {
worker.log.Warnf("Delete ConfigMap: not found: %v", obj)
return nil
}
return worker.syncCfgMap(cfgMap.Name)
worker.log.Infof("Delete ConfigMap %s/%s: no sync action taken",
cfgMap.Namespace, cfgMap.Name)
return nil
}
......@@ -30,6 +30,8 @@ package apiclient
import (
"fmt"
"os"
"path/filepath"
"time"
"code.uplex.de/uplex-varnish/k8s-vcl-reloader/pkg/varnish"
......@@ -50,6 +52,7 @@ type SpecUpdaterConfig struct {
CfgMapName string
CfgMapNamespace string
VersionKey string
VCLRoot string
}
// SpecUpdater encapsulates updating the VCL spec from the current
......@@ -57,6 +60,7 @@ type SpecUpdaterConfig struct {
type SpecUpdater struct {
log *logrus.Logger
cfg SpecUpdaterConfig
vcls map[string]time.Time
vc *varnish.Controller
svc core_v1_listers.ServiceLister
endp core_v1_listers.EndpointsLister
......@@ -185,26 +189,179 @@ func (updater *SpecUpdater) getSpec() (vcl.Spec, error) {
return spec, nil
}
func getKeySet(cfgMap *api_v1.ConfigMap) map[string]struct{} {
keys := make(map[string]struct{})
if cfgMap.Data != nil {
for k := range cfgMap.Data {
keys[k] = struct{}{}
}
}
if cfgMap.BinaryData != nil {
for k := range cfgMap.BinaryData {
keys[k] = struct{}{}
}
}
return keys
}
// VCLChanged returns true if the VCL files denoted by the keys in the
// ConfigMap have changed since its last update.
//
// Return true if the ConfigMap has never been synced. Return false if
// the ConfigMap has no keys (then there are no VCL files to reload).
//
// If a key has been added to the ConfigMap since the last update,
// return true if the corresponding file exists, false if the file is
// not found. If a key has been deleted, return true if the
// corresponding file has also been deleted, false if the file still
// exists.
//
// Otherwise, return true if and only if the file modification times
// have changed since the last update.
//
// Return any error related to accessing files, except for file not
// found errors that are expected after key addition or deletion.
func (updater *SpecUpdater) VCLChanged(cfgMap *api_v1.ConfigMap) (bool, error) {
if cfgMap.Data == nil && cfgMap.BinaryData == nil {
updater.log.Warnf("ConfigMap %s/%s has no data",
cfgMap.Namespace, cfgMap.Name)
return false, nil
}
files := getKeySet(cfgMap)
if len(files) == 0 {
updater.log.Warnf("ConfigMap %s/%s has no data",
cfgMap.Namespace, cfgMap.Name)
return false, nil
}
if updater.vcls == nil || len(updater.vcls) == 0 {
updater.log.Trace("Never synced")
return true, nil
}
for k := range files {
if _, ok := updater.vcls[k]; !ok {
updater.log.Trace("New key: ", k)
path := filepath.Join(updater.cfg.VCLRoot, k)
_, err := os.Stat(path)
if err == nil {
updater.log.Trace("File added: ", k)
return true, nil
}
if os.IsNotExist(err) {
updater.log.Trace("File not added: ", k)
return false, nil
}
return false, err
}
}
for k := range updater.vcls {
if _, ok := files[k]; !ok {
updater.log.Trace("Key deleted: ", k)
path := filepath.Join(updater.cfg.VCLRoot, k)
_, err := os.Stat(path)
if err == nil {
updater.log.Trace("File not deleted: ", k)
return false, nil
}
if os.IsNotExist(err) {
updater.log.Trace("File deleted: ", k)
return true, nil
}
return false, err
}
}
for file := range files {
path := filepath.Join(updater.cfg.VCLRoot, file)
info, err := os.Stat(path)
if err != nil {
return false, err
}
if updater.vcls[file] != info.ModTime() {
updater.log.Trace(file, " new mtime ", info.ModTime())
return true, nil
}
}
return false, nil
}
// ClearVCL resets the record of VCL files and their modification
// times.
func (updater *SpecUpdater) ClearVCL() {
updater.vcls = nil
}
func (updater *SpecUpdater) updatedVCLMap(
cfgMap *api_v1.ConfigMap) (vclMap map[string]time.Time, err error) {
if cfgMap.Data == nil && cfgMap.BinaryData == nil {
updater.log.Warnf("ConfigMap %s/%s has no data",
cfgMap.Namespace, cfgMap.Name)
return
}
files := getKeySet(cfgMap)
vclMap = make(map[string]time.Time, len(files))
for file := range files {
path := filepath.Join(updater.cfg.VCLRoot, file)
info, err := os.Stat(path)
if err != nil {
return nil, err
}
vclMap[file] = info.ModTime()
}
return
}
// Update generates a new configuration from the current state of the
// k8s cluster, and loads it as a new instance of VCL.
func (updater *SpecUpdater) Update() error {
updater.log.Info("Update signal received")
updater.log.Info("Current VCL file modification times: ", updater.vcls)
spec, err := updater.getSpec()
if err != nil {
// XXX generate Warn event
updater.log.Errorf("Cannot create spec: %s", err)
return err
}
cfgMap, err := updater.cfgMap.ConfigMaps(spec.CfgMap.Namespace).
Get(spec.CfgMap.Name)
if err != nil {
updater.log.Errorf("Cannot retrieve ConfigMap %s/%s: %v",
spec.CfgMap.Namespace, spec.CfgMap.Name, err)
return err
}
if !updater.vc.HasConfigMap(spec.CfgMap) {
if changed, err := updater.VCLChanged(cfgMap); err != nil {
return err
} else if !changed {
updater.log.Warnf("ConfigMap %s/%s has changed but "+
"VCL files have not been updated, not "+
"updating Varnish config",
spec.CfgMap.Namespace, spec.CfgMap.Name)
return nil
}
}
if updater.vc.HasConfig(spec) {
updater.log.Infof("Varnish VCL config is up to date")
return nil
}
updatedMap, err := updater.updatedVCLMap(cfgMap)
if err != nil {
updater.log.Errorf("Cannot update VCL files and modification "+
"times: %v", err)
return err
}
if err = updater.vc.Update(&spec); err != nil {
// XXX generate Warn event, inc counter
updater.log.Errorf("Update failed: %s", err)
return err
}
// XXX generate Info event, inc counter(?)
updater.vcls = updatedMap
updater.log.Info("Updated VCL file modification times: ", updater.vcls)
updater.log.Info("Varnish update succeeded")
return nil
}
/*
* Copyright (c) 2020 UPLEX Nils Goroll Systemoptimierung
* All rights reserved
*
* Author: Geoffrey Simmons <geoffrey.simmons@uplex.de>
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
package apiclient
import (
"io/ioutil"
"os"
"path/filepath"
"strings"
"testing"
"time"
"github.com/sirupsen/logrus"
"github.com/sirupsen/logrus/hooks/test"
api_v1 "k8s.io/api/core/v1"
meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
func TestVCLChanged(t *testing.T) {
logger, hook := test.NewNullLogger()
updater := &SpecUpdater{
log: logger,
}
updater.log.Level = logrus.TraceLevel
cfgMap := &api_v1.ConfigMap{
ObjectMeta: meta_v1.ObjectMeta{
Namespace: "default",
Name: "vcl-cfgmap",
},
}
changed, err := updater.VCLChanged(cfgMap)
if err != nil {
t.Fatalf("VCLChanged(%v): %v", cfgMap, err)
}
if changed {
t.Errorf("VCLChanged(%v) got: true, want: false", cfgMap)
}
if hook.LastEntry().Level != logrus.WarnLevel {
t.Errorf("VCLChanged(%v) log level got: %v, want: WarnLevel",
cfgMap, hook.LastEntry().Level)
}
msg := "ConfigMap default/vcl-cfgmap has no data"
if hook.LastEntry().Message != msg {
t.Errorf("VCLChanged(%v) log message got: %s, want: %s",
cfgMap, hook.LastEntry().Message, msg)
}
vclMap, err := updater.updatedVCLMap(cfgMap)
if err != nil {
t.Fatalf("updatedVCLMap(%v): %v", cfgMap, err)
}
if vclMap != nil {
t.Fatalf("updatedVCLMap(%v): got %v, want: nil", cfgMap, vclMap)
}
tmpdir, err := ioutil.TempDir("", "k8s-vcl-reloader")
if err != nil {
t.Fatalf("Cannot create tmp directory: %v", err)
}
defer os.RemoveAll(tmpdir)
tmpdir = filepath.Join(tmpdir, "apiclient_spec_test")
if err = os.Mkdir(tmpdir, 0755); err != nil {
t.Fatalf("Cannot make tmp directory %s: %s", tmpdir, err)
}
updater.cfg.VCLRoot = tmpdir
cfgMap.Data = make(map[string]string)
cfgMap.BinaryData = make(map[string][]byte)
cfgMap.Data["foo"] = ""
cfgMap.Data["bar"] = ""
cfgMap.BinaryData["baz"] = []byte("")
cfgMap.BinaryData["quux"] = []byte("")
changed, err = updater.VCLChanged(cfgMap)
if err != nil {
t.Fatalf("VCLChanged(%v): %v", cfgMap, err)
}
if !changed {
t.Errorf("VCLChanged(%v) got: false, want: true", cfgMap)
}
for _, file := range []string{"foo", "bar", "baz", "quux"} {
path := filepath.Join(tmpdir, file)
if err = ioutil.WriteFile(path, []byte{}, 0644); err != nil {
t.Fatalf("Cannot write %s: %v", path, err)
}
defer os.Remove(path)
}
changed, err = updater.VCLChanged(cfgMap)
if err != nil {
t.Fatalf("VCLChanged(%v): %v", cfgMap, err)
}
if !changed {
t.Errorf("VCLChanged(%v) got: false, want: true", cfgMap)
}
vclMap, err = updater.updatedVCLMap(cfgMap)
if err != nil {
t.Fatalf("updatedVCLMap(%v): %v", cfgMap, err)
}
if vclMap == nil {
t.Errorf("updatedVCLMap(%v): got nil, want: non-nil", cfgMap)
}
if len(vclMap) != 4 {
t.Errorf("updatedVCLMap(%v): got len(map) = %d, want: 4",
cfgMap, len(vclMap))
}
updater.vcls = vclMap
changed, err = updater.VCLChanged(cfgMap)
if err != nil {
t.Fatalf("VCLChanged(%v): %v", cfgMap, err)
}
if changed {
t.Errorf("VCLChanged(%v) got: true, want: false", cfgMap)
}
path := filepath.Join(tmpdir, "foo")
if err = os.Chtimes(path, time.Now(), time.Now()); err != nil {
t.Fatalf("os.Chtimes(%s): %v", path, err)
}
changed, err = updater.VCLChanged(cfgMap)
if err != nil {
t.Fatalf("VCLChanged(%v): %v", cfgMap, err)
}
if !changed {
t.Errorf("VCLChanged(%v) got: false, want: true", cfgMap)
}
if hook.LastEntry().Level != logrus.TraceLevel {
t.Errorf("VCLChanged(%v) log level got: %v, want: TraceLevel",
cfgMap, hook.LastEntry().Level)
}
if !strings.Contains(hook.LastEntry().Message, "foo new mtime") {
t.Errorf("VCLChanged(%v) log message got: %s, should contain: "+
"foo new mtime", cfgMap, hook.LastEntry().Message)
}
vclMap, err = updater.updatedVCLMap(cfgMap)
if err != nil {
t.Fatalf("updatedVCLMap(%v): %v", cfgMap, err)
}
if vclMap == nil {
t.Errorf("updatedVCLMap(%v): got nil, want: non-nil", cfgMap)
}
if len(vclMap) != 4 {
t.Errorf("updatedVCLMap(%v): got len(map) = %d, want: 4",
cfgMap, len(vclMap))
}
updater.vcls = vclMap
changed, err = updater.VCLChanged(cfgMap)
if err != nil {
t.Fatalf("VCLChanged(%v): %v", cfgMap, err)
}
if changed {
t.Errorf("VCLChanged(%v) got: true, want: false", cfgMap)
}
if hook.LastEntry().Level != logrus.TraceLevel {
t.Errorf("VCLChanged(%v) log level got: %v, want: TraceLevel",
cfgMap, hook.LastEntry().Level)
}
if !strings.Contains(hook.LastEntry().Message, "foo new mtime") {
t.Errorf("VCLChanged(%v) log message got: %s, should contain: "+
"foo new mtime", cfgMap, hook.LastEntry().Message)
}
cfgMap.Data["4711"] = ""
changed, err = updater.VCLChanged(cfgMap)
if err != nil {
t.Fatalf("VCLChanged(%v): %v", cfgMap, err)
}
if changed {
t.Errorf("VCLChanged(%v) got: true, want: false", cfgMap)
}
if hook.LastEntry().Level != logrus.TraceLevel {
t.Errorf("VCLChanged(%v) log level got: %v, want: TraceLevel",
cfgMap, hook.LastEntry().Level)
}
if !strings.HasPrefix(hook.LastEntry().Message, "File not added: ") {
t.Errorf("VCLChanged(%v) log message got: %s, should contain: "+
"File not added: ", cfgMap, hook.LastEntry().Message)
}
path = filepath.Join(tmpdir, "4711")
if err = ioutil.WriteFile(path, []byte{}, 0644); err != nil {
t.Fatalf("Cannot write %s: %v", path, err)
}
defer os.Remove(path)
changed, err = updater.VCLChanged(cfgMap)
if err != nil {
t.Fatalf("VCLChanged(%v): %v", cfgMap, err)
}
if !changed {
t.Errorf("VCLChanged(%v) got: false, want: true", cfgMap)
}
if hook.LastEntry().Level != logrus.TraceLevel {
t.Errorf("VCLChanged(%v) log level got: %v, want: TraceLevel",
cfgMap, hook.LastEntry().Level)
}
if !strings.HasPrefix(hook.LastEntry().Message, "File added: ") {
t.Errorf("VCLChanged(%v) log message got: %s, should contain: "+
"File added: ", cfgMap, hook.LastEntry().Message)
}
vclMap, err = updater.updatedVCLMap(cfgMap)
if err != nil {
t.Fatalf("updatedVCLMap(%v): %v", cfgMap, err)
}
if vclMap == nil {
t.Errorf("updatedVCLMap(%v): got nil, want: non-nil", cfgMap)
}
if len(vclMap) != 5 {
t.Errorf("updatedVCLMap(%v): got len(map) = %d, want: 5",
cfgMap, len(vclMap))
}
updater.vcls = vclMap
changed, err = updater.VCLChanged(cfgMap)
if err != nil {
t.Fatalf("VCLChanged(%v): %v", cfgMap, err)
}
if changed {
t.Errorf("VCLChanged(%v) got: true, want: false", cfgMap)
}
delete(cfgMap.Data, "4711")
changed, err = updater.VCLChanged(cfgMap)
if err != nil {
t.Fatalf("VCLChanged(%v): %v", cfgMap, err)
}
if changed {
t.Errorf("VCLChanged(%v) got: true, want: false", cfgMap)
}
if hook.LastEntry().Level != logrus.TraceLevel {
t.Errorf("VCLChanged(%v) log level got: %v, want: TraceLevel",
cfgMap, hook.LastEntry().Level)
}
msg = "File not deleted: 4711"
if hook.LastEntry().Message != msg {
t.Errorf("VCLChanged(%v) log message got: %s, want: %s",
cfgMap, hook.LastEntry().Message, msg)
}
if err = os.Remove(path); err != nil {
t.Fatalf("os.Remove(%s): %v", path, err)
}
changed, err = updater.VCLChanged(cfgMap)
if err != nil {
t.Fatalf("VCLChanged(%v): %v", cfgMap, err)
}
if !changed {
t.Errorf("VCLChanged(%v) got: false, want: true", cfgMap)
}
if hook.LastEntry().Level != logrus.TraceLevel {
t.Errorf("VCLChanged(%v) log level got: %v, want: TraceLevel",
cfgMap, hook.LastEntry().Level)
}
if hook.LastEntry().Message != "File deleted: 4711" {
t.Errorf("VCLChanged(%v) log message got: %s, want: "+
"File deleted: 4711", cfgMap, hook.LastEntry().Message)
}
vclMap, err = updater.updatedVCLMap(cfgMap)
if err != nil {
t.Fatalf("updatedVCLMap(%v): %v", cfgMap, err)
}
if vclMap == nil {
t.Errorf("updatedVCLMap(%v): got nil, want: non-nil", cfgMap)
}
if len(vclMap) != 4 {
t.Errorf("updatedVCLMap(%v): got len(map) = %d, want: 4",
cfgMap, len(vclMap))
}
updater.vcls = vclMap
changed, err = updater.VCLChanged(cfgMap)
if err != nil {
t.Fatalf("VCLChanged(%v): %v", cfgMap, err)
}
if changed {
t.Errorf("VCLChanged(%v) got: true, want: false", cfgMap)
}
}
......@@ -38,6 +38,7 @@ import (
"fmt"
"io/ioutil"
"os"
"reflect"
"sync"
"text/template"
"time"
......@@ -268,6 +269,21 @@ func (vc *Controller) HasConfig(spec vcl.Spec) bool {
return bytes.Equal(vc.spec.DeepHash(), spec.DeepHash())
}
// HasConfigMap returns true if the configuration corresponding to
// given ConfigMap has been loaded.
//
// Returns false if no VCL config is loaded. Otherwise, returns true
// if cfgMap and the loaded vcl.ConfigMap object are deeply equal.
func (vc *Controller) HasConfigMap(cfgMap vcl.ConfigMap) bool {
if vc.spec == nil {
return false
}
if !vc.cfgLoaded {
return false
}
return reflect.DeepEqual(vc.spec.CfgMap, cfgMap)
}
// Quit stops the Varnish controller.
func (vc *Controller) Quit() {
vc.log.Info("Wait for admin interactions with Varnish instances to " +
......
/*
* Copyright (c) 2020 UPLEX Nils Goroll Systemoptimierung
* All rights reserved
*
* Author: Geoffrey Simmons <geoffrey.simmons@uplex.de>
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
package varnish
import (
"testing"
"code.uplex.de/uplex-varnish/k8s-vcl-reloader/pkg/varnish/vcl"
)
func TestHasConfigMap(t *testing.T) {
vc := &Controller{}
cfgMap := vcl.ConfigMap{
Name: "vcl-cfgmap",
Version: "47.11",
Meta: vcl.Meta{
Namespace: "default",
UID: "d4b65c3f-c8d6-40eb-92cb-a3184c81dca2",
ResourceVersion: "12345",
},
}
if vc.HasConfigMap(cfgMap) {
t.Errorf("HasConfigMap(%v) for nil spec, got:true, want:false",
cfgMap)
}
vc.spec = &vcl.Spec{}
vc.cfgLoaded = false
if vc.HasConfigMap(cfgMap) {
t.Errorf("HasConfigMap(%v) with cfgLoaded=false, "+
"got:true, want:false", cfgMap)
}
vc.cfgLoaded = true
vc.spec.CfgMap = vcl.ConfigMap{
Name: cfgMap.Name,
Version: cfgMap.Version,
Meta: vcl.Meta{
Namespace: cfgMap.Namespace,
UID: cfgMap.UID,
ResourceVersion: cfgMap.ResourceVersion,
},
}
if !vc.HasConfigMap(cfgMap) {
t.Errorf("HasConfigMap(%v) with spec=%v, got:false, want:true",
cfgMap, vc.spec)
}
vc.spec.CfgMap.Name = "foo"
if vc.HasConfigMap(cfgMap) {
t.Errorf("HasConfigMap(%v) with spec=%v, got:true, want:false",
cfgMap, vc.spec)
}
vc.spec.CfgMap.Name = cfgMap.Name
vc.spec.CfgMap.Version = "foo"
if vc.HasConfigMap(cfgMap) {
t.Errorf("HasConfigMap(%v) with spec=%v, got:true, want:false",
cfgMap, vc.spec)
}
vc.spec.CfgMap.Version = cfgMap.Version
vc.spec.CfgMap.Namespace = "foo"
if vc.HasConfigMap(cfgMap) {
t.Errorf("HasConfigMap(%v) with spec=%v, got:true, want:false",
cfgMap, vc.spec)
}
vc.spec.CfgMap.Namespace = cfgMap.Namespace
vc.spec.CfgMap.UID = "foo"
if vc.HasConfigMap(cfgMap) {
t.Errorf("HasConfigMap(%v) with spec=%v, got:true, want:false",
cfgMap, vc.spec)
}
vc.spec.CfgMap.UID = cfgMap.UID
vc.spec.CfgMap.ResourceVersion = "foo"
if vc.HasConfigMap(cfgMap) {
t.Errorf("HasConfigMap(%v) with spec=%v, got:true, want:false",
cfgMap, vc.spec)
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment