spanner: remove the support for resource-based routing

The Spanner team has decided to implement this functionality on the
server side so we no longer need to add this support on the client
side.

Change-Id: I7d8665710252ff5002c1dc8bae0e787a1ccdc66a
Reviewed-on: https://code-review.googlesource.com/c/gocloud/+/55238
Reviewed-by: kokoro <noreply+kokoro@google.com>
Reviewed-by: Cody Oss <codyoss@google.com>
Reviewed-by: Knut Olav Løite <koloite@gmail.com>
diff --git a/spanner/client.go b/spanner/client.go
index 7ce2f25..6095741 100644
--- a/spanner/client.go
+++ b/spanner/client.go
@@ -25,16 +25,12 @@
 	"time"
 
 	"cloud.google.com/go/internal/trace"
-	instance "cloud.google.com/go/spanner/admin/instance/apiv1"
 	"google.golang.org/api/option"
 	gtransport "google.golang.org/api/transport/grpc"
-	instancepb "google.golang.org/genproto/googleapis/spanner/admin/instance/v1"
 	sppb "google.golang.org/genproto/googleapis/spanner/v1"
-	field_mask "google.golang.org/genproto/protobuf/field_mask"
 	"google.golang.org/grpc"
 	"google.golang.org/grpc/codes"
 	"google.golang.org/grpc/metadata"
-	"google.golang.org/grpc/status"
 )
 
 const (
@@ -54,8 +50,7 @@
 )
 
 var (
-	validDBPattern       = regexp.MustCompile("^projects/(?P<project>[^/]+)/instances/(?P<instance>[^/]+)/databases/(?P<database>[^/]+)$")
-	validInstancePattern = regexp.MustCompile("^projects/(?P<project>[^/]+)/instances/(?P<instance>[^/]+)")
+	validDBPattern = regexp.MustCompile("^projects/(?P<project>[^/]+)/instances/(?P<instance>[^/]+)/databases/(?P<database>[^/]+)$")
 )
 
 func validDatabaseName(db string) error {
@@ -66,15 +61,6 @@
 	return nil
 }
 
-func getInstanceName(db string) (string, error) {
-	matches := validInstancePattern.FindStringSubmatch(db)
-	if len(matches) == 0 {
-		return "", fmt.Errorf("Failed to retrieve instance name from %q according to pattern %q",
-			db, validInstancePattern.String())
-	}
-	return matches[0], nil
-}
-
 func parseDatabaseName(db string) (project, instance, database string, err error) {
 	matches := validDBPattern.FindStringSubmatch(db)
 	if len(matches) == 0 {
@@ -135,42 +121,6 @@
 	return metadata.NewOutgoingContext(ctx, md)
 }
 
-// getInstanceEndpoint returns an instance-specific endpoint if one exists. If
-// multiple endpoints exist, it returns the first one.
-func getInstanceEndpoint(ctx context.Context, database string, opts ...option.ClientOption) (string, error) {
-	instanceName, err := getInstanceName(database)
-	if err != nil {
-		return "", fmt.Errorf("Failed to resolve endpoint: %v", err)
-	}
-
-	c, err := instance.NewInstanceAdminClient(ctx, opts...)
-	if err != nil {
-		return "", err
-	}
-	defer c.Close()
-
-	req := &instancepb.GetInstanceRequest{
-		Name: instanceName,
-		FieldMask: &field_mask.FieldMask{
-			Paths: []string{"endpoint_uris"},
-		},
-	}
-
-	resp, err := c.GetInstance(ctx, req)
-	if err != nil {
-		return "", err
-	}
-
-	endpointURIs := resp.GetEndpointUris()
-
-	if len(endpointURIs) > 0 {
-		return endpointURIs[0], nil
-	}
-
-	// Return empty string when no endpoints exist.
-	return "", nil
-}
-
 // NewClient creates a client to a database. A valid database name has the
 // form projects/PROJECT_ID/instances/INSTANCE_ID/databases/DATABASE_ID. It uses
 // a default configuration.
@@ -197,32 +147,6 @@
 			option.WithoutAuthentication(),
 		}
 		opts = append(emulatorOpts, opts...)
-	} else if os.Getenv("GOOGLE_CLOUD_SPANNER_ENABLE_RESOURCE_BASED_ROUTING") == "true" {
-		// Fetch the instance-specific endpoint.
-		reqOpts := []option.ClientOption{option.WithEndpoint(endpoint)}
-		reqOpts = append(reqOpts, opts...)
-		instanceEndpoint, err := getInstanceEndpoint(ctx, database, reqOpts...)
-
-		if err != nil {
-			// If there is a PermissionDenied error, fall back to use the global endpoint
-			// or the user-specified endpoint.
-			if status.Code(err) == codes.PermissionDenied {
-				logf(config.logger, `
-Warning: The client library attempted to connect to an endpoint closer to your
-Cloud Spanner data but was unable to do so. The client library will fall back
-and route requests to the endpoint given in the client options, which may
-result in increased latency. We recommend including the scope
-https://www.googleapis.com/auth/spanner.admin so that the client library can
-get an instance-specific endpoint and efficiently route requests.
-`)
-			} else {
-				return nil, err
-			}
-		}
-
-		if instanceEndpoint != "" {
-			opts = append(opts, option.WithEndpoint(instanceEndpoint))
-		}
 	}
 
 	// Prepare gRPC channels.
diff --git a/spanner/client_test.go b/spanner/client_test.go
index 613d443..1c9fed2 100644
--- a/spanner/client_test.go
+++ b/spanner/client_test.go
@@ -20,8 +20,6 @@
 	"context"
 	"fmt"
 	"io"
-	"io/ioutil"
-	"log"
 	"os"
 	"strings"
 	"testing"
@@ -30,11 +28,9 @@
 	"cloud.google.com/go/civil"
 	itestutil "cloud.google.com/go/internal/testutil"
 	. "cloud.google.com/go/spanner/internal/testutil"
-	"github.com/golang/protobuf/proto"
 	structpb "github.com/golang/protobuf/ptypes/struct"
 	"google.golang.org/api/iterator"
 	"google.golang.org/api/option"
-	instancepb "google.golang.org/genproto/googleapis/spanner/admin/instance/v1"
 	sppb "google.golang.org/genproto/googleapis/spanner/v1"
 	"google.golang.org/grpc/codes"
 	"google.golang.org/grpc/status"
@@ -102,34 +98,6 @@
 	}
 }
 
-// Test getInstanceName()
-func TestGetInstanceName(t *testing.T) {
-	validDbURI := "projects/spanner-cloud-test/instances/foo/databases/foodb"
-	invalidDbUris := []string{
-		// Completely wrong DB URI.
-		"foobarDB",
-		// Project ID contains "/".
-		"projects/spanner-cloud/test/instances/foo/databases/foodb",
-		// No instance ID.
-		"projects/spanner-cloud-test/instances//databases/foodb",
-	}
-	want := "projects/spanner-cloud-test/instances/foo"
-	got, err := getInstanceName(validDbURI)
-	if err != nil {
-		t.Errorf("getInstanceName(%q) has an error: %q, want nil", validDbURI, err)
-	}
-	if got != want {
-		t.Errorf("getInstanceName(%q) = %q, want %q", validDbURI, got, want)
-	}
-	for _, d := range invalidDbUris {
-		wantErr := "Failed to retrieve instance name"
-		_, err = getInstanceName(d)
-		if !strings.Contains(err.Error(), wantErr) {
-			t.Errorf("getInstanceName(%q) has an error: %q, want error pattern %q", validDbURI, err, wantErr)
-		}
-	}
-}
-
 func TestReadOnlyTransactionClose(t *testing.T) {
 	// Closing a ReadOnlyTransaction shouldn't panic.
 	c := &Client{}
@@ -353,157 +321,6 @@
 	}
 }
 
-func TestClient_ResourceBasedRouting_WithEndpointsReturned(t *testing.T) {
-	os.Setenv("GOOGLE_CLOUD_SPANNER_ENABLE_RESOURCE_BASED_ROUTING", "true")
-	defer os.Setenv("GOOGLE_CLOUD_SPANNER_ENABLE_RESOURCE_BASED_ROUTING", "")
-
-	// Create two servers. The base server receives the GetInstance request and
-	// returns the instance endpoint of the target server. The client should contact
-	// the target server after getting the instance endpoint.
-	serverBase, optsBase, serverTeardownBase := NewMockedSpannerInMemTestServerWithAddr(t, "localhost:8081")
-	defer serverTeardownBase()
-	serverTarget, optsTarget, serverTeardownTarget := NewMockedSpannerInMemTestServerWithAddr(t, "localhost:8082")
-	defer serverTeardownTarget()
-
-	// Return the instance endpoint.
-	instanceEndpoint := fmt.Sprintf("%s", optsTarget[0])
-	resps := []proto.Message{&instancepb.Instance{
-		EndpointUris: []string{instanceEndpoint},
-	}}
-	serverBase.TestInstanceAdmin.SetResps(resps)
-
-	ctx := context.Background()
-	formattedDatabase := fmt.Sprintf("projects/%s/instances/%s/databases/%s", "some-project", "some-instance", "some-database")
-	client, err := NewClientWithConfig(ctx, formattedDatabase, ClientConfig{}, optsBase...)
-	if err != nil {
-		t.Fatal(err)
-	}
-
-	if err := executeSingerQuery(ctx, client.Single()); err != nil {
-		t.Fatal(err)
-	}
-
-	// The base server should not receive any requests.
-	if _, err := shouldHaveReceived(serverBase.TestSpanner, []interface{}{}); err != nil {
-		t.Fatal(err)
-	}
-
-	// The target server should receive requests.
-	if _, err = shouldHaveReceived(serverTarget.TestSpanner, []interface{}{
-		&sppb.BatchCreateSessionsRequest{},
-		&sppb.ExecuteSqlRequest{},
-	}); err != nil {
-		t.Fatal(err)
-	}
-}
-
-func TestClient_ResourceBasedRouting_WithoutEndpointsReturned(t *testing.T) {
-	os.Setenv("GOOGLE_CLOUD_SPANNER_ENABLE_RESOURCE_BASED_ROUTING", "true")
-	defer os.Setenv("GOOGLE_CLOUD_SPANNER_ENABLE_RESOURCE_BASED_ROUTING", "")
-
-	server, opts, serverTeardown := NewMockedSpannerInMemTestServer(t)
-	defer serverTeardown()
-
-	// Return an empty list of endpoints.
-	resps := []proto.Message{&instancepb.Instance{
-		EndpointUris: []string{},
-	}}
-	server.TestInstanceAdmin.SetResps(resps)
-
-	ctx := context.Background()
-	formattedDatabase := fmt.Sprintf("projects/%s/instances/%s/databases/%s", "some-project", "some-instance", "some-database")
-	client, err := NewClientWithConfig(ctx, formattedDatabase, ClientConfig{}, opts...)
-	if err != nil {
-		t.Fatal(err)
-	}
-
-	if err := executeSingerQuery(ctx, client.Single()); err != nil {
-		t.Fatal(err)
-	}
-
-	// Check if the request goes to the default endpoint.
-	if _, err := shouldHaveReceived(server.TestSpanner, []interface{}{
-		&sppb.BatchCreateSessionsRequest{},
-		&sppb.ExecuteSqlRequest{},
-	}); err != nil {
-		t.Fatal(err)
-	}
-}
-
-func TestClient_ResourceBasedRouting_WithPermissionDeniedError(t *testing.T) {
-	os.Setenv("GOOGLE_CLOUD_SPANNER_ENABLE_RESOURCE_BASED_ROUTING", "true")
-	defer os.Setenv("GOOGLE_CLOUD_SPANNER_ENABLE_RESOURCE_BASED_ROUTING", "")
-
-	server, opts, serverTeardown := NewMockedSpannerInMemTestServer(t)
-	defer serverTeardown()
-
-	server.TestInstanceAdmin.SetErr(status.Error(codes.PermissionDenied, "Permission Denied"))
-
-	ctx := context.Background()
-	formattedDatabase := fmt.Sprintf("projects/%s/instances/%s/databases/%s", "some-project", "some-instance", "some-database")
-	// `PermissionDeniedError` causes a warning message to be logged, which is expected.
-	// We set the output to be discarded to avoid spamming the log.
-	logger := log.New(ioutil.Discard, "", log.LstdFlags)
-	client, err := NewClientWithConfig(ctx, formattedDatabase, ClientConfig{logger: logger}, opts...)
-	if err != nil {
-		t.Fatal(err)
-	}
-
-	if err := executeSingerQuery(ctx, client.Single()); err != nil {
-		t.Fatal(err)
-	}
-
-	// Fallback to use the default endpoint when calling GetInstance() returns
-	// a PermissionDenied error.
-	if _, err := shouldHaveReceived(server.TestSpanner, []interface{}{
-		&sppb.BatchCreateSessionsRequest{},
-		&sppb.ExecuteSqlRequest{},
-	}); err != nil {
-		t.Fatal(err)
-	}
-}
-
-func TestClient_ResourceBasedRouting_WithUnavailableError(t *testing.T) {
-	os.Setenv("GOOGLE_CLOUD_SPANNER_ENABLE_RESOURCE_BASED_ROUTING", "true")
-	defer os.Setenv("GOOGLE_CLOUD_SPANNER_ENABLE_RESOURCE_BASED_ROUTING", "")
-
-	server, opts, serverTeardown := NewMockedSpannerInMemTestServer(t)
-	defer serverTeardown()
-
-	resps := []proto.Message{&instancepb.Instance{
-		EndpointUris: []string{},
-	}}
-	server.TestInstanceAdmin.SetResps(resps)
-	server.TestInstanceAdmin.SetErr(status.Error(codes.Unavailable, "Temporary unavailable"))
-
-	ctx := context.Background()
-	formattedDatabase := fmt.Sprintf("projects/%s/instances/%s/databases/%s", "some-project", "some-instance", "some-database")
-	_, err := NewClientWithConfig(ctx, formattedDatabase, ClientConfig{}, opts...)
-	// The first request will get an error and the server resets the error to nil,
-	// so the next request will be fine. Due to retrying, there is no errors.
-	if err != nil {
-		t.Fatal(err)
-	}
-}
-
-func TestClient_ResourceBasedRouting_WithInvalidArgumentError(t *testing.T) {
-	os.Setenv("GOOGLE_CLOUD_SPANNER_ENABLE_RESOURCE_BASED_ROUTING", "true")
-	defer os.Setenv("GOOGLE_CLOUD_SPANNER_ENABLE_RESOURCE_BASED_ROUTING", "")
-
-	server, opts, serverTeardown := NewMockedSpannerInMemTestServer(t)
-	defer serverTeardown()
-
-	server.TestInstanceAdmin.SetErr(status.Error(codes.InvalidArgument, "Invalid argument"))
-
-	ctx := context.Background()
-	formattedDatabase := fmt.Sprintf("projects/%s/instances/%s/databases/%s", "some-project", "some-instance", "some-database")
-	_, err := NewClientWithConfig(ctx, formattedDatabase, ClientConfig{}, opts...)
-
-	if status.Code(err) != codes.InvalidArgument {
-		t.Fatalf("got unexpected exception %v, expected InvalidArgument", err)
-	}
-}
-
 func TestClient_Single_QueryOptions(t *testing.T) {
 	for _, tt := range queryOptionsTestCases() {
 		t.Run(tt.name, func(t *testing.T) {
diff --git a/spanner/integration_test.go b/spanner/integration_test.go
index 77f22a4..3a647b5 100644
--- a/spanner/integration_test.go
+++ b/spanner/integration_test.go
@@ -492,50 +492,6 @@
 	}
 }
 
-// Test resource-based routing enabled.
-func TestIntegration_SingleUse_WithResourceBasedRouting(t *testing.T) {
-	os.Setenv("GOOGLE_CLOUD_SPANNER_ENABLE_RESOURCE_BASED_ROUTING", "true")
-	defer os.Setenv("GOOGLE_CLOUD_SPANNER_ENABLE_RESOURCE_BASED_ROUTING", "")
-
-	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute)
-	defer cancel()
-	// Set up testing environment.
-	client, _, cleanup := prepareIntegrationTest(ctx, t, DefaultSessionPoolConfig, singerDBStatements)
-	defer cleanup()
-
-	writes := []struct {
-		row []interface{}
-		ts  time.Time
-	}{
-		{row: []interface{}{1, "Marc", "Foo"}},
-		{row: []interface{}{2, "Tars", "Bar"}},
-		{row: []interface{}{3, "Alpha", "Beta"}},
-		{row: []interface{}{4, "Last", "End"}},
-	}
-	// Try to write four rows through the Apply API.
-	for i, w := range writes {
-		var err error
-		m := InsertOrUpdate("Singers",
-			[]string{"SingerId", "FirstName", "LastName"},
-			w.row)
-		if writes[i].ts, err = client.Apply(ctx, []*Mutation{m}, ApplyAtLeastOnce()); err != nil {
-			t.Fatal(err)
-		}
-	}
-
-	row, err := client.Single().ReadRow(ctx, "Singers", Key{3}, []string{"FirstName"})
-	if err != nil {
-		t.Errorf("SingleUse.ReadRow returns error %v, want nil", err)
-	}
-	var got string
-	if err := row.Column(0, &got); err != nil {
-		t.Errorf("row.Column returns error %v, want nil", err)
-	}
-	if want := "Alpha"; got != want {
-		t.Errorf("got %q, want %q", got, want)
-	}
-}
-
 // Test custom query options provided on query-level configuration.
 func TestIntegration_SingleUse_WithQueryOptions(t *testing.T) {
 	skipEmulatorTest(t)