github.com/osdi23p228/fabric@v0.0.0-20221218062954-77808885f5db/integration/e2e/health_test.go (about)

     1  /*
     2  Copyright IBM Corp All Rights Reserved.
     3  
     4  SPDX-License-Identifier: Apache-2.0
     5  */
     6  
     7  package e2e
     8  
     9  import (
    10  	"encoding/json"
    11  	"fmt"
    12  	"io/ioutil"
    13  	"net"
    14  	"net/http"
    15  	"os"
    16  	"syscall"
    17  
    18  	docker "github.com/fsouza/go-dockerclient"
    19  	"github.com/hyperledger/fabric-lib-go/healthz"
    20  	"github.com/osdi23p228/fabric/integration/nwo"
    21  	"github.com/osdi23p228/fabric/integration/runner"
    22  	. "github.com/onsi/ginkgo"
    23  	. "github.com/onsi/gomega"
    24  	"github.com/tedsuo/ifrit"
    25  	"github.com/tedsuo/ifrit/ginkgomon"
    26  )
    27  
    28  var _ = Describe("Health", func() {
    29  	var (
    30  		testDir string
    31  		client  *docker.Client
    32  		network *nwo.Network
    33  		process ifrit.Process
    34  	)
    35  
    36  	BeforeEach(func() {
    37  		var err error
    38  		testDir, err = ioutil.TempDir("", "e2e")
    39  		Expect(err).NotTo(HaveOccurred())
    40  
    41  		client, err = docker.NewClientFromEnv()
    42  		Expect(err).NotTo(HaveOccurred())
    43  
    44  		config := nwo.BasicKafka()
    45  		config.Consensus.Brokers = 3
    46  
    47  		network = nwo.New(config, testDir, client, StartPort(), components)
    48  		network.GenerateConfigTree()
    49  		network.Bootstrap()
    50  	})
    51  
    52  	AfterEach(func() {
    53  		if process != nil {
    54  			process.Signal(syscall.SIGTERM)
    55  			Eventually(process.Wait, network.EventuallyTimeout).Should(Receive())
    56  		}
    57  		if network != nil {
    58  			network.Cleanup()
    59  		}
    60  		os.RemoveAll(testDir)
    61  	})
    62  
    63  	Describe("CouchDB health checks", func() {
    64  		var (
    65  			couchAddr    string
    66  			authClient   *http.Client
    67  			healthURL    string
    68  			peer         *nwo.Peer
    69  			couchDB      *runner.CouchDB
    70  			couchProcess ifrit.Process
    71  		)
    72  
    73  		BeforeEach(func() {
    74  			couchDB = &runner.CouchDB{}
    75  			couchProcess = ifrit.Invoke(couchDB)
    76  			Eventually(couchProcess.Ready(), runner.DefaultStartTimeout).Should(BeClosed())
    77  			Consistently(couchProcess.Wait()).ShouldNot(Receive())
    78  			couchAddr = couchDB.Address()
    79  
    80  			peer = network.Peer("Org1", "peer0")
    81  			core := network.ReadPeerConfig(peer)
    82  			core.Ledger.State.StateDatabase = "CouchDB"
    83  			core.Ledger.State.CouchDBConfig.CouchDBAddress = couchAddr
    84  			network.WritePeerConfig(peer, core)
    85  
    86  			peerRunner := network.PeerRunner(peer)
    87  			process = ginkgomon.Invoke(peerRunner)
    88  			Eventually(process.Ready(), network.EventuallyTimeout).Should(BeClosed())
    89  
    90  			authClient, _ = PeerOperationalClients(network, peer)
    91  			healthURL = fmt.Sprintf("https://127.0.0.1:%d/healthz", network.PeerPort(peer, nwo.OperationsPort))
    92  		})
    93  
    94  		AfterEach(func() {
    95  			couchProcess.Signal(syscall.SIGTERM)
    96  			Eventually(couchProcess.Wait(), network.EventuallyTimeout).Should(Receive())
    97  		})
    98  
    99  		When("running health checks on Couch DB", func() {
   100  			It("returns appropriate response codes", func() {
   101  				By("returning 200 when able to reach Couch DB")
   102  				statusCode, status := DoHealthCheck(authClient, healthURL)
   103  				Expect(statusCode).To(Equal(http.StatusOK))
   104  				Expect(status.Status).To(Equal("OK"))
   105  
   106  				By("terminating CouchDB")
   107  				couchProcess.Signal(syscall.SIGTERM)
   108  				Eventually(couchProcess.Wait(), network.EventuallyTimeout).Should(Receive())
   109  
   110  				By("waiting for termination to complete")
   111  				Eventually(func() bool {
   112  					if c, err := net.Dial("tcp", couchDB.Address()); err == nil {
   113  						c.Close()
   114  						return false
   115  					}
   116  					return true
   117  				}, network.EventuallyTimeout).Should(BeTrue())
   118  
   119  				By("returning 503 when unable to reach Couch DB")
   120  				Eventually(func() int {
   121  					statusCode, _ := DoHealthCheck(authClient, healthURL)
   122  					return statusCode
   123  				}, network.EventuallyTimeout).Should(Equal(http.StatusServiceUnavailable))
   124  				statusCode, status = DoHealthCheck(authClient, healthURL)
   125  				Expect(statusCode).To(Equal(http.StatusServiceUnavailable))
   126  				Expect(status.Status).To(Equal("Service Unavailable"))
   127  				Expect(status.FailedChecks[0].Component).To(Equal("couchdb"))
   128  				Expect(status.FailedChecks[0].Reason).To(MatchRegexp(fmt.Sprintf(`failed to connect to couch db \[http error calling couchdb: Head "?http://%s"?: dial tcp %s: .*\]`, couchAddr, couchAddr)))
   129  			})
   130  		})
   131  	})
   132  
   133  	Describe("Kafka health checks", func() {
   134  		var (
   135  			oProcess ifrit.Process
   136  			zProcess ifrit.Process
   137  			kProcess []ifrit.Process
   138  
   139  			authClient *http.Client
   140  			healthURL  string
   141  		)
   142  
   143  		BeforeEach(func() {
   144  			// Start Zookeeper
   145  			zookeepers := []string{}
   146  			zk := network.ZooKeeperRunner(0)
   147  			zookeepers = append(zookeepers, fmt.Sprintf("%s:2181", zk.Name))
   148  			zProcess = ifrit.Invoke(zk)
   149  			Eventually(zProcess.Ready(), network.EventuallyTimeout).Should(BeClosed())
   150  
   151  			// Start Kafka Brokers
   152  			for i := 0; i < network.Consensus.Brokers; i++ {
   153  				kafkaRunner := network.BrokerRunner(i, zookeepers)
   154  				kp := ifrit.Invoke(kafkaRunner)
   155  				Eventually(kp.Ready(), network.EventuallyTimeout).Should(BeClosed())
   156  				kProcess = append(kProcess, kp)
   157  			}
   158  
   159  			// Start Orderer
   160  			ordererRunner := network.OrdererGroupRunner()
   161  			oProcess = ifrit.Invoke(ordererRunner)
   162  			Eventually(oProcess.Ready(), network.EventuallyTimeout).Should(BeClosed())
   163  
   164  			orderer := network.Orderers[0]
   165  			authClient, _ = OrdererOperationalClients(network, orderer)
   166  			healthURL = fmt.Sprintf("https://127.0.0.1:%d/healthz", network.OrdererPort(orderer, nwo.OperationsPort))
   167  		})
   168  
   169  		AfterEach(func() {
   170  			if zProcess != nil {
   171  				zProcess.Signal(syscall.SIGTERM)
   172  				Eventually(zProcess.Wait, network.EventuallyTimeout).Should(Receive())
   173  			}
   174  			if oProcess != nil {
   175  				oProcess.Signal(syscall.SIGTERM)
   176  				Eventually(oProcess.Wait, network.EventuallyTimeout).Should(Receive())
   177  			}
   178  			for _, k := range kProcess {
   179  				if k != nil {
   180  					k.Signal(syscall.SIGTERM)
   181  					Eventually(k.Wait, network.EventuallyTimeout).Should(Receive())
   182  				}
   183  			}
   184  		})
   185  
   186  		Context("when running health checks on orderer the kafka health check", func() {
   187  			It("returns appropriate response code", func() {
   188  				By("returning 200 when all brokers are online", func() {
   189  					statusCode, status := DoHealthCheck(authClient, healthURL)
   190  					Expect(statusCode).To(Equal(http.StatusOK))
   191  					Expect(status.Status).To(Equal("OK"))
   192  				})
   193  
   194  				By("returning a 200 when one of the three brokers goes offline", func() {
   195  					k := kProcess[1]
   196  					k.Signal(syscall.SIGTERM)
   197  					Eventually(k.Wait, network.EventuallyTimeout).Should(Receive())
   198  
   199  					var statusCode int
   200  					var status *healthz.HealthStatus
   201  					Consistently(func() int {
   202  						statusCode, status = DoHealthCheck(authClient, healthURL)
   203  						return statusCode
   204  					}).Should(Equal(http.StatusOK))
   205  					Expect(status.Status).To(Equal("OK"))
   206  				})
   207  
   208  				By("returning a 503 when two of the three brokers go offline", func() {
   209  					k := kProcess[0]
   210  					k.Signal(syscall.SIGTERM)
   211  					Eventually(k.Wait, network.EventuallyTimeout).Should(Receive())
   212  
   213  					var statusCode int
   214  					var status *healthz.HealthStatus
   215  					Eventually(func() int {
   216  						statusCode, status = DoHealthCheck(authClient, healthURL)
   217  						return statusCode
   218  					}, network.EventuallyTimeout).Should(Equal(http.StatusServiceUnavailable))
   219  					Expect(status.Status).To(Equal("Service Unavailable"))
   220  					Expect(status.FailedChecks[0].Component).To(Equal("systemchannel/0"))
   221  					Expect(status.FailedChecks[0].Reason).To(MatchRegexp(`\[replica ids: \[\d \d \d\]\]: kafka server: Messages are rejected since there are fewer in-sync replicas than required\.`))
   222  				})
   223  			})
   224  		})
   225  	})
   226  })
   227  
   228  func DoHealthCheck(client *http.Client, url string) (int, *healthz.HealthStatus) {
   229  	resp, err := client.Get(url)
   230  	Expect(err).NotTo(HaveOccurred())
   231  
   232  	bodyBytes, err := ioutil.ReadAll(resp.Body)
   233  	Expect(err).NotTo(HaveOccurred())
   234  	resp.Body.Close()
   235  
   236  	// This occurs when a request to the health check server times out, no body is
   237  	// returned when a timeout occurs
   238  	if len(bodyBytes) == 0 {
   239  		return resp.StatusCode, nil
   240  	}
   241  
   242  	healthStatus := &healthz.HealthStatus{}
   243  	err = json.Unmarshal(bodyBytes, &healthStatus)
   244  	Expect(err).NotTo(HaveOccurred())
   245  
   246  	return resp.StatusCode, healthStatus
   247  }