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