Implementation of the xds_experimental resolver. (#2967)

This resolver doesn't do much at this point, except returning an empty
address list and a hard-coded service config which picks the xds
balancer with a round_robin child policy.

Also moved the xdsConfig struct to the xds/internal package and exported
it as LBConfig, so that both the resolver and the balancer packages can
make use of this.
diff --git a/vet.sh b/vet.sh
index 661e1e1..2bdfbc8 100755
--- a/vet.sh
+++ b/vet.sh
@@ -111,6 +111,7 @@
 google.golang.org/grpc/balancer/grpclb/grpclb_remote_balancer.go:SA1019
 google.golang.org/grpc/balancer/roundrobin/roundrobin_test.go:SA1019
 google.golang.org/grpc/xds/internal/balancer/edsbalancer/balancergroup.go:SA1019
+google.golang.org/grpc/xds/internal/resolver/xds_resolver.go:SA1019
 google.golang.org/grpc/xds/internal/balancer/xds.go:SA1019
 google.golang.org/grpc/xds/internal/balancer/xds_client.go:SA1019
 google.golang.org/grpc/balancer_conn_wrappers.go:SA1019
diff --git a/xds/experimental/xds_experimental.go b/xds/experimental/xds_experimental.go
index 7477ea3..ff722ad 100644
--- a/xds/experimental/xds_experimental.go
+++ b/xds/experimental/xds_experimental.go
@@ -24,9 +24,12 @@
 
 import (
 	"google.golang.org/grpc/balancer"
+	"google.golang.org/grpc/resolver"
 	xdsbalancer "google.golang.org/grpc/xds/internal/balancer"
+	xdsresolver "google.golang.org/grpc/xds/internal/resolver"
 )
 
 func init() {
+	resolver.Register(xdsresolver.NewBuilder())
 	balancer.Register(xdsbalancer.NewBalancerBuilder())
 }
diff --git a/xds/internal/balancer/xds.go b/xds/internal/balancer/xds.go
index 4da30ae..3f80d41 100644
--- a/xds/internal/balancer/xds.go
+++ b/xds/internal/balancer/xds.go
@@ -33,6 +33,7 @@
 	"google.golang.org/grpc/grpclog"
 	"google.golang.org/grpc/resolver"
 	"google.golang.org/grpc/serviceconfig"
+	xdsinternal "google.golang.org/grpc/xds/internal"
 	"google.golang.org/grpc/xds/internal/balancer/edsbalancer"
 	"google.golang.org/grpc/xds/internal/balancer/lrs"
 	cdspb "google.golang.org/grpc/xds/internal/proto/envoy/api/v2/cds"
@@ -89,7 +90,7 @@
 }
 
 func (b *xdsBalancerBuilder) ParseConfig(c json.RawMessage) (serviceconfig.LoadBalancingConfig, error) {
-	var cfg xdsConfig
+	var cfg xdsinternal.LBConfig
 	if err := json.Unmarshal(c, &cfg); err != nil {
 		return nil, fmt.Errorf("unable to unmarshal balancer config %s into xds config", string(c))
 	}
@@ -130,15 +131,15 @@
 	timer           *time.Timer
 	noSubConnAlert  <-chan struct{}
 
-	client           *client    // may change when passed a different service config
-	config           *xdsConfig // may change when passed a different service config
+	client           *client               // may change when passed a different service config
+	config           *xdsinternal.LBConfig // may change when passed a different service config
 	xdsLB            edsBalancerInterface
 	fallbackLB       balancer.Balancer
 	fallbackInitData *resolver.State // may change when HandleResolved address is called
 	loadStore        lrs.Store
 }
 
-func (x *xdsBalancer) startNewXDSClient(u *xdsConfig) {
+func (x *xdsBalancer) startNewXDSClient(u *xdsinternal.LBConfig) {
 	// If the xdsBalancer is in startup stage, then we need to apply the startup timeout for the first
 	// xdsClient to get a response from the traffic director.
 	if x.startup {
@@ -237,7 +238,7 @@
 			}
 		}
 	case *balancer.ClientConnState:
-		cfg, _ := u.BalancerConfig.(*xdsConfig)
+		cfg, _ := u.BalancerConfig.(*xdsinternal.LBConfig)
 		if cfg == nil {
 			// service config parsing failed. should never happen.
 			return
@@ -497,16 +498,16 @@
 	}
 }
 
-func (x *xdsBalancer) buildFallBackBalancer(c *xdsConfig) {
+func (x *xdsBalancer) buildFallBackBalancer(c *xdsinternal.LBConfig) {
 	if c.FallBackPolicy == nil {
-		x.buildFallBackBalancer(&xdsConfig{
-			FallBackPolicy: &loadBalancingConfig{
+		x.buildFallBackBalancer(&xdsinternal.LBConfig{
+			FallBackPolicy: &xdsinternal.LoadBalancingConfig{
 				Name: "round_robin",
 			},
 		})
 		return
 	}
-	// builder will always be non-nil, since when parse JSON into xdsConfig, we check whether the specified
+	// builder will always be non-nil, since when parse JSON into xdsinternal.LBConfig, we check whether the specified
 	// balancer is registered or not.
 	builder := balancer.Get(c.FallBackPolicy.Name)
 
@@ -566,77 +567,3 @@
 	}
 	return timer
 }
-
-type xdsConfig struct {
-	serviceconfig.LoadBalancingConfig
-	BalancerName   string
-	ChildPolicy    *loadBalancingConfig
-	FallBackPolicy *loadBalancingConfig
-}
-
-// When unmarshalling json to xdsConfig, we iterate through the childPolicy/fallbackPolicy lists
-// and select the first LB policy which has been registered to be stored in the returned xdsConfig.
-func (p *xdsConfig) UnmarshalJSON(data []byte) error {
-	var val map[string]json.RawMessage
-	if err := json.Unmarshal(data, &val); err != nil {
-		return err
-	}
-	for k, v := range val {
-		switch k {
-		case "balancerName":
-			if err := json.Unmarshal(v, &p.BalancerName); err != nil {
-				return err
-			}
-		case "childPolicy":
-			var lbcfgs []*loadBalancingConfig
-			if err := json.Unmarshal(v, &lbcfgs); err != nil {
-				return err
-			}
-			for _, lbcfg := range lbcfgs {
-				if balancer.Get(lbcfg.Name) != nil {
-					p.ChildPolicy = lbcfg
-					break
-				}
-			}
-		case "fallbackPolicy":
-			var lbcfgs []*loadBalancingConfig
-			if err := json.Unmarshal(v, &lbcfgs); err != nil {
-				return err
-			}
-			for _, lbcfg := range lbcfgs {
-				if balancer.Get(lbcfg.Name) != nil {
-					p.FallBackPolicy = lbcfg
-					break
-				}
-			}
-		}
-	}
-	return nil
-}
-
-func (p *xdsConfig) MarshalJSON() ([]byte, error) {
-	return nil, nil
-}
-
-type loadBalancingConfig struct {
-	Name   string
-	Config json.RawMessage
-}
-
-func (l *loadBalancingConfig) MarshalJSON() ([]byte, error) {
-	m := make(map[string]json.RawMessage)
-	m[l.Name] = l.Config
-	return json.Marshal(m)
-}
-
-func (l *loadBalancingConfig) UnmarshalJSON(data []byte) error {
-	var cfg map[string]json.RawMessage
-	if err := json.Unmarshal(data, &cfg); err != nil {
-		return err
-	}
-	for name, config := range cfg {
-		l.Name = name
-		l.Config = config
-	}
-	return nil
-}
diff --git a/xds/internal/balancer/xds_lrs_test.go b/xds/internal/balancer/xds_lrs_test.go
index 6fe8527..77db370 100644
--- a/xds/internal/balancer/xds_lrs_test.go
+++ b/xds/internal/balancer/xds_lrs_test.go
@@ -33,6 +33,7 @@
 	"google.golang.org/grpc/resolver"
 	"google.golang.org/grpc/status"
 	"google.golang.org/grpc/xds/internal"
+	xdsinternal "google.golang.org/grpc/xds/internal"
 	basepb "google.golang.org/grpc/xds/internal/proto/envoy/api/v2/core/base"
 	lrsgrpc "google.golang.org/grpc/xds/internal/proto/envoy/service/load_stats/v2/lrs"
 	lrspb "google.golang.org/grpc/xds/internal/proto/envoy/service/load_stats/v2/lrs"
@@ -112,9 +113,9 @@
 		Nanos:   intervalNano,
 	}
 
-	cfg := &xdsConfig{
+	cfg := &xdsinternal.LBConfig{
 		BalancerName: addr,
-		ChildPolicy:  &loadBalancingConfig{Name: fakeBalancerA}, // Set this to skip cds.
+		ChildPolicy:  &xdsinternal.LoadBalancingConfig{Name: fakeBalancerA}, // Set this to skip cds.
 	}
 	lb.UpdateClientConnState(balancer.ClientConnState{BalancerConfig: cfg})
 	td.sendResp(&response{resp: testEDSRespWithoutEndpoints})
diff --git a/xds/internal/balancer/xds_test.go b/xds/internal/balancer/xds_test.go
index 85bd56a..5b344aa 100644
--- a/xds/internal/balancer/xds_test.go
+++ b/xds/internal/balancer/xds_test.go
@@ -31,6 +31,7 @@
 	"google.golang.org/grpc/internal/grpctest"
 	"google.golang.org/grpc/internal/leakcheck"
 	"google.golang.org/grpc/resolver"
+	xdsinternal "google.golang.org/grpc/xds/internal"
 	"google.golang.org/grpc/xds/internal/balancer/lrs"
 	discoverypb "google.golang.org/grpc/xds/internal/proto/envoy/api/v2/discovery"
 	edspb "google.golang.org/grpc/xds/internal/proto/envoy/api/v2/eds"
@@ -62,10 +63,10 @@
 
 var (
 	testBalancerNameFooBar = "foo.bar"
-	testLBConfigFooBar     = &xdsConfig{
+	testLBConfigFooBar     = &xdsinternal.LBConfig{
 		BalancerName:   testBalancerNameFooBar,
-		ChildPolicy:    &loadBalancingConfig{Name: fakeBalancerA},
-		FallBackPolicy: &loadBalancingConfig{Name: fakeBalancerA},
+		ChildPolicy:    &xdsinternal.LoadBalancingConfig{Name: fakeBalancerA},
+		FallBackPolicy: &xdsinternal.LoadBalancingConfig{Name: fakeBalancerA},
 	}
 
 	specialAddrForBalancerA = resolver.Address{Addr: "this.is.balancer.A"}
@@ -178,8 +179,8 @@
 type fakeEDSBalancer struct {
 	cc                 balancer.ClientConn
 	edsChan            chan *edspb.ClusterLoadAssignment
-	childPolicy        chan *loadBalancingConfig
-	fallbackPolicy     chan *loadBalancingConfig
+	childPolicy        chan *xdsinternal.LoadBalancingConfig
+	fallbackPolicy     chan *xdsinternal.LoadBalancingConfig
 	subconnStateChange chan *scStateChange
 	loadStore          lrs.Store
 }
@@ -199,7 +200,7 @@
 }
 
 func (f *fakeEDSBalancer) HandleChildPolicy(name string, config json.RawMessage) {
-	f.childPolicy <- &loadBalancingConfig{
+	f.childPolicy <- &xdsinternal.LoadBalancingConfig{
 		Name:   name,
 		Config: config,
 	}
@@ -209,8 +210,8 @@
 	lb := &fakeEDSBalancer{
 		cc:                 cc,
 		edsChan:            make(chan *edspb.ClusterLoadAssignment, 10),
-		childPolicy:        make(chan *loadBalancingConfig, 10),
-		fallbackPolicy:     make(chan *loadBalancingConfig, 10),
+		childPolicy:        make(chan *xdsinternal.LoadBalancingConfig, 10),
+		fallbackPolicy:     make(chan *xdsinternal.LoadBalancingConfig, 10),
 		subconnStateChange: make(chan *scStateChange, 10),
 		loadStore:          loadStore,
 	}
@@ -308,10 +309,10 @@
 	for i := 0; i < 2; i++ {
 		addr, td, _, cleanup := setupServer(t)
 		cleanups = append(cleanups, cleanup)
-		workingLBConfig := &xdsConfig{
+		workingLBConfig := &xdsinternal.LBConfig{
 			BalancerName:   addr,
-			ChildPolicy:    &loadBalancingConfig{Name: fakeBalancerA},
-			FallBackPolicy: &loadBalancingConfig{Name: fakeBalancerA},
+			ChildPolicy:    &xdsinternal.LoadBalancingConfig{Name: fakeBalancerA},
+			FallBackPolicy: &xdsinternal.LoadBalancingConfig{Name: fakeBalancerA},
 		}
 		lb.UpdateClientConnState(balancer.ClientConnState{
 			ResolverState:  resolver.State{Addresses: addrs},
@@ -364,39 +365,39 @@
 		}
 	}()
 	for _, test := range []struct {
-		cfg                 *xdsConfig
+		cfg                 *xdsinternal.LBConfig
 		responseToSend      *discoverypb.DiscoveryResponse
-		expectedChildPolicy *loadBalancingConfig
+		expectedChildPolicy *xdsinternal.LoadBalancingConfig
 	}{
 		{
-			cfg: &xdsConfig{
-				ChildPolicy: &loadBalancingConfig{
+			cfg: &xdsinternal.LBConfig{
+				ChildPolicy: &xdsinternal.LoadBalancingConfig{
 					Name:   fakeBalancerA,
 					Config: json.RawMessage("{}"),
 				},
 			},
 			responseToSend: testEDSRespWithoutEndpoints,
-			expectedChildPolicy: &loadBalancingConfig{
+			expectedChildPolicy: &xdsinternal.LoadBalancingConfig{
 				Name:   string(fakeBalancerA),
 				Config: json.RawMessage(`{}`),
 			},
 		},
 		{
-			cfg: &xdsConfig{
-				ChildPolicy: &loadBalancingConfig{
+			cfg: &xdsinternal.LBConfig{
+				ChildPolicy: &xdsinternal.LoadBalancingConfig{
 					Name:   fakeBalancerB,
 					Config: json.RawMessage("{}"),
 				},
 			},
-			expectedChildPolicy: &loadBalancingConfig{
+			expectedChildPolicy: &xdsinternal.LoadBalancingConfig{
 				Name:   string(fakeBalancerB),
 				Config: json.RawMessage(`{}`),
 			},
 		},
 		{
-			cfg:            &xdsConfig{},
+			cfg:            &xdsinternal.LBConfig{},
 			responseToSend: testCDSResp,
-			expectedChildPolicy: &loadBalancingConfig{
+			expectedChildPolicy: &xdsinternal.LoadBalancingConfig{
 				Name: "ROUND_ROBIN",
 			},
 		},
@@ -449,16 +450,16 @@
 
 	addr, td, _, cleanup := setupServer(t)
 
-	cfg := xdsConfig{
+	cfg := xdsinternal.LBConfig{
 		BalancerName:   addr,
-		ChildPolicy:    &loadBalancingConfig{Name: fakeBalancerA},
-		FallBackPolicy: &loadBalancingConfig{Name: fakeBalancerA},
+		ChildPolicy:    &xdsinternal.LoadBalancingConfig{Name: fakeBalancerA},
+		FallBackPolicy: &xdsinternal.LoadBalancingConfig{Name: fakeBalancerA},
 	}
 	lb.UpdateClientConnState(balancer.ClientConnState{BalancerConfig: &cfg})
 
 	addrs := []resolver.Address{{Addr: "1.1.1.1:10001"}, {Addr: "2.2.2.2:10002"}, {Addr: "3.3.3.3:10003"}}
 	cfg2 := cfg
-	cfg2.FallBackPolicy = &loadBalancingConfig{Name: fakeBalancerB}
+	cfg2.FallBackPolicy = &xdsinternal.LoadBalancingConfig{Name: fakeBalancerB}
 	lb.UpdateClientConnState(balancer.ClientConnState{
 		ResolverState:  resolver.State{Addresses: addrs},
 		BalancerConfig: &cfg2,
@@ -490,7 +491,7 @@
 	}
 
 	cfg3 := cfg
-	cfg3.FallBackPolicy = &loadBalancingConfig{Name: fakeBalancerA}
+	cfg3.FallBackPolicy = &xdsinternal.LoadBalancingConfig{Name: fakeBalancerA}
 	lb.UpdateClientConnState(balancer.ClientConnState{
 		ResolverState:  resolver.State{Addresses: addrs},
 		BalancerConfig: &cfg3,
@@ -524,10 +525,10 @@
 
 	addr, td, _, cleanup := setupServer(t)
 	defer cleanup()
-	cfg := &xdsConfig{
+	cfg := &xdsinternal.LBConfig{
 		BalancerName:   addr,
-		ChildPolicy:    &loadBalancingConfig{Name: fakeBalancerA},
-		FallBackPolicy: &loadBalancingConfig{Name: fakeBalancerA},
+		ChildPolicy:    &xdsinternal.LoadBalancingConfig{Name: fakeBalancerA},
+		FallBackPolicy: &xdsinternal.LoadBalancingConfig{Name: fakeBalancerA},
 	}
 	lb.UpdateClientConnState(balancer.ClientConnState{BalancerConfig: cfg})
 
@@ -602,10 +603,10 @@
 
 	addr, td, _, cleanup := setupServer(t)
 	defer cleanup()
-	cfg := &xdsConfig{
+	cfg := &xdsinternal.LBConfig{
 		BalancerName:   addr,
-		ChildPolicy:    &loadBalancingConfig{Name: fakeBalancerA},
-		FallBackPolicy: &loadBalancingConfig{Name: fakeBalancerA},
+		ChildPolicy:    &xdsinternal.LoadBalancingConfig{Name: fakeBalancerA},
+		FallBackPolicy: &xdsinternal.LoadBalancingConfig{Name: fakeBalancerA},
 	}
 	lb.UpdateClientConnState(balancer.ClientConnState{BalancerConfig: cfg})
 
@@ -673,12 +674,12 @@
 	if err != nil {
 		t.Fatalf("unable to unmarshal balancer config into xds config: %v", err)
 	}
-	xdsCfg := cfg.(*xdsConfig)
-	wantChildPolicy := &loadBalancingConfig{Name: string(fakeBalancerA), Config: json.RawMessage(`{}`)}
+	xdsCfg := cfg.(*xdsinternal.LBConfig)
+	wantChildPolicy := &xdsinternal.LoadBalancingConfig{Name: string(fakeBalancerA), Config: json.RawMessage(`{}`)}
 	if !reflect.DeepEqual(xdsCfg.ChildPolicy, wantChildPolicy) {
 		t.Fatalf("got child policy %v, want %v", xdsCfg.ChildPolicy, wantChildPolicy)
 	}
-	wantFallbackPolicy := &loadBalancingConfig{Name: string(fakeBalancerB), Config: json.RawMessage(`{}`)}
+	wantFallbackPolicy := &xdsinternal.LoadBalancingConfig{Name: string(fakeBalancerB), Config: json.RawMessage(`{}`)}
 	if !reflect.DeepEqual(xdsCfg.FallBackPolicy, wantFallbackPolicy) {
 		t.Fatalf("got fallback policy %v, want %v", xdsCfg.FallBackPolicy, wantFallbackPolicy)
 	}
@@ -688,18 +689,18 @@
 	tests := []struct {
 		name string
 		s    string
-		want *xdsConfig
+		want *xdsinternal.LBConfig
 	}{
 		{
 			name: "empty",
 			s:    "{}",
-			want: &xdsConfig{},
+			want: &xdsinternal.LBConfig{},
 		},
 		{
 			name: "success1",
 			s:    `{"childPolicy":[{"pick_first":{}}]}`,
-			want: &xdsConfig{
-				ChildPolicy: &loadBalancingConfig{
+			want: &xdsinternal.LBConfig{
+				ChildPolicy: &xdsinternal.LoadBalancingConfig{
 					Name:   "pick_first",
 					Config: json.RawMessage(`{}`),
 				},
@@ -708,8 +709,8 @@
 		{
 			name: "success2",
 			s:    `{"childPolicy":[{"round_robin":{}},{"pick_first":{}}]}`,
-			want: &xdsConfig{
-				ChildPolicy: &loadBalancingConfig{
+			want: &xdsinternal.LBConfig{
+				ChildPolicy: &xdsinternal.LoadBalancingConfig{
 					Name:   "round_robin",
 					Config: json.RawMessage(`{}`),
 				},
@@ -718,7 +719,7 @@
 	}
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
-			var cfg xdsConfig
+			var cfg xdsinternal.LBConfig
 			if err := json.Unmarshal([]byte(tt.s), &cfg); err != nil || !reflect.DeepEqual(&cfg, tt.want) {
 				t.Errorf("test name: %s, parseFullServiceConfig() = %+v, err: %v, want %+v, <nil>", tt.name, cfg, err, tt.want)
 			}
diff --git a/xds/internal/internal.go b/xds/internal/internal.go
index 7403e3f..85717fd 100644
--- a/xds/internal/internal.go
+++ b/xds/internal/internal.go
@@ -18,8 +18,11 @@
 package internal
 
 import (
+	"encoding/json"
 	"fmt"
 
+	"google.golang.org/grpc/balancer"
+	"google.golang.org/grpc/serviceconfig"
 	basepb "google.golang.org/grpc/xds/internal/proto/envoy/api/v2/core/base"
 )
 
@@ -48,3 +51,88 @@
 		SubZone: lamk.SubZone,
 	}
 }
+
+// LBConfig represents the loadBalancingConfig section of the service config
+// for xDS balancers.
+type LBConfig struct {
+	serviceconfig.LoadBalancingConfig
+	// BalancerName represents the load balancer to use.
+	BalancerName string
+	// ChildPolicy represents the load balancing config for the child policy.
+	ChildPolicy *LoadBalancingConfig
+	// FallBackPolicy represents the load balancing config for the fallback.
+	FallBackPolicy *LoadBalancingConfig
+}
+
+// UnmarshalJSON parses the JSON-encoded byte slice in data and stores it in l.
+// When unmarshalling, we iterate through the childPolicy/fallbackPolicy lists
+// and select the first LB policy which has been registered.
+func (l *LBConfig) UnmarshalJSON(data []byte) error {
+	var val map[string]json.RawMessage
+	if err := json.Unmarshal(data, &val); err != nil {
+		return err
+	}
+	for k, v := range val {
+		switch k {
+		case "balancerName":
+			if err := json.Unmarshal(v, &l.BalancerName); err != nil {
+				return err
+			}
+		case "childPolicy":
+			var lbcfgs []*LoadBalancingConfig
+			if err := json.Unmarshal(v, &lbcfgs); err != nil {
+				return err
+			}
+			for _, lbcfg := range lbcfgs {
+				if balancer.Get(lbcfg.Name) != nil {
+					l.ChildPolicy = lbcfg
+					break
+				}
+			}
+		case "fallbackPolicy":
+			var lbcfgs []*LoadBalancingConfig
+			if err := json.Unmarshal(v, &lbcfgs); err != nil {
+				return err
+			}
+			for _, lbcfg := range lbcfgs {
+				if balancer.Get(lbcfg.Name) != nil {
+					l.FallBackPolicy = lbcfg
+					break
+				}
+			}
+		}
+	}
+	return nil
+}
+
+// MarshalJSON returns a JSON enconding of l.
+func (l *LBConfig) MarshalJSON() ([]byte, error) {
+	return nil, nil
+}
+
+// LoadBalancingConfig represents a single load balancing config,
+// stored in JSON format.
+type LoadBalancingConfig struct {
+	Name   string
+	Config json.RawMessage
+}
+
+// MarshalJSON returns a JSON enconding of l.
+func (l *LoadBalancingConfig) MarshalJSON() ([]byte, error) {
+	m := make(map[string]json.RawMessage)
+	m[l.Name] = l.Config
+	return json.Marshal(m)
+}
+
+// UnmarshalJSON parses the JSON-encoded byte slice in data and stores it in l.
+func (l *LoadBalancingConfig) UnmarshalJSON(data []byte) error {
+	var cfg map[string]json.RawMessage
+	if err := json.Unmarshal(data, &cfg); err != nil {
+		return err
+	}
+	for name, config := range cfg {
+		l.Name = name
+		l.Config = config
+	}
+	return nil
+}
diff --git a/xds/internal/resolver/xds_resolver.go b/xds/internal/resolver/xds_resolver.go
new file mode 100644
index 0000000..a2e8773
--- /dev/null
+++ b/xds/internal/resolver/xds_resolver.go
@@ -0,0 +1,103 @@
+/*
+ *
+ * Copyright 2019 gRPC authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+// Package resolver implements the xds resolver.
+//
+// At this point, the resolver is named xds-experimental, and doesn't do very
+// much at all, except for returning a hard-coded service config which selects
+// the xds_experimental balancer.
+package resolver
+
+import (
+	"fmt"
+	"sync"
+
+	"google.golang.org/grpc"
+	"google.golang.org/grpc/internal"
+	"google.golang.org/grpc/resolver"
+	"google.golang.org/grpc/serviceconfig"
+)
+
+const (
+	// The JSON form of the hard-coded service config which picks the
+	// xds_experimental balancer with round_robin as the child policy.
+	jsonSC = `{
+    "loadBalancingConfig":[
+      {
+        "xds_experimental":{
+          "childPolicy":[
+            {
+              "round_robin": {}
+            }
+          ]
+        }
+      }
+    ]
+  }`
+	// xDS balancer name is xds_experimental while resolver scheme is
+	// xds-experimental since "_" is not a valid character in the URL.
+	xdsScheme = "xds-experimental"
+)
+
+var (
+	parseOnce sync.Once
+	parsedSC  serviceconfig.Config
+)
+
+// NewBuilder creates a new implementation of the resolver.Builder interface
+// for the xDS resolver.
+func NewBuilder() resolver.Builder {
+	return &xdsBuilder{}
+}
+
+type xdsBuilder struct{}
+
+// Build helps implement the resolver.Builder interface.
+func (b *xdsBuilder) Build(t resolver.Target, cc resolver.ClientConn, o resolver.BuildOption) (resolver.Resolver, error) {
+	parseOnce.Do(func() {
+		// The xds balancer must have been registered at this point for the service
+		// config to be parsed properly.
+		psc, err := internal.ParseServiceConfig(jsonSC)
+		if err != nil {
+			panic(fmt.Sprintf("service config %s parsing failed: %v", jsonSC, err))
+		}
+
+		var ok bool
+		if parsedSC, ok = psc.(*grpc.ServiceConfig); !ok {
+			panic(fmt.Sprintf("service config type is [%T], want [grpc.ServiceConfig]", psc))
+		}
+	})
+
+	// We return a resolver which bacically does nothing. The hard-coded service
+	// config returned here picks the xds balancer.
+	cc.UpdateState(resolver.State{ServiceConfig: parsedSC})
+	return &xdsResolver{}, nil
+}
+
+// Name helps implement the resolver.Builder interface.
+func (*xdsBuilder) Scheme() string {
+	return xdsScheme
+}
+
+type xdsResolver struct{}
+
+// ResolveNow is a no-op at this point.
+func (*xdsResolver) ResolveNow(o resolver.ResolveNowOption) {}
+
+// Close is a no-op at this point.
+func (*xdsResolver) Close() {}
diff --git a/xds/internal/resolver/xds_resolver_test.go b/xds/internal/resolver/xds_resolver_test.go
new file mode 100644
index 0000000..bfcbfa1
--- /dev/null
+++ b/xds/internal/resolver/xds_resolver_test.go
@@ -0,0 +1,196 @@
+/*
+ *
+ * Copyright 2019 gRPC authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package resolver
+
+import (
+	"encoding/json"
+	"fmt"
+	"reflect"
+	"testing"
+	"time"
+
+	"google.golang.org/grpc"
+	"google.golang.org/grpc/balancer"
+	"google.golang.org/grpc/connectivity"
+	"google.golang.org/grpc/resolver"
+	"google.golang.org/grpc/serviceconfig"
+	xdsinternal "google.golang.org/grpc/xds/internal"
+)
+
+// This is initialized at init time.
+var fbb *fakeBalancerBuilder
+
+// We register a fake balancer builder and the actual xds_resolver here. We use
+// the fake balancer builder to verify the service config pushed by the
+// resolver.
+func init() {
+	resolver.Register(NewBuilder())
+	fbb = &fakeBalancerBuilder{
+		wantLBConfig: &wrappedLBConfig{lbCfg: json.RawMessage(`{
+      "childPolicy":[
+        {
+          "round_robin": {}
+        }
+      ]
+    }`)},
+		errCh: make(chan error),
+	}
+	balancer.Register(fbb)
+}
+
+// testClientConn is a fake implemetation of resolver.ClientConn. All is does
+// is to store the state received from the resolver locally and close the
+// provided done channel.
+type testClientConn struct {
+	done     chan struct{}
+	gotState resolver.State
+}
+
+func (t *testClientConn) UpdateState(s resolver.State) {
+	t.gotState = s
+	close(t.done)
+}
+
+func (*testClientConn) NewAddress(addresses []resolver.Address) { panic("unimplemented") }
+func (*testClientConn) NewServiceConfig(serviceConfig string)   { panic("unimplemented") }
+
+// TestXDSRsolverSchemeAndAddresses creates a new xds resolver, verifies that
+// it returns an empty address list and the appropriate xds-experimental
+// scheme.
+func TestXDSRsolverSchemeAndAddresses(t *testing.T) {
+	b := NewBuilder()
+	wantScheme := "xds-experimental"
+	if b.Scheme() != wantScheme {
+		t.Fatalf("got scheme %s, want %s", b.Scheme(), wantScheme)
+	}
+
+	tcc := &testClientConn{done: make(chan struct{})}
+	r, err := b.Build(resolver.Target{}, tcc, resolver.BuildOption{})
+	if err != nil {
+		t.Fatalf("xdsBuilder.Build() failed with error: %v", err)
+	}
+	defer r.Close()
+
+	<-tcc.done
+	if len(tcc.gotState.Addresses) != 0 {
+		t.Fatalf("got address list from resolver %v, want empty list", tcc.gotState.Addresses)
+	}
+}
+
+// fakeBalancer is used to verify that the xds_resolver returns the expected
+// serice config.
+type fakeBalancer struct {
+	wantLBConfig *wrappedLBConfig
+	errCh        chan error
+}
+
+func (*fakeBalancer) HandleSubConnStateChange(_ balancer.SubConn, _ connectivity.State) {
+	panic("unimplemented")
+}
+func (*fakeBalancer) HandleResolvedAddrs(_ []resolver.Address, _ error) {
+	panic("unimplemented")
+}
+
+// UpdateClientConnState verifies that the received LBConfig matches the
+// provided one, and if not, sends an error on the provided channel.
+func (f *fakeBalancer) UpdateClientConnState(ccs balancer.ClientConnState) {
+	gotLBConfig, ok := ccs.BalancerConfig.(*wrappedLBConfig)
+	if !ok {
+		f.errCh <- fmt.Errorf("in fakeBalancer got lbConfig of type %T, want %T", ccs.BalancerConfig, &wrappedLBConfig{})
+		return
+	}
+
+	var gotCfg, wantCfg xdsinternal.LBConfig
+	if err := wantCfg.UnmarshalJSON(f.wantLBConfig.lbCfg); err != nil {
+		f.errCh <- fmt.Errorf("unable to unmarshal balancer config %s into xds config", string(f.wantLBConfig.lbCfg))
+		return
+	}
+	if err := gotCfg.UnmarshalJSON(gotLBConfig.lbCfg); err != nil {
+		f.errCh <- fmt.Errorf("unable to unmarshal balancer config %s into xds config", string(gotLBConfig.lbCfg))
+		return
+	}
+	if !reflect.DeepEqual(gotCfg, wantCfg) {
+		f.errCh <- fmt.Errorf("in fakeBalancer got lbConfig %v, want %v", gotCfg, wantCfg)
+		return
+	}
+
+	f.errCh <- nil
+}
+
+func (*fakeBalancer) UpdateSubConnState(_ balancer.SubConn, _ balancer.SubConnState) {
+	panic("unimplemented")
+}
+
+func (*fakeBalancer) Close() {}
+
+// fakeBalancerBuilder builds a fake balancer and also provides a ParseConfig
+// method (which doesn't really the parse config, but just stores it as is).
+type fakeBalancerBuilder struct {
+	wantLBConfig *wrappedLBConfig
+	errCh        chan error
+}
+
+func (f *fakeBalancerBuilder) Build(cc balancer.ClientConn, opts balancer.BuildOptions) balancer.Balancer {
+	return &fakeBalancer{f.wantLBConfig, f.errCh}
+}
+
+func (f *fakeBalancerBuilder) Name() string {
+	return "xds_experimental"
+}
+
+func (f *fakeBalancerBuilder) ParseConfig(c json.RawMessage) (serviceconfig.LoadBalancingConfig, error) {
+	return &wrappedLBConfig{lbCfg: c}, nil
+}
+
+// wrappedLBConfig simply wraps the provided LB config with a
+// serviceconfig.LoadBalancingConfig interface.
+type wrappedLBConfig struct {
+	serviceconfig.LoadBalancingConfig
+	lbCfg json.RawMessage
+}
+
+// TestXDSRsolverServiceConfig verifies that the xds_resolver returns the
+// expected service config.
+//
+// The following sequence of events happen in this test:
+// * The xds_experimental balancer (fake) and resolver builders are initialized
+//   at init time.
+// * We dial a dummy address here with the xds-experimental scheme. This should
+//   pick the xds_resolver, which should return the hard-coded service config,
+//   which should reach the fake balancer that we registered (because the
+//   service config asks for the xds balancer).
+// * In the fake balancer, we verify that we receive the expected LB config.
+func TestXDSRsolverServiceConfig(t *testing.T) {
+	xdsAddr := fmt.Sprintf("%s:///dummy", xdsScheme)
+	cc, err := grpc.Dial(xdsAddr, grpc.WithInsecure())
+	if err != nil {
+		t.Fatalf("grpc.Dial(%s) failed with error: %v", xdsAddr, err)
+	}
+	defer cc.Close()
+
+	timer := time.NewTimer(5 * time.Second)
+	select {
+	case <-timer.C:
+		t.Fatal("timed out waiting for service config to reach balancer")
+	case err := <-fbb.errCh:
+		if err != nil {
+			t.Error(err)
+		}
+	}
+}