...

Source file src/go.etcd.io/etcd/server/v3/etcdmain/config_test.go

Documentation: go.etcd.io/etcd/server/v3/etcdmain

     1  // Copyright 2015 The etcd Authors
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package etcdmain
    16  
    17  import (
    18  	"flag"
    19  	"fmt"
    20  	"io/ioutil"
    21  	"net/url"
    22  	"os"
    23  	"reflect"
    24  	"strings"
    25  	"testing"
    26  
    27  	"go.etcd.io/etcd/pkg/v3/flags"
    28  	"go.etcd.io/etcd/server/v3/embed"
    29  	"sigs.k8s.io/yaml"
    30  )
    31  
    32  func TestConfigParsingMemberFlags(t *testing.T) {
    33  	args := []string{
    34  		"-data-dir=testdir",
    35  		"-name=testname",
    36  		"-max-wals=10",
    37  		"-max-snapshots=10",
    38  		"-snapshot-count=10",
    39  		"-listen-peer-urls=http://localhost:8000,https://localhost:8001",
    40  		"-listen-client-urls=http://localhost:7000,https://localhost:7001",
    41  		"-listen-client-http-urls=http://localhost:7002,https://localhost:7003",
    42  		// it should be set if -listen-client-urls is set
    43  		"-advertise-client-urls=http://localhost:7000,https://localhost:7001",
    44  	}
    45  
    46  	cfg := newConfig()
    47  	err := cfg.parse(args)
    48  	if err != nil {
    49  		t.Fatal(err)
    50  	}
    51  
    52  	validateMemberFlags(t, cfg)
    53  }
    54  
    55  func TestConfigFileMemberFields(t *testing.T) {
    56  	yc := struct {
    57  		Dir                  string `json:"data-dir"`
    58  		MaxSnapFiles         uint   `json:"max-snapshots"`
    59  		MaxWalFiles          uint   `json:"max-wals"`
    60  		Name                 string `json:"name"`
    61  		SnapshotCount        uint64 `json:"snapshot-count"`
    62  		ListenPeerUrls       string `json:"listen-peer-urls"`
    63  		ListenClientUrls     string `json:"listen-client-urls"`
    64  		ListenClientHttpUrls string `json:"listen-client-http-urls"`
    65  		AdvertiseClientUrls  string `json:"advertise-client-urls"`
    66  	}{
    67  		"testdir",
    68  		10,
    69  		10,
    70  		"testname",
    71  		10,
    72  		"http://localhost:8000,https://localhost:8001",
    73  		"http://localhost:7000,https://localhost:7001",
    74  		"http://localhost:7002,https://localhost:7003",
    75  		"http://localhost:7000,https://localhost:7001",
    76  	}
    77  
    78  	b, err := yaml.Marshal(&yc)
    79  	if err != nil {
    80  		t.Fatal(err)
    81  	}
    82  
    83  	tmpfile := mustCreateCfgFile(t, b)
    84  	defer os.Remove(tmpfile.Name())
    85  
    86  	args := []string{fmt.Sprintf("--config-file=%s", tmpfile.Name())}
    87  
    88  	cfg := newConfig()
    89  	if err = cfg.parse(args); err != nil {
    90  		t.Fatal(err)
    91  	}
    92  
    93  	validateMemberFlags(t, cfg)
    94  }
    95  
    96  func TestConfigParsingClusteringFlags(t *testing.T) {
    97  	args := []string{
    98  		"-initial-cluster=0=http://localhost:8000",
    99  		"-initial-cluster-state=existing",
   100  		"-initial-cluster-token=etcdtest",
   101  		"-initial-advertise-peer-urls=http://localhost:8000,https://localhost:8001",
   102  		"-advertise-client-urls=http://localhost:7000,https://localhost:7001",
   103  		"-discovery-fallback=exit",
   104  	}
   105  
   106  	cfg := newConfig()
   107  	if err := cfg.parse(args); err != nil {
   108  		t.Fatal(err)
   109  	}
   110  
   111  	validateClusteringFlags(t, cfg)
   112  }
   113  
   114  func TestConfigFileClusteringFields(t *testing.T) {
   115  	yc := struct {
   116  		InitialCluster      string `json:"initial-cluster"`
   117  		ClusterState        string `json:"initial-cluster-state"`
   118  		InitialClusterToken string `json:"initial-cluster-token"`
   119  		Apurls              string `json:"initial-advertise-peer-urls"`
   120  		Acurls              string `json:"advertise-client-urls"`
   121  		Fallback            string `json:"discovery-fallback"`
   122  	}{
   123  		"0=http://localhost:8000",
   124  		"existing",
   125  		"etcdtest",
   126  		"http://localhost:8000,https://localhost:8001",
   127  		"http://localhost:7000,https://localhost:7001",
   128  		"exit",
   129  	}
   130  
   131  	b, err := yaml.Marshal(&yc)
   132  	if err != nil {
   133  		t.Fatal(err)
   134  	}
   135  
   136  	tmpfile := mustCreateCfgFile(t, b)
   137  	defer os.Remove(tmpfile.Name())
   138  
   139  	args := []string{fmt.Sprintf("--config-file=%s", tmpfile.Name())}
   140  	cfg := newConfig()
   141  	err = cfg.parse(args)
   142  	if err != nil {
   143  		t.Fatal(err)
   144  	}
   145  
   146  	validateClusteringFlags(t, cfg)
   147  }
   148  
   149  func TestConfigFileClusteringFlags(t *testing.T) {
   150  	tests := []struct {
   151  		Name           string `json:"name"`
   152  		InitialCluster string `json:"initial-cluster"`
   153  		DNSCluster     string `json:"discovery-srv"`
   154  		Durl           string `json:"discovery"`
   155  	}{
   156  		// Use default name and generate a default initial-cluster
   157  		{},
   158  		{
   159  			Name: "non-default",
   160  		},
   161  		{
   162  			InitialCluster: "0=localhost:8000",
   163  		},
   164  		{
   165  			Name:           "non-default",
   166  			InitialCluster: "0=localhost:8000",
   167  		},
   168  		{
   169  			DNSCluster: "example.com",
   170  		},
   171  		{
   172  			Name:       "non-default",
   173  			DNSCluster: "example.com",
   174  		},
   175  		{
   176  			Durl: "http://example.com/abc",
   177  		},
   178  		{
   179  			Name: "non-default",
   180  			Durl: "http://example.com/abc",
   181  		},
   182  	}
   183  
   184  	for i, tt := range tests {
   185  		b, err := yaml.Marshal(&tt)
   186  		if err != nil {
   187  			t.Fatal(err)
   188  		}
   189  
   190  		tmpfile := mustCreateCfgFile(t, b)
   191  		defer os.Remove(tmpfile.Name())
   192  
   193  		args := []string{fmt.Sprintf("--config-file=%s", tmpfile.Name())}
   194  
   195  		cfg := newConfig()
   196  		if err := cfg.parse(args); err != nil {
   197  			t.Errorf("%d: err = %v", i, err)
   198  		}
   199  	}
   200  }
   201  
   202  func TestConfigParsingOtherFlags(t *testing.T) {
   203  	args := []string{"-proxy=readonly"}
   204  
   205  	cfg := newConfig()
   206  	err := cfg.parse(args)
   207  	if err != nil {
   208  		t.Fatal(err)
   209  	}
   210  
   211  	validateOtherFlags(t, cfg)
   212  }
   213  
   214  func TestConfigFileOtherFields(t *testing.T) {
   215  	yc := struct {
   216  		ProxyCfgFile string `json:"proxy"`
   217  	}{
   218  		"readonly",
   219  	}
   220  
   221  	b, err := yaml.Marshal(&yc)
   222  	if err != nil {
   223  		t.Fatal(err)
   224  	}
   225  
   226  	tmpfile := mustCreateCfgFile(t, b)
   227  	defer os.Remove(tmpfile.Name())
   228  
   229  	args := []string{fmt.Sprintf("--config-file=%s", tmpfile.Name())}
   230  
   231  	cfg := newConfig()
   232  	err = cfg.parse(args)
   233  	if err != nil {
   234  		t.Fatal(err)
   235  	}
   236  
   237  	validateOtherFlags(t, cfg)
   238  }
   239  
   240  func TestConfigParsingConflictClusteringFlags(t *testing.T) {
   241  	conflictArgs := [][]string{
   242  		{
   243  			"-initial-cluster=0=localhost:8000",
   244  			"-discovery=http://example.com/abc",
   245  		},
   246  		{
   247  			"-discovery-srv=example.com",
   248  			"-discovery=http://example.com/abc",
   249  		},
   250  		{
   251  			"-initial-cluster=0=localhost:8000",
   252  			"-discovery-srv=example.com",
   253  		},
   254  		{
   255  			"-initial-cluster=0=localhost:8000",
   256  			"-discovery=http://example.com/abc",
   257  			"-discovery-srv=example.com",
   258  		},
   259  	}
   260  
   261  	for i, tt := range conflictArgs {
   262  		cfg := newConfig()
   263  		if err := cfg.parse(tt); err != embed.ErrConflictBootstrapFlags {
   264  			t.Errorf("%d: err = %v, want %v", i, err, embed.ErrConflictBootstrapFlags)
   265  		}
   266  	}
   267  }
   268  
   269  func TestConfigFileConflictClusteringFlags(t *testing.T) {
   270  	tests := []struct {
   271  		InitialCluster string `json:"initial-cluster"`
   272  		DNSCluster     string `json:"discovery-srv"`
   273  		Durl           string `json:"discovery"`
   274  	}{
   275  		{
   276  			InitialCluster: "0=localhost:8000",
   277  			Durl:           "http://example.com/abc",
   278  		},
   279  		{
   280  			DNSCluster: "example.com",
   281  			Durl:       "http://example.com/abc",
   282  		},
   283  		{
   284  			InitialCluster: "0=localhost:8000",
   285  			DNSCluster:     "example.com",
   286  		},
   287  		{
   288  			InitialCluster: "0=localhost:8000",
   289  			Durl:           "http://example.com/abc",
   290  			DNSCluster:     "example.com",
   291  		},
   292  	}
   293  
   294  	for i, tt := range tests {
   295  		b, err := yaml.Marshal(&tt)
   296  		if err != nil {
   297  			t.Fatal(err)
   298  		}
   299  
   300  		tmpfile := mustCreateCfgFile(t, b)
   301  		defer os.Remove(tmpfile.Name())
   302  
   303  		args := []string{fmt.Sprintf("--config-file=%s", tmpfile.Name())}
   304  
   305  		cfg := newConfig()
   306  		if err := cfg.parse(args); err != embed.ErrConflictBootstrapFlags {
   307  			t.Errorf("%d: err = %v, want %v", i, err, embed.ErrConflictBootstrapFlags)
   308  		}
   309  	}
   310  }
   311  
   312  func TestConfigParsingMissedAdvertiseClientURLsFlag(t *testing.T) {
   313  	tests := []struct {
   314  		args []string
   315  		werr error
   316  	}{
   317  		{
   318  			[]string{
   319  				"-initial-cluster=infra1=http://127.0.0.1:2380",
   320  				"-listen-client-urls=http://127.0.0.1:2379",
   321  			},
   322  			embed.ErrUnsetAdvertiseClientURLsFlag,
   323  		},
   324  		{
   325  			[]string{
   326  				"-discovery-srv=example.com",
   327  				"-listen-client-urls=http://127.0.0.1:2379",
   328  			},
   329  			embed.ErrUnsetAdvertiseClientURLsFlag,
   330  		},
   331  		{
   332  			[]string{
   333  				"-discovery=http://example.com/abc",
   334  				"-discovery-fallback=exit",
   335  				"-listen-client-urls=http://127.0.0.1:2379",
   336  			},
   337  			embed.ErrUnsetAdvertiseClientURLsFlag,
   338  		},
   339  		{
   340  			[]string{
   341  				"-listen-client-urls=http://127.0.0.1:2379",
   342  			},
   343  			embed.ErrUnsetAdvertiseClientURLsFlag,
   344  		},
   345  		{
   346  			[]string{
   347  				"-discovery=http://example.com/abc",
   348  				"-listen-client-urls=http://127.0.0.1:2379",
   349  			},
   350  			nil,
   351  		},
   352  		{
   353  			[]string{
   354  				"-proxy=on",
   355  				"-listen-client-urls=http://127.0.0.1:2379",
   356  			},
   357  			nil,
   358  		},
   359  		{
   360  			[]string{
   361  				"-proxy=readonly",
   362  				"-listen-client-urls=http://127.0.0.1:2379",
   363  			},
   364  			nil,
   365  		},
   366  	}
   367  
   368  	for i, tt := range tests {
   369  		cfg := newConfig()
   370  		if err := cfg.parse(tt.args); err != tt.werr {
   371  			t.Errorf("%d: err = %v, want %v", i, err, tt.werr)
   372  		}
   373  	}
   374  }
   375  
   376  func TestConfigIsNewCluster(t *testing.T) {
   377  	tests := []struct {
   378  		state  string
   379  		wIsNew bool
   380  	}{
   381  		{embed.ClusterStateFlagExisting, false},
   382  		{embed.ClusterStateFlagNew, true},
   383  	}
   384  	for i, tt := range tests {
   385  		cfg := newConfig()
   386  		args := []string{"--initial-cluster-state", tests[i].state}
   387  		if err := cfg.parse(args); err != nil {
   388  			t.Fatalf("#%d: unexpected clusterState.Set error: %v", i, err)
   389  		}
   390  		if g := cfg.ec.IsNewCluster(); g != tt.wIsNew {
   391  			t.Errorf("#%d: isNewCluster = %v, want %v", i, g, tt.wIsNew)
   392  		}
   393  	}
   394  }
   395  
   396  func TestConfigIsProxy(t *testing.T) {
   397  	tests := []struct {
   398  		proxy    string
   399  		wIsProxy bool
   400  	}{
   401  		{proxyFlagOff, false},
   402  		{proxyFlagReadonly, true},
   403  		{proxyFlagOn, true},
   404  	}
   405  	for i, tt := range tests {
   406  		cfg := newConfig()
   407  		if err := cfg.cf.proxy.Set(tt.proxy); err != nil {
   408  			t.Fatalf("#%d: unexpected proxy.Set error: %v", i, err)
   409  		}
   410  		if g := cfg.isProxy(); g != tt.wIsProxy {
   411  			t.Errorf("#%d: isProxy = %v, want %v", i, g, tt.wIsProxy)
   412  		}
   413  	}
   414  }
   415  
   416  func TestConfigIsReadonlyProxy(t *testing.T) {
   417  	tests := []struct {
   418  		proxy       string
   419  		wIsReadonly bool
   420  	}{
   421  		{proxyFlagOff, false},
   422  		{proxyFlagReadonly, true},
   423  		{proxyFlagOn, false},
   424  	}
   425  	for i, tt := range tests {
   426  		cfg := newConfig()
   427  		if err := cfg.cf.proxy.Set(tt.proxy); err != nil {
   428  			t.Fatalf("#%d: unexpected proxy.Set error: %v", i, err)
   429  		}
   430  		if g := cfg.isReadonlyProxy(); g != tt.wIsReadonly {
   431  			t.Errorf("#%d: isReadonlyProxy = %v, want %v", i, g, tt.wIsReadonly)
   432  		}
   433  	}
   434  }
   435  
   436  func TestConfigShouldFallbackToProxy(t *testing.T) {
   437  	tests := []struct {
   438  		fallback  string
   439  		wFallback bool
   440  	}{
   441  		{fallbackFlagProxy, true},
   442  		{fallbackFlagExit, false},
   443  	}
   444  	for i, tt := range tests {
   445  		cfg := newConfig()
   446  		if err := cfg.cf.fallback.Set(tt.fallback); err != nil {
   447  			t.Fatalf("#%d: unexpected fallback.Set error: %v", i, err)
   448  		}
   449  		if g := cfg.shouldFallbackToProxy(); g != tt.wFallback {
   450  			t.Errorf("#%d: shouldFallbackToProxy = %v, want %v", i, g, tt.wFallback)
   451  		}
   452  	}
   453  }
   454  
   455  func TestConfigFileElectionTimeout(t *testing.T) {
   456  	tests := []struct {
   457  		TickMs     uint `json:"heartbeat-interval"`
   458  		ElectionMs uint `json:"election-timeout"`
   459  		errStr     string
   460  	}{
   461  		{
   462  			ElectionMs: 1000,
   463  			TickMs:     800,
   464  			errStr:     "should be at least as 5 times as",
   465  		},
   466  		{
   467  			ElectionMs: 60000,
   468  			TickMs:     10000,
   469  			errStr:     "is too long, and should be set less than",
   470  		},
   471  		{
   472  			ElectionMs: 100,
   473  			TickMs:     0,
   474  			errStr:     "--heartbeat-interval must be >0 (set to 0ms)",
   475  		},
   476  		{
   477  			ElectionMs: 0,
   478  			TickMs:     100,
   479  			errStr:     "--election-timeout must be >0 (set to 0ms)",
   480  		},
   481  	}
   482  
   483  	for i, tt := range tests {
   484  		b, err := yaml.Marshal(&tt)
   485  		if err != nil {
   486  			t.Fatal(err)
   487  		}
   488  
   489  		tmpfile := mustCreateCfgFile(t, b)
   490  		defer os.Remove(tmpfile.Name())
   491  
   492  		args := []string{fmt.Sprintf("--config-file=%s", tmpfile.Name())}
   493  
   494  		cfg := newConfig()
   495  		if err := cfg.parse(args); err == nil || !strings.Contains(err.Error(), tt.errStr) {
   496  			t.Errorf("%d: Wrong err = %v", i, err)
   497  		}
   498  	}
   499  }
   500  
   501  func TestFlagsPresentInHelp(t *testing.T) {
   502  	cfg := newConfig()
   503  	cfg.cf.flagSet.VisitAll(func(f *flag.Flag) {
   504  		if _, ok := f.Value.(*flags.IgnoredFlag); ok {
   505  			// Ignored flags do not need to be in the help
   506  			return
   507  		}
   508  
   509  		flagText := fmt.Sprintf("--%s", f.Name)
   510  		if !strings.Contains(flagsline, flagText) && !strings.Contains(usageline, flagText) {
   511  			t.Errorf("Neither flagsline nor usageline in help.go contains flag named %s", flagText)
   512  		}
   513  	})
   514  }
   515  
   516  func mustCreateCfgFile(t *testing.T, b []byte) *os.File {
   517  	tmpfile, err := ioutil.TempFile("", "servercfg")
   518  	if err != nil {
   519  		t.Fatal(err)
   520  	}
   521  
   522  	_, err = tmpfile.Write(b)
   523  	if err != nil {
   524  		t.Fatal(err)
   525  	}
   526  	err = tmpfile.Close()
   527  	if err != nil {
   528  		t.Fatal(err)
   529  	}
   530  
   531  	return tmpfile
   532  }
   533  
   534  func validateMemberFlags(t *testing.T, cfg *config) {
   535  	wcfg := &embed.Config{
   536  		Dir:                  "testdir",
   537  		ListenPeerUrls:       []url.URL{{Scheme: "http", Host: "localhost:8000"}, {Scheme: "https", Host: "localhost:8001"}},
   538  		ListenClientUrls:     []url.URL{{Scheme: "http", Host: "localhost:7000"}, {Scheme: "https", Host: "localhost:7001"}},
   539  		ListenClientHttpUrls: []url.URL{{Scheme: "http", Host: "localhost:7002"}, {Scheme: "https", Host: "localhost:7003"}},
   540  		MaxSnapFiles:         10,
   541  		MaxWalFiles:          10,
   542  		Name:                 "testname",
   543  		SnapshotCount:        10,
   544  	}
   545  
   546  	if cfg.ec.Dir != wcfg.Dir {
   547  		t.Errorf("dir = %v, want %v", cfg.ec.Dir, wcfg.Dir)
   548  	}
   549  	if cfg.ec.MaxSnapFiles != wcfg.MaxSnapFiles {
   550  		t.Errorf("maxsnap = %v, want %v", cfg.ec.MaxSnapFiles, wcfg.MaxSnapFiles)
   551  	}
   552  	if cfg.ec.MaxWalFiles != wcfg.MaxWalFiles {
   553  		t.Errorf("maxwal = %v, want %v", cfg.ec.MaxWalFiles, wcfg.MaxWalFiles)
   554  	}
   555  	if cfg.ec.Name != wcfg.Name {
   556  		t.Errorf("name = %v, want %v", cfg.ec.Name, wcfg.Name)
   557  	}
   558  	if cfg.ec.SnapshotCount != wcfg.SnapshotCount {
   559  		t.Errorf("snapcount = %v, want %v", cfg.ec.SnapshotCount, wcfg.SnapshotCount)
   560  	}
   561  	if !reflect.DeepEqual(cfg.ec.ListenPeerUrls, wcfg.ListenPeerUrls) {
   562  		t.Errorf("listen-peer-urls = %v, want %v", cfg.ec.ListenPeerUrls, wcfg.ListenPeerUrls)
   563  	}
   564  	if !reflect.DeepEqual(cfg.ec.ListenClientUrls, wcfg.ListenClientUrls) {
   565  		t.Errorf("listen-client-urls = %v, want %v", cfg.ec.ListenClientUrls, wcfg.ListenClientUrls)
   566  	}
   567  	if !reflect.DeepEqual(cfg.ec.ListenClientHttpUrls, wcfg.ListenClientHttpUrls) {
   568  		t.Errorf("listen-client-http-urls = %v, want %v", cfg.ec.ListenClientHttpUrls, wcfg.ListenClientHttpUrls)
   569  	}
   570  }
   571  
   572  func validateClusteringFlags(t *testing.T, cfg *config) {
   573  	wcfg := newConfig()
   574  	wcfg.ec.AdvertisePeerUrls = []url.URL{{Scheme: "http", Host: "localhost:8000"}, {Scheme: "https", Host: "localhost:8001"}}
   575  	wcfg.ec.AdvertiseClientUrls = []url.URL{{Scheme: "http", Host: "localhost:7000"}, {Scheme: "https", Host: "localhost:7001"}}
   576  	wcfg.ec.ClusterState = embed.ClusterStateFlagExisting
   577  	wcfg.cf.fallback.Set(fallbackFlagExit)
   578  	wcfg.ec.InitialCluster = "0=http://localhost:8000"
   579  	wcfg.ec.InitialClusterToken = "etcdtest"
   580  
   581  	if cfg.ec.ClusterState != wcfg.ec.ClusterState {
   582  		t.Errorf("clusterState = %v, want %v", cfg.ec.ClusterState, wcfg.ec.ClusterState)
   583  	}
   584  	if cfg.cf.fallback.String() != wcfg.cf.fallback.String() {
   585  		t.Errorf("fallback = %v, want %v", cfg.cf.fallback, wcfg.cf.fallback)
   586  	}
   587  	if cfg.ec.InitialCluster != wcfg.ec.InitialCluster {
   588  		t.Errorf("initialCluster = %v, want %v", cfg.ec.InitialCluster, wcfg.ec.InitialCluster)
   589  	}
   590  	if cfg.ec.InitialClusterToken != wcfg.ec.InitialClusterToken {
   591  		t.Errorf("initialClusterToken = %v, want %v", cfg.ec.InitialClusterToken, wcfg.ec.InitialClusterToken)
   592  	}
   593  	if !reflect.DeepEqual(cfg.ec.AdvertisePeerUrls, wcfg.ec.AdvertisePeerUrls) {
   594  		t.Errorf("initial-advertise-peer-urls = %v, want %v", cfg.ec.AdvertisePeerUrls, wcfg.ec.AdvertisePeerUrls)
   595  	}
   596  	if !reflect.DeepEqual(cfg.ec.AdvertiseClientUrls, wcfg.ec.AdvertiseClientUrls) {
   597  		t.Errorf("advertise-client-urls = %v, want %v", cfg.ec.AdvertiseClientUrls, wcfg.ec.AdvertiseClientUrls)
   598  	}
   599  }
   600  
   601  func validateOtherFlags(t *testing.T, cfg *config) {
   602  	wcfg := newConfig()
   603  	wcfg.cf.proxy.Set(proxyFlagReadonly)
   604  	if cfg.cf.proxy.String() != wcfg.cf.proxy.String() {
   605  		t.Errorf("proxy = %v, want %v", cfg.cf.proxy, wcfg.cf.proxy)
   606  	}
   607  }
   608  

View as plain text