| // Copyright Istio Authors |
| // |
| // 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 envoy_test |
| |
| import ( |
| "context" |
| "errors" |
| "os" |
| "path/filepath" |
| "runtime" |
| "testing" |
| "time" |
| ) |
| |
| import ( |
| envoyAdmin "github.com/envoyproxy/go-control-plane/envoy/admin/v3" |
| . "github.com/onsi/gomega" |
| ) |
| |
| import ( |
| "github.com/apache/dubbo-go-pixiu/pkg/envoy" |
| testEnvoy "github.com/apache/dubbo-go-pixiu/pkg/test/envoy" |
| "github.com/apache/dubbo-go-pixiu/pkg/test/util/reserveport" |
| "github.com/apache/dubbo-go-pixiu/pkg/test/util/tmpl" |
| ) |
| |
| var envoyLogFormat = envoy.LogFormat("[ENVOY][%Y-%m-%d %T.%e][%t][%l][%n] %v") |
| |
| func TestNewWithoutConfigShouldFail(t *testing.T) { |
| g := NewWithT(t) |
| |
| _, err := envoy.New(envoy.Config{}) |
| g.Expect(err).ToNot(BeNil()) |
| } |
| |
| func TestNewWithDuplicateOptionsShouldFail(t *testing.T) { |
| g := NewWithT(t) |
| |
| h := newBootstrapHelper(t) |
| defer h.Close() |
| |
| _, err := envoy.New(envoy.Config{ |
| BinaryPath: testEnvoy.FindBinaryOrFail(t), |
| Options: options( |
| envoy.ConfigPath(h.BootstrapFile()), |
| envoy.ConfigPath(h.BootstrapFile())), |
| }) |
| g.Expect(err).ToNot(BeNil()) |
| } |
| |
| func TestNewWithInvalidOptionShouldFail(t *testing.T) { |
| g := NewWithT(t) |
| |
| _, err := envoy.New(envoy.Config{ |
| BinaryPath: testEnvoy.FindBinaryOrFail(t), |
| Options: options(envoy.ConfigPath("file/does/not/exist")), |
| }) |
| g.Expect(err).ToNot(BeNil()) |
| } |
| |
| func TestNewWithoutAdminPortShouldFail(t *testing.T) { |
| g := NewWithT(t) |
| |
| _, err := envoy.New(envoy.Config{ |
| BinaryPath: testEnvoy.FindBinaryOrFail(t), |
| Options: options(envoy.ConfigYaml("{}")), |
| }) |
| g.Expect(err).ToNot(BeNil()) |
| } |
| |
| func TestNewWithoutBinaryPathShouldFail(t *testing.T) { |
| g := NewWithT(t) |
| |
| h := newBootstrapHelper(t) |
| defer h.Close() |
| |
| _, err := envoy.New(envoy.Config{ |
| Options: options(envoy.ConfigPath(h.BootstrapFile())), |
| }) |
| g.Expect(err).ToNot(BeNil()) |
| } |
| |
| func TestNewWithConfigPathShouldSucceed(t *testing.T) { |
| g := NewWithT(t) |
| |
| h := newBootstrapHelper(t) |
| defer h.Close() |
| |
| i := h.NewOrFail(t, envoy.Config{ |
| BinaryPath: testEnvoy.FindBinaryOrFail(t), |
| Options: options(envoy.ConfigPath(h.BootstrapFile())), |
| }) |
| g.Expect(i.Config().Name).To(Equal("envoy")) |
| g.Expect(i.Config().BinaryPath).ToNot(Equal("")) |
| } |
| |
| func TestNewWithConfigYamlShouldSucceed(t *testing.T) { |
| g := NewWithT(t) |
| |
| h := newBootstrapHelper(t) |
| defer h.Close() |
| |
| i := h.NewOrFail(t, envoy.Config{ |
| BinaryPath: testEnvoy.FindBinaryOrFail(t), |
| Options: options(envoy.ConfigYaml(h.BootstrapContent(t))), |
| }) |
| g.Expect(i.Config().Name).To(Equal("envoy")) |
| g.Expect(i.Config().BinaryPath).ToNot(Equal("")) |
| } |
| |
| func TestStartWithBadBinaryShouldFail(t *testing.T) { |
| runLinuxOnly(t) |
| |
| h := newBootstrapHelper(t) |
| defer h.Close() |
| |
| i := h.NewOrFail(t, envoy.Config{ |
| BinaryPath: absPath("testdata/envoy_bootstrap.json"), // Not a binary file. |
| Options: options(envoy.ConfigYaml(h.BootstrapContent(t))), |
| }) |
| |
| ctx, cancel := context.WithCancel(context.Background()) |
| defer cancel() |
| |
| i.Start(ctx) |
| waitForError(t, i) |
| } |
| |
| func TestStartEnvoyShouldSucceed(t *testing.T) { |
| runLinuxOnly(t) |
| |
| g := NewWithT(t) |
| |
| h := newBootstrapHelper(t) |
| defer h.Close() |
| |
| i := h.NewOrFail(t, envoy.Config{ |
| BinaryPath: testEnvoy.FindBinaryOrFail(t), |
| Options: options(envoy.ConfigPath(h.BootstrapFile())), |
| }) |
| |
| ctx, cancel := context.WithCancel(context.Background()) |
| defer cancel() |
| |
| i.Start(ctx) |
| waitLive(t, i) |
| |
| // Wait for a bit and verify that Envoy hasn't terminated. |
| err := i.Wait().WithTimeout(time.Second * 1).Do() |
| g.Expect(err).To(Equal(context.DeadlineExceeded)) |
| } |
| |
| func TestStartTwiceShouldDoNothing(t *testing.T) { |
| runLinuxOnly(t) |
| |
| h := newBootstrapHelper(t) |
| defer h.Close() |
| |
| i := h.NewOrFail(t, envoy.Config{ |
| BinaryPath: testEnvoy.FindBinaryOrFail(t), |
| Options: options(envoy.ConfigPath(h.BootstrapFile())), |
| }) |
| |
| ctx, cancel := context.WithCancel(context.Background()) |
| defer cancel() |
| |
| i.Start(ctx) |
| i.Start(ctx) |
| waitLive(t, i) |
| } |
| |
| func TestHotRestartTwiceShouldFail(t *testing.T) { |
| runLinuxOnly(t) |
| |
| g := NewWithT(t) |
| |
| h := newBootstrapHelper(t) |
| defer h.Close() |
| |
| i := h.NewOrFail(t, envoy.Config{ |
| BinaryPath: testEnvoy.FindBinaryOrFail(t), |
| Options: options(envoy.ConfigPath(h.BootstrapFile())), |
| }) |
| |
| ctx, cancel := context.WithCancel(context.Background()) |
| defer cancel() |
| |
| i.Start(ctx) |
| |
| _, err := i.NewInstanceForHotRestart() |
| g.Expect(err).To(BeNil()) |
| |
| _, err = i.NewInstanceForHotRestart() |
| g.Expect(err).ToNot(BeNil()) |
| } |
| |
| func TestHotRestart(t *testing.T) { |
| runLinuxOnly(t) |
| |
| g := NewWithT(t) |
| |
| h := newBootstrapHelper(t) |
| defer h.Close() |
| |
| i := h.NewOrFail(t, envoy.Config{ |
| BinaryPath: testEnvoy.FindBinaryOrFail(t), |
| Options: options( |
| envoy.ConfigPath(h.BootstrapFile()), |
| // Setting parameters to force shutdown of the first Envoy quickly. |
| envoy.DrainDuration(1*time.Second), |
| envoy.ParentShutdownDuration(1*time.Second)), |
| }) |
| |
| ctx, cancel := context.WithCancel(context.Background()) |
| defer cancel() |
| |
| i.Start(ctx) |
| restartInstance, err := i.NewInstanceForHotRestart() |
| g.Expect(err).To(BeNil()) |
| g.Expect(restartInstance.Epoch()).To(Equal(envoy.Epoch(1))) |
| |
| // Confirm that the first instance is live. |
| info, err := i.GetServerInfo() |
| g.Expect(err).To(BeNil()) |
| g.Expect(info.State).To(Equal(envoyAdmin.ServerInfo_LIVE)) |
| |
| // Start the restart instance. |
| restartInstance.Start(ctx) |
| |
| // Wait for the first instance to exit |
| if err := i.Wait().WithTimeout(5 * time.Second).Do(); err != nil { |
| t.Fatal(err) |
| } |
| |
| waitLive(t, restartInstance) |
| } |
| |
| func TestCommandLineArgs(t *testing.T) { |
| runLinuxOnly(t) |
| |
| g := NewWithT(t) |
| |
| h := newBootstrapHelper(t) |
| defer h.Close() |
| |
| logPath := filepath.Join(h.tempDir, "envoyLogPath.txt") |
| drainDuration := time.Second * 30 |
| parentShutdownDuration := time.Second * 60 |
| i := h.NewOrFail(t, envoy.Config{ |
| BinaryPath: testEnvoy.FindBinaryOrFail(t), |
| Options: options( |
| envoy.ConfigPath(h.BootstrapFile()), |
| envoy.ConfigYaml("{}"), |
| envoy.LogLevelDebug, |
| envoy.LogPath(logPath), |
| envoy.ComponentLogLevels{ |
| envoy.ComponentLogLevel{ |
| Name: "upstream", |
| Level: envoy.LogLevelWarning, |
| }, |
| }, |
| envoy.LocalAddressIPVersion(envoy.IPV4), |
| envoy.Concurrency(7), |
| envoy.DisableHotRestart(true), |
| envoy.Epoch(1), |
| envoy.ServiceCluster("mycluster"), |
| envoy.ServiceNode("mynode"), |
| envoy.DrainDuration(drainDuration), |
| envoy.ParentShutdownDuration(parentShutdownDuration), |
| ), |
| }) |
| g.Expect(i.Epoch()).To(Equal(envoy.Epoch(1))) |
| |
| ctx, cancel := context.WithCancel(context.Background()) |
| defer cancel() |
| |
| i.Start(ctx) |
| waitLive(t, i) |
| |
| info, err := i.GetServerInfo() |
| g.Expect(err).To(BeNil()) |
| |
| // Just verify that options we specified are reflected in the binary. We don't |
| // do an exhaustive test of the various options here, since that is done in the options tests. |
| opts := info.CommandLineOptions |
| g.Expect(opts.ConfigPath).To(Equal(h.BootstrapFile())) |
| g.Expect(opts.ConfigYaml).To(Equal("{}")) |
| g.Expect(opts.LogLevel).To(Equal(envoy.LogLevelDebug.FlagValue())) |
| g.Expect(opts.LogFormat).To(Equal(envoyLogFormat.FlagValue())) |
| g.Expect(opts.LogPath).To(Equal(logPath)) |
| g.Expect(opts.ComponentLogLevel).To(Equal("upstream:warning")) |
| g.Expect(opts.LocalAddressIpVersion).To(Equal(envoyAdmin.CommandLineOptions_v4)) |
| g.Expect(opts.BaseId).To(Equal(h.baseID.GetInternalEnvoyValue())) |
| g.Expect(opts.Concurrency).To(Equal(uint32(7))) |
| g.Expect(opts.DisableHotRestart).To(BeTrue()) |
| g.Expect(opts.RestartEpoch).To(Equal(uint32(1))) |
| g.Expect(opts.ServiceCluster).To(Equal("mycluster")) |
| g.Expect(opts.ServiceNode).To(Equal("mynode")) |
| g.Expect(opts.DrainTime.AsDuration()).To(Equal(drainDuration)) |
| g.Expect(opts.ParentShutdownTime.AsDuration()).To(Equal(parentShutdownDuration)) |
| } |
| |
| func TestShutdown(t *testing.T) { |
| runLinuxOnly(t) |
| |
| g := NewWithT(t) |
| |
| h := newBootstrapHelper(t) |
| defer h.Close() |
| |
| i := h.NewOrFail(t, envoy.Config{ |
| BinaryPath: testEnvoy.FindBinaryOrFail(t), |
| Options: options(envoy.ConfigPath(h.BootstrapFile())), |
| }) |
| |
| ctx, cancel := context.WithCancel(context.Background()) |
| defer cancel() |
| |
| i.Start(ctx) |
| waitLive(t, i) |
| |
| // Initiate the shutdown. |
| g.Expect(i.Shutdown()).To(BeNil()) |
| |
| // Wait for the shutdown to complete. |
| wait(t, i) |
| } |
| |
| func TestKillEnvoy(t *testing.T) { |
| runLinuxOnly(t) |
| |
| g := NewWithT(t) |
| |
| h := newBootstrapHelper(t) |
| defer h.Close() |
| |
| i := h.NewOrFail(t, envoy.Config{ |
| BinaryPath: testEnvoy.FindBinaryOrFail(t), |
| Options: options(envoy.ConfigPath(h.BootstrapFile())), |
| }) |
| |
| ctx, cancel := context.WithCancel(context.Background()) |
| defer cancel() |
| |
| i.Start(ctx) |
| waitLive(t, i) |
| |
| // Kill the process. |
| g.Expect(i.Kill()).To(BeNil()) |
| |
| // Expect the process to return an error. |
| waitForError(t, i) |
| } |
| |
| func TestCancelContext(t *testing.T) { |
| runLinuxOnly(t) |
| |
| h := newBootstrapHelper(t) |
| defer h.Close() |
| |
| i := h.NewOrFail(t, envoy.Config{ |
| BinaryPath: testEnvoy.FindBinaryOrFail(t), |
| Options: options(envoy.ConfigPath(h.BootstrapFile())), |
| }) |
| |
| ctx, cancel := context.WithCancel(context.Background()) |
| defer cancel() |
| |
| i.Start(ctx) |
| waitLive(t, i) |
| |
| // Cancel the context. |
| cancel() |
| |
| // Expect the process to return an error. |
| waitForError(t, i) |
| } |
| |
| func TestConfigDump(t *testing.T) { |
| runLinuxOnly(t) |
| |
| g := NewWithT(t) |
| |
| h := newBootstrapHelper(t) |
| defer h.Close() |
| |
| i := h.NewOrFail(t, envoy.Config{ |
| BinaryPath: testEnvoy.FindBinaryOrFail(t), |
| Options: options(envoy.ConfigPath(h.BootstrapFile())), |
| }) |
| |
| ctx, cancel := context.WithCancel(context.Background()) |
| defer cancel() |
| |
| i.Start(ctx) |
| waitLive(t, i) |
| |
| cd, err := i.GetConfigDump() |
| g.Expect(err).To(BeNil()) |
| |
| // Basic verification of the config dump.. |
| for _, c := range cd.Configs { |
| if c.TypeUrl == "type.googleapis.com/envoy.admin.v3.BootstrapConfigDump" { |
| b := envoyAdmin.BootstrapConfigDump{} |
| g.Expect(c.UnmarshalTo(&b)).To(BeNil()) |
| |
| g.Expect(b.Bootstrap.Admin.GetAddress().GetSocketAddress().GetPortValue()).To(Equal(i.AdminPort())) |
| return |
| } |
| } |
| t.Fatal("failed validating envoy config dump") |
| } |
| |
| type bootstrapHelper struct { |
| tempDir string |
| portMgr reserveport.PortManager |
| bootstrapFile string |
| baseID envoy.BaseID |
| } |
| |
| func newBootstrapHelper(t *testing.T) *bootstrapHelper { |
| t.Helper() |
| tempDir := t.TempDir() |
| portMgr := reserveport.NewPortManagerOrFail(t) |
| adminPort := portMgr.ReservePortNumberOrFail(t) |
| listenerPort := portMgr.ReservePortNumberOrFail(t) |
| bootstrapFile := newBootstrapFile(t, tempDir, adminPort, listenerPort) |
| return &bootstrapHelper{ |
| tempDir: tempDir, |
| portMgr: portMgr, |
| bootstrapFile: bootstrapFile, |
| baseID: envoy.GenerateBaseID(), |
| } |
| } |
| |
| func (h *bootstrapHelper) BootstrapFile() string { |
| return h.bootstrapFile |
| } |
| |
| func (h *bootstrapHelper) BootstrapContent(t *testing.T) string { |
| t.Helper() |
| content, err := os.ReadFile(h.bootstrapFile) |
| if err != nil { |
| t.Fatal(err) |
| } |
| return string(content) |
| } |
| |
| func (h *bootstrapHelper) Close() { |
| _ = os.RemoveAll(h.tempDir) |
| h.portMgr.CloseSilently() |
| } |
| |
| func (h *bootstrapHelper) New(cfg envoy.Config) (envoy.Instance, error) { |
| cfg.Options = append(cfg.Options, h.baseID) |
| return envoy.New(cfg) |
| } |
| |
| func (h *bootstrapHelper) NewOrFail(t *testing.T, cfg envoy.Config) envoy.Instance { |
| t.Helper() |
| i, err := h.New(cfg) |
| if err != nil { |
| t.Fatal(err) |
| } |
| return i |
| } |
| |
| func absPath(path string) string { |
| path, err := filepath.Abs(path) |
| if err != nil { |
| panic(err) |
| } |
| return path |
| } |
| |
| func newBootstrapFile(t *testing.T, tempDir string, adminPort, listenerPort uint16) string { |
| t.Helper() |
| data := map[string]interface{}{ |
| "nodeID": t.Name(), |
| "cluster": t.Name(), |
| "localhost": "127.0.0.1", |
| "wildcard": "0.0.0.0", |
| "adminPort": adminPort, |
| "listenerPort": listenerPort, |
| } |
| |
| // Read the template file. |
| tmplContent, err := os.ReadFile("testdata/envoy_bootstrap_v2.tmpl.json") |
| if err != nil { |
| t.Fatal(err) |
| } |
| |
| // Parse the template file. |
| tpl := tmpl.ParseOrFail(t, string(tmplContent)) |
| |
| // Execute the template with the given data. |
| content := tmpl.ExecuteOrFail(t, tpl, data) |
| |
| // Write out the result to the output file. |
| outputFile := filepath.Join(tempDir, t.Name()+".json") |
| if err := os.WriteFile(outputFile, []byte(content), os.ModePerm); err != nil { |
| t.Fatal(err) |
| } |
| return outputFile |
| } |
| |
| func wait(t *testing.T, i envoy.Instance) { |
| t.Helper() |
| if err := i.Wait().WithTimeout(2 * time.Second).Do(); err != nil { |
| t.Fatal(err) |
| } |
| } |
| |
| func waitForError(t *testing.T, i envoy.Instance) { |
| t.Helper() |
| if err := i.Wait().WithTimeout(2 * time.Second).Do(); err == nil { |
| t.Fatal(errors.New("expected error")) |
| } |
| } |
| |
| func waitLive(t *testing.T, i envoy.Instance) { |
| t.Helper() |
| if err := i.WaitLive().WithTimeout(10 * time.Second).Do(); err != nil { |
| t.Fatal(err) |
| } |
| } |
| |
| func options(opts ...envoy.Option) []envoy.Option { |
| return append([]envoy.Option{envoyLogFormat}, opts...) |
| } |
| |
| func runLinuxOnly(t *testing.T) { |
| t.Helper() |
| if runtime.GOOS != "linux" { |
| t.Skip("test requires GOOS=linux") |
| } |
| } |