github.com/ewagmig/fabric@v2.1.1+incompatible/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/hyperledger/fabric/integration/nwo"
    21  	"github.com/hyperledger/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(status.Status).To(Equal("Service Unavailable"))
   126  				Expect(status.FailedChecks[0].Component).To(Equal("couchdb"))
   127  				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)))
   128  			})
   129  		})
   130  	})
   131  
   132  	Describe("Kafka health checks", func() {
   133  		var (
   134  			oProcess ifrit.Process
   135  			zProcess ifrit.Process
   136  			kProcess []ifrit.Process
   137  
   138  			authClient *http.Client
   139  			healthURL  string
   140  		)
   141  
   142  		BeforeEach(func() {
   143  			// Start Zookeeper
   144  			zookeepers := []string{}
   145  			zk := network.ZooKeeperRunner(0)
   146  			zookeepers = append(zookeepers, fmt.Sprintf("%s:2181", zk.Name))
   147  			zProcess = ifrit.Invoke(zk)
   148  			Eventually(zProcess.Ready(), network.EventuallyTimeout).Should(BeClosed())
   149  
   150  			// Start Kafka Brokers
   151  			for i := 0; i < network.Consensus.Brokers; i++ {
   152  				kafkaRunner := network.BrokerRunner(i, zookeepers)
   153  				kp := ifrit.Invoke(kafkaRunner)
   154  				Eventually(kp.Ready(), network.EventuallyTimeout).Should(BeClosed())
   155  				kProcess = append(kProcess, kp)
   156  			}
   157  
   158  			// Start Orderer
   159  			ordererRunner := network.OrdererGroupRunner()
   160  			oProcess = ifrit.Invoke(ordererRunner)
   161  			Eventually(oProcess.Ready(), network.EventuallyTimeout).Should(BeClosed())
   162  
   163  			orderer := network.Orderers[0]
   164  			authClient, _ = OrdererOperationalClients(network, orderer)
   165  			healthURL = fmt.Sprintf("https://127.0.0.1:%d/healthz", network.OrdererPort(orderer, nwo.OperationsPort))
   166  		})
   167  
   168  		AfterEach(func() {
   169  			if zProcess != nil {
   170  				zProcess.Signal(syscall.SIGTERM)
   171  				Eventually(zProcess.Wait, network.EventuallyTimeout).Should(Receive())
   172  			}
   173  			if oProcess != nil {
   174  				oProcess.Signal(syscall.SIGTERM)
   175  				Eventually(oProcess.Wait, network.EventuallyTimeout).Should(Receive())
   176  			}
   177  			for _, k := range kProcess {
   178  				if k != nil {
   179  					k.Signal(syscall.SIGTERM)
   180  					Eventually(k.Wait, network.EventuallyTimeout).Should(Receive())
   181  				}
   182  			}
   183  		})
   184  
   185  		Context("when running health checks on orderer the kafka health check", func() {
   186  			It("returns appropriate response code", func() {
   187  				By("returning 200 when all brokers are online", func() {
   188  					statusCode, status := DoHealthCheck(authClient, healthURL)
   189  					Expect(statusCode).To(Equal(http.StatusOK))
   190  					Expect(status.Status).To(Equal("OK"))
   191  				})
   192  
   193  				By("returning a 200 when one of the three brokers goes offline", func() {
   194  					k := kProcess[1]
   195  					k.Signal(syscall.SIGTERM)
   196  					Eventually(k.Wait, network.EventuallyTimeout).Should(Receive())
   197  
   198  					var statusCode int
   199  					var status *healthz.HealthStatus
   200  					Consistently(func() int {
   201  						statusCode, status = DoHealthCheck(authClient, healthURL)
   202  						return statusCode
   203  					}).Should(Equal(http.StatusOK))
   204  					Expect(status.Status).To(Equal("OK"))
   205  				})
   206  
   207  				By("returning a 503 when two of the three brokers go offline", func() {
   208  					k := kProcess[0]
   209  					k.Signal(syscall.SIGTERM)
   210  					Eventually(k.Wait, network.EventuallyTimeout).Should(Receive())
   211  
   212  					var statusCode int
   213  					var status *healthz.HealthStatus
   214  					Eventually(func() int {
   215  						statusCode, status = DoHealthCheck(authClient, healthURL)
   216  						return statusCode
   217  					}, network.EventuallyTimeout).Should(Equal(http.StatusServiceUnavailable))
   218  					Expect(status.Status).To(Equal("Service Unavailable"))
   219  					Expect(status.FailedChecks[0].Component).To(Equal("systemchannel/0"))
   220  					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\.`))
   221  				})
   222  			})
   223  		})
   224  	})
   225  })
   226  
   227  func DoHealthCheck(client *http.Client, url string) (int, *healthz.HealthStatus) {
   228  	resp, err := client.Get(url)
   229  	Expect(err).NotTo(HaveOccurred())
   230  
   231  	bodyBytes, err := ioutil.ReadAll(resp.Body)
   232  	Expect(err).NotTo(HaveOccurred())
   233  	resp.Body.Close()
   234  
   235  	// This occurs when a request to the health check server times out, no body is
   236  	// returned when a timeout occurs
   237  	if len(bodyBytes) == 0 {
   238  		return resp.StatusCode, nil
   239  	}
   240  
   241  	healthStatus := &healthz.HealthStatus{}
   242  	err = json.Unmarshal(bodyBytes, &healthStatus)
   243  	Expect(err).NotTo(HaveOccurred())
   244  
   245  	return resp.StatusCode, healthStatus
   246  }