mirror of
https://github.com/edgelesssys/constellation.git
synced 2025-09-21 21:44:39 -04:00
cloud: fix discovery of GCP nodes across multiple zones (#1943)
This commit is contained in:
parent
de2c21b555
commit
0b262a08bc
4 changed files with 418 additions and 8 deletions
|
@ -23,6 +23,8 @@ import (
|
||||||
"path"
|
"path"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
compute "cloud.google.com/go/compute/apiv1"
|
compute "cloud.google.com/go/compute/apiv1"
|
||||||
"cloud.google.com/go/compute/apiv1/computepb"
|
"cloud.google.com/go/compute/apiv1/computepb"
|
||||||
|
@ -38,6 +40,8 @@ import (
|
||||||
const (
|
const (
|
||||||
// tagUsage is a label key used to indicate the use of the resource.
|
// tagUsage is a label key used to indicate the use of the resource.
|
||||||
tagUsage = "constellation-use"
|
tagUsage = "constellation-use"
|
||||||
|
// maxCacheAgeInvariantResource is the maximum age of cached metadata for invariant resources.
|
||||||
|
maxCacheAgeInvariantResource = 24 * time.Hour // 1 day
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -52,8 +56,18 @@ type Cloud struct {
|
||||||
imds imdsAPI
|
imds imdsAPI
|
||||||
instanceAPI instanceAPI
|
instanceAPI instanceAPI
|
||||||
subnetAPI subnetAPI
|
subnetAPI subnetAPI
|
||||||
|
zoneAPI zoneAPI
|
||||||
|
|
||||||
closers []func() error
|
closers []func() error
|
||||||
|
|
||||||
|
// cached metadata
|
||||||
|
cacheMux sync.Mutex
|
||||||
|
regionCache string
|
||||||
|
regionCacheTime time.Time
|
||||||
|
zoneCache map[string]struct {
|
||||||
|
zones []string
|
||||||
|
zoneCacheTime time.Time
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// New creates and initializes Cloud.
|
// New creates and initializes Cloud.
|
||||||
|
@ -85,12 +99,19 @@ func New(ctx context.Context) (cloud *Cloud, err error) {
|
||||||
}
|
}
|
||||||
closers = append(closers, subnetAPI.Close)
|
closers = append(closers, subnetAPI.Close)
|
||||||
|
|
||||||
|
zoneAPI, err := compute.NewZonesRESTClient(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
closers = append(closers, zoneAPI.Close)
|
||||||
|
|
||||||
return &Cloud{
|
return &Cloud{
|
||||||
imds: imds.NewClient(nil),
|
imds: imds.NewClient(nil),
|
||||||
instanceAPI: &instanceClient{insAPI},
|
instanceAPI: &instanceClient{insAPI},
|
||||||
globalForwardingRulesAPI: &globalForwardingRulesClient{globalForwardingRulesAPI},
|
globalForwardingRulesAPI: &globalForwardingRulesClient{globalForwardingRulesAPI},
|
||||||
regionalForwardingRulesAPI: ®ionalForwardingRulesClient{regionalForwardingRulesAPI},
|
regionalForwardingRulesAPI: ®ionalForwardingRulesClient{regionalForwardingRulesAPI},
|
||||||
subnetAPI: subnetAPI,
|
subnetAPI: subnetAPI,
|
||||||
|
zoneAPI: &zoneClient{zoneAPI},
|
||||||
closers: closers,
|
closers: closers,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
@ -192,6 +213,7 @@ func (c *Cloud) getRegionalForwardingRule(ctx context.Context, project, uid, reg
|
||||||
}
|
}
|
||||||
|
|
||||||
// List retrieves all instances belonging to the current constellation.
|
// List retrieves all instances belonging to the current constellation.
|
||||||
|
// On GCP, this is done by listing all instances in a region by requesting all instances in each zone.
|
||||||
func (c *Cloud) List(ctx context.Context) ([]metadata.InstanceMetadata, error) {
|
func (c *Cloud) List(ctx context.Context) ([]metadata.InstanceMetadata, error) {
|
||||||
project, zone, instanceName, err := c.retrieveInstanceInfo()
|
project, zone, instanceName, err := c.retrieveInstanceInfo()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -202,8 +224,32 @@ func (c *Cloud) List(ctx context.Context) ([]metadata.InstanceMetadata, error) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
region, err := c.region()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("getting region: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
zones, err := c.zones(ctx, project, region)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("getting zones: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var instances []metadata.InstanceMetadata
|
||||||
|
for _, zone := range zones {
|
||||||
|
zoneInstances, err := c.listInZone(ctx, project, zone, uid)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("listing instances in zone %s: %w", zone, err)
|
||||||
|
}
|
||||||
|
instances = append(instances, zoneInstances...)
|
||||||
|
}
|
||||||
|
return instances, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// listInZone retrieves all instances belonging to the current constellation in a given zone.
|
||||||
|
func (c *Cloud) listInZone(ctx context.Context, project, zone, uid string) ([]metadata.InstanceMetadata, error) {
|
||||||
var instances []metadata.InstanceMetadata
|
var instances []metadata.InstanceMetadata
|
||||||
var resp *computepb.Instance
|
var resp *computepb.Instance
|
||||||
|
var err error
|
||||||
iter := c.instanceAPI.List(ctx, &computepb.ListInstancesRequest{
|
iter := c.instanceAPI.List(ctx, &computepb.ListInstancesRequest{
|
||||||
Filter: proto.String(fmt.Sprintf("labels.%s:%s", cloud.TagUID, uid)),
|
Filter: proto.String(fmt.Sprintf("labels.%s:%s", cloud.TagUID, uid)),
|
||||||
Project: project,
|
Project: project,
|
||||||
|
@ -399,6 +445,70 @@ func (c *Cloud) initSecretHash(ctx context.Context, project, zone, instanceName
|
||||||
return "", errors.New("retrieving compute instance: received instance with no init secret hash label")
|
return "", errors.New("retrieving compute instance: received instance with no init secret hash label")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// region retrieves the region that this instance is located in.
|
||||||
|
func (c *Cloud) region() (string, error) {
|
||||||
|
c.cacheMux.Lock()
|
||||||
|
defer c.cacheMux.Unlock()
|
||||||
|
// try to retrieve from cache first
|
||||||
|
if c.regionCache != "" &&
|
||||||
|
time.Since(c.regionCacheTime) < maxCacheAgeInvariantResource {
|
||||||
|
return c.regionCache, nil
|
||||||
|
}
|
||||||
|
zone, err := c.imds.Zone()
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("retrieving zone from imds: %w", err)
|
||||||
|
}
|
||||||
|
region, err := regionFromZone(zone)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("retrieving region from zone: %w", err)
|
||||||
|
}
|
||||||
|
c.regionCache = region
|
||||||
|
c.regionCacheTime = time.Now()
|
||||||
|
return region, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// zones retrieves all zones that are within a region.
|
||||||
|
func (c *Cloud) zones(ctx context.Context, project, region string) ([]string, error) {
|
||||||
|
c.cacheMux.Lock()
|
||||||
|
defer c.cacheMux.Unlock()
|
||||||
|
// try to retrieve from cache first
|
||||||
|
if cachedZones, ok := c.zoneCache[region]; ok &&
|
||||||
|
time.Since(cachedZones.zoneCacheTime) < maxCacheAgeInvariantResource {
|
||||||
|
return cachedZones.zones, nil
|
||||||
|
}
|
||||||
|
req := &computepb.ListZonesRequest{
|
||||||
|
Project: project,
|
||||||
|
Filter: proto.String(fmt.Sprintf("name = \"%s*\"", region)),
|
||||||
|
}
|
||||||
|
zonesIter := c.zoneAPI.List(ctx, req)
|
||||||
|
var zones []string
|
||||||
|
var resp *computepb.Zone
|
||||||
|
var err error
|
||||||
|
for resp, err = zonesIter.Next(); err == nil; resp, err = zonesIter.Next() {
|
||||||
|
if resp == nil || resp.Name == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
zones = append(zones, *resp.Name)
|
||||||
|
}
|
||||||
|
if err != nil && err != iterator.Done {
|
||||||
|
return nil, fmt.Errorf("listing zones: %w", err)
|
||||||
|
}
|
||||||
|
if c.zoneCache == nil {
|
||||||
|
c.zoneCache = make(map[string]struct {
|
||||||
|
zones []string
|
||||||
|
zoneCacheTime time.Time
|
||||||
|
})
|
||||||
|
}
|
||||||
|
c.zoneCache[region] = struct {
|
||||||
|
zones []string
|
||||||
|
zoneCacheTime time.Time
|
||||||
|
}{
|
||||||
|
zones: zones,
|
||||||
|
zoneCacheTime: time.Now(),
|
||||||
|
}
|
||||||
|
return zones, nil
|
||||||
|
}
|
||||||
|
|
||||||
// convertToInstanceMetadata converts a *computepb.Instance to a metadata.InstanceMetadata.
|
// convertToInstanceMetadata converts a *computepb.Instance to a metadata.InstanceMetadata.
|
||||||
func convertToInstanceMetadata(in *computepb.Instance, project string, zone string) (metadata.InstanceMetadata, error) {
|
func convertToInstanceMetadata(in *computepb.Instance, project string, zone string) (metadata.InstanceMetadata, error) {
|
||||||
if in.Name == nil {
|
if in.Name == nil {
|
||||||
|
@ -435,3 +545,11 @@ func convertToInstanceMetadata(in *computepb.Instance, project string, zone stri
|
||||||
AliasIPRanges: ips,
|
AliasIPRanges: ips,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func regionFromZone(zone string) (string, error) {
|
||||||
|
zoneParts := strings.Split(zone, "-")
|
||||||
|
if len(zoneParts) != 3 {
|
||||||
|
return "", fmt.Errorf("invalid zone format: %s", zone)
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%s-%s", zoneParts[0], zoneParts[1]), nil
|
||||||
|
}
|
||||||
|
|
|
@ -506,6 +506,26 @@ func TestList(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
otherZoneInstance := &computepb.Instance{
|
||||||
|
Name: proto.String("otherZoneInstance"),
|
||||||
|
Zone: proto.String("someZone-east1-a"),
|
||||||
|
Labels: map[string]string{
|
||||||
|
cloud.TagUID: "1234",
|
||||||
|
cloud.TagRole: role.ControlPlane.String(),
|
||||||
|
},
|
||||||
|
NetworkInterfaces: []*computepb.NetworkInterface{
|
||||||
|
{
|
||||||
|
Name: proto.String("nic0"),
|
||||||
|
NetworkIP: proto.String("192.0.2.1"),
|
||||||
|
AliasIpRanges: []*computepb.AliasIpRange{
|
||||||
|
{
|
||||||
|
IpCidrRange: proto.String("198.51.100.0/24"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Subnetwork: proto.String("projects/someProject/regions/someRegion/subnetworks/someSubnetwork"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
goodSubnet := &computepb.Subnetwork{
|
goodSubnet := &computepb.Subnetwork{
|
||||||
SecondaryIpRanges: []*computepb.SubnetworkSecondaryRange{
|
SecondaryIpRanges: []*computepb.SubnetworkSecondaryRange{
|
||||||
{
|
{
|
||||||
|
@ -516,8 +536,9 @@ func TestList(t *testing.T) {
|
||||||
|
|
||||||
testCases := map[string]struct {
|
testCases := map[string]struct {
|
||||||
imds stubIMDS
|
imds stubIMDS
|
||||||
instanceAPI stubInstanceAPI
|
instanceAPI instanceAPI
|
||||||
subnetAPI stubSubnetAPI
|
subnetAPI stubSubnetAPI
|
||||||
|
zoneAPI stubZoneAPI
|
||||||
wantErr bool
|
wantErr bool
|
||||||
wantInstances []metadata.InstanceMetadata
|
wantInstances []metadata.InstanceMetadata
|
||||||
}{
|
}{
|
||||||
|
@ -527,7 +548,7 @@ func TestList(t *testing.T) {
|
||||||
zone: "someZone-west3-b",
|
zone: "someZone-west3-b",
|
||||||
instanceName: "someInstance",
|
instanceName: "someInstance",
|
||||||
},
|
},
|
||||||
instanceAPI: stubInstanceAPI{
|
instanceAPI: &stubInstanceAPI{
|
||||||
instance: goodInstance,
|
instance: goodInstance,
|
||||||
iterator: &stubInstanceIterator{
|
iterator: &stubInstanceIterator{
|
||||||
instances: []*computepb.Instance{
|
instances: []*computepb.Instance{
|
||||||
|
@ -538,6 +559,15 @@ func TestList(t *testing.T) {
|
||||||
subnetAPI: stubSubnetAPI{
|
subnetAPI: stubSubnetAPI{
|
||||||
subnet: goodSubnet,
|
subnet: goodSubnet,
|
||||||
},
|
},
|
||||||
|
zoneAPI: stubZoneAPI{
|
||||||
|
iterator: &stubZoneIterator{
|
||||||
|
zones: []*computepb.Zone{
|
||||||
|
{
|
||||||
|
Name: proto.String("someZone-west3-b"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
wantInstances: []metadata.InstanceMetadata{
|
wantInstances: []metadata.InstanceMetadata{
|
||||||
{
|
{
|
||||||
Name: "someInstance",
|
Name: "someInstance",
|
||||||
|
@ -549,13 +579,64 @@ func TestList(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
"multi-zone": {
|
||||||
|
imds: stubIMDS{
|
||||||
|
projectID: "someProject",
|
||||||
|
zone: "someZone-west3-b",
|
||||||
|
instanceName: "someInstance",
|
||||||
|
},
|
||||||
|
instanceAPI: &fakeInstanceAPI{
|
||||||
|
instance: goodInstance,
|
||||||
|
iterators: map[string]*stubInstanceIterator{
|
||||||
|
"someZone-east1-a": {
|
||||||
|
instances: []*computepb.Instance{
|
||||||
|
otherZoneInstance,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"someZone-west3-b": {
|
||||||
|
instances: []*computepb.Instance{
|
||||||
|
goodInstance,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
subnetAPI: stubSubnetAPI{
|
||||||
|
subnet: goodSubnet,
|
||||||
|
},
|
||||||
|
zoneAPI: stubZoneAPI{
|
||||||
|
iterator: &stubZoneIterator{
|
||||||
|
zones: []*computepb.Zone{
|
||||||
|
{Name: proto.String("someZone-east1-a")},
|
||||||
|
{Name: proto.String("someZone-west3-b")},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
wantInstances: []metadata.InstanceMetadata{
|
||||||
|
{
|
||||||
|
Name: "otherZoneInstance",
|
||||||
|
Role: role.ControlPlane,
|
||||||
|
ProviderID: "gce://someProject/someZone-east1-a/otherZoneInstance",
|
||||||
|
VPCIP: "192.0.2.1",
|
||||||
|
AliasIPRanges: []string{"198.51.100.0/24"},
|
||||||
|
SecondaryIPRange: "198.51.100.0/24",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "someInstance",
|
||||||
|
Role: role.ControlPlane,
|
||||||
|
ProviderID: "gce://someProject/someZone-west3-b/someInstance",
|
||||||
|
VPCIP: "192.0.2.0",
|
||||||
|
AliasIPRanges: []string{"198.51.100.0/24"},
|
||||||
|
SecondaryIPRange: "198.51.100.0/24",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
"list multiple instances": {
|
"list multiple instances": {
|
||||||
imds: stubIMDS{
|
imds: stubIMDS{
|
||||||
projectID: "someProject",
|
projectID: "someProject",
|
||||||
zone: "someZone-west3-b",
|
zone: "someZone-west3-b",
|
||||||
instanceName: "someInstance",
|
instanceName: "someInstance",
|
||||||
},
|
},
|
||||||
instanceAPI: stubInstanceAPI{
|
instanceAPI: &stubInstanceAPI{
|
||||||
instance: goodInstance,
|
instance: goodInstance,
|
||||||
iterator: &stubInstanceIterator{
|
iterator: &stubInstanceIterator{
|
||||||
instances: []*computepb.Instance{
|
instances: []*computepb.Instance{
|
||||||
|
@ -586,6 +667,15 @@ func TestList(t *testing.T) {
|
||||||
subnetAPI: stubSubnetAPI{
|
subnetAPI: stubSubnetAPI{
|
||||||
subnet: goodSubnet,
|
subnet: goodSubnet,
|
||||||
},
|
},
|
||||||
|
zoneAPI: stubZoneAPI{
|
||||||
|
iterator: &stubZoneIterator{
|
||||||
|
zones: []*computepb.Zone{
|
||||||
|
{
|
||||||
|
Name: proto.String("someZone-west3-b"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
wantInstances: []metadata.InstanceMetadata{
|
wantInstances: []metadata.InstanceMetadata{
|
||||||
{
|
{
|
||||||
Name: "someInstance",
|
Name: "someInstance",
|
||||||
|
@ -609,7 +699,7 @@ func TestList(t *testing.T) {
|
||||||
imds: stubIMDS{
|
imds: stubIMDS{
|
||||||
projectIDErr: someErr,
|
projectIDErr: someErr,
|
||||||
},
|
},
|
||||||
instanceAPI: stubInstanceAPI{
|
instanceAPI: &stubInstanceAPI{
|
||||||
instance: goodInstance,
|
instance: goodInstance,
|
||||||
iterator: &stubInstanceIterator{
|
iterator: &stubInstanceIterator{
|
||||||
instances: []*computepb.Instance{
|
instances: []*computepb.Instance{
|
||||||
|
@ -628,7 +718,7 @@ func TestList(t *testing.T) {
|
||||||
zone: "someZone-west3-b",
|
zone: "someZone-west3-b",
|
||||||
instanceName: "someInstance",
|
instanceName: "someInstance",
|
||||||
},
|
},
|
||||||
instanceAPI: stubInstanceAPI{
|
instanceAPI: &stubInstanceAPI{
|
||||||
instance: goodInstance,
|
instance: goodInstance,
|
||||||
iterator: &stubInstanceIterator{
|
iterator: &stubInstanceIterator{
|
||||||
err: someErr,
|
err: someErr,
|
||||||
|
@ -637,6 +727,15 @@ func TestList(t *testing.T) {
|
||||||
subnetAPI: stubSubnetAPI{
|
subnetAPI: stubSubnetAPI{
|
||||||
subnet: goodSubnet,
|
subnet: goodSubnet,
|
||||||
},
|
},
|
||||||
|
zoneAPI: stubZoneAPI{
|
||||||
|
iterator: &stubZoneIterator{
|
||||||
|
zones: []*computepb.Zone{
|
||||||
|
{
|
||||||
|
Name: proto.String("someZone-west3-b"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
wantErr: true,
|
wantErr: true,
|
||||||
},
|
},
|
||||||
"get instance error": {
|
"get instance error": {
|
||||||
|
@ -645,7 +744,7 @@ func TestList(t *testing.T) {
|
||||||
zone: "someZone-west3-b",
|
zone: "someZone-west3-b",
|
||||||
instanceName: "someInstance",
|
instanceName: "someInstance",
|
||||||
},
|
},
|
||||||
instanceAPI: stubInstanceAPI{
|
instanceAPI: &stubInstanceAPI{
|
||||||
instanceErr: someErr,
|
instanceErr: someErr,
|
||||||
iterator: &stubInstanceIterator{
|
iterator: &stubInstanceIterator{
|
||||||
instances: []*computepb.Instance{
|
instances: []*computepb.Instance{
|
||||||
|
@ -656,6 +755,15 @@ func TestList(t *testing.T) {
|
||||||
subnetAPI: stubSubnetAPI{
|
subnetAPI: stubSubnetAPI{
|
||||||
subnet: goodSubnet,
|
subnet: goodSubnet,
|
||||||
},
|
},
|
||||||
|
zoneAPI: stubZoneAPI{
|
||||||
|
iterator: &stubZoneIterator{
|
||||||
|
zones: []*computepb.Zone{
|
||||||
|
{
|
||||||
|
Name: proto.String("someZone-west3-b"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
wantErr: true,
|
wantErr: true,
|
||||||
},
|
},
|
||||||
"get subnet error": {
|
"get subnet error": {
|
||||||
|
@ -664,7 +772,7 @@ func TestList(t *testing.T) {
|
||||||
zone: "someZone-west3-b",
|
zone: "someZone-west3-b",
|
||||||
instanceName: "someInstance",
|
instanceName: "someInstance",
|
||||||
},
|
},
|
||||||
instanceAPI: stubInstanceAPI{
|
instanceAPI: &stubInstanceAPI{
|
||||||
instance: goodInstance,
|
instance: goodInstance,
|
||||||
iterator: &stubInstanceIterator{
|
iterator: &stubInstanceIterator{
|
||||||
instances: []*computepb.Instance{
|
instances: []*computepb.Instance{
|
||||||
|
@ -675,6 +783,15 @@ func TestList(t *testing.T) {
|
||||||
subnetAPI: stubSubnetAPI{
|
subnetAPI: stubSubnetAPI{
|
||||||
subnetErr: someErr,
|
subnetErr: someErr,
|
||||||
},
|
},
|
||||||
|
zoneAPI: stubZoneAPI{
|
||||||
|
iterator: &stubZoneIterator{
|
||||||
|
zones: []*computepb.Zone{
|
||||||
|
{
|
||||||
|
Name: proto.String("someZone-west3-b"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
wantErr: true,
|
wantErr: true,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -686,8 +803,9 @@ func TestList(t *testing.T) {
|
||||||
|
|
||||||
cloud := &Cloud{
|
cloud := &Cloud{
|
||||||
imds: &tc.imds,
|
imds: &tc.imds,
|
||||||
instanceAPI: &tc.instanceAPI,
|
instanceAPI: tc.instanceAPI,
|
||||||
subnetAPI: &tc.subnetAPI,
|
subnetAPI: &tc.subnetAPI,
|
||||||
|
zoneAPI: &tc.zoneAPI,
|
||||||
}
|
}
|
||||||
|
|
||||||
instances, err := cloud.List(context.Background())
|
instances, err := cloud.List(context.Background())
|
||||||
|
@ -701,6 +819,112 @@ func TestList(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestRegion(t *testing.T) {
|
||||||
|
someErr := errors.New("failed")
|
||||||
|
|
||||||
|
testCases := map[string]struct {
|
||||||
|
imds stubIMDS
|
||||||
|
wantRegion string
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
"success": {
|
||||||
|
imds: stubIMDS{
|
||||||
|
projectID: "someProject",
|
||||||
|
zone: "someregion-west3-b",
|
||||||
|
instanceName: "someInstance",
|
||||||
|
},
|
||||||
|
wantRegion: "someregion-west3",
|
||||||
|
},
|
||||||
|
"get zone error": {
|
||||||
|
imds: stubIMDS{
|
||||||
|
zoneErr: someErr,
|
||||||
|
},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
"invalid zone format": {
|
||||||
|
imds: stubIMDS{
|
||||||
|
projectID: "someProject",
|
||||||
|
zone: "invalid",
|
||||||
|
instanceName: "someInstance",
|
||||||
|
},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for name, tc := range testCases {
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
|
||||||
|
cloud := &Cloud{
|
||||||
|
imds: &tc.imds,
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.Empty(cloud.regionCache)
|
||||||
|
|
||||||
|
gotRegion, err := cloud.region()
|
||||||
|
if tc.wantErr {
|
||||||
|
assert.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
assert.NoError(err)
|
||||||
|
assert.Equal(tc.wantRegion, gotRegion)
|
||||||
|
assert.Equal(tc.wantRegion, cloud.regionCache)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestZones(t *testing.T) {
|
||||||
|
someErr := errors.New("failed")
|
||||||
|
|
||||||
|
testCases := map[string]struct {
|
||||||
|
zoneAPI stubZoneAPI
|
||||||
|
wantZones []string
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
"success": {
|
||||||
|
zoneAPI: stubZoneAPI{
|
||||||
|
&stubZoneIterator{
|
||||||
|
zones: []*computepb.Zone{
|
||||||
|
{Name: proto.String("someregion-west3-b")},
|
||||||
|
{}, // missing name (should be ignored)
|
||||||
|
nil, // nil (should be ignored)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
wantZones: []string{"someregion-west3-b"},
|
||||||
|
},
|
||||||
|
"get zones error": {
|
||||||
|
zoneAPI: stubZoneAPI{
|
||||||
|
&stubZoneIterator{
|
||||||
|
err: someErr,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for name, tc := range testCases {
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
|
||||||
|
cloud := &Cloud{
|
||||||
|
zoneAPI: &tc.zoneAPI,
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.Empty(cloud.zoneCache)
|
||||||
|
|
||||||
|
gotZones, err := cloud.zones(context.Background(), "someProject", "someregion-west3")
|
||||||
|
if tc.wantErr {
|
||||||
|
assert.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
assert.NoError(err)
|
||||||
|
assert.Equal(tc.wantZones, gotZones)
|
||||||
|
assert.Equal(tc.wantZones, cloud.zoneCache["someregion-west3"].zones)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestRetrieveInstanceInfo(t *testing.T) {
|
func TestRetrieveInstanceInfo(t *testing.T) {
|
||||||
someErr := errors.New("failed")
|
someErr := errors.New("failed")
|
||||||
|
|
||||||
|
@ -1035,6 +1259,27 @@ func (s *stubInstanceAPI) List(
|
||||||
|
|
||||||
func (s *stubInstanceAPI) Close() error { return nil }
|
func (s *stubInstanceAPI) Close() error { return nil }
|
||||||
|
|
||||||
|
type fakeInstanceAPI struct {
|
||||||
|
instance *computepb.Instance
|
||||||
|
instanceErr error
|
||||||
|
// iterators is a map of zone to instance iterator.
|
||||||
|
iterators map[string]*stubInstanceIterator
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *fakeInstanceAPI) Get(
|
||||||
|
_ context.Context, _ *computepb.GetInstanceRequest, _ ...gax.CallOption,
|
||||||
|
) (*computepb.Instance, error) {
|
||||||
|
return s.instance, s.instanceErr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *fakeInstanceAPI) List(
|
||||||
|
_ context.Context, req *computepb.ListInstancesRequest, _ ...gax.CallOption,
|
||||||
|
) instanceIterator {
|
||||||
|
return s.iterators[req.GetZone()]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *fakeInstanceAPI) Close() error { return nil }
|
||||||
|
|
||||||
type stubInstanceIterator struct {
|
type stubInstanceIterator struct {
|
||||||
ctr int
|
ctr int
|
||||||
instances []*computepb.Instance
|
instances []*computepb.Instance
|
||||||
|
@ -1063,3 +1308,30 @@ func (s *stubSubnetAPI) Get(
|
||||||
return s.subnet, s.subnetErr
|
return s.subnet, s.subnetErr
|
||||||
}
|
}
|
||||||
func (s *stubSubnetAPI) Close() error { return nil }
|
func (s *stubSubnetAPI) Close() error { return nil }
|
||||||
|
|
||||||
|
type stubZoneAPI struct {
|
||||||
|
iterator *stubZoneIterator
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *stubZoneAPI) List(_ context.Context,
|
||||||
|
_ *computepb.ListZonesRequest, _ ...gax.CallOption,
|
||||||
|
) zoneIterator {
|
||||||
|
return s.iterator
|
||||||
|
}
|
||||||
|
|
||||||
|
type stubZoneIterator struct {
|
||||||
|
ctr int
|
||||||
|
zones []*computepb.Zone
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *stubZoneIterator) Next() (*computepb.Zone, error) {
|
||||||
|
if s.err != nil {
|
||||||
|
return nil, s.err
|
||||||
|
}
|
||||||
|
if s.ctr >= len(s.zones) {
|
||||||
|
return nil, iterator.Done
|
||||||
|
}
|
||||||
|
s.ctr++
|
||||||
|
return s.zones[s.ctr-1], nil
|
||||||
|
}
|
||||||
|
|
|
@ -40,3 +40,7 @@ type subnetAPI interface {
|
||||||
Get(ctx context.Context, req *computepb.GetSubnetworkRequest, opts ...gax.CallOption) (*computepb.Subnetwork, error)
|
Get(ctx context.Context, req *computepb.GetSubnetworkRequest, opts ...gax.CallOption) (*computepb.Subnetwork, error)
|
||||||
Close() error
|
Close() error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type zoneAPI interface {
|
||||||
|
List(ctx context.Context, req *computepb.ListZonesRequest, opts ...gax.CallOption) zoneIterator
|
||||||
|
}
|
||||||
|
|
|
@ -22,6 +22,10 @@ type instanceIterator interface {
|
||||||
Next() (*computepb.Instance, error)
|
Next() (*computepb.Instance, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type zoneIterator interface {
|
||||||
|
Next() (*computepb.Zone, error)
|
||||||
|
}
|
||||||
|
|
||||||
type globalForwardingRulesClient struct {
|
type globalForwardingRulesClient struct {
|
||||||
*compute.GlobalForwardingRulesClient
|
*compute.GlobalForwardingRulesClient
|
||||||
}
|
}
|
||||||
|
@ -63,3 +67,15 @@ func (c *instanceClient) List(ctx context.Context, req *computepb.ListInstancesR
|
||||||
) instanceIterator {
|
) instanceIterator {
|
||||||
return c.InstancesClient.List(ctx, req)
|
return c.InstancesClient.List(ctx, req)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type zoneClient struct {
|
||||||
|
*compute.ZonesClient
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *zoneClient) Close() error {
|
||||||
|
return c.ZonesClient.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *zoneClient) List(ctx context.Context, req *computepb.ListZonesRequest, opts ...gax.CallOption) zoneIterator {
|
||||||
|
return c.ZonesClient.List(ctx, req, opts...)
|
||||||
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue