github.com/hechain20/hechain@v0.0.0-20220316014945-b544036ba106/core/operations/system_test.go (about)

     1  /*
     2  Copyright hechain All Rights Reserved.
     3  
     4  SPDX-License-Identifier: Apache-2.0
     5  */
     6  
     7  package operations_test
     8  
     9  import (
    10  	"encoding/json"
    11  	"errors"
    12  	"fmt"
    13  	"io"
    14  	"io/ioutil"
    15  	"net"
    16  	"net/http"
    17  	"os"
    18  	"path/filepath"
    19  	"syscall"
    20  	"time"
    21  
    22  	"github.com/hechain20/hechain/common/fabhttp"
    23  	"github.com/hechain20/hechain/common/metrics/disabled"
    24  	"github.com/hechain20/hechain/common/metrics/prometheus"
    25  	"github.com/hechain20/hechain/common/metrics/statsd"
    26  	"github.com/hechain20/hechain/core/operations"
    27  	"github.com/hechain20/hechain/core/operations/fakes"
    28  	"github.com/hyperledger/fabric-lib-go/healthz"
    29  	. "github.com/onsi/ginkgo"
    30  	. "github.com/onsi/gomega"
    31  	"github.com/onsi/gomega/gbytes"
    32  	"github.com/tedsuo/ifrit"
    33  )
    34  
    35  var _ = Describe("System", func() {
    36  	const AdditionalTestApiPath = "/some-additional-test-api"
    37  
    38  	var (
    39  		fakeLogger *fakes.Logger
    40  		tempDir    string
    41  
    42  		client       *http.Client
    43  		unauthClient *http.Client
    44  		options      operations.Options
    45  		system       *operations.System
    46  	)
    47  
    48  	BeforeEach(func() {
    49  		var err error
    50  		tempDir, err = ioutil.TempDir("", "opssys")
    51  		Expect(err).NotTo(HaveOccurred())
    52  
    53  		generateCertificates(tempDir)
    54  		client = newHTTPClient(tempDir, true)
    55  		unauthClient = newHTTPClient(tempDir, false)
    56  
    57  		fakeLogger = &fakes.Logger{}
    58  		options = operations.Options{
    59  			Options: fabhttp.Options{
    60  				Logger:        fakeLogger,
    61  				ListenAddress: "127.0.0.1:0",
    62  				TLS: fabhttp.TLS{
    63  					Enabled:            true,
    64  					CertFile:           filepath.Join(tempDir, "server-cert.pem"),
    65  					KeyFile:            filepath.Join(tempDir, "server-key.pem"),
    66  					ClientCertRequired: false,
    67  					ClientCACertFiles:  []string{filepath.Join(tempDir, "client-ca.pem")},
    68  				},
    69  			},
    70  			Metrics: operations.MetricsOptions{
    71  				Provider: "disabled",
    72  			},
    73  			Version: "test-version",
    74  		}
    75  
    76  		system = operations.NewSystem(options)
    77  	})
    78  
    79  	AfterEach(func() {
    80  		os.RemoveAll(tempDir)
    81  		if system != nil {
    82  			system.Stop()
    83  		}
    84  	})
    85  
    86  	It("hosts an unsecured endpoint for the version information", func() {
    87  		err := system.Start()
    88  		Expect(err).NotTo(HaveOccurred())
    89  
    90  		versionURL := fmt.Sprintf("https://%s/version", system.Addr())
    91  		resp, err := client.Get(versionURL)
    92  		Expect(err).NotTo(HaveOccurred())
    93  		Expect(resp.StatusCode).To(Equal(http.StatusOK))
    94  		resp.Body.Close()
    95  	})
    96  
    97  	It("hosts a secure endpoint for logging", func() {
    98  		err := system.Start()
    99  		Expect(err).NotTo(HaveOccurred())
   100  
   101  		logspecURL := fmt.Sprintf("https://%s/logspec", system.Addr())
   102  		resp, err := client.Get(logspecURL)
   103  		Expect(err).NotTo(HaveOccurred())
   104  		Expect(resp.StatusCode).To(Equal(http.StatusOK))
   105  		resp.Body.Close()
   106  
   107  		resp, err = unauthClient.Get(logspecURL)
   108  		Expect(err).NotTo(HaveOccurred())
   109  		Expect(resp.StatusCode).To(Equal(http.StatusUnauthorized))
   110  	})
   111  
   112  	It("does not host a secure endpoint for additional APIs by default", func() {
   113  		err := system.Start()
   114  		Expect(err).NotTo(HaveOccurred())
   115  
   116  		addApiURL := fmt.Sprintf("https://%s%s", system.Addr(), AdditionalTestApiPath)
   117  		resp, err := client.Get(addApiURL)
   118  		Expect(err).NotTo(HaveOccurred())
   119  		Expect(resp.StatusCode).To(Equal(http.StatusNotFound)) // service is not handled by default, i.e. in peer
   120  		resp.Body.Close()
   121  
   122  		resp, err = unauthClient.Get(addApiURL)
   123  		Expect(err).NotTo(HaveOccurred())
   124  		Expect(resp.StatusCode).To(Equal(http.StatusNotFound))
   125  	})
   126  
   127  	It("hosts a secure endpoint for additional APIs when added", func() {
   128  		system.RegisterHandler(AdditionalTestApiPath, &fakes.Handler{Code: http.StatusOK, Text: "secure"}, options.TLS.Enabled)
   129  		err := system.Start()
   130  		Expect(err).NotTo(HaveOccurred())
   131  
   132  		addApiURL := fmt.Sprintf("https://%s%s", system.Addr(), AdditionalTestApiPath)
   133  		resp, err := client.Get(addApiURL)
   134  		Expect(err).NotTo(HaveOccurred())
   135  		Expect(resp.StatusCode).To(Equal(http.StatusOK))
   136  		Expect(resp.Header.Get("Content-Type")).To(Equal("text/plain; charset=utf-8"))
   137  		buff, err := ioutil.ReadAll(resp.Body)
   138  		Expect(err).NotTo(HaveOccurred())
   139  		Expect(string(buff)).To(Equal("secure"))
   140  		resp.Body.Close()
   141  
   142  		resp, err = unauthClient.Get(addApiURL)
   143  		Expect(err).NotTo(HaveOccurred())
   144  		Expect(resp.StatusCode).To(Equal(http.StatusUnauthorized))
   145  	})
   146  
   147  	Context("when TLS is disabled", func() {
   148  		BeforeEach(func() {
   149  			options.TLS.Enabled = false
   150  			system = operations.NewSystem(options)
   151  		})
   152  
   153  		It("hosts an insecure endpoint for logging", func() {
   154  			err := system.Start()
   155  			Expect(err).NotTo(HaveOccurred())
   156  
   157  			resp, err := client.Get(fmt.Sprintf("http://%s/logspec", system.Addr()))
   158  			Expect(err).NotTo(HaveOccurred())
   159  			Expect(resp.StatusCode).To(Equal(http.StatusOK))
   160  			resp.Body.Close()
   161  		})
   162  
   163  		It("does not host an insecure endpoint for additional APIs by default", func() {
   164  			err := system.Start()
   165  			Expect(err).NotTo(HaveOccurred())
   166  
   167  			addApiURL := fmt.Sprintf("http://%s%s", system.Addr(), AdditionalTestApiPath)
   168  			resp, err := client.Get(addApiURL)
   169  			Expect(err).NotTo(HaveOccurred())
   170  			Expect(resp.StatusCode).To(Equal(http.StatusNotFound)) // service is not handled by default, i.e. in peer
   171  			resp.Body.Close()
   172  		})
   173  
   174  		It("hosts an insecure endpoint for additional APIs when added", func() {
   175  			system.RegisterHandler(AdditionalTestApiPath, &fakes.Handler{Code: http.StatusOK, Text: "insecure"}, options.TLS.Enabled)
   176  			err := system.Start()
   177  			Expect(err).NotTo(HaveOccurred())
   178  
   179  			addApiURL := fmt.Sprintf("http://%s%s", system.Addr(), AdditionalTestApiPath)
   180  			resp, err := client.Get(addApiURL)
   181  			Expect(err).NotTo(HaveOccurred())
   182  			Expect(resp.StatusCode).To(Equal(http.StatusOK))
   183  			Expect(resp.Header.Get("Content-Type")).To(Equal("text/plain; charset=utf-8"))
   184  			buff, err := ioutil.ReadAll(resp.Body)
   185  			Expect(err).NotTo(HaveOccurred())
   186  			Expect(string(buff)).To(Equal("insecure"))
   187  			resp.Body.Close()
   188  		})
   189  	})
   190  
   191  	Context("when ClientCertRequired is true", func() {
   192  		BeforeEach(func() {
   193  			options.TLS.ClientCertRequired = true
   194  			system = operations.NewSystem(options)
   195  		})
   196  
   197  		It("requires a client cert to connect", func() {
   198  			err := system.Start()
   199  			Expect(err).NotTo(HaveOccurred())
   200  
   201  			_, err = unauthClient.Get(fmt.Sprintf("https://%s/healthz", system.Addr()))
   202  			Expect(err).To(MatchError(ContainSubstring("remote error: tls: bad certificate")))
   203  		})
   204  	})
   205  
   206  	Context("when listen fails", func() {
   207  		var listener net.Listener
   208  
   209  		BeforeEach(func() {
   210  			var err error
   211  			listener, err = net.Listen("tcp", "127.0.0.1:0")
   212  			Expect(err).NotTo(HaveOccurred())
   213  
   214  			options.ListenAddress = listener.Addr().String()
   215  			system = operations.NewSystem(options)
   216  		})
   217  
   218  		AfterEach(func() {
   219  			listener.Close()
   220  		})
   221  
   222  		It("returns an error", func() {
   223  			err := system.Start()
   224  			Expect(err).To(MatchError(ContainSubstring("bind: address already in use")))
   225  		})
   226  	})
   227  
   228  	Context("when a bad TLS configuration is provided", func() {
   229  		BeforeEach(func() {
   230  			options.TLS.CertFile = "cert-file-does-not-exist"
   231  			system = operations.NewSystem(options)
   232  		})
   233  
   234  		It("returns an error", func() {
   235  			err := system.Start()
   236  			Expect(err).To(MatchError("open cert-file-does-not-exist: no such file or directory"))
   237  		})
   238  	})
   239  
   240  	It("proxies Log to the provided logger", func() {
   241  		err := system.Log("key", "value")
   242  		Expect(err).NotTo(HaveOccurred())
   243  
   244  		Expect(fakeLogger.WarnCallCount()).To(Equal(1))
   245  		Expect(fakeLogger.WarnArgsForCall(0)).To(Equal([]interface{}{"key", "value"}))
   246  	})
   247  
   248  	Context("when a logger is not provided", func() {
   249  		BeforeEach(func() {
   250  			options.Logger = nil
   251  			system = operations.NewSystem(options)
   252  		})
   253  
   254  		It("does not panic when logging", func() {
   255  			Expect(func() { system.Log("key", "value") }).NotTo(Panic())
   256  		})
   257  
   258  		It("returns nil from Log", func() {
   259  			err := system.Log("key", "value")
   260  			Expect(err).NotTo(HaveOccurred())
   261  		})
   262  	})
   263  
   264  	It("hosts a health check endpoint", func() {
   265  		err := system.Start()
   266  		Expect(err).NotTo(HaveOccurred())
   267  
   268  		healthy := &fakes.HealthChecker{}
   269  		unhealthy := &fakes.HealthChecker{}
   270  		unhealthy.HealthCheckReturns(errors.New("Unfortunately, I am not feeling well."))
   271  
   272  		system.RegisterChecker("healthy", healthy)
   273  		system.RegisterChecker("unhealthy", unhealthy)
   274  
   275  		resp, err := client.Get(fmt.Sprintf("https://%s/healthz", system.Addr()))
   276  		Expect(err).NotTo(HaveOccurred())
   277  		Expect(resp.StatusCode).To(Equal(http.StatusServiceUnavailable))
   278  		body, err := ioutil.ReadAll(resp.Body)
   279  		Expect(err).NotTo(HaveOccurred())
   280  		resp.Body.Close()
   281  
   282  		var healthStatus healthz.HealthStatus
   283  		err = json.Unmarshal(body, &healthStatus)
   284  		Expect(err).NotTo(HaveOccurred())
   285  		Expect(healthStatus.Status).To(Equal(healthz.StatusUnavailable))
   286  		Expect(healthStatus.FailedChecks).To(ConsistOf(healthz.FailedCheck{
   287  			Component: "unhealthy",
   288  			Reason:    "Unfortunately, I am not feeling well.",
   289  		}))
   290  	})
   291  
   292  	Context("when the metrics provider is disabled", func() {
   293  		BeforeEach(func() {
   294  			options.Metrics = operations.MetricsOptions{
   295  				Provider: "disabled",
   296  			}
   297  			system = operations.NewSystem(options)
   298  			Expect(system).NotTo(BeNil())
   299  		})
   300  
   301  		It("sets up a disabled provider", func() {
   302  			Expect(system.Provider).To(Equal(&disabled.Provider{}))
   303  		})
   304  	})
   305  
   306  	Context("when the metrics provider is prometheus", func() {
   307  		BeforeEach(func() {
   308  			options.Metrics = operations.MetricsOptions{
   309  				Provider: "prometheus",
   310  			}
   311  			system = operations.NewSystem(options)
   312  			Expect(system).NotTo(BeNil())
   313  		})
   314  
   315  		It("sets up prometheus as a provider", func() {
   316  			Expect(system.Provider).To(Equal(&prometheus.Provider{}))
   317  		})
   318  
   319  		It("hosts a secure endpoint for metrics", func() {
   320  			err := system.Start()
   321  			Expect(err).NotTo(HaveOccurred())
   322  
   323  			metricsURL := fmt.Sprintf("https://%s/metrics", system.Addr())
   324  			resp, err := client.Get(metricsURL)
   325  			Expect(err).NotTo(HaveOccurred())
   326  			Expect(resp.StatusCode).To(Equal(http.StatusOK))
   327  			body, err := ioutil.ReadAll(resp.Body)
   328  			resp.Body.Close()
   329  			Expect(err).NotTo(HaveOccurred())
   330  			Expect(body).To(ContainSubstring("# TYPE go_gc_duration_seconds summary"))
   331  
   332  			resp, err = unauthClient.Get(metricsURL)
   333  			Expect(err).NotTo(HaveOccurred())
   334  			Expect(resp.StatusCode).To(Equal(http.StatusUnauthorized))
   335  		})
   336  
   337  		It("records the fabric version", func() {
   338  			err := system.Start()
   339  			Expect(err).NotTo(HaveOccurred())
   340  
   341  			metricsURL := fmt.Sprintf("https://%s/metrics", system.Addr())
   342  			resp, err := client.Get(metricsURL)
   343  			Expect(err).NotTo(HaveOccurred())
   344  			Expect(resp.StatusCode).To(Equal(http.StatusOK))
   345  			body, err := ioutil.ReadAll(resp.Body)
   346  			resp.Body.Close()
   347  			Expect(err).NotTo(HaveOccurred())
   348  			Expect(string(body)).To(ContainSubstring("# TYPE fabric_version gauge"))
   349  			Expect(string(body)).To(ContainSubstring(`fabric_version{version="test-version"}`))
   350  		})
   351  	})
   352  
   353  	Context("when the metrics provider is statsd", func() {
   354  		var listener net.Listener
   355  
   356  		BeforeEach(func() {
   357  			var err error
   358  			listener, err = net.Listen("tcp", "127.0.0.1:0")
   359  			Expect(err).NotTo(HaveOccurred())
   360  
   361  			options.Metrics = operations.MetricsOptions{
   362  				Provider: "statsd",
   363  				Statsd: &operations.Statsd{
   364  					Network:       "tcp",
   365  					Address:       listener.Addr().String(),
   366  					WriteInterval: 100 * time.Millisecond,
   367  					Prefix:        "prefix",
   368  				},
   369  			}
   370  			system = operations.NewSystem(options)
   371  			Expect(system).NotTo(BeNil())
   372  		})
   373  
   374  		recordStats := func(w io.Writer) {
   375  			defer GinkgoRecover()
   376  
   377  			// handle the dial check
   378  			conn, err := listener.Accept()
   379  			if err != nil {
   380  				return
   381  			}
   382  			conn.Close()
   383  
   384  			// handle the payload
   385  			conn, err = listener.Accept()
   386  			Expect(err).NotTo(HaveOccurred())
   387  			defer conn.Close()
   388  
   389  			conn.SetReadDeadline(time.Now().Add(time.Minute))
   390  			_, err = io.Copy(w, conn)
   391  			if err != nil && err != io.EOF {
   392  				Expect(err).NotTo(HaveOccurred())
   393  			}
   394  		}
   395  
   396  		AfterEach(func() {
   397  			listener.Close()
   398  		})
   399  
   400  		It("sets up statsd as a provider", func() {
   401  			provider, ok := system.Provider.(*statsd.Provider)
   402  			Expect(ok).To(BeTrue())
   403  			Expect(provider.Statsd).NotTo(BeNil())
   404  		})
   405  
   406  		It("emits statsd metrics", func() {
   407  			statsBuffer := gbytes.NewBuffer()
   408  			go recordStats(statsBuffer)
   409  
   410  			err := system.Start()
   411  			Expect(err).NotTo(HaveOccurred())
   412  			Eventually(statsBuffer).Should(gbytes.Say(`\Qprefix.go.mem.gc_last_epoch_nanotime:\E`))
   413  		})
   414  
   415  		It("emits the fabric version statsd metric", func() {
   416  			statsBuffer := gbytes.NewBuffer()
   417  			go recordStats(statsBuffer)
   418  
   419  			err := system.Start()
   420  			Expect(err).NotTo(HaveOccurred())
   421  			Eventually(statsBuffer).Should(gbytes.Say(`\Qprefix.fabric_version.test-version:1.000000|g\E`))
   422  		})
   423  
   424  		Context("when checking the network and address fails", func() {
   425  			BeforeEach(func() {
   426  				options.Metrics.Statsd.Network = "bob-the-network"
   427  				system = operations.NewSystem(options)
   428  			})
   429  
   430  			It("returns an error", func() {
   431  				err := system.Start()
   432  				Expect(err).To(MatchError(ContainSubstring("bob-the-network")))
   433  			})
   434  		})
   435  	})
   436  
   437  	Context("when the metrics provider is unknown", func() {
   438  		BeforeEach(func() {
   439  			options.Metrics.Provider = "something-unknown"
   440  			system = operations.NewSystem(options)
   441  		})
   442  
   443  		It("sets up a disabled provider", func() {
   444  			Expect(system.Provider).To(Equal(&disabled.Provider{}))
   445  		})
   446  
   447  		It("logs the issue", func() {
   448  			Expect(fakeLogger.WarnfCallCount()).To(Equal(1))
   449  			msg, args := fakeLogger.WarnfArgsForCall(0)
   450  			Expect(msg).To(Equal("Unknown provider type: %s; metrics disabled"))
   451  			Expect(args).To(Equal([]interface{}{"something-unknown"}))
   452  		})
   453  	})
   454  
   455  	It("supports ifrit", func() {
   456  		process := ifrit.Invoke(system)
   457  		Eventually(process.Ready()).Should(BeClosed())
   458  
   459  		process.Signal(syscall.SIGTERM)
   460  		Eventually(process.Wait()).Should(Receive(BeNil()))
   461  	})
   462  
   463  	Context("when start fails and ifrit is used", func() {
   464  		BeforeEach(func() {
   465  			options.TLS.CertFile = "non-existent-file"
   466  			system = operations.NewSystem(options)
   467  		})
   468  
   469  		It("does not close the ready chan", func() {
   470  			process := ifrit.Invoke(system)
   471  			Consistently(process.Ready()).ShouldNot(BeClosed())
   472  			Eventually(process.Wait()).Should(Receive(MatchError("open non-existent-file: no such file or directory")))
   473  		})
   474  	})
   475  })