You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

216 lines
6.7 KiB

  1. /*
  2. Copyright 2016 Google LLC
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. http://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. package bigtable
  14. import (
  15. "context"
  16. "errors"
  17. "flag"
  18. "fmt"
  19. "strings"
  20. "time"
  21. "cloud.google.com/go/bigtable/bttest"
  22. "google.golang.org/api/option"
  23. "google.golang.org/grpc"
  24. )
  25. var legacyUseProd string
  26. var integrationConfig IntegrationTestConfig
  27. func init() {
  28. c := &integrationConfig
  29. flag.BoolVar(&c.UseProd, "it.use-prod", false, "Use remote bigtable instead of local emulator")
  30. flag.StringVar(&c.AdminEndpoint, "it.admin-endpoint", "", "Admin api host and port")
  31. flag.StringVar(&c.DataEndpoint, "it.data-endpoint", "", "Data api host and port")
  32. flag.StringVar(&c.Project, "it.project", "", "Project to use for integration test")
  33. flag.StringVar(&c.Instance, "it.instance", "", "Bigtable instance to use")
  34. flag.StringVar(&c.Cluster, "it.cluster", "", "Bigtable cluster to use")
  35. flag.StringVar(&c.Table, "it.table", "", "Bigtable table to create")
  36. // Backwards compat
  37. flag.StringVar(&legacyUseProd, "use_prod", "", `DEPRECATED: if set to "proj,instance,table", run integration test against production`)
  38. }
  39. // IntegrationTestConfig contains parameters to pick and setup a IntegrationEnv for testing
  40. type IntegrationTestConfig struct {
  41. UseProd bool
  42. AdminEndpoint string
  43. DataEndpoint string
  44. Project string
  45. Instance string
  46. Cluster string
  47. Table string
  48. }
  49. // IntegrationEnv represents a testing environment.
  50. // The environment can be implemented using production or an emulator
  51. type IntegrationEnv interface {
  52. Config() IntegrationTestConfig
  53. NewAdminClient() (*AdminClient, error)
  54. // NewInstanceAdminClient will return nil if instance administration is unsupported in this environment
  55. NewInstanceAdminClient() (*InstanceAdminClient, error)
  56. NewClient() (*Client, error)
  57. Close()
  58. }
  59. // NewIntegrationEnv creates a new environment based on the command line args
  60. func NewIntegrationEnv() (IntegrationEnv, error) {
  61. c := integrationConfig
  62. if legacyUseProd != "" {
  63. fmt.Println("WARNING: using legacy commandline arg -use_prod, please switch to -it.*")
  64. parts := strings.SplitN(legacyUseProd, ",", 3)
  65. c.UseProd = true
  66. c.Project = parts[0]
  67. c.Instance = parts[1]
  68. c.Table = parts[2]
  69. }
  70. if integrationConfig.UseProd {
  71. return NewProdEnv(c)
  72. }
  73. return NewEmulatedEnv(c)
  74. }
  75. // EmulatedEnv encapsulates the state of an emulator
  76. type EmulatedEnv struct {
  77. config IntegrationTestConfig
  78. server *bttest.Server
  79. }
  80. // NewEmulatedEnv builds and starts the emulator based environment
  81. func NewEmulatedEnv(config IntegrationTestConfig) (*EmulatedEnv, error) {
  82. srv, err := bttest.NewServer("localhost:0", grpc.MaxRecvMsgSize(200<<20), grpc.MaxSendMsgSize(100<<20))
  83. if err != nil {
  84. return nil, err
  85. }
  86. if config.Project == "" {
  87. config.Project = "project"
  88. }
  89. if config.Instance == "" {
  90. config.Instance = "instance"
  91. }
  92. if config.Table == "" {
  93. config.Table = "mytable"
  94. }
  95. config.AdminEndpoint = srv.Addr
  96. config.DataEndpoint = srv.Addr
  97. env := &EmulatedEnv{
  98. config: config,
  99. server: srv,
  100. }
  101. return env, nil
  102. }
  103. // Close stops & cleans up the emulator
  104. func (e *EmulatedEnv) Close() {
  105. e.server.Close()
  106. }
  107. // Config gets the config used to build this environment
  108. func (e *EmulatedEnv) Config() IntegrationTestConfig {
  109. return e.config
  110. }
  111. // NewAdminClient builds a new connected admin client for this environment
  112. func (e *EmulatedEnv) NewAdminClient() (*AdminClient, error) {
  113. timeout := 20 * time.Second
  114. ctx, _ := context.WithTimeout(context.Background(), timeout)
  115. conn, err := grpc.Dial(e.server.Addr, grpc.WithInsecure(), grpc.WithBlock())
  116. if err != nil {
  117. return nil, err
  118. }
  119. return NewAdminClient(ctx, e.config.Project, e.config.Instance, option.WithGRPCConn(conn))
  120. }
  121. // NewInstanceAdminClient returns nil for the emulated environment since the API is not implemented.
  122. func (e *EmulatedEnv) NewInstanceAdminClient() (*InstanceAdminClient, error) {
  123. return nil, nil
  124. }
  125. // NewClient builds a new connected data client for this environment
  126. func (e *EmulatedEnv) NewClient() (*Client, error) {
  127. timeout := 20 * time.Second
  128. ctx, _ := context.WithTimeout(context.Background(), timeout)
  129. conn, err := grpc.Dial(e.server.Addr, grpc.WithInsecure(), grpc.WithBlock(),
  130. grpc.WithDefaultCallOptions(grpc.MaxCallSendMsgSize(100<<20), grpc.MaxCallRecvMsgSize(100<<20)))
  131. if err != nil {
  132. return nil, err
  133. }
  134. return NewClient(ctx, e.config.Project, e.config.Instance, option.WithGRPCConn(conn))
  135. }
  136. // ProdEnv encapsulates the state necessary to connect to the external Bigtable service
  137. type ProdEnv struct {
  138. config IntegrationTestConfig
  139. }
  140. // NewProdEnv builds the environment representation
  141. func NewProdEnv(config IntegrationTestConfig) (*ProdEnv, error) {
  142. if config.Project == "" {
  143. return nil, errors.New("Project not set")
  144. }
  145. if config.Instance == "" {
  146. return nil, errors.New("Instance not set")
  147. }
  148. if config.Table == "" {
  149. return nil, errors.New("Table not set")
  150. }
  151. return &ProdEnv{config}, nil
  152. }
  153. // Close is a no-op for production environments
  154. func (e *ProdEnv) Close() {}
  155. // Config gets the config used to build this environment
  156. func (e *ProdEnv) Config() IntegrationTestConfig {
  157. return e.config
  158. }
  159. // NewAdminClient builds a new connected admin client for this environment
  160. func (e *ProdEnv) NewAdminClient() (*AdminClient, error) {
  161. var clientOpts []option.ClientOption
  162. if endpoint := e.config.AdminEndpoint; endpoint != "" {
  163. clientOpts = append(clientOpts, option.WithEndpoint(endpoint))
  164. }
  165. return NewAdminClient(context.Background(), e.config.Project, e.config.Instance, clientOpts...)
  166. }
  167. // NewInstanceAdminClient returns a new connected instance admin client for this environment
  168. func (e *ProdEnv) NewInstanceAdminClient() (*InstanceAdminClient, error) {
  169. var clientOpts []option.ClientOption
  170. if endpoint := e.config.AdminEndpoint; endpoint != "" {
  171. clientOpts = append(clientOpts, option.WithEndpoint(endpoint))
  172. }
  173. return NewInstanceAdminClient(context.Background(), e.config.Project, clientOpts...)
  174. }
  175. // NewClient builds a connected data client for this environment
  176. func (e *ProdEnv) NewClient() (*Client, error) {
  177. var clientOpts []option.ClientOption
  178. if endpoint := e.config.DataEndpoint; endpoint != "" {
  179. clientOpts = append(clientOpts, option.WithEndpoint(endpoint))
  180. }
  181. return NewClient(context.Background(), e.config.Project, e.config.Instance, clientOpts...)
  182. }