1
0
Fork 0
mirror of https://github.com/kubernetes-sigs/node-feature-discovery.git synced 2024-12-14 11:57:51 +00:00

nfd-master: implement --instance flag

This can be used to help running multiple parallel NFD deployments in
the same cluster. The flag changes the node annotation namespace to
<instance>.nfd.node.kubernetes.io allowing different nfd-master intances
to store metadata in separate annotations.
This commit is contained in:
Markus Lehtonen 2021-02-03 19:49:02 +02:00
parent 705687192d
commit e52ec3480f
6 changed files with 119 additions and 62 deletions

View file

@ -66,7 +66,7 @@ func argsParse(argv []string) (master.Args, error) {
%s [--prune] [--no-publish] [--label-whitelist=<pattern>] [--port=<port>]
[--ca-file=<path>] [--cert-file=<path>] [--key-file=<path>]
[--verify-node-name] [--extra-label-ns=<list>] [--resource-labels=<list>]
[--kubeconfig=<path>]
[--kubeconfig=<path>] [--instance=<name>]
%s -h | --help
%s --version
@ -75,6 +75,9 @@ func argsParse(argv []string) (master.Args, error) {
--version Output version and exit.
--prune Prune all NFD related attributes from all nodes
of the cluster and exit.
--instance=<name> Instance name. Used to separate annotation
namespaces for multiple parallel deployments.
[Default: ]
--kubeconfig=<path> Kubeconfig to use [Default: ]
of the cluster and exit.
--port=<port> Port on which to listen for connections.
@ -109,6 +112,12 @@ func argsParse(argv []string) (master.Args, error) {
// Parse argument values as usable types.
var err error
args.Instance = arguments["--instance"].(string)
if ok, _ := regexp.MatchString(`^([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9]$`, args.Instance); args.Instance != "" && !ok {
return args, fmt.Errorf("invalid --instance %q: instance name "+
"must start and end with an alphanumeric character and may only contain "+
"alphanumerics, `-`, `_` or `.`", args.Instance)
}
args.CaFile = arguments["--ca-file"].(string)
args.CertFile = arguments["--cert-file"].(string)
args.KeyFile = arguments["--key-file"].(string)

View file

@ -48,6 +48,22 @@ Example:
nfd-master --port=443
```
### --instance
The `--instance` flag makes it possible to run multiple NFD deployments in
parallel. In practice, it separates the node annotations between deployments so
that each of them can store metadata independently. The instance name must
start and end with an alphanumeric character and may only contain alphanumeric
characters, `-`, `_` or `.`.
Default: *empty*
Example:
```bash
nfd-master --instance=network
```
### --ca-file
The `--ca-file` is one of the three flags (together with `--cert-file` and

View file

@ -81,11 +81,15 @@ An overview of the default feature labels:
NFD also annotates nodes it is running on:
| Annotation | Description
| ----------------------------------------- | -----------
| nfd.node.kubernetes.io/master.version | Version of the nfd-master instance running on the node. Informative use only.
| nfd.node.kubernetes.io/worker.version | Version of the nfd-worker instance running on the node. Informative use only.
| nfd.node.kubernetes.io/feature-labels | Comma-separated list of node labels managed by NFD. NFD uses this internally so must not be edited by users.
| nfd.node.kubernetes.io/extended-resources | Comma-separated list of node extended resources managed by NFD. NFD uses this internally so must not be edited by users.
| ------------------------------------------------------------ | -----------
| [&lt;instance&gt;.]nfd.node.kubernetes.io/master.version | Version of the nfd-master instance running on the node. Informative use only.
| [&lt;instance&gt;.]nfd.node.kubernetes.io/worker.version | Version of the nfd-worker instance running on the node. Informative use only.
| [&lt;instance&gt;.]nfd.node.kubernetes.io/feature-labels | Comma-separated list of node labels managed by NFD. NFD uses this internally so must not be edited by users.
| [&lt;instance&gt;.]nfd.node.kubernetes.io/extended-resources | Comma-separated list of node extended resources managed by NFD. NFD uses this internally so must not be edited by users.
NOTE: the [`--instance`](../advanced/master-commandline-reference.md#instance)
command line flag affects the annotation names
Unapplicable annotations are not created, i.e. for example master.version is only created on nodes running nfd-master.

View file

@ -17,6 +17,7 @@ limitations under the License.
package nfdmaster
import (
"path"
"regexp"
"sort"
"strings"
@ -53,6 +54,7 @@ func newMockNode() *api.Node {
func newMockMaster(apihelper apihelper.APIHelpers) *nfdMaster {
return &nfdMaster{
nodeName: mockNodeName,
annotationNs: AnnotationNsBase,
args: Args{LabelWhiteList: regexp.MustCompile("")},
apihelper: apihelper,
}
@ -82,13 +84,13 @@ func TestUpdateNodeFeatures(t *testing.T) {
// Mock node with old features
mockNode := newMockNode()
mockNode.Labels[LabelNs+"/old-feature"] = "old-value"
mockNode.Annotations[AnnotationNs+"/feature-labels"] = "old-feature"
mockNode.Annotations[AnnotationNsBase+"/feature-labels"] = "old-feature"
Convey("When I successfully update the node with feature labels", func() {
// Create a list of expected node metadata patches
metadataPatches := []apihelper.JsonPatch{
apihelper.NewJsonPatch("replace", "/metadata/annotations", AnnotationNs+"/feature-labels", strings.Join(fakeFeatureLabelNames, ",")),
apihelper.NewJsonPatch("add", "/metadata/annotations", AnnotationNs+"/extended-resources", strings.Join(fakeExtResourceNames, ",")),
apihelper.NewJsonPatch("replace", "/metadata/annotations", AnnotationNsBase+"/feature-labels", strings.Join(fakeFeatureLabelNames, ",")),
apihelper.NewJsonPatch("add", "/metadata/annotations", AnnotationNsBase+"/extended-resources", strings.Join(fakeExtResourceNames, ",")),
apihelper.NewJsonPatch("remove", "/metadata/labels", LabelNs+"/old-feature", ""),
}
for k, v := range fakeFeatureLabels {
@ -169,7 +171,7 @@ func TestUpdateMasterNode(t *testing.T) {
mockNode := newMockNode()
Convey("When update operation succeeds", func() {
expectedPatches := []apihelper.JsonPatch{
apihelper.NewJsonPatch("add", "/metadata/annotations", AnnotationNs+"/master.version", version.Get())}
apihelper.NewJsonPatch("add", "/metadata/annotations", AnnotationNsBase+"/master.version", version.Get())}
mockHelper.On("GetClient").Return(mockClient, nil)
mockHelper.On("GetNode", mockClient, mockNodeName).Return(mockNode, nil)
mockHelper.On("PatchNode", mockClient, mockNodeName, mock.MatchedBy(jsonPatchMatcher(expectedPatches))).Return(nil)
@ -211,10 +213,11 @@ func TestUpdateMasterNode(t *testing.T) {
func TestAddingExtResources(t *testing.T) {
Convey("When adding extended resources", t, func() {
mockMaster := newMockMaster(nil)
Convey("When there are no matching labels", func() {
mockNode := newMockNode()
mockResourceLabels := ExtendedResources{}
patches := createExtendedResourcePatches(mockNode, mockResourceLabels)
patches := mockMaster.createExtendedResourcePatches(mockNode, mockResourceLabels)
So(len(patches), ShouldEqual, 0)
})
@ -225,7 +228,7 @@ func TestAddingExtResources(t *testing.T) {
apihelper.NewJsonPatch("add", "/status/capacity", "feature-1", "1"),
apihelper.NewJsonPatch("add", "/status/capacity", "feature-2", "2"),
}
patches := createExtendedResourcePatches(mockNode, mockResourceLabels)
patches := mockMaster.createExtendedResourcePatches(mockNode, mockResourceLabels)
So(sortJsonPatches(patches), ShouldResemble, sortJsonPatches(expectedPatches))
})
@ -233,7 +236,7 @@ func TestAddingExtResources(t *testing.T) {
mockNode := newMockNode()
mockNode.Status.Capacity[api.ResourceName(LabelNs+"/feature-1")] = *resource.NewQuantity(1, resource.BinarySI)
mockResourceLabels := ExtendedResources{LabelNs + "/feature-1": "1"}
patches := createExtendedResourcePatches(mockNode, mockResourceLabels)
patches := mockMaster.createExtendedResourcePatches(mockNode, mockResourceLabels)
So(len(patches), ShouldEqual, 0)
})
@ -245,7 +248,7 @@ func TestAddingExtResources(t *testing.T) {
apihelper.NewJsonPatch("replace", "/status/capacity", "feature-1", "1"),
apihelper.NewJsonPatch("replace", "/status/allocatable", "feature-1", "1"),
}
patches := createExtendedResourcePatches(mockNode, mockResourceLabels)
patches := mockMaster.createExtendedResourcePatches(mockNode, mockResourceLabels)
So(sortJsonPatches(patches), ShouldResemble, sortJsonPatches(expectedPatches))
})
})
@ -253,22 +256,23 @@ func TestAddingExtResources(t *testing.T) {
func TestRemovingExtResources(t *testing.T) {
Convey("When removing extended resources", t, func() {
mockMaster := newMockMaster(nil)
Convey("When none are removed", func() {
mockNode := newMockNode()
mockResourceLabels := ExtendedResources{LabelNs + "/feature-1": "1", LabelNs + "/feature-2": "2"}
mockNode.Annotations[AnnotationNs+"/extended-resources"] = "feature-1,feature-2"
mockNode.Annotations[AnnotationNsBase+"/extended-resources"] = "feature-1,feature-2"
mockNode.Status.Capacity[api.ResourceName(LabelNs+"/feature-1")] = *resource.NewQuantity(1, resource.BinarySI)
mockNode.Status.Capacity[api.ResourceName(LabelNs+"/feature-2")] = *resource.NewQuantity(2, resource.BinarySI)
patches := createExtendedResourcePatches(mockNode, mockResourceLabels)
patches := mockMaster.createExtendedResourcePatches(mockNode, mockResourceLabels)
So(len(patches), ShouldEqual, 0)
})
Convey("When the related label is gone", func() {
mockNode := newMockNode()
mockResourceLabels := ExtendedResources{LabelNs + "/feature-4": "", LabelNs + "/feature-2": "2"}
mockNode.Annotations[AnnotationNs+"/extended-resources"] = "feature-4,feature-2"
mockNode.Annotations[AnnotationNsBase+"/extended-resources"] = "feature-4,feature-2"
mockNode.Status.Capacity[api.ResourceName(LabelNs+"/feature-4")] = *resource.NewQuantity(4, resource.BinarySI)
mockNode.Status.Capacity[api.ResourceName(LabelNs+"/feature-2")] = *resource.NewQuantity(2, resource.BinarySI)
patches := createExtendedResourcePatches(mockNode, mockResourceLabels)
patches := mockMaster.createExtendedResourcePatches(mockNode, mockResourceLabels)
So(len(patches), ShouldBeGreaterThan, 0)
})
Convey("When the extended resource is no longer wanted", func() {
@ -276,8 +280,8 @@ func TestRemovingExtResources(t *testing.T) {
mockNode.Status.Capacity[api.ResourceName(LabelNs+"/feature-1")] = *resource.NewQuantity(1, resource.BinarySI)
mockNode.Status.Capacity[api.ResourceName(LabelNs+"/feature-2")] = *resource.NewQuantity(2, resource.BinarySI)
mockResourceLabels := ExtendedResources{LabelNs + "/feature-2": "2"}
mockNode.Annotations[AnnotationNs+"/extended-resources"] = "feature-1,feature-2"
patches := createExtendedResourcePatches(mockNode, mockResourceLabels)
mockNode.Annotations[AnnotationNsBase+"/extended-resources"] = "feature-1,feature-2"
patches := mockMaster.createExtendedResourcePatches(mockNode, mockResourceLabels)
So(len(patches), ShouldBeGreaterThan, 0)
})
})
@ -304,11 +308,14 @@ func TestSetLabels(t *testing.T) {
expectedStatusPatches := []apihelper.JsonPatch{}
wvAnnotation := path.Join(AnnotationNsBase, workerVersionAnnotation)
flAnnotation := path.Join(AnnotationNsBase, featureLabelAnnotation)
erAnnotation := path.Join(AnnotationNsBase, extendedResourceAnnotation)
Convey("When node update succeeds", func() {
expectedPatches := []apihelper.JsonPatch{
apihelper.NewJsonPatch("add", "/metadata/annotations", workerVersionAnnotation, workerVer),
apihelper.NewJsonPatch("add", "/metadata/annotations", featureLabelAnnotation, strings.Join(mockLabelNames, ",")),
apihelper.NewJsonPatch("add", "/metadata/annotations", extendedResourceAnnotation, ""),
apihelper.NewJsonPatch("add", "/metadata/annotations", wvAnnotation, workerVer),
apihelper.NewJsonPatch("add", "/metadata/annotations", flAnnotation, strings.Join(mockLabelNames, ",")),
apihelper.NewJsonPatch("add", "/metadata/annotations", erAnnotation, ""),
}
for k, v := range mockLabels {
expectedPatches = append(expectedPatches, apihelper.NewJsonPatch("add", "/metadata/labels", LabelNs+"/"+k, v))
@ -326,9 +333,9 @@ func TestSetLabels(t *testing.T) {
Convey("When --label-whitelist is specified", func() {
expectedPatches := []apihelper.JsonPatch{
apihelper.NewJsonPatch("add", "/metadata/annotations", workerVersionAnnotation, workerVer),
apihelper.NewJsonPatch("add", "/metadata/annotations", featureLabelAnnotation, "feature-2"),
apihelper.NewJsonPatch("add", "/metadata/annotations", extendedResourceAnnotation, ""),
apihelper.NewJsonPatch("add", "/metadata/annotations", wvAnnotation, workerVer),
apihelper.NewJsonPatch("add", "/metadata/annotations", flAnnotation, "feature-2"),
apihelper.NewJsonPatch("add", "/metadata/annotations", erAnnotation, ""),
apihelper.NewJsonPatch("add", "/metadata/labels", LabelNs+"/feature-2", mockLabels["feature-2"]),
}
@ -343,20 +350,22 @@ func TestSetLabels(t *testing.T) {
})
})
Convey("When --extra-label-ns is specified", func() {
Convey("When --extra-label-ns and --instance are specified", func() {
// In the gRPC request the label names may omit the default ns
instance := "foo"
mockLabels := map[string]string{"feature-1": "val-1",
"valid.ns/feature-2": "val-2",
"invalid.ns/feature-3": "val-3"}
expectedPatches := []apihelper.JsonPatch{
apihelper.NewJsonPatch("add", "/metadata/annotations", workerVersionAnnotation, workerVer),
apihelper.NewJsonPatch("add", "/metadata/annotations", featureLabelAnnotation, "feature-1,valid.ns/feature-2"),
apihelper.NewJsonPatch("add", "/metadata/annotations", extendedResourceAnnotation, ""),
apihelper.NewJsonPatch("add", "/metadata/annotations", instance+"."+wvAnnotation, workerVer),
apihelper.NewJsonPatch("add", "/metadata/annotations", instance+"."+flAnnotation, "feature-1,valid.ns/feature-2"),
apihelper.NewJsonPatch("add", "/metadata/annotations", instance+"."+erAnnotation, ""),
apihelper.NewJsonPatch("add", "/metadata/labels", LabelNs+"/feature-1", mockLabels["feature-1"]),
apihelper.NewJsonPatch("add", "/metadata/labels", "valid.ns/feature-2", mockLabels["valid.ns/feature-2"]),
}
mockMaster.args.ExtraLabelNs = map[string]struct{}{"valid.ns": struct{}{}}
mockMaster.annotationNs = instance + "." + AnnotationNsBase
mockHelper.On("GetClient").Return(mockClient, nil)
mockHelper.On("GetNode", mockClient, workerName).Return(mockNode, nil)
mockHelper.On("PatchNode", mockClient, mockNodeName, mock.MatchedBy(jsonPatchMatcher(expectedPatches))).Return(nil)
@ -366,14 +375,14 @@ func TestSetLabels(t *testing.T) {
Convey("Error is nil", func() {
So(err, ShouldBeNil)
})
mockMaster.annotationNs = AnnotationNsBase
})
Convey("When --resource-labels is specified", func() {
expectedPatches := []apihelper.JsonPatch{
apihelper.NewJsonPatch("add", "/metadata/annotations", workerVersionAnnotation, workerVer),
apihelper.NewJsonPatch("add", "/metadata/annotations", featureLabelAnnotation, "feature-2"),
apihelper.NewJsonPatch("add", "/metadata/annotations", extendedResourceAnnotation, "feature-1,feature-3"),
apihelper.NewJsonPatch("add", "/metadata/annotations", wvAnnotation, workerVer),
apihelper.NewJsonPatch("add", "/metadata/annotations", flAnnotation, "feature-2"),
apihelper.NewJsonPatch("add", "/metadata/annotations", erAnnotation, "feature-1,feature-3"),
apihelper.NewJsonPatch("add", "/metadata/labels", LabelNs+"/feature-2", mockLabels["feature-2"]),
}
expectedStatusPatches := []apihelper.JsonPatch{

View file

@ -45,14 +45,14 @@ const (
// Namespace for feature labels
LabelNs = "feature.node.kubernetes.io"
// Namespace for all NFD-related annotations
AnnotationNs = "nfd.node.kubernetes.io"
// Base namespace for all NFD-related annotations
AnnotationNsBase = "nfd.node.kubernetes.io"
// NFD Annotations
extendedResourceAnnotation = AnnotationNs + "/extended-resources"
featureLabelAnnotation = AnnotationNs + "/feature-labels"
masterVersionAnnotation = AnnotationNs + "/master.version"
workerVersionAnnotation = AnnotationNs + "/worker.version"
extendedResourceAnnotation = "extended-resources"
featureLabelAnnotation = "feature-labels"
masterVersionAnnotation = "master.version"
workerVersionAnnotation = "worker.version"
)
// package loggers
@ -75,6 +75,7 @@ type Args struct {
CaFile string
CertFile string
ExtraLabelNs map[string]struct{}
Instance string
KeyFile string
Kubeconfig string
LabelWhiteList *regexp.Regexp
@ -94,6 +95,7 @@ type NfdMaster interface {
type nfdMaster struct {
args Args
nodeName string
annotationNs string
server *grpc.Server
ready chan bool
apihelper apihelper.APIHelpers
@ -103,7 +105,14 @@ type nfdMaster struct {
func NewNfdMaster(args Args) (NfdMaster, error) {
nfd := &nfdMaster{args: args,
nodeName: os.Getenv("NODE_NAME"),
ready: make(chan bool, 1)}
ready: make(chan bool, 1),
}
if args.Instance == "" {
nfd.annotationNs = AnnotationNsBase
} else {
nfd.annotationNs = args.Instance + "." + AnnotationNsBase
}
// Check TLS related args
if args.CertFile != "" || args.KeyFile != "" || args.CaFile != "" {
@ -128,6 +137,9 @@ func NewNfdMaster(args Args) (NfdMaster, error) {
// is called.
func (m *nfdMaster) Run() error {
stdoutLogger.Printf("Node Feature Discovery Master %s", version.Get())
if m.args.Instance != "" {
stdoutLogger.Printf("Master instance: '%s'", m.args.Instance)
}
stdoutLogger.Printf("NodeName: '%s'", m.nodeName)
if m.args.Prune {
@ -229,7 +241,7 @@ func (m *nfdMaster) prune() error {
return err
}
for a := range node.Annotations {
if strings.HasPrefix(a, AnnotationNs) {
if strings.HasPrefix(a, m.annotationNs) {
delete(node.Annotations, a)
}
}
@ -254,7 +266,10 @@ func (m *nfdMaster) updateMasterNode() error {
}
// Advertise NFD version as an annotation
p := createPatches(nil, node.Annotations, Annotations{masterVersionAnnotation: version.Get()}, "/metadata/annotations")
p := createPatches(nil,
node.Annotations,
Annotations{m.annotationName(masterVersionAnnotation): version.Get()},
"/metadata/annotations")
err = m.apihelper.PatchNode(cli, node.Name, p)
if err != nil {
stderrLogger.Printf("failed to patch node annotations: %v", err)
@ -343,7 +358,7 @@ func (m *nfdMaster) SetLabels(c context.Context, r *pb.SetLabelsRequest) (*pb.Se
if !m.args.NoPublish {
// Advertise NFD worker version as an annotation
annotations := Annotations{workerVersionAnnotation: r.NfdVersion}
annotations := Annotations{m.annotationName(workerVersionAnnotation): r.NfdVersion}
err := m.updateNodeFeatures(r.NodeName, labels, annotations, extendedResources)
if err != nil {
@ -376,7 +391,7 @@ func (m *nfdMaster) updateNodeFeatures(nodeName string, labels Labels, annotatio
labelKeys = append(labelKeys, strings.TrimPrefix(key, LabelNs+"/"))
}
sort.Strings(labelKeys)
annotations[featureLabelAnnotation] = strings.Join(labelKeys, ",")
annotations[m.annotationName(featureLabelAnnotation)] = strings.Join(labelKeys, ",")
// Store names of extended resources in an annotation
extendedResourceKeys := make([]string, 0, len(extendedResources))
@ -385,10 +400,10 @@ func (m *nfdMaster) updateNodeFeatures(nodeName string, labels Labels, annotatio
extendedResourceKeys = append(extendedResourceKeys, strings.TrimPrefix(key, LabelNs+"/"))
}
sort.Strings(extendedResourceKeys)
annotations[extendedResourceAnnotation] = strings.Join(extendedResourceKeys, ",")
annotations[m.annotationName(extendedResourceAnnotation)] = strings.Join(extendedResourceKeys, ",")
// Create JSON patches for changes in labels and annotations
oldLabels := stringToNsNames(node.Annotations[featureLabelAnnotation], LabelNs)
oldLabels := stringToNsNames(node.Annotations[m.annotationName(featureLabelAnnotation)], LabelNs)
patches := createPatches(oldLabels, node.Labels, labels, "/metadata/labels")
patches = append(patches, createPatches(nil, node.Annotations, annotations, "/metadata/annotations")...)
@ -404,7 +419,7 @@ func (m *nfdMaster) updateNodeFeatures(nodeName string, labels Labels, annotatio
}
// patch node status with extended resource changes
patches = createExtendedResourcePatches(node, extendedResources)
patches = m.createExtendedResourcePatches(node, extendedResources)
err = m.apihelper.PatchNodeStatus(cli, node.Name, patches)
if err != nil {
stderrLogger.Printf("error while patching extended resources: %s", err.Error())
@ -414,6 +429,10 @@ func (m *nfdMaster) updateNodeFeatures(nodeName string, labels Labels, annotatio
return err
}
func (m *nfdMaster) annotationName(name string) string {
return path.Join(m.annotationNs, name)
}
// Remove any labels having the given prefix
func removeLabelsWithPrefix(n *api.Node, search string) []apihelper.JsonPatch {
var p []apihelper.JsonPatch
@ -456,11 +475,11 @@ func createPatches(removeKeys []string, oldItems map[string]string, newItems map
// createExtendedResourcePatches returns a slice of operations to perform on
// the node status
func createExtendedResourcePatches(n *api.Node, extendedResources ExtendedResources) []apihelper.JsonPatch {
func (m *nfdMaster) createExtendedResourcePatches(n *api.Node, extendedResources ExtendedResources) []apihelper.JsonPatch {
patches := []apihelper.JsonPatch{}
// Form a list of namespaced resource names managed by us
oldResources := stringToNsNames(n.Annotations[extendedResourceAnnotation], LabelNs)
oldResources := stringToNsNames(n.Annotations[m.annotationName(extendedResourceAnnotation)], LabelNs)
// figure out which resources to remove
for _, resource := range oldResources {

View file

@ -393,7 +393,7 @@ func cleanupNode(cs clientset.Interface) {
// Remove annotations
for key := range node.Annotations {
if strings.HasPrefix(key, master.AnnotationNs) {
if strings.HasPrefix(key, master.AnnotationNsBase) {
delete(node.Annotations, key)
update = true
}
@ -573,7 +573,7 @@ var _ = framework.KubeDescribe("[NFD] Node Feature Discovery", func() {
gomega.Expect(node.Annotations).To(gomega.HaveKey(k))
}
for k := range node.Annotations {
if strings.HasPrefix(k, master.AnnotationNs) {
if strings.HasPrefix(k, master.AnnotationNsBase) {
if _, ok := nodeConf.ExpectedAnnotationValues[k]; ok {
continue
}
@ -587,7 +587,7 @@ var _ = framework.KubeDescribe("[NFD] Node Feature Discovery", func() {
// Node running nfd-master should have master version annotation
if node.Name == masterPod.Spec.NodeName {
gomega.Expect(node.Annotations).To(gomega.HaveKey(master.AnnotationNs + "master.version"))
gomega.Expect(node.Annotations).To(gomega.HaveKey(master.AnnotationNsBase + "master.version"))
}
}