github.com/m3db/m3@v1.5.1-0.20231129193456-75a402aa583b/src/integration/cluster_integration_tests.md (about)

     1  # Cluster Integration Tests
     2  
     3  Cluster integration tests are purely Go tests that allow us to test behavior within a single M3 component or across multiple components in a setup that closely resembles an actual M3 deployment. 
     4  
     5  ## Overview
     6  
     7  ### Motivation & Use
     8  Cluster integration tests were created to allow us to write tests that were faster to run, easier to write, and easier to debug than the previously docker-based counterparts.
     9  
    10  These integration tests can be run via `go test` (and subsequently an IDE). They look similar to the standard unit test you'd write in Go (e.g. `func TestFoo(t *testing.T)`).
    11  
    12  ### API
    13  The integration test framework provides an API for creating and interacting with a cluster. The API and its implementation lives [here](https://github.com/m3db/m3/tree/master/src/integration/resources). This [types file](https://github.com/m3db/m3/blob/11a38384efb6d00f26536941e8265009931ead06/src/integration/resources/types.go#L55-L208) outlines the objects representing M3 components and the API calls that can be made to each component. Let's review the cluster interface:
    14  
    15  _M3 Interface_
    16  
    17  ```golang
    18  // M3Resources represents a set of test M3 components.
    19  type M3Resources interface {
    20  	// Cleanup cleans up after each started component.
    21  	Cleanup() error
    22  	// Nodes returns all node resources.
    23  	Nodes() Nodes
    24  	// Coordinator returns the coordinator resource.
    25  	Coordinator() Coordinator
    26  	// Aggregators returns all aggregator resources.
    27  	Aggregators() Aggregators
    28  }
    29  ```
    30  An instantiation of the interface above gives us an M3 cluster to operate on. DB nodes, aggregators, and the coordinator can be manipulated as desired by retrieving the component and invoking API calls. See the types file above to see all the operations that can be done on each component.
    31  
    32  
    33  ### When to Use
    34  Consider writing a cluster integration test when any of the following apply:
    35  
    36  * Test involves multiple parts of a component
    37  * Test requires cross component communication
    38  * Test can be driven entirely by component config and API calls
    39  * Investigating an issue where you'd typically spin up a real cluster
    40  * Local development
    41  
    42  ### When Not to Use
    43  There are some occasions where cluster integration tests may not be the best tool. Consider other options if:
    44  
    45  * Test requires you to manipulate the clock.
    46     * This is currently unsupported. Component-specific integration tests that are configured programmatically may be a better option. Here are some examples for [dbnodes](https://github.com/m3db/m3/tree/master/src/dbnode/integration)
    47  * Test requires changing options not exposed by config.
    48      * Since cluster integration tests start M3 components via the same entry point as the actual binary, all configuration has to be done via the component configuration file or public APIs. If you need to manipulate some options that are not exposed this way, then these tests may not be a good fit.
    49  
    50  ## Example Usage
    51  
    52  Let's walk through a few examples that outline the most common use cases.
    53  
    54  ### Denoting test as a cluster integration test
    55  Any test meant to be considered as a cluster integration test should be tagged with the correct build tag:
    56  
    57  ```golang
    58  // +build cluster_integration
    59  ```
    60  
    61  ### Spinning up a cluster
    62  
    63  
    64  Below is an example that will spin up a cluster with a coordinator, DB node, and aggregator that can interact with each other. It also creates you an unaggregated and an aggregated namespace that you can read and write to.
    65  
    66  ```golang
    67  import (
    68  	"github.com/m3db/m3/src/integration/resources"
    69  	"github.com/m3db/m3/src/integration/resources/inprocess"
    70  )
    71  
    72  // The {} represent empty configs which will start each component
    73  // with the default configuration
    74  cfgs, _ := inprocess.NewClusterConfigsFromYAML(
    75  	`db: {}`, `{}`, `{}`,
    76  )
    77  
    78  m3, _ = inprocess.NewCluster(cfgs,
    79  	resources.ClusterOptions{
    80  		DBNode: resources.NewDBNodeClusterOptions(),
    81  		Aggregator: resources.NewAggregatorClusterOptions()
    82  	},
    83  )
    84  ```
    85  
    86  Naturally, you can spin the cluster up with whatever configuration you like. Also, you can use `DBNodeClusterOptions` and `AggregatorClusterOptions` to spin up more interesting clusters. For example:
    87  
    88  ```golang
    89  m3, _ := inprocess.NewCluster(configs, resources.ClusterOptions{
    90  	DBNode: &resources.DBNodeClusterOptions{
    91  		RF:                 3,
    92  		NumInstances:       1,
    93  		NumShards:          4,
    94  		NumIsolationGroups: 3,
    95  	},
    96  	Aggregator: &resources.AggregatorClusterOptions{
    97  		RF:                 2,
    98  		NumShards:          4,
    99  		NumInstances:       2,
   100  		NumIsolationGroups: 2,
   101  	}
   102  })
   103  ```
   104  This configuration will spin up the following:
   105  
   106  * DB nodes with an RF of 3 and 1 instance for each RF (i.e. 3 separate invocations of M3DB in a single process)
   107  * 1 coordinator
   108  * Aggregators with an RF of 2 and 2 instances for each RF (i.e. 4 separate aggregator invocations in a single process)
   109  
   110  It's worth pointing out that you aren't required to spin up a full cluster each time. `inprocess.NewCluster` also allows you to just spin up a dbnode and coordinator, and later we demonstrate how to spin up just a single component.
   111  
   112  ### Reading and writing to a cluster
   113  Continuing from where we left off in the [Spinning up a cluster](#Spinning up a cluster) section, let's read and write some data to the new cluster.
   114  
   115  ```golang
   116  // Write some data
   117  coordinator := m3.Coordinator()
   118  
   119  /*
   120   * Using this method on the resources.Coordinator interface:
   121   *
   122   *	// WriteProm writes a prometheus metric. Takes tags/labels as a map for convenience.
   123   *  WriteProm(name string, tags map[string]string, samples []prompb.Sample, headers Headers) error
   124   *  
   125   */
   126  _ := coordinator.WriteProm("foo_metric", map[string]string{
   127  	"bar_label":            "baz",
   128  }, []prompb.Sample{
   129  	{
   130  		Value:     42,
   131  		Timestamp: storage.TimeToPromTimestamp(xtime.Now()),
   132  	},
   133  }, nil)
   134  
   135  // Read some data
   136  /*
   137   * Using this method on the resources.Coordinator interface:
   138   *
   139   *  // RangeQuery runs a range query with provided headers
   140   *  RangeQuery(req RangeQueryRequest, headers Headers) (model.Matrix, error)
   141   *  
   142   */
   143  result, err := coord.RangeQuery(
   144  	resources.RangeQueryRequest{
   145  		Query: "foo_metric",
   146  		Start: time.Now().Add(-30 * time.Second),
   147  		End:   time.Now(),
   148  	},
   149  	nil)
   150  
   151  ```
   152  NB: If using this code in tests, the `RangeQuery` may need to be retried to yield a result. Writes in M3 are async by default, so they're not immediately available for reads. `resources.Retry` is provided for convenience to assist with this.
   153  
   154  ### Spinning up an external resource
   155  Occasionally, it is convenient to test M3 alongside some component it works closely with. Prometheus is the most common example. The cluster integration test framework supports this by spinning up external resources in docker containers. External resources must adhere to the following interface:
   156  
   157  ```golang
   158  // ExternalResources represents an external (i.e. non-M3)
   159  // resource that we'd like to be able to spin up for an
   160  // integration test.
   161  type ExternalResources interface {
   162  	// Setup sets up the external resource so that it's ready
   163  	// for use.
   164  	Setup() error
   165  
   166  	// Close stops and cleans up all the resources associated with
   167  	// the external resource.
   168  	Close() error
   169  }
   170  ```
   171  
   172  Since it's so commonly used in conjunction with M3, an implementation for [Prometheus](https://github.com/m3db/m3/blob/master/src/integration/resources/docker/prometheus.go) already exists.
   173  
   174  Here's an example of a test spinning up M3 and Prometheus:
   175  
   176  ```golang
   177  cfgs, _ := inprocess.NewClusterConfigsFromConfigFile(pathToDBCfg, pathToCoordCfg, "")
   178  m3, _ := inprocess.NewCluster(cfgs,
   179          resources.ClusterOptions{
   180                  DBNode: resources.NewDBNodeClusterOptions(),
   181          },
   182  )
   183  defer m3.Cleanup()
   184  
   185  // Spin up external resources. In this case, prometheus.
   186  pool, _ := dockertest.NewPool("")
   187  
   188  prom := docker.NewPrometheus(docker.PrometheusOptions{
   189  	Pool:      pool,
   190  	PathToCfg: pathToPromCfg,
   191  })
   192  prom.Setup()
   193  defer prom.Close()
   194  
   195  // Run tests...
   196  
   197  
   198  ```
   199  
   200  ### Spinning up an individual component
   201  We've been mostly referring to a cluster with a coordinator, db node, and potentially an aggregator, but it's also possible to spin up each component individually. Each component has the same handful of constructors in the `inprocess` [package](https://github.com/m3db/m3/tree/master/src/integration/resources/inprocess). Here's an example of starting up a DB node.
   202  
   203  ```golang
   204  dbnode, _ := inprocess.NewDBNodeFromYAML(dbnodeCfg, inprocess.DBNodeOptions{})
   205  
   206  ```
   207  
   208  ## More Information
   209  * [Some](https://github.com/m3db/m3/tree/master/src/integration/simple) [example](https://github.com/m3db/m3/tree/master/src/integration/repair) [tests](https://github.com/m3db/m3/tree/master/src/integration/prometheus)
   210  * [Component APIs](https://github.com/m3db/m3/blob/1f98da6c2addca3001ff7b7b7a00a99a2c70bbbb/src/integration/resources/types.go#L55-L183)