runtime: Fail when GCE metadata is inaccessible

When V23_EXPECT_GOOGLE_COMPUTE_ENGINE is set and non-empty, the runtime
will fail to initialize when the GCE metadata server is inaccessible or
some of its data cannot be retrieved.

When V23_EXPECT_GOOGLE_COMPUTE_ENGINE is not set (or is empty), the
previous behavior is preserved, i.e. we give up after 1 second and
assume we're not on GCE.

Production services cannot function properly when the metadata isn't
there. It's better to abort immediately than to keep running in a bad
state.

While at it, clean up the code around cloud VM metadata.

Fixes https://github.com/vanadium/issues/issues/1267

Change-Id: I933f97179b994c83460b2efe6658c8f098689531
diff --git a/envvar.go b/envvar.go
index b768d61..051028c 100644
--- a/envvar.go
+++ b/envvar.go
@@ -42,6 +42,10 @@
 	// to the url of the OAuth identity provider used by the principal
 	// seekblessings command.
 	EnvOAuthIdentityProvider = "V23_OAUTH_IDENTITY_PROVIDER"
+
+	// When EnvExpectGoogleComputeEngine is set and non-empty, the runtime
+	// initialization will fail if the GCE metadata is inaccessible.
+	EnvExpectGoogleComputeEngine = "V23_EXPECT_GOOGLE_COMPUTE_ENGINE"
 )
 
 // EnvNamespaceRoots returns the set of namespace roots to be used by the
diff --git a/runtime/factories/gce/gce.go b/runtime/factories/gce/gce.go
deleted file mode 100644
index a78018f..0000000
--- a/runtime/factories/gce/gce.go
+++ /dev/null
@@ -1,80 +0,0 @@
-// Copyright 2015 The Vanadium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-// +build linux
-
-// Package gce implements a RuntimeFactory for binaries that only run on Google
-// Compute Engine (GCE).
-package gce
-
-import (
-	"flag"
-	"fmt"
-	"net"
-
-	"v.io/v23"
-	"v.io/v23/context"
-	"v.io/v23/flow"
-	"v.io/v23/rpc"
-
-	"v.io/x/lib/netstate"
-	"v.io/x/ref/lib/flags"
-	"v.io/x/ref/runtime/internal"
-	"v.io/x/ref/runtime/internal/gce"
-	"v.io/x/ref/runtime/internal/lib/appcycle"
-	grt "v.io/x/ref/runtime/internal/rt"
-	"v.io/x/ref/runtime/protocols/lib/websocket"
-	_ "v.io/x/ref/runtime/protocols/tcp"
-	_ "v.io/x/ref/runtime/protocols/ws"
-	_ "v.io/x/ref/runtime/protocols/wsh"
-)
-
-var commonFlags *flags.Flags
-
-func init() {
-	v23.RegisterRuntimeFactory(Init)
-	flow.RegisterUnknownProtocol("wsh", websocket.WSH{})
-	commonFlags = flags.CreateAndRegister(flag.CommandLine, flags.Runtime, flags.Listen)
-}
-
-func Init(ctx *context.T) (v23.Runtime, *context.T, v23.Shutdown, error) {
-	if err := internal.ParseFlagsAndConfigureGlobalLogger(commonFlags); err != nil {
-		return nil, nil, nil, err
-	}
-
-	if !gce.RunningOnGCE() {
-		return nil, nil, nil, fmt.Errorf("GCE profile used on a non-GCE system")
-	}
-
-	ac := appcycle.New()
-
-	lf := commonFlags.ListenFlags()
-	listenSpec := rpc.ListenSpec{
-		Addrs: rpc.ListenAddrs(lf.Addrs),
-		Proxy: lf.Proxy,
-	}
-
-	if ip, err := gce.ExternalIPAddress(); err != nil {
-		ac.Shutdown()
-		return nil, nil, nil, err
-	} else {
-		listenSpec.AddressChooser = netstate.AddressChooserFunc(func(network string, addrs []net.Addr) ([]net.Addr, error) {
-			return []net.Addr{netstate.NewNetAddr("wsh", ip.String())}, nil
-		})
-	}
-
-	runtime, ctx, shutdown, err := grt.Init(ctx, ac, nil, nil, nil, &listenSpec, nil, commonFlags.RuntimeFlags(), nil, 0)
-	if err != nil {
-		ac.Shutdown()
-		return nil, nil, nil, err
-	}
-
-	ctx.VI(1).Infof("Initializing GCE RuntimeFactory.")
-
-	runtimeFactoryShutdown := func() {
-		ac.Shutdown()
-		shutdown()
-	}
-	return runtime, ctx, runtimeFactoryShutdown, nil
-}
diff --git a/runtime/factories/roaming/roaming.go b/runtime/factories/roaming/roaming.go
index 32b596d..7c84342 100644
--- a/runtime/factories/roaming/roaming.go
+++ b/runtime/factories/roaming/roaming.go
@@ -50,6 +50,10 @@
 		return nil, nil, nil, err
 	}
 
+	if err := internal.InitCloudVM(); err != nil {
+		return nil, nil, nil, err
+	}
+
 	ac := appcycle.New()
 	discoveryFactory, err := dfactory.New(ctx)
 	if err != nil {
diff --git a/runtime/internal/address_chooser.go b/runtime/internal/address_chooser.go
index a317972..3a0ca64 100644
--- a/runtime/internal/address_chooser.go
+++ b/runtime/internal/address_chooser.go
@@ -6,57 +6,29 @@
 
 import (
 	"net"
-	"sync"
 
 	"v.io/v23/logging"
 	"v.io/v23/rpc"
 )
 
 type addressChooser struct {
-	logger               logging.Logger
-	gcePublicAddressOnce sync.Once
-	gcePublicAddress     net.Addr
-	ipChooser            IPAddressChooser
-}
-
-func (c *addressChooser) setGCEPublicAddress() {
-	c.gcePublicAddressOnce.Do(func() {
-		if ipaddr := GCEPublicAddress(c.logger); ipaddr != nil {
-			c.gcePublicAddress = ipaddr
-		}
-	})
+	logger    logging.Logger
+	ipChooser IPAddressChooser
 }
 
 func (c *addressChooser) ChooseAddresses(protocol string, candidates []net.Addr) ([]net.Addr, error) {
-	c.setGCEPublicAddress() // Blocks till the address is set
-	if c.gcePublicAddress == nil {
-		return c.ipChooser.ChooseAddresses(protocol, candidates)
+	if ipaddr := CloudVMPublicAddress(); ipaddr != nil {
+		return []net.Addr{ipaddr}, nil
 	}
-	return []net.Addr{c.gcePublicAddress}, nil
+	return c.ipChooser.ChooseAddresses(protocol, candidates)
 }
 
 // NewAddressChooser will return the public IP of process if the process is
-// is being hosted by a cloud service provider (e.g. Google Compute Engine,
+// being hosted by a cloud service provider (e.g. Google Compute Engine,
 // Amazon EC2), and if not will be the same as IPAddressChooser.
 func NewAddressChooser(logger logging.Logger) rpc.AddressChooser {
 	if HasPublicIP(logger) {
 		return IPAddressChooser{}
 	}
-	// Our address is private, so we test for running on GCE and for its 1:1 NAT
-	// configuration. GCEPublicAddress returns a non-nil addr if we are
-	// running on GCE/AWS.
-	//
-	// GCEPublicAddress can unforunately take up to 1 second to determine that the
-	// external address (see https://github.com/vanadium/issues/issues/776).
-	//
-	// So NewAddressChooser fires it up in a goroutine and returns immediately,
-	// thus avoiding any blockage till the AddressChooser is actually invoked.
-	//
-	// I apologize for the existence of this code! It is ugly, so if you have any
-	// suggestions please do share. Ideally, the operation to "detect whether the
-	// process is running under an Amazon EC2 instance" wouldn't block for a
-	// timeout of 1 second and we can do away with this mess.
-	ret := &addressChooser{logger: logger}
-	go ret.setGCEPublicAddress()
-	return ret
+	return &addressChooser{logger: logger}
 }
diff --git a/runtime/internal/cloudvm/cloud_android.go b/runtime/internal/cloudvm/cloud_android.go
new file mode 100644
index 0000000..9761d95
--- /dev/null
+++ b/runtime/internal/cloudvm/cloud_android.go
@@ -0,0 +1,30 @@
+// Copyright 2015 The Vanadium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// +build android
+
+package cloudvm
+
+import (
+	"net"
+	"time"
+)
+
+func InitGCE(time.Duration) {
+}
+
+func InitAWS(time.Duration) {
+}
+
+func RunningOnGCE() bool {
+	return false
+}
+
+func RunningOnAWS() bool {
+	return false
+}
+
+func ExternalIPAddress() net.IP {
+	return nil
+}
diff --git a/runtime/internal/cloudvm/cloud_linux.go b/runtime/internal/cloudvm/cloud_linux.go
new file mode 100644
index 0000000..ed9fa71
--- /dev/null
+++ b/runtime/internal/cloudvm/cloud_linux.go
@@ -0,0 +1,151 @@
+// Copyright 2015 The Vanadium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// +build linux,!android
+
+// Package cloudvm proides functions to test whether the current process is
+// running on Google Compute Engine or Amazon Web Services, and to extract
+// settings from this environment.
+package cloudvm
+
+import (
+	"fmt"
+	"io/ioutil"
+	"net"
+	"net/http"
+	"sync"
+	"time"
+
+	"v.io/x/ref/lib/stats"
+)
+
+// This URL returns the external IP address assigned to the local GCE instance.
+// If a HTTP GET request fails for any reason, this is not a GCE instance. If
+// the result of the GET request doesn't contain a "Metadata-Flavor: Google"
+// header, it is also not a GCE instance. The body of the document contains the
+// external IP address, if present. Otherwise, the body is empty.
+// See https://developers.google.com/compute/docs/metadata for details.
+const gceUrl = "http://metadata.google.internal/computeMetadata/v1/instance/network-interfaces/0/access-configs/0/external-ip"
+const awsUrl = "http://169.254.169.254/latest/meta-data/public-ipv4"
+
+var (
+	onceGCE    sync.Once
+	onceAWS    sync.Once
+	onGCE      bool
+	onAWS      bool
+	externalIP net.IP
+)
+
+func InitGCE(timeout time.Duration) {
+	onceGCE.Do(func() {
+		if onAWS {
+			return
+		}
+		gceTest(timeout)
+	})
+}
+
+func InitAWS(timeout time.Duration) {
+	onceAWS.Do(func() {
+		if onGCE {
+			return
+		}
+		awsTest(timeout)
+	})
+}
+
+func RunningOnGCE() bool {
+	return onGCE
+}
+
+func RunningOnAWS() bool {
+	return onAWS
+}
+
+// ExternalIPAddress returns the external IP address of this Google Compute
+// Engine or AWS instance, or nil if there is none. Must be called after
+// InitGCE / InitAWS.
+func ExternalIPAddress() net.IP {
+	return externalIP
+}
+
+func gceTest(timeout time.Duration) {
+	var err error
+	if externalIP, err = gceGetIP(gceUrl, timeout); err != nil {
+		return
+	}
+
+	vars := []struct {
+		name, url string
+	}{
+		{"system/gce/project-id", "http://metadata.google.internal/computeMetadata/v1/project/project-id"},
+		{"system/gce/zone", "http://metadata.google.internal/computeMetadata/v1/instance/zone"},
+	}
+	for _, v := range vars {
+		body, err := gceGetMeta(v.url, timeout)
+		if err != nil || body == "" {
+			return
+		}
+		stats.NewString(v.name).Set(body)
+	}
+	onGCE = true
+}
+
+func gceGetIP(url string, timeout time.Duration) (net.IP, error) {
+	body, err := gceGetMeta(url, timeout)
+	if err != nil {
+		return nil, err
+	}
+	return net.ParseIP(body), nil
+}
+
+func gceGetMeta(url string, timeout time.Duration) (string, error) {
+	client := &http.Client{Timeout: timeout}
+	req, err := http.NewRequest("GET", url, nil)
+	if err != nil {
+		return "", err
+	}
+	req.Header.Add("Metadata-Flavor", "Google")
+	resp, err := client.Do(req)
+	if err != nil {
+		return "", err
+	}
+	defer resp.Body.Close()
+	if resp.StatusCode != 200 {
+		return "", fmt.Errorf("http error: %d", resp.StatusCode)
+	}
+	if flavor := resp.Header["Metadata-Flavor"]; len(flavor) != 1 || flavor[0] != "Google" {
+		return "", fmt.Errorf("unexpected http header: %q", flavor)
+	}
+	body, err := ioutil.ReadAll(resp.Body)
+	if err != nil {
+		return "", err
+	}
+	return string(body), nil
+}
+
+func awsTest(timeout time.Duration) {
+	client := &http.Client{Timeout: timeout}
+	req, err := http.NewRequest("GET", awsUrl, nil)
+	if err != nil {
+		return
+	}
+	resp, err := client.Do(req)
+	if err != nil {
+		return
+	}
+	defer resp.Body.Close()
+	if resp.StatusCode != 200 {
+		return
+	}
+	if server := resp.Header["Server"]; len(server) != 1 || server[0] != "EC2ws" {
+		return
+	}
+	body, err := ioutil.ReadAll(resp.Body)
+	if err != nil {
+		return
+	}
+	externalIP = net.ParseIP(string(body))
+	onAWS = true
+}
diff --git a/runtime/internal/gce/gce_other.go b/runtime/internal/cloudvm/cloud_other.go
similarity index 94%
rename from runtime/internal/gce/gce_other.go
rename to runtime/internal/cloudvm/cloud_other.go
index bfc9198..a4d8f58 100644
--- a/runtime/internal/gce/gce_other.go
+++ b/runtime/internal/cloudvm/cloud_other.go
@@ -4,7 +4,7 @@
 
 // +build !linux
 
-package gce
+package cloudvm
 
 // This package is a no-op on non-linux systems, but we still need at least
 // one file in the package to keep go happy!
diff --git a/runtime/internal/gce/gce_test.go b/runtime/internal/cloudvm/cloud_test.go
similarity index 81%
rename from runtime/internal/gce/gce_test.go
rename to runtime/internal/cloudvm/cloud_test.go
index 0cfe65e..45f86f4 100644
--- a/runtime/internal/gce/gce_test.go
+++ b/runtime/internal/cloudvm/cloud_test.go
@@ -4,13 +4,14 @@
 
 // +build linux,!android
 
-package gce
+package cloudvm
 
 import (
 	"fmt"
 	"net"
 	"net/http"
 	"testing"
+	"time"
 )
 
 func startServer(t *testing.T) (net.Addr, func()) {
@@ -53,16 +54,16 @@
 	defer stop()
 	baseURL := "http://" + addr.String()
 
-	if ip, err := gceTest(baseURL + "/404"); err == nil || ip != nil {
+	if ip, err := gceGetIP(baseURL+"/404", time.Second); err == nil || ip != nil {
 		t.Errorf("expected error, but not got nil")
 	}
-	if ip, err := gceTest(baseURL + "/200_not_gce"); err == nil || ip != nil {
+	if ip, err := gceGetIP(baseURL+"/200_not_gce", time.Second); err == nil || ip != nil {
 		t.Errorf("expected error, but not got nil")
 	}
-	if ip, err := gceTest(baseURL + "/gce_no_ip"); err != nil || ip != nil {
+	if ip, err := gceGetIP(baseURL+"/gce_no_ip", time.Second); err != nil || ip != nil {
 		t.Errorf("Unexpected result. Got (%v, %v), want nil:nil", ip, err)
 	}
-	if ip, err := gceTest(baseURL + "/gce_with_ip"); err != nil || ip.String() != "1.2.3.4" {
+	if ip, err := gceGetIP(baseURL+"/gce_with_ip", time.Second); err != nil || ip.String() != "1.2.3.4" {
 		t.Errorf("Unexpected result. Got (%v, %v), want nil:1.2.3.4", ip, err)
 	}
 }
diff --git a/runtime/internal/cloudvm_linux.go b/runtime/internal/cloudvm_linux.go
new file mode 100644
index 0000000..98cca46
--- /dev/null
+++ b/runtime/internal/cloudvm_linux.go
@@ -0,0 +1,77 @@
+// Copyright 2015 The Vanadium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// +build linux
+
+package internal
+
+import (
+	"net"
+	"os"
+	"sync"
+	"time"
+
+	"v.io/v23/verror"
+
+	"v.io/x/ref"
+	"v.io/x/ref/runtime/internal/cloudvm"
+)
+
+const pkgPath = "v.io/x/ref/runtime/internal"
+
+var (
+	errNotGoogleComputeEngine = verror.Register(pkgPath+".errNotGoogleComputeEngine", verror.NoRetry, "{1:}{2:} failed to access gce metadata")
+
+	initialized bool
+	mu          sync.Mutex
+)
+
+// InitCloudVM initializes the CloudVM metadata.
+//
+// If EnvExpectGoogleComputeEngine is set, this function returns after the
+// initialization is done. Otherwise, the initialization is done asynchronously
+// and future calls to CloudVMPublicAddress() will block until the
+// initialization is complete.
+//
+// Returns an error if EnvExpectGoogleComputeEngine is set and the metadata
+// server is inaccessible.
+func InitCloudVM() error {
+	mu.Lock()
+	if initialized {
+		mu.Unlock()
+		return nil
+	}
+	initialized = true
+	if os.Getenv(ref.EnvExpectGoogleComputeEngine) != "" {
+		defer mu.Unlock()
+		cloudvm.InitGCE(30 * time.Second)
+		if !cloudvm.RunningOnGCE() {
+			return verror.New(errNotGoogleComputeEngine, nil)
+		}
+		return nil
+	}
+	go func() {
+		defer mu.Unlock()
+		cloudvm.InitGCE(time.Second)
+		cloudvm.InitAWS(time.Second)
+	}()
+	return nil
+}
+
+// CloudVMPublicAddress returns the public IP address of the Cloud VM instance
+// it is run from, or nil if run from anywhere else. The returned address is the
+// public address of a 1:1 NAT tunnel to this host.
+func CloudVMPublicAddress() *net.IPAddr {
+	mu.Lock()
+	defer mu.Unlock()
+	if !cloudvm.RunningOnGCE() && !cloudvm.RunningOnAWS() {
+		return nil
+	}
+	// Determine the IP address from VM's metadata
+	if ip := cloudvm.ExternalIPAddress(); ip != nil {
+		// 1:1 NAT case, our network config will not change.
+		return &net.IPAddr{IP: ip}
+	}
+	return nil
+}
diff --git a/runtime/internal/cloudvm_other.go b/runtime/internal/cloudvm_other.go
new file mode 100644
index 0000000..cdb707c
--- /dev/null
+++ b/runtime/internal/cloudvm_other.go
@@ -0,0 +1,23 @@
+// Copyright 2015 The Vanadium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// +build !linux
+
+package internal
+
+import (
+	"net"
+)
+
+// InitCloudVM initializes the CloudVM metadata.
+func InitCloudVM() error {
+	return nil
+}
+
+// CloudVMPublicAddress returns the public IP address of the Cloud VM instance
+// it is run from, or nil if run from anywhere else. The returned address is the
+// public address of a 1:1 NAT tunnel to this host.
+func CloudVMPublicAddress() *net.IPAddr {
+	return nil
+}
diff --git a/runtime/internal/gce/gce_android.go b/runtime/internal/gce/gce_android.go
deleted file mode 100644
index ce22de5..0000000
--- a/runtime/internal/gce/gce_android.go
+++ /dev/null
@@ -1,19 +0,0 @@
-// Copyright 2015 The Vanadium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-// +build android
-
-package gce
-
-import (
-	"net"
-)
-
-func RunningOnGCE() bool {
-	return false
-}
-
-func ExternalIPAddress() (net.IP, error) {
-	panic("The GCE profile was unexpectedly used with android.")
-}
diff --git a/runtime/internal/gce/gce_linux.go b/runtime/internal/gce/gce_linux.go
deleted file mode 100644
index 73176ad..0000000
--- a/runtime/internal/gce/gce_linux.go
+++ /dev/null
@@ -1,141 +0,0 @@
-// Copyright 2015 The Vanadium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-// +build linux,!android
-
-// Package gce functions to test whether the current process is running on
-// Google Compute Engine, and to extract settings from this environment.
-// Any server that knows it will only ever run on GCE can import this Profile,
-// but in most cases, other Profiles will use the utility routines provided
-// by it to test to ensure that they are running on GCE and to obtain
-// metadata directly from its service APIs.
-//
-// TODO -- rename the package to "CloudVM" rather than gce, as it handles both gce
-// and AWS, and perhaps, in future, other cases of NAT'ed VMs.
-package gce
-
-import (
-	"fmt"
-	"io/ioutil"
-	"net"
-	"net/http"
-	"sync"
-	"time"
-
-	"v.io/x/ref/lib/stats"
-)
-
-// This URL returns the external IP address assigned to the local GCE instance.
-// If a HTTP GET request fails for any reason, this is not a GCE instance. If
-// the result of the GET request doesn't contain a "Metadata-Flavor: Google"
-// header, it is also not a GCE instance. The body of the document contains the
-// external IP address, if present. Otherwise, the body is empty.
-// See https://developers.google.com/compute/docs/metadata for details.
-const gceUrl = "http://metadata.google.internal/computeMetadata/v1/instance/network-interfaces/0/access-configs/0/external-ip"
-const awsUrl = "http://169.254.169.254/latest/meta-data/public-ipv4"
-
-// How long to wait for the HTTP request to return.
-const timeout = time.Second
-
-var (
-	once       sync.Once
-	isGCEErr   error
-	externalIP net.IP
-)
-
-// RunningOnGCE returns true if the current process is running
-// on a Google Compute Engine instance.
-func RunningOnGCE() bool {
-	once.Do(func() {
-		externalIP, isGCEErr = gceTest(gceUrl)
-		if isGCEErr == nil {
-			gceExportVariables()
-		} else {
-			// try AWS instead
-			externalIP, isGCEErr = awsTest(awsUrl)
-		}
-	})
-	return isGCEErr == nil
-}
-
-// ExternalIPAddress returns the external IP address of this
-// Google Compute Engine instance, or nil if there is none. Must be
-// called after RunningOnGCE.
-func ExternalIPAddress() (net.IP, error) {
-	return externalIP, isGCEErr
-}
-
-func gceTest(url string) (net.IP, error) {
-	body, err := gceGetMeta(url, timeout)
-	if err != nil {
-		return nil, err
-	}
-	return net.ParseIP(body), nil
-}
-
-func gceExportVariables() {
-	vars := []struct {
-		name, url string
-	}{
-		{"system/gce/project-id", "http://metadata.google.internal/computeMetadata/v1/project/project-id"},
-		{"system/gce/zone", "http://metadata.google.internal/computeMetadata/v1/instance/zone"},
-	}
-	for _, v := range vars {
-		// At this point, we know we're on GCE. So, we might as well use a longer timeout.
-		if body, err := gceGetMeta(v.url, 10*time.Second); err == nil {
-			stats.NewString(v.name).Set(body)
-		} else {
-			stats.NewString(v.name).Set("unknown")
-		}
-	}
-}
-
-func gceGetMeta(url string, timeout time.Duration) (string, error) {
-	client := &http.Client{Timeout: timeout}
-	req, err := http.NewRequest("GET", url, nil)
-	if err != nil {
-		return "", err
-	}
-	req.Header.Add("Metadata-Flavor", "Google")
-	resp, err := client.Do(req)
-	if err != nil {
-		return "", err
-	}
-	defer resp.Body.Close()
-	if resp.StatusCode != 200 {
-		return "", fmt.Errorf("http error: %d", resp.StatusCode)
-	}
-	if flavor := resp.Header["Metadata-Flavor"]; len(flavor) != 1 || flavor[0] != "Google" {
-		return "", fmt.Errorf("unexpected http header: %q", flavor)
-	}
-	body, err := ioutil.ReadAll(resp.Body)
-	if err != nil {
-		return "", err
-	}
-	return string(body), nil
-}
-
-func awsTest(url string) (net.IP, error) {
-	client := &http.Client{Timeout: timeout}
-	req, err := http.NewRequest("GET", url, nil)
-	if err != nil {
-		return nil, err
-	}
-	resp, err := client.Do(req)
-	if err != nil {
-		return nil, err
-	}
-	defer resp.Body.Close()
-	if resp.StatusCode != 200 {
-		return nil, fmt.Errorf("http error: %d", resp.StatusCode)
-	}
-	if server := resp.Header["Server"]; len(server) != 1 || server[0] != "EC2ws" {
-		return nil, fmt.Errorf("unexpected http Server header: %q", server)
-	}
-	body, err := ioutil.ReadAll(resp.Body)
-	if err != nil {
-		return nil, err
-	}
-	return net.ParseIP(string(body)), nil
-}
diff --git a/runtime/internal/gce_linux.go b/runtime/internal/gce_linux.go
deleted file mode 100644
index aaf489f..0000000
--- a/runtime/internal/gce_linux.go
+++ /dev/null
@@ -1,36 +0,0 @@
-// Copyright 2015 The Vanadium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-// +build linux
-
-package internal
-
-import (
-	"net"
-
-	"v.io/v23/logging"
-
-	"v.io/x/ref/runtime/internal/gce"
-)
-
-// GCEPublicAddress returns the public IP address of the GCE instance
-// it is run from, or nil if run from anywhere else. The returned address
-// is the public address of a 1:1 NAT tunnel to this host.
-func GCEPublicAddress(log logging.Logger) *net.IPAddr {
-	if !gce.RunningOnGCE() {
-		return nil
-	}
-	var pub *net.IPAddr
-	// Determine the IP address from GCE's metadata
-	if ip, err := gce.ExternalIPAddress(); err != nil {
-		log.Infof("failed to query GCE metadata: %s", err)
-	} else {
-		// 1:1 NAT case, our network config will not change.
-		pub = &net.IPAddr{IP: ip}
-	}
-	if pub == nil {
-		log.Infof("failed to determine public IP address to publish with")
-	}
-	return pub
-}
diff --git a/runtime/internal/gce_other.go b/runtime/internal/gce_other.go
deleted file mode 100644
index 4aea338..0000000
--- a/runtime/internal/gce_other.go
+++ /dev/null
@@ -1,20 +0,0 @@
-// Copyright 2015 The Vanadium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-// +build !linux
-
-package internal
-
-import (
-	"net"
-
-	"v.io/v23/logging"
-)
-
-// GCEPublicAddress returns the public IP address of the GCE instance
-// it is run from, or nil if run from anywhere else. The returned address
-// is the public address of a 1:1 NAT tunnel to this host.
-func GCEPublicAddress(logging.Logger) *net.IPAddr {
-	return nil
-}