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 "errors" 21 "flag" 22 "fmt" 23 "strings" 24 "time" 25 26 "cloud.google.com/go/bigtable/bttest" 27 "golang.org/x/net/context" 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 } else { 89 return NewEmulatedEnv(c) 90 } 91} 92 93// EmulatedEnv encapsulates the state of an emulator 94type EmulatedEnv struct { 95 config IntegrationTestConfig 96 server *bttest.Server 97} 98 99// NewEmulatedEnv builds and starts the emulator based environment 100func NewEmulatedEnv(config IntegrationTestConfig) (*EmulatedEnv, error) { 101 srv, err := bttest.NewServer("localhost:0", grpc.MaxRecvMsgSize(200<<20), grpc.MaxSendMsgSize(100<<20)) 102 if err != nil { 103 return nil, err 104 } 105 106 if config.Project == "" { 107 config.Project = "project" 108 } 109 if config.Instance == "" { 110 config.Instance = "instance" 111 } 112 if config.Table == "" { 113 config.Table = "mytable" 114 } 115 config.AdminEndpoint = srv.Addr 116 config.DataEndpoint = srv.Addr 117 118 env := &EmulatedEnv{ 119 config: config, 120 server: srv, 121 } 122 return env, nil 123} 124 125// Close stops & cleans up the emulator 126func (e *EmulatedEnv) Close() { 127 e.server.Close() 128} 129 130// Config gets the config used to build this environment 131func (e *EmulatedEnv) Config() IntegrationTestConfig { 132 return e.config 133} 134 135// NewAdminClient builds a new connected admin client for this environment 136func (e *EmulatedEnv) NewAdminClient() (*AdminClient, error) { 137 timeout := 20 * time.Second 138 ctx, _ := context.WithTimeout(context.Background(), timeout) 139 conn, err := grpc.Dial(e.server.Addr, grpc.WithInsecure(), grpc.WithBlock()) 140 if err != nil { 141 return nil, err 142 } 143 return NewAdminClient(ctx, e.config.Project, e.config.Instance, option.WithGRPCConn(conn)) 144} 145 146// NewInstanceAdminClient returns nil for the emulated environment since the API is not implemented. 147func (e *EmulatedEnv) NewInstanceAdminClient() (*InstanceAdminClient, error) { 148 return nil, nil 149} 150 151// NewClient builds a new connected data client for this environment 152func (e *EmulatedEnv) NewClient() (*Client, error) { 153 timeout := 20 * time.Second 154 ctx, _ := context.WithTimeout(context.Background(), timeout) 155 conn, err := grpc.Dial(e.server.Addr, grpc.WithInsecure(), grpc.WithBlock(), 156 grpc.WithDefaultCallOptions(grpc.MaxCallSendMsgSize(100<<20), grpc.MaxCallRecvMsgSize(100<<20))) 157 if err != nil { 158 return nil, err 159 } 160 return NewClient(ctx, e.config.Project, e.config.Instance, option.WithGRPCConn(conn)) 161} 162 163// ProdEnv encapsulates the state necessary to connect to the external Bigtable service 164type ProdEnv struct { 165 config IntegrationTestConfig 166} 167 168// NewProdEnv builds the environment representation 169func NewProdEnv(config IntegrationTestConfig) (*ProdEnv, error) { 170 if config.Project == "" { 171 return nil, errors.New("Project not set") 172 } 173 if config.Instance == "" { 174 return nil, errors.New("Instance not set") 175 } 176 if config.Table == "" { 177 return nil, errors.New("Table not set") 178 } 179 180 return &ProdEnv{config}, nil 181} 182 183// Close is a no-op for production environments 184func (e *ProdEnv) Close() {} 185 186// Config gets the config used to build this environment 187func (e *ProdEnv) Config() IntegrationTestConfig { 188 return e.config 189} 190 191// NewAdminClient builds a new connected admin client for this environment 192func (e *ProdEnv) NewAdminClient() (*AdminClient, error) { 193 var clientOpts []option.ClientOption 194 if endpoint := e.config.AdminEndpoint; endpoint != "" { 195 clientOpts = append(clientOpts, option.WithEndpoint(endpoint)) 196 } 197 return NewAdminClient(context.Background(), e.config.Project, e.config.Instance, clientOpts...) 198} 199 200// NewInstanceAdminClient returns a new connected instance admin client for this environment 201func (e *ProdEnv) NewInstanceAdminClient() (*InstanceAdminClient, error) { 202 var clientOpts []option.ClientOption 203 if endpoint := e.config.AdminEndpoint; endpoint != "" { 204 clientOpts = append(clientOpts, option.WithEndpoint(endpoint)) 205 } 206 return NewInstanceAdminClient(context.Background(), e.config.Project, clientOpts...) 207} 208 209// NewClient builds a connected data client for this environment 210func (e *ProdEnv) NewClient() (*Client, error) { 211 var clientOpts []option.ClientOption 212 if endpoint := e.config.DataEndpoint; endpoint != "" { 213 clientOpts = append(clientOpts, option.WithEndpoint(endpoint)) 214 } 215 return NewClient(context.Background(), e.config.Project, e.config.Instance, clientOpts...) 216} 217