github.com/mirantis/virtlet@v1.5.2-0.20191204181327-1659b8a48e9b/pkg/config/config_test.go (about)

     1  /*
     2  Copyright 2018 Mirantis
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or ≈git-agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package config
    18  
    19  import (
    20  	"bufio"
    21  	"reflect"
    22  	"regexp"
    23  	"strings"
    24  	"testing"
    25  
    26  	"github.com/Mirantis/virtlet/tests/gm"
    27  	"github.com/ghodss/yaml"
    28  	"github.com/kballard/go-shellquote"
    29  	flag "github.com/spf13/pflag"
    30  	v1 "k8s.io/api/core/v1"
    31  	meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    32  	fakekube "k8s.io/client-go/kubernetes/fake"
    33  
    34  	virtlet_v1 "github.com/Mirantis/virtlet/pkg/api/virtlet.k8s/v1"
    35  	"github.com/Mirantis/virtlet/pkg/client/clientset/versioned/fake"
    36  )
    37  
    38  func TestDefaultVirtletConfig(t *testing.T) {
    39  	gm.Verify(t, gm.NewYamlVerifier(GetDefaultConfig()))
    40  }
    41  
    42  func verifyEnv(t *testing.T, c *virtlet_v1.VirtletConfig) {
    43  	envText := DumpEnv(c)
    44  	gm.Verify(t, envText)
    45  	fakeEnv := map[string]string{}
    46  	scanner := bufio.NewScanner(strings.NewReader(envText))
    47  	lineRx := regexp.MustCompile("^export ([^=]*)=(.*)")
    48  	for scanner.Scan() {
    49  		s := scanner.Text()
    50  		parts := lineRx.FindStringSubmatch(s)
    51  		if parts == nil {
    52  			t.Errorf("couldn't parse env string: %q", s)
    53  		} else {
    54  			subparts, err := shellquote.Split(parts[2])
    55  			if err != nil {
    56  				t.Errorf("couldn't parse env string: %q: %v", s, err)
    57  			}
    58  			if len(subparts) != 1 {
    59  				t.Errorf("couldn't parse env string: %q", s)
    60  			}
    61  			fakeEnv[parts[1]] = subparts[0]
    62  		}
    63  	}
    64  	binder := NewBinder(nil)
    65  	binder.lookupEnv = func(name string) (string, bool) {
    66  		r, found := fakeEnv[name]
    67  		return r, found
    68  	}
    69  	newConf := binder.GetConfig()
    70  	// this is a special case (test-only flag)
    71  	newConf.SkipImageTranslation = c.SkipImageTranslation
    72  	if !reflect.DeepEqual(newConf, c) {
    73  		origConfYaml, err := yaml.Marshal(c)
    74  		if err != nil {
    75  			t.Fatalf("Error marshalling yaml: %v", err)
    76  		}
    77  		newConfYaml, err := yaml.Marshal(newConf)
    78  		if err != nil {
    79  			t.Fatalf("Error marshalling yaml: %v", err)
    80  		}
    81  		t.Errorf("error reloading config from env. Was:\n%s\n--- became: ---\n%s", origConfYaml, newConfYaml)
    82  	}
    83  }
    84  
    85  func TestMergeConfigs(t *testing.T) {
    86  	pstr := func(s string) *string { return &s }
    87  	pbool := func(b bool) *bool { return &b }
    88  	pint := func(i int) *int { return &i }
    89  	for _, tc := range []struct {
    90  		name, args string
    91  		configs    []*virtlet_v1.VirtletConfig
    92  	}{
    93  		{
    94  			name: "defaults",
    95  			args: "",
    96  		},
    97  		{
    98  			name:    "defaults (explicitly set as a config)",
    99  			args:    "",
   100  			configs: []*virtlet_v1.VirtletConfig{GetDefaultConfig()},
   101  		},
   102  		{
   103  			name: "all cli opts",
   104  			args: "--fd-server-socket-path /some/fd/server.sock" +
   105  				" --database-path /some/file.db" +
   106  				" --image-download-protocol http" +
   107  				" --image-dir /some/image/dir" +
   108  				" --image-translation-configs-dir /some/translation/dir" +
   109  				" --libvirt-uri qemu:///foobar" +
   110  				" --raw-devices sd*" +
   111  				" --listen /some/cri.sock" +
   112  				" --disable-logging" +
   113  				" --disable-kvm" +
   114  				" --enable-sriov" +
   115  				" --cni-bin-dir /some/cni/bin/dir" +
   116  				" --cni-conf-dir /some/cni/conf/dir" +
   117  				" --calico-subnet-size 22" +
   118  				" --cpu-model host-model" +
   119  				" --enable-regexp-image-translation=false",
   120  		},
   121  		{
   122  			name: "all cli opts and explicit default config",
   123  			args: "--fd-server-socket-path /some/fd/server.sock" +
   124  				" --database-path /some/file.db" +
   125  				" --image-download-protocol http" +
   126  				" --image-dir /some/image/dir" +
   127  				" --image-translation-configs-dir /some/translation/dir" +
   128  				" --libvirt-uri qemu:///foobar" +
   129  				" --raw-devices sd*" +
   130  				" --listen /some/cri.sock" +
   131  				" --disable-logging" +
   132  				" --disable-kvm" +
   133  				" --enable-sriov" +
   134  				" --cni-bin-dir /some/cni/bin/dir" +
   135  				" --cni-conf-dir /some/cni/conf/dir" +
   136  				" --calico-subnet-size 22" +
   137  				" --cpu-model host-model" +
   138  				" --enable-regexp-image-translation=false",
   139  			configs: []*virtlet_v1.VirtletConfig{GetDefaultConfig()},
   140  		},
   141  		{
   142  			name: "opts and configs",
   143  			args: "--raw-devices sd* --libvirt-uri qemu:///foobar",
   144  			configs: []*virtlet_v1.VirtletConfig{
   145  				{
   146  					DisableKVM: pbool(true),
   147  					RawDevices: pstr("vd*"),
   148  				},
   149  				{
   150  					EnableSriov:      pbool(true),
   151  					CalicoSubnetSize: pint(22),
   152  				},
   153  			},
   154  		},
   155  	} {
   156  		t.Run(tc.name, func(t *testing.T) {
   157  			flags := flag.NewFlagSet("virtlet", flag.ContinueOnError)
   158  			configBinder := NewBinder(flags)
   159  			if err := flags.Parse(strings.Split(tc.args, " ")); err != nil {
   160  				t.Fatalf("error parsing flags: %v", err)
   161  			}
   162  			cfg := MergeConfigs(append(tc.configs, configBinder.GetConfig()))
   163  			gm.Verify(t, gm.NewYamlVerifier(cfg))
   164  			t.Run("env", func(t *testing.T) {
   165  				verifyEnv(t, cfg)
   166  			})
   167  		})
   168  	}
   169  }
   170  
   171  const (
   172  	fullConfig = `
   173    config:
   174      fdServerSocketPath: /some/fd/server.sock
   175      databasePath: /some/file.db
   176      downloadProtocol: http
   177      imageTranslationConfigsDir: /some/translation/dir
   178      libvirtURI: qemu:///foobar
   179      rawDevices: sd*
   180      criSocketPath: /some/cri.sock
   181      disableLogging: true
   182      disableKVM: true
   183      enableSriov: true
   184      cniPluginDir: /some/cni/bin/dir
   185      cniConfigDir: /some/cni/conf/dir
   186      calicoSubnetSize: 22
   187      cpuModel: host-model
   188      enableRegexpImageTranslation: false
   189      logLevel: 3`
   190  	kubeNode1FullMapping = `
   191  spec:
   192    nodeName: kube-node-1
   193    priority: 10` + fullConfig
   194  	labelAFullMapping = `
   195  spec:
   196    nodeSelector:
   197      label-a: "1"` + fullConfig
   198  	globalFullMapping = "spec:" + fullConfig
   199  	anotherMapping1   = `
   200  spec:
   201    nodeName: kube-node-1
   202    priority: 10
   203    config:
   204      enableSriov: false
   205      disableKVM: true`
   206  	anotherMapping2 = `
   207  spec:
   208    nodeSelector:
   209      label-b: "1"
   210    priority: 1
   211    config:
   212      rawDevices: vd*
   213      downloadProtocol: http`
   214  	anotherMapping3 = `
   215  spec:
   216    nodeName: kube-node-2
   217    priority: 10
   218    config:
   219      disableLogging: true`
   220  	anotherMapping4 = `
   221  spec:
   222    nodeSelector:
   223      label-a: "1"
   224    config:
   225      enableSriov: true
   226      rawDevices: sd*`
   227  	sampleLocalConfig = `
   228  disableKVM: false
   229  `
   230  )
   231  
   232  func TestConfigForNode(t *testing.T) {
   233  	for _, tc := range []struct {
   234  		name        string
   235  		mappings    []string
   236  		nodeName    string
   237  		nodeLabels  map[string]string
   238  		localConfig string
   239  	}{
   240  		{
   241  			name: "no mappings",
   242  		},
   243  		{
   244  			name:       "mapping by node name",
   245  			nodeName:   "kube-node-1",
   246  			nodeLabels: map[string]string{"label-a": "1"},
   247  			mappings:   []string{kubeNode1FullMapping},
   248  		},
   249  		{
   250  			name:       "mapping by node labels",
   251  			nodeName:   "kube-node-1",
   252  			nodeLabels: map[string]string{"label-a": "1"},
   253  			mappings:   []string{labelAFullMapping},
   254  		},
   255  		{
   256  			name:       "mapping by node name (no match)",
   257  			nodeName:   "kube-node-2",
   258  			nodeLabels: map[string]string{"label-a": "1"},
   259  			mappings:   []string{kubeNode1FullMapping},
   260  		},
   261  		{
   262  			name:       "mapping by node labels (no match)",
   263  			nodeName:   "kube-node-1",
   264  			nodeLabels: map[string]string{"label-x": "1"},
   265  			mappings:   []string{labelAFullMapping},
   266  		},
   267  		{
   268  			name:       "global mapping",
   269  			nodeName:   "kube-node-1",
   270  			nodeLabels: map[string]string{"label-a": "1"},
   271  			mappings:   []string{globalFullMapping},
   272  		},
   273  		{
   274  			name:       "mapping by node name and multiple labels",
   275  			nodeName:   "kube-node-1",
   276  			nodeLabels: map[string]string{"label-a": "1", "label-b": "1"},
   277  			mappings:   []string{anotherMapping1, anotherMapping2, anotherMapping3, anotherMapping4},
   278  		},
   279  		{
   280  			name:        "mapping by node name and multiple labels and a local config",
   281  			nodeName:    "kube-node-1",
   282  			nodeLabels:  map[string]string{"label-a": "1", "label-b": "1"},
   283  			mappings:    []string{anotherMapping1, anotherMapping2, anotherMapping3, anotherMapping4},
   284  			localConfig: sampleLocalConfig,
   285  		},
   286  	} {
   287  		t.Run(tc.name, func(t *testing.T) {
   288  			var mappings []virtlet_v1.VirtletConfigMapping
   289  			for _, s := range tc.mappings {
   290  				var m virtlet_v1.VirtletConfigMapping
   291  				if err := yaml.Unmarshal([]byte(s), &m); err != nil {
   292  					t.Fatalf("Error parsing yaml: %v", err)
   293  				}
   294  				mappings = append(mappings, m)
   295  				copiedMapping := m.DeepCopyObject()
   296  				if !reflect.DeepEqual(copiedMapping, &m) {
   297  					t.Fatal("deep copy failed")
   298  				}
   299  			}
   300  			var localConfig *virtlet_v1.VirtletConfig
   301  			if tc.localConfig != "" {
   302  				if err := yaml.Unmarshal([]byte(tc.localConfig), &localConfig); err != nil {
   303  					t.Fatalf("Error parsing yaml: %v", err)
   304  				}
   305  			}
   306  			cfg := configForNode(mappings, localConfig, tc.nodeName, tc.nodeLabels)
   307  			gm.Verify(t, gm.NewYamlVerifier(cfg))
   308  		})
   309  	}
   310  }
   311  
   312  func TestLoadMappings(t *testing.T) {
   313  	pstr := func(s string) *string { return &s }
   314  	pbool := func(b bool) *bool { return &b }
   315  	nc := NewNodeConfig(nil)
   316  	nc.kubeClient = fakekube.NewSimpleClientset(
   317  		&v1.Node{
   318  			ObjectMeta: meta_v1.ObjectMeta{
   319  				Name: "kube-node-1",
   320  				Labels: map[string]string{
   321  					"label-a": "1",
   322  					"label-b": "1",
   323  				},
   324  			},
   325  		})
   326  	nc.virtletClient = fake.NewSimpleClientset(
   327  		&virtlet_v1.VirtletConfigMapping{
   328  			ObjectMeta: meta_v1.ObjectMeta{
   329  				Name:      "mapping-1",
   330  				Namespace: "kube-system",
   331  			},
   332  			Spec: virtlet_v1.VirtletConfigMappingSpec{
   333  				NodeName: "kube-node-1",
   334  				Config: &virtlet_v1.VirtletConfig{
   335  					EnableSriov: pbool(true),
   336  					DisableKVM:  pbool(true),
   337  				},
   338  			},
   339  		},
   340  		&virtlet_v1.VirtletConfigMapping{
   341  			ObjectMeta: meta_v1.ObjectMeta{
   342  				Name:      "mapping-2",
   343  				Namespace: "kube-system",
   344  			},
   345  			Spec: virtlet_v1.VirtletConfigMappingSpec{
   346  				NodeSelector: map[string]string{"label-a": "1"},
   347  				Config: &virtlet_v1.VirtletConfig{
   348  					RawDevices: pstr("sd*"),
   349  				},
   350  			},
   351  		})
   352  	cfg, err := nc.LoadConfig(&virtlet_v1.VirtletConfig{
   353  		EnableSriov: pbool(false),
   354  	}, "kube-node-1")
   355  	if err != nil {
   356  		t.Fatalf("LoadConfig(): %v", err)
   357  	}
   358  	gm.Verify(t, gm.NewYamlVerifier(cfg))
   359  }
   360  
   361  func TestGenerateDoc(t *testing.T) {
   362  	gm.Verify(t, GenerateDoc())
   363  }