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 })