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