mirror of
https://github.com/edgelesssys/constellation.git
synced 2025-07-25 00:05:17 -04:00
Use multiple loadbalancers on GCP
This commit is contained in:
parent
c954ec089f
commit
a02a46e454
59 changed files with 1629 additions and 557 deletions
|
@ -105,15 +105,30 @@ type instanceGroupManagersAPI interface {
|
|||
|
||||
type iamAPI interface {
|
||||
Close() error
|
||||
CreateServiceAccount(ctx context.Context, req *adminpb.CreateServiceAccountRequest, opts ...gax.CallOption) (*adminpb.ServiceAccount, error)
|
||||
CreateServiceAccountKey(ctx context.Context, req *adminpb.CreateServiceAccountKeyRequest, opts ...gax.CallOption) (*adminpb.ServiceAccountKey, error)
|
||||
DeleteServiceAccount(ctx context.Context, req *adminpb.DeleteServiceAccountRequest, opts ...gax.CallOption) error
|
||||
CreateServiceAccount(ctx context.Context, req *adminpb.CreateServiceAccountRequest,
|
||||
opts ...gax.CallOption) (*adminpb.ServiceAccount, error)
|
||||
CreateServiceAccountKey(ctx context.Context, req *adminpb.CreateServiceAccountKeyRequest,
|
||||
opts ...gax.CallOption) (*adminpb.ServiceAccountKey, error)
|
||||
DeleteServiceAccount(ctx context.Context, req *adminpb.DeleteServiceAccountRequest,
|
||||
opts ...gax.CallOption) error
|
||||
}
|
||||
|
||||
type projectsAPI interface {
|
||||
Close() error
|
||||
GetIamPolicy(ctx context.Context, req *iampb.GetIamPolicyRequest, opts ...gax.CallOption) (*iampb.Policy, error)
|
||||
SetIamPolicy(ctx context.Context, req *iampb.SetIamPolicyRequest, opts ...gax.CallOption) (*iampb.Policy, error)
|
||||
GetIamPolicy(ctx context.Context, req *iampb.GetIamPolicyRequest,
|
||||
opts ...gax.CallOption) (*iampb.Policy, error)
|
||||
SetIamPolicy(ctx context.Context, req *iampb.SetIamPolicyRequest,
|
||||
opts ...gax.CallOption) (*iampb.Policy, error)
|
||||
}
|
||||
|
||||
type addressesAPI interface {
|
||||
Close() error
|
||||
Insert(ctx context.Context, req *computepb.InsertAddressRequest,
|
||||
opts ...gax.CallOption) (Operation, error)
|
||||
Get(ctx context.Context, req *computepb.GetAddressRequest,
|
||||
opts ...gax.CallOption) (*computepb.Address, error)
|
||||
Delete(ctx context.Context, req *computepb.DeleteAddressRequest,
|
||||
opts ...gax.CallOption) (Operation, error)
|
||||
}
|
||||
|
||||
type Operation interface {
|
||||
|
|
|
@ -548,3 +548,48 @@ func (i *stubManagedInstanceIterator) Next() (*computepb.ManagedInstance, error)
|
|||
i.internalCounter++
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
type stubAddressesAPI struct {
|
||||
insertErr error
|
||||
getAddr *string
|
||||
getErr error
|
||||
deleteErr error
|
||||
}
|
||||
|
||||
func (a stubAddressesAPI) Insert(context.Context, *computepb.InsertAddressRequest,
|
||||
...gax.CallOption,
|
||||
) (Operation, error) {
|
||||
if a.insertErr != nil {
|
||||
return nil, a.insertErr
|
||||
}
|
||||
return &stubOperation{
|
||||
&computepb.Operation{
|
||||
Name: proto.String("name"),
|
||||
Region: proto.String("region"),
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (a stubAddressesAPI) Get(ctx context.Context, req *computepb.GetAddressRequest,
|
||||
opts ...gax.CallOption,
|
||||
) (*computepb.Address, error) {
|
||||
return &computepb.Address{Address: a.getAddr}, a.getErr
|
||||
}
|
||||
|
||||
func (a stubAddressesAPI) Delete(context.Context, *computepb.DeleteAddressRequest,
|
||||
...gax.CallOption,
|
||||
) (Operation, error) {
|
||||
if a.deleteErr != nil {
|
||||
return nil, a.deleteErr
|
||||
}
|
||||
return &stubOperation{
|
||||
&computepb.Operation{
|
||||
Name: proto.String("name"),
|
||||
Region: proto.String("region"),
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (a stubAddressesAPI) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -36,6 +36,7 @@ type Client struct {
|
|||
instanceGroupManagersAPI
|
||||
iamAPI
|
||||
projectsAPI
|
||||
addressesAPI
|
||||
|
||||
workers cloudtypes.Instances
|
||||
controlPlanes cloudtypes.Instances
|
||||
|
@ -56,9 +57,9 @@ type Client struct {
|
|||
serviceAccount string
|
||||
|
||||
// loadbalancer
|
||||
healthCheck string
|
||||
backendService string
|
||||
forwardingRule string
|
||||
loadbalancerIP string
|
||||
loadbalancerIPname string
|
||||
loadbalancers []*loadBalancer
|
||||
}
|
||||
|
||||
// NewFromDefault creates an uninitialized client.
|
||||
|
@ -152,6 +153,13 @@ func NewFromDefault(ctx context.Context) (*Client, error) {
|
|||
_ = closeAll(closers)
|
||||
return nil, err
|
||||
}
|
||||
closers = append(closers, projectsAPI)
|
||||
addressesAPI, err := compute.NewAddressesRESTClient(ctx)
|
||||
if err != nil {
|
||||
_ = closeAll(closers)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &Client{
|
||||
instanceAPI: &instanceClient{insAPI},
|
||||
operationRegionAPI: opRegionAPI,
|
||||
|
@ -167,6 +175,7 @@ func NewFromDefault(ctx context.Context) (*Client, error) {
|
|||
instanceGroupManagersAPI: &instanceGroupManagersClient{groupAPI},
|
||||
iamAPI: &iamClient{iamAPI},
|
||||
projectsAPI: &projectsClient{projectsAPI},
|
||||
addressesAPI: &addressesClient{addressesAPI},
|
||||
workers: make(cloudtypes.Instances),
|
||||
controlPlanes: make(cloudtypes.Instances),
|
||||
}, nil
|
||||
|
@ -221,6 +230,7 @@ func (c *Client) Close() error {
|
|||
c.instanceGroupManagersAPI,
|
||||
c.iamAPI,
|
||||
c.projectsAPI,
|
||||
c.addressesAPI,
|
||||
}
|
||||
return closeAll(closers)
|
||||
}
|
||||
|
@ -242,11 +252,11 @@ func (c *Client) init(project, zone, region, name string) error {
|
|||
|
||||
// GetState returns the state of the client as ConstellationState.
|
||||
func (c *Client) GetState() state.ConstellationState {
|
||||
return state.ConstellationState{
|
||||
stat := state.ConstellationState{
|
||||
Name: c.name,
|
||||
UID: c.uid,
|
||||
CloudProvider: cloudprovider.GCP.String(),
|
||||
BootstrapperHost: c.controlPlanes.PublicIPs()[0],
|
||||
LoadBalancerIP: c.loadbalancerIP,
|
||||
GCPProject: c.project,
|
||||
GCPZone: c.zone,
|
||||
GCPRegion: c.region,
|
||||
|
@ -259,11 +269,13 @@ func (c *Client) GetState() state.ConstellationState {
|
|||
GCPFirewalls: c.firewalls,
|
||||
GCPNetwork: c.network,
|
||||
GCPSubnetwork: c.subnetwork,
|
||||
GCPHealthCheck: c.healthCheck,
|
||||
GCPBackendService: c.backendService,
|
||||
GCPForwardingRule: c.forwardingRule,
|
||||
GCPServiceAccount: c.serviceAccount,
|
||||
GCPLoadbalancerIPname: c.loadbalancerIPname,
|
||||
}
|
||||
for _, lb := range c.loadbalancers {
|
||||
stat.GCPLoadbalancers = append(stat.GCPLoadbalancers, lb.name)
|
||||
}
|
||||
return stat
|
||||
}
|
||||
|
||||
// SetState sets the state of the client to the handed ConstellationState.
|
||||
|
@ -282,10 +294,18 @@ func (c *Client) SetState(stat state.ConstellationState) {
|
|||
c.subnetwork = stat.GCPSubnetwork
|
||||
c.workerTemplate = stat.GCPWorkerInstanceTemplate
|
||||
c.controlPlaneTemplate = stat.GCPControlPlaneInstanceTemplate
|
||||
c.healthCheck = stat.GCPHealthCheck
|
||||
c.backendService = stat.GCPBackendService
|
||||
c.forwardingRule = stat.GCPForwardingRule
|
||||
c.loadbalancerIPname = stat.GCPLoadbalancerIPname
|
||||
c.loadbalancerIP = stat.LoadBalancerIP
|
||||
c.serviceAccount = stat.GCPServiceAccount
|
||||
for _, lbName := range stat.GCPLoadbalancers {
|
||||
lb := &loadBalancer{
|
||||
name: lbName,
|
||||
hasForwardingRules: true,
|
||||
hasBackendService: true,
|
||||
hasHealthCheck: true,
|
||||
}
|
||||
c.loadbalancers = append(c.loadbalancers, lb)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Client) generateUID() (string, error) {
|
||||
|
|
|
@ -2,6 +2,7 @@ package client
|
|||
|
||||
import (
|
||||
"errors"
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"github.com/edgelesssys/constellation/internal/cloud/cloudprovider"
|
||||
|
@ -10,6 +11,7 @@ import (
|
|||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"go.uber.org/goleak"
|
||||
"google.golang.org/api/googleapi"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
|
@ -41,16 +43,13 @@ func TestSetGetState(t *testing.T) {
|
|||
GCPRegion: "region-id",
|
||||
Name: "name",
|
||||
UID: "uid",
|
||||
BootstrapperHost: "ip3",
|
||||
LoadBalancerIP: "ip5",
|
||||
GCPNetwork: "net-id",
|
||||
GCPSubnetwork: "subnet-id",
|
||||
GCPFirewalls: []string{"fw-1", "fw-2"},
|
||||
GCPWorkerInstanceTemplate: "temp-id",
|
||||
GCPControlPlaneInstanceTemplate: "temp-id",
|
||||
GCPServiceAccount: "service-account",
|
||||
GCPBackendService: "backend-service-id",
|
||||
GCPHealthCheck: "health-check-id",
|
||||
GCPForwardingRule: "forwarding-rule-id",
|
||||
GCPLoadbalancers: []string{"lb-1", "lb-2"},
|
||||
}
|
||||
|
||||
t.Run("SetState", func(t *testing.T) {
|
||||
|
@ -71,6 +70,13 @@ func TestSetGetState(t *testing.T) {
|
|||
assert.Equal(state.GCPControlPlaneInstanceTemplate, client.controlPlaneTemplate)
|
||||
assert.Equal(state.GCPWorkerInstanceTemplate, client.workerTemplate)
|
||||
assert.Equal(state.GCPServiceAccount, client.serviceAccount)
|
||||
assert.Equal(state.LoadBalancerIP, client.loadbalancerIP)
|
||||
for _, lb := range client.loadbalancers {
|
||||
assert.Contains(state.GCPLoadbalancers, lb.name)
|
||||
assert.True(lb.hasBackendService)
|
||||
assert.True(lb.hasHealthCheck)
|
||||
assert.True(lb.hasForwardingRules)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("GetState", func(t *testing.T) {
|
||||
|
@ -92,9 +98,11 @@ func TestSetGetState(t *testing.T) {
|
|||
workerTemplate: state.GCPWorkerInstanceTemplate,
|
||||
controlPlaneTemplate: state.GCPControlPlaneInstanceTemplate,
|
||||
serviceAccount: state.GCPServiceAccount,
|
||||
healthCheck: state.GCPHealthCheck,
|
||||
backendService: state.GCPBackendService,
|
||||
forwardingRule: state.GCPForwardingRule,
|
||||
loadbalancerIP: state.LoadBalancerIP,
|
||||
loadbalancerIPname: state.GCPLoadbalancerIPname,
|
||||
}
|
||||
for _, lbName := range state.GCPLoadbalancers {
|
||||
client.loadbalancers = append(client.loadbalancers, &loadBalancer{name: lbName})
|
||||
}
|
||||
|
||||
stat := client.GetState()
|
||||
|
@ -141,3 +149,21 @@ func (c *someCloser) Close() error {
|
|||
c.closed = true
|
||||
return c.closeErr
|
||||
}
|
||||
|
||||
func TestIsNotFoundError(t *testing.T) {
|
||||
testCases := map[string]struct {
|
||||
err error
|
||||
result bool
|
||||
}{
|
||||
"not found error": {err: &googleapi.Error{Code: http.StatusNotFound}, result: true},
|
||||
"nil error": {err: nil, result: false},
|
||||
"other error": {err: errors.New("failed"), result: false},
|
||||
}
|
||||
|
||||
for name, tc := range testCases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
assert.Equal(tc.result, isNotFoundError(tc.err))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -212,15 +212,21 @@ func (c *iamClient) Close() error {
|
|||
return c.IamClient.Close()
|
||||
}
|
||||
|
||||
func (c *iamClient) CreateServiceAccount(ctx context.Context, req *adminpb.CreateServiceAccountRequest, opts ...gax.CallOption) (*adminpb.ServiceAccount, error) {
|
||||
func (c *iamClient) CreateServiceAccount(ctx context.Context, req *adminpb.CreateServiceAccountRequest,
|
||||
opts ...gax.CallOption,
|
||||
) (*adminpb.ServiceAccount, error) {
|
||||
return c.IamClient.CreateServiceAccount(ctx, req)
|
||||
}
|
||||
|
||||
func (c *iamClient) CreateServiceAccountKey(ctx context.Context, req *adminpb.CreateServiceAccountKeyRequest, opts ...gax.CallOption) (*adminpb.ServiceAccountKey, error) {
|
||||
func (c *iamClient) CreateServiceAccountKey(ctx context.Context, req *adminpb.CreateServiceAccountKeyRequest,
|
||||
opts ...gax.CallOption,
|
||||
) (*adminpb.ServiceAccountKey, error) {
|
||||
return c.IamClient.CreateServiceAccountKey(ctx, req)
|
||||
}
|
||||
|
||||
func (c *iamClient) DeleteServiceAccount(ctx context.Context, req *adminpb.DeleteServiceAccountRequest, opts ...gax.CallOption) error {
|
||||
func (c *iamClient) DeleteServiceAccount(ctx context.Context, req *adminpb.DeleteServiceAccountRequest,
|
||||
opts ...gax.CallOption,
|
||||
) error {
|
||||
return c.IamClient.DeleteServiceAccount(ctx, req)
|
||||
}
|
||||
|
||||
|
@ -232,10 +238,34 @@ func (c *projectsClient) Close() error {
|
|||
return c.ProjectsClient.Close()
|
||||
}
|
||||
|
||||
func (c *projectsClient) GetIamPolicy(ctx context.Context, req *iampb.GetIamPolicyRequest, opts ...gax.CallOption) (*iampb.Policy, error) {
|
||||
func (c *projectsClient) GetIamPolicy(ctx context.Context, req *iampb.GetIamPolicyRequest,
|
||||
opts ...gax.CallOption,
|
||||
) (*iampb.Policy, error) {
|
||||
return c.ProjectsClient.GetIamPolicy(ctx, req)
|
||||
}
|
||||
|
||||
func (c *projectsClient) SetIamPolicy(ctx context.Context, req *iampb.SetIamPolicyRequest, opts ...gax.CallOption) (*iampb.Policy, error) {
|
||||
func (c *projectsClient) SetIamPolicy(ctx context.Context, req *iampb.SetIamPolicyRequest,
|
||||
opts ...gax.CallOption,
|
||||
) (*iampb.Policy, error) {
|
||||
return c.ProjectsClient.SetIamPolicy(ctx, req)
|
||||
}
|
||||
|
||||
type addressesClient struct {
|
||||
*compute.AddressesClient
|
||||
}
|
||||
|
||||
func (c *addressesClient) Insert(ctx context.Context, req *computepb.InsertAddressRequest,
|
||||
opts ...gax.CallOption,
|
||||
) (Operation, error) {
|
||||
return c.AddressesClient.Insert(ctx, req)
|
||||
}
|
||||
|
||||
func (c *addressesClient) Delete(ctx context.Context, req *computepb.DeleteAddressRequest,
|
||||
opts ...gax.CallOption,
|
||||
) (Operation, error) {
|
||||
return c.AddressesClient.Delete(ctx, req)
|
||||
}
|
||||
|
||||
func (c *addressesClient) Close() error {
|
||||
return c.AddressesClient.Close()
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@ import (
|
|||
|
||||
"github.com/edgelesssys/constellation/bootstrapper/role"
|
||||
"github.com/edgelesssys/constellation/internal/cloud/cloudtypes"
|
||||
"github.com/edgelesssys/constellation/internal/constants"
|
||||
"google.golang.org/api/iterator"
|
||||
computepb "google.golang.org/genproto/googleapis/cloud/compute/v1"
|
||||
"google.golang.org/protobuf/proto"
|
||||
|
@ -76,8 +77,14 @@ func (c *Client) CreateInstances(ctx context.Context, input CreateInstancesInput
|
|||
ops = []Operation{}
|
||||
|
||||
controlPlaneGroupInput := instanceGroupManagerInput{
|
||||
Count: input.CountControlPlanes,
|
||||
Name: strings.Join([]string{c.name, "control-plane", c.uid}, "-"),
|
||||
Count: input.CountControlPlanes,
|
||||
Name: strings.Join([]string{c.name, "control-plane", c.uid}, "-"),
|
||||
NamedPorts: []*computepb.NamedPort{
|
||||
{Name: proto.String("kubernetes"), Port: proto.Int32(constants.KubernetesPort)},
|
||||
{Name: proto.String("debugd"), Port: proto.Int32(constants.DebugdPort)},
|
||||
{Name: proto.String("bootstrapper"), Port: proto.Int32(constants.BootstrapperPort)},
|
||||
{Name: proto.String("verify"), Port: proto.Int32(constants.VerifyServiceNodePortGRPC)},
|
||||
},
|
||||
Template: c.controlPlaneTemplate,
|
||||
UID: c.uid,
|
||||
Project: c.project,
|
||||
|
@ -277,18 +284,20 @@ func (c *Client) getInstanceIPs(ctx context.Context, groupID string, list cloudt
|
|||
}
|
||||
|
||||
type instanceGroupManagerInput struct {
|
||||
Count int
|
||||
Name string
|
||||
Template string
|
||||
Project string
|
||||
Zone string
|
||||
UID string
|
||||
Count int
|
||||
Name string
|
||||
NamedPorts []*computepb.NamedPort
|
||||
Template string
|
||||
Project string
|
||||
Zone string
|
||||
UID string
|
||||
}
|
||||
|
||||
func (i *instanceGroupManagerInput) InsertInstanceGroupManagerRequest() computepb.InsertInstanceGroupManagerRequest {
|
||||
return computepb.InsertInstanceGroupManagerRequest{
|
||||
InstanceGroupManagerResource: &computepb.InstanceGroupManager{
|
||||
BaseInstanceName: proto.String(i.Name),
|
||||
NamedPorts: i.NamedPorts,
|
||||
InstanceTemplate: proto.String("projects/" + i.Project + "/global/instanceTemplates/" + i.Template),
|
||||
Name: proto.String(i.Name),
|
||||
TargetSize: proto.Int32(int32(i.Count)),
|
||||
|
|
426
cli/internal/gcp/client/loadbalancer.go
Normal file
426
cli/internal/gcp/client/loadbalancer.go
Normal file
|
@ -0,0 +1,426 @@
|
|||
package client
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"github.com/edgelesssys/constellation/internal/constants"
|
||||
"go.uber.org/multierr"
|
||||
computepb "google.golang.org/genproto/googleapis/cloud/compute/v1"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
type loadBalancer struct {
|
||||
name string
|
||||
|
||||
// For creation.
|
||||
ip string
|
||||
frontendPort int
|
||||
backendPortName string
|
||||
healthCheck computepb.HealthCheck_Type
|
||||
label bool
|
||||
|
||||
// For resource management.
|
||||
hasHealthCheck bool
|
||||
hasBackendService bool
|
||||
hasForwardingRules bool
|
||||
}
|
||||
|
||||
// CreateLoadBalancers creates all necessary load balancers.
|
||||
func (c *Client) CreateLoadBalancers(ctx context.Context) error {
|
||||
if err := c.createIPAddr(ctx); err != nil {
|
||||
return fmt.Errorf("creating load balancer IP address: %w", err)
|
||||
}
|
||||
|
||||
//
|
||||
// LoadBalancer definitions.
|
||||
//
|
||||
|
||||
c.loadbalancers = append(c.loadbalancers, &loadBalancer{
|
||||
name: c.name + "-" + "kube" + "-" + c.uid,
|
||||
ip: c.loadbalancerIP,
|
||||
frontendPort: constants.KubernetesPort,
|
||||
backendPortName: "kubernetes",
|
||||
healthCheck: computepb.HealthCheck_HTTPS,
|
||||
label: true, // Label, so bootstrapper can find kube-apiserver.
|
||||
})
|
||||
|
||||
c.loadbalancers = append(c.loadbalancers, &loadBalancer{
|
||||
name: c.name + "-" + "boot" + "-" + c.uid,
|
||||
ip: c.loadbalancerIPname,
|
||||
frontendPort: constants.BootstrapperPort,
|
||||
backendPortName: "bootstrapper",
|
||||
healthCheck: computepb.HealthCheck_TCP,
|
||||
})
|
||||
|
||||
c.loadbalancers = append(c.loadbalancers, &loadBalancer{
|
||||
name: c.name + "-" + "verify" + "-" + c.uid,
|
||||
ip: c.loadbalancerIPname,
|
||||
frontendPort: constants.VerifyServiceNodePortGRPC,
|
||||
backendPortName: "verify",
|
||||
healthCheck: computepb.HealthCheck_TCP,
|
||||
})
|
||||
|
||||
c.loadbalancers = append(c.loadbalancers, &loadBalancer{
|
||||
name: c.name + "-" + "debugd" + "-" + c.uid,
|
||||
ip: c.loadbalancerIPname,
|
||||
frontendPort: constants.DebugdPort,
|
||||
backendPortName: "debugd",
|
||||
healthCheck: computepb.HealthCheck_TCP,
|
||||
})
|
||||
|
||||
// Load balancer creation.
|
||||
|
||||
errC := make(chan error)
|
||||
createLB := func(ctx context.Context, lb *loadBalancer) {
|
||||
errC <- c.createLoadBalancer(ctx, lb)
|
||||
}
|
||||
|
||||
for _, lb := range c.loadbalancers {
|
||||
go createLB(ctx, lb)
|
||||
}
|
||||
|
||||
var err error
|
||||
for i := 0; i < len(c.loadbalancers); i++ {
|
||||
err = multierr.Append(err, <-errC)
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// createLoadBalancer creates a load balancer.
|
||||
func (c *Client) createLoadBalancer(ctx context.Context, lb *loadBalancer) error {
|
||||
if err := c.createHealthCheck(ctx, lb); err != nil {
|
||||
return fmt.Errorf("creating health checks: %w", err)
|
||||
}
|
||||
if err := c.createBackendService(ctx, lb); err != nil {
|
||||
return fmt.Errorf("creating backend services: %w", err)
|
||||
}
|
||||
if err := c.createForwardingRules(ctx, lb); err != nil {
|
||||
return fmt.Errorf("creating forwarding rules: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Client) createHealthCheck(ctx context.Context, lb *loadBalancer) error {
|
||||
req := &computepb.InsertRegionHealthCheckRequest{
|
||||
Project: c.project,
|
||||
Region: c.region,
|
||||
HealthCheckResource: &computepb.HealthCheck{
|
||||
Name: proto.String(lb.name),
|
||||
Type: proto.String(computepb.HealthCheck_Type_name[int32(lb.healthCheck)]),
|
||||
CheckIntervalSec: proto.Int32(1),
|
||||
TimeoutSec: proto.Int32(1),
|
||||
},
|
||||
}
|
||||
switch lb.healthCheck {
|
||||
case computepb.HealthCheck_HTTPS:
|
||||
req.HealthCheckResource.HttpsHealthCheck = newHealthCheckHTTPS(lb.frontendPort)
|
||||
case computepb.HealthCheck_TCP:
|
||||
req.HealthCheckResource.TcpHealthCheck = newHealthCheckTCP(lb.frontendPort)
|
||||
}
|
||||
resp, err := c.healthChecksAPI.Insert(ctx, req)
|
||||
if err != nil {
|
||||
return fmt.Errorf("inserting health check: %w", err)
|
||||
}
|
||||
|
||||
if err := c.waitForOperations(ctx, []Operation{resp}); err != nil {
|
||||
return fmt.Errorf("waiting for health check creation: %w", err)
|
||||
}
|
||||
|
||||
lb.hasHealthCheck = true
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Client) createBackendService(ctx context.Context, lb *loadBalancer) error {
|
||||
req := &computepb.InsertRegionBackendServiceRequest{
|
||||
Project: c.project,
|
||||
Region: c.region,
|
||||
BackendServiceResource: &computepb.BackendService{
|
||||
Name: proto.String(lb.name),
|
||||
Protocol: proto.String(computepb.BackendService_Protocol_name[int32(computepb.BackendService_TCP)]),
|
||||
LoadBalancingScheme: proto.String(computepb.BackendService_LoadBalancingScheme_name[int32(computepb.BackendService_EXTERNAL)]),
|
||||
HealthChecks: []string{"https://www.googleapis.com/compute/v1/projects/" + c.project + "/regions/" + c.region + "/healthChecks/" + lb.name},
|
||||
PortName: proto.String(lb.backendPortName),
|
||||
Backends: []*computepb.Backend{
|
||||
{
|
||||
BalancingMode: proto.String(computepb.Backend_BalancingMode_name[int32(computepb.Backend_CONNECTION)]),
|
||||
Group: proto.String("https://www.googleapis.com/compute/v1/projects/" + c.project + "/zones/" + c.zone + "/instanceGroups/" + c.controlPlaneInstanceGroup),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
resp, err := c.backendServicesAPI.Insert(ctx, req)
|
||||
if err != nil {
|
||||
return fmt.Errorf("inserting backend services: %w", err)
|
||||
}
|
||||
|
||||
if err := c.waitForOperations(ctx, []Operation{resp}); err != nil {
|
||||
return fmt.Errorf("waiting for backend services creation: %w", err)
|
||||
}
|
||||
|
||||
lb.hasBackendService = true
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Client) createForwardingRules(ctx context.Context, lb *loadBalancer) error {
|
||||
req := &computepb.InsertForwardingRuleRequest{
|
||||
Project: c.project,
|
||||
Region: c.region,
|
||||
ForwardingRuleResource: &computepb.ForwardingRule{
|
||||
Name: proto.String(lb.name),
|
||||
IPAddress: proto.String("https://www.googleapis.com/compute/v1/projects/" + c.project + "/regions/" + c.region + "/addresses/" + c.loadbalancerIPname),
|
||||
IPProtocol: proto.String(computepb.ForwardingRule_IPProtocolEnum_name[int32(computepb.ForwardingRule_TCP)]),
|
||||
LoadBalancingScheme: proto.String(computepb.ForwardingRule_LoadBalancingScheme_name[int32(computepb.ForwardingRule_EXTERNAL)]),
|
||||
Ports: []string{strconv.Itoa(lb.frontendPort)},
|
||||
BackendService: proto.String("https://www.googleapis.com/compute/v1/projects/" + c.project + "/regions/" + c.region + "/backendServices/" + lb.name),
|
||||
},
|
||||
}
|
||||
resp, err := c.forwardingRulesAPI.Insert(ctx, req)
|
||||
if err != nil {
|
||||
return fmt.Errorf("inserting forwarding rules: %w", err)
|
||||
}
|
||||
|
||||
if err := c.waitForOperations(ctx, []Operation{resp}); err != nil {
|
||||
return err
|
||||
}
|
||||
lb.hasForwardingRules = true
|
||||
|
||||
if lb.label {
|
||||
return c.labelLoadBalancer(ctx, lb.name)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// labelLoadBalancer labels a load balancer (its forwarding rules) so that it can be found by applications in the cluster.
|
||||
func (c *Client) labelLoadBalancer(ctx context.Context, name string) error {
|
||||
forwardingRule, err := c.forwardingRulesAPI.Get(ctx, &computepb.GetForwardingRuleRequest{
|
||||
Project: c.project,
|
||||
Region: c.region,
|
||||
ForwardingRule: name,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("getting forwarding rule: %w", err)
|
||||
}
|
||||
if forwardingRule.LabelFingerprint == nil {
|
||||
return fmt.Errorf("forwarding rule %s has no label fingerprint", name)
|
||||
}
|
||||
|
||||
resp, err := c.forwardingRulesAPI.SetLabels(ctx, &computepb.SetLabelsForwardingRuleRequest{
|
||||
Project: c.project,
|
||||
Region: c.region,
|
||||
Resource: name,
|
||||
RegionSetLabelsRequestResource: &computepb.RegionSetLabelsRequest{
|
||||
Labels: map[string]string{"constellation-uid": c.uid},
|
||||
LabelFingerprint: forwardingRule.LabelFingerprint,
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("setting forwarding rule labels: %w", err)
|
||||
}
|
||||
|
||||
return c.waitForOperations(ctx, []Operation{resp})
|
||||
}
|
||||
|
||||
// TerminateLoadBalancers terminates all load balancers.
|
||||
func (c *Client) TerminateLoadBalancers(ctx context.Context) error {
|
||||
errC := make(chan error)
|
||||
|
||||
terminateLB := func(ctx context.Context, lb *loadBalancer) {
|
||||
errC <- c.terminateLoadBalancer(ctx, lb)
|
||||
}
|
||||
|
||||
for _, lb := range c.loadbalancers {
|
||||
go terminateLB(ctx, lb)
|
||||
}
|
||||
|
||||
var err error
|
||||
for i := 0; i < len(c.loadbalancers); i++ {
|
||||
err = multierr.Append(err, <-errC)
|
||||
}
|
||||
if err != nil && !isNotFoundError(err) {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := c.deleteIPAddr(ctx); err != nil {
|
||||
return fmt.Errorf("deleting load balancer IP address: %w", err)
|
||||
}
|
||||
|
||||
c.loadbalancers = nil
|
||||
return nil
|
||||
}
|
||||
|
||||
// terminateLoadBalancer removes the load balancer and its associated resources.
|
||||
func (c *Client) terminateLoadBalancer(ctx context.Context, lb *loadBalancer) error {
|
||||
if lb == nil {
|
||||
return nil
|
||||
}
|
||||
if lb.name == "" {
|
||||
return errors.New("load balancer name is empty")
|
||||
}
|
||||
|
||||
if lb.hasForwardingRules {
|
||||
if err := c.terminateForwadingRules(ctx, lb); err != nil {
|
||||
return fmt.Errorf("terminating forwarding rules: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
if lb.hasBackendService {
|
||||
if err := c.terminateBackendService(ctx, lb); err != nil {
|
||||
return fmt.Errorf("terminating backend services: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
if lb.hasHealthCheck {
|
||||
if err := c.terminateHealthCheck(ctx, lb); err != nil {
|
||||
return fmt.Errorf("terminating health checks: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
lb.name = ""
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Client) terminateForwadingRules(ctx context.Context, lb *loadBalancer) error {
|
||||
resp, err := c.forwardingRulesAPI.Delete(ctx, &computepb.DeleteForwardingRuleRequest{
|
||||
Project: c.project,
|
||||
Region: c.region,
|
||||
ForwardingRule: lb.name,
|
||||
})
|
||||
if isNotFoundError(err) {
|
||||
lb.hasForwardingRules = false
|
||||
return nil
|
||||
}
|
||||
if err != nil {
|
||||
return fmt.Errorf("deleting forwarding rules: %w", err)
|
||||
}
|
||||
|
||||
if err := c.waitForOperations(ctx, []Operation{resp}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
lb.hasForwardingRules = false
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Client) terminateBackendService(ctx context.Context, lb *loadBalancer) error {
|
||||
resp, err := c.backendServicesAPI.Delete(ctx, &computepb.DeleteRegionBackendServiceRequest{
|
||||
Project: c.project,
|
||||
Region: c.region,
|
||||
BackendService: lb.name,
|
||||
})
|
||||
if isNotFoundError(err) {
|
||||
lb.hasBackendService = false
|
||||
return nil
|
||||
}
|
||||
if err != nil {
|
||||
return fmt.Errorf("deleting backend services: %w", err)
|
||||
}
|
||||
|
||||
if err := c.waitForOperations(ctx, []Operation{resp}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
lb.hasBackendService = false
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Client) terminateHealthCheck(ctx context.Context, lb *loadBalancer) error {
|
||||
resp, err := c.healthChecksAPI.Delete(ctx, &computepb.DeleteRegionHealthCheckRequest{
|
||||
Project: c.project,
|
||||
Region: c.region,
|
||||
HealthCheck: lb.name,
|
||||
})
|
||||
if isNotFoundError(err) {
|
||||
lb.hasHealthCheck = false
|
||||
return nil
|
||||
}
|
||||
if err != nil {
|
||||
return fmt.Errorf("deleting health checks: %w", err)
|
||||
}
|
||||
|
||||
if err := c.waitForOperations(ctx, []Operation{resp}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
lb.hasHealthCheck = false
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Client) createIPAddr(ctx context.Context) error {
|
||||
ipName := c.name + "-" + c.uid
|
||||
insertReq := &computepb.InsertAddressRequest{
|
||||
Project: c.project,
|
||||
Region: c.region,
|
||||
AddressResource: &computepb.Address{
|
||||
Name: proto.String(ipName),
|
||||
},
|
||||
}
|
||||
op, err := c.addressesAPI.Insert(ctx, insertReq)
|
||||
if err != nil {
|
||||
return fmt.Errorf("inserting address: %w", err)
|
||||
}
|
||||
if err := c.waitForOperations(ctx, []Operation{op}); err != nil {
|
||||
return err
|
||||
}
|
||||
c.loadbalancerIPname = c.name + "-" + c.uid
|
||||
|
||||
getReq := &computepb.GetAddressRequest{
|
||||
Project: c.project,
|
||||
Region: c.region,
|
||||
Address: c.loadbalancerIPname,
|
||||
}
|
||||
addr, err := c.addressesAPI.Get(ctx, getReq)
|
||||
if err != nil {
|
||||
return fmt.Errorf("getting address: %w", err)
|
||||
}
|
||||
if addr.Address == nil {
|
||||
return fmt.Errorf("address response without address: %q", addr)
|
||||
}
|
||||
|
||||
c.loadbalancerIP = *addr.Address
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Client) deleteIPAddr(ctx context.Context) error {
|
||||
if c.loadbalancerIPname == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
req := &computepb.DeleteAddressRequest{
|
||||
Project: c.project,
|
||||
Region: c.region,
|
||||
Address: c.loadbalancerIPname,
|
||||
}
|
||||
op, err := c.addressesAPI.Delete(ctx, req)
|
||||
if isNotFoundError(err) {
|
||||
c.loadbalancerIPname = ""
|
||||
return nil
|
||||
}
|
||||
if err != nil {
|
||||
return fmt.Errorf("deleting address: %w", err)
|
||||
}
|
||||
|
||||
if err := c.waitForOperations(ctx, []Operation{op}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c.loadbalancerIPname = ""
|
||||
return nil
|
||||
}
|
||||
|
||||
func newHealthCheckHTTPS(port int) *computepb.HTTPSHealthCheck {
|
||||
return &computepb.HTTPSHealthCheck{
|
||||
Host: proto.String(""),
|
||||
Port: proto.Int32(int32(port)),
|
||||
RequestPath: proto.String("/readyz"),
|
||||
}
|
||||
}
|
||||
|
||||
func newHealthCheckTCP(port int) *computepb.TCPHealthCheck {
|
||||
return &computepb.TCPHealthCheck{
|
||||
Port: proto.Int32(int32(port)),
|
||||
}
|
||||
}
|
628
cli/internal/gcp/client/loadbalancer_test.go
Normal file
628
cli/internal/gcp/client/loadbalancer_test.go
Normal file
|
@ -0,0 +1,628 @@
|
|||
package client
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"google.golang.org/api/googleapi"
|
||||
"google.golang.org/genproto/googleapis/cloud/compute/v1"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
func TestCreateLoadBalancers(t *testing.T) {
|
||||
someErr := errors.New("failed")
|
||||
forwardingRule := &compute.ForwardingRule{LabelFingerprint: proto.String("fingerprint")}
|
||||
|
||||
testCases := map[string]struct {
|
||||
addrAPI addressesAPI
|
||||
healthAPI healthChecksAPI
|
||||
backendAPI backendServicesAPI
|
||||
forwardAPI forwardingRulesAPI
|
||||
opRegAPI operationRegionAPI
|
||||
wantErr bool
|
||||
}{
|
||||
"successful create": {
|
||||
addrAPI: &stubAddressesAPI{getAddr: proto.String("192.0.2.1")},
|
||||
healthAPI: &stubHealthChecksAPI{},
|
||||
backendAPI: &stubBackendServicesAPI{},
|
||||
forwardAPI: &stubForwardingRulesAPI{forwardingRule: forwardingRule},
|
||||
opRegAPI: stubOperationRegionAPI{},
|
||||
},
|
||||
"createIPAddr fails": {
|
||||
addrAPI: &stubAddressesAPI{insertErr: someErr},
|
||||
healthAPI: &stubHealthChecksAPI{},
|
||||
backendAPI: &stubBackendServicesAPI{},
|
||||
forwardAPI: &stubForwardingRulesAPI{forwardingRule: forwardingRule},
|
||||
opRegAPI: stubOperationRegionAPI{},
|
||||
wantErr: true,
|
||||
},
|
||||
"createLB fails": {
|
||||
addrAPI: &stubAddressesAPI{},
|
||||
healthAPI: &stubHealthChecksAPI{},
|
||||
backendAPI: &stubBackendServicesAPI{insertErr: someErr},
|
||||
forwardAPI: &stubForwardingRulesAPI{forwardingRule: forwardingRule},
|
||||
opRegAPI: stubOperationRegionAPI{},
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for name, tc := range testCases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
ctx := context.Background()
|
||||
client := Client{
|
||||
project: "project",
|
||||
zone: "zone",
|
||||
name: "name",
|
||||
uid: "uid",
|
||||
addressesAPI: tc.addrAPI,
|
||||
healthChecksAPI: tc.healthAPI,
|
||||
backendServicesAPI: tc.backendAPI,
|
||||
forwardingRulesAPI: tc.forwardAPI,
|
||||
operationRegionAPI: tc.opRegAPI,
|
||||
}
|
||||
|
||||
err := client.CreateLoadBalancers(ctx)
|
||||
|
||||
if tc.wantErr {
|
||||
assert.Error(err)
|
||||
} else {
|
||||
assert.NoError(err)
|
||||
assert.NotEmpty(client.loadbalancerIPname)
|
||||
assert.Equal(4, len(client.loadbalancers))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateLoadBalancer(t *testing.T) {
|
||||
someErr := errors.New("failed")
|
||||
testCases := map[string]struct {
|
||||
operationRegionAPI operationRegionAPI
|
||||
healthChecksAPI healthChecksAPI
|
||||
backendServicesAPI backendServicesAPI
|
||||
forwardingRulesAPI forwardingRulesAPI
|
||||
wantErr bool
|
||||
wantLB *loadBalancer
|
||||
}{
|
||||
"successful create": {
|
||||
healthChecksAPI: stubHealthChecksAPI{},
|
||||
backendServicesAPI: stubBackendServicesAPI{},
|
||||
forwardingRulesAPI: stubForwardingRulesAPI{forwardingRule: &compute.ForwardingRule{LabelFingerprint: proto.String("fingerprint")}},
|
||||
operationRegionAPI: stubOperationRegionAPI{},
|
||||
wantLB: &loadBalancer{
|
||||
name: "name",
|
||||
frontendPort: 1234,
|
||||
backendPortName: "testport",
|
||||
hasHealthCheck: true,
|
||||
hasBackendService: true,
|
||||
hasForwardingRules: true,
|
||||
},
|
||||
},
|
||||
"successful create with label": {
|
||||
healthChecksAPI: stubHealthChecksAPI{},
|
||||
backendServicesAPI: stubBackendServicesAPI{},
|
||||
forwardingRulesAPI: stubForwardingRulesAPI{forwardingRule: &compute.ForwardingRule{LabelFingerprint: proto.String("fingerprint")}},
|
||||
operationRegionAPI: stubOperationRegionAPI{},
|
||||
wantLB: &loadBalancer{
|
||||
name: "name",
|
||||
frontendPort: 1234,
|
||||
backendPortName: "testport",
|
||||
label: true,
|
||||
hasHealthCheck: true,
|
||||
hasBackendService: true,
|
||||
hasForwardingRules: true,
|
||||
},
|
||||
},
|
||||
"CreateLoadBalancer fails when getting forwarding rule": {
|
||||
healthChecksAPI: stubHealthChecksAPI{},
|
||||
backendServicesAPI: stubBackendServicesAPI{},
|
||||
forwardingRulesAPI: stubForwardingRulesAPI{getErr: someErr},
|
||||
operationRegionAPI: stubOperationRegionAPI{},
|
||||
wantErr: true,
|
||||
wantLB: &loadBalancer{
|
||||
name: "name",
|
||||
frontendPort: 1234,
|
||||
backendPortName: "testport",
|
||||
label: true,
|
||||
hasHealthCheck: true,
|
||||
hasBackendService: true,
|
||||
hasForwardingRules: true,
|
||||
},
|
||||
},
|
||||
"CreateLoadBalancer fails when label fingerprint is missing": {
|
||||
healthChecksAPI: stubHealthChecksAPI{},
|
||||
backendServicesAPI: stubBackendServicesAPI{},
|
||||
forwardingRulesAPI: stubForwardingRulesAPI{forwardingRule: &compute.ForwardingRule{}},
|
||||
operationRegionAPI: stubOperationRegionAPI{},
|
||||
wantErr: true,
|
||||
wantLB: &loadBalancer{
|
||||
name: "name",
|
||||
frontendPort: 1234,
|
||||
backendPortName: "testport",
|
||||
label: true,
|
||||
hasHealthCheck: true,
|
||||
hasBackendService: true,
|
||||
hasForwardingRules: true,
|
||||
},
|
||||
},
|
||||
"CreateLoadBalancer fails when creating health check": {
|
||||
healthChecksAPI: stubHealthChecksAPI{insertErr: someErr},
|
||||
backendServicesAPI: stubBackendServicesAPI{},
|
||||
forwardingRulesAPI: stubForwardingRulesAPI{forwardingRule: &compute.ForwardingRule{LabelFingerprint: proto.String("fingerprint")}},
|
||||
operationRegionAPI: stubOperationRegionAPI{},
|
||||
wantErr: true,
|
||||
wantLB: &loadBalancer{
|
||||
name: "name",
|
||||
frontendPort: 1234,
|
||||
backendPortName: "testport",
|
||||
hasHealthCheck: false,
|
||||
hasBackendService: false,
|
||||
hasForwardingRules: false,
|
||||
},
|
||||
},
|
||||
"CreateLoadBalancer fails when creating backend service": {
|
||||
healthChecksAPI: stubHealthChecksAPI{},
|
||||
backendServicesAPI: stubBackendServicesAPI{insertErr: someErr},
|
||||
forwardingRulesAPI: stubForwardingRulesAPI{},
|
||||
operationRegionAPI: stubOperationRegionAPI{},
|
||||
wantErr: true,
|
||||
wantLB: &loadBalancer{
|
||||
name: "name",
|
||||
frontendPort: 1234,
|
||||
backendPortName: "testport",
|
||||
hasHealthCheck: true,
|
||||
hasBackendService: false,
|
||||
hasForwardingRules: false,
|
||||
},
|
||||
},
|
||||
"CreateLoadBalancer fails when creating forwarding rule": {
|
||||
healthChecksAPI: stubHealthChecksAPI{},
|
||||
backendServicesAPI: stubBackendServicesAPI{},
|
||||
forwardingRulesAPI: stubForwardingRulesAPI{insertErr: someErr},
|
||||
operationRegionAPI: stubOperationRegionAPI{},
|
||||
wantErr: true,
|
||||
wantLB: &loadBalancer{
|
||||
name: "name",
|
||||
frontendPort: 1234,
|
||||
backendPortName: "testport",
|
||||
hasHealthCheck: true,
|
||||
hasBackendService: true,
|
||||
hasForwardingRules: false,
|
||||
},
|
||||
},
|
||||
"CreateLoadBalancer fails when waiting on operation": {
|
||||
healthChecksAPI: stubHealthChecksAPI{},
|
||||
backendServicesAPI: stubBackendServicesAPI{},
|
||||
forwardingRulesAPI: stubForwardingRulesAPI{forwardingRule: &compute.ForwardingRule{LabelFingerprint: proto.String("fingerprint")}},
|
||||
operationRegionAPI: stubOperationRegionAPI{waitErr: someErr},
|
||||
wantErr: true,
|
||||
wantLB: &loadBalancer{
|
||||
name: "name",
|
||||
frontendPort: 1234,
|
||||
backendPortName: "testport",
|
||||
hasHealthCheck: false,
|
||||
hasBackendService: false,
|
||||
hasForwardingRules: false,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for name, tc := range testCases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
ctx := context.Background()
|
||||
client := Client{
|
||||
project: "project",
|
||||
zone: "zone",
|
||||
name: "name",
|
||||
uid: "uid",
|
||||
backendServicesAPI: tc.backendServicesAPI,
|
||||
forwardingRulesAPI: tc.forwardingRulesAPI,
|
||||
healthChecksAPI: tc.healthChecksAPI,
|
||||
operationRegionAPI: tc.operationRegionAPI,
|
||||
}
|
||||
lb := &loadBalancer{
|
||||
name: tc.wantLB.name,
|
||||
frontendPort: tc.wantLB.frontendPort,
|
||||
backendPortName: tc.wantLB.backendPortName,
|
||||
label: tc.wantLB.label,
|
||||
}
|
||||
|
||||
err := client.createLoadBalancer(ctx, lb)
|
||||
|
||||
if tc.wantErr {
|
||||
assert.Error(err)
|
||||
assert.Equal(tc.wantLB, lb)
|
||||
} else {
|
||||
assert.NoError(err)
|
||||
assert.Equal(tc.wantLB, lb)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestTerminateLoadbalancers(t *testing.T) {
|
||||
someErr := errors.New("failed")
|
||||
newRunningLB := func() *loadBalancer {
|
||||
return &loadBalancer{
|
||||
name: "name",
|
||||
hasHealthCheck: true,
|
||||
hasBackendService: true,
|
||||
hasForwardingRules: true,
|
||||
}
|
||||
}
|
||||
|
||||
testCases := map[string]struct {
|
||||
addrAPI addressesAPI
|
||||
healthAPI healthChecksAPI
|
||||
backendAPI backendServicesAPI
|
||||
forwardAPI forwardingRulesAPI
|
||||
opRegionAPI operationRegionAPI
|
||||
wantErr bool
|
||||
}{
|
||||
"successful terminate": {
|
||||
addrAPI: &stubAddressesAPI{},
|
||||
healthAPI: &stubHealthChecksAPI{},
|
||||
backendAPI: &stubBackendServicesAPI{},
|
||||
forwardAPI: &stubForwardingRulesAPI{},
|
||||
opRegionAPI: stubOperationRegionAPI{},
|
||||
},
|
||||
"deleteIPAddr fails": {
|
||||
addrAPI: &stubAddressesAPI{deleteErr: someErr},
|
||||
healthAPI: &stubHealthChecksAPI{},
|
||||
backendAPI: &stubBackendServicesAPI{},
|
||||
forwardAPI: &stubForwardingRulesAPI{},
|
||||
opRegionAPI: stubOperationRegionAPI{},
|
||||
wantErr: true,
|
||||
},
|
||||
"deleteLB fails": {
|
||||
addrAPI: &stubAddressesAPI{},
|
||||
healthAPI: &stubHealthChecksAPI{},
|
||||
backendAPI: &stubBackendServicesAPI{deleteErr: someErr},
|
||||
forwardAPI: &stubForwardingRulesAPI{},
|
||||
opRegionAPI: stubOperationRegionAPI{},
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for name, tc := range testCases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
ctx := context.Background()
|
||||
client := Client{
|
||||
project: "project",
|
||||
zone: "zone",
|
||||
name: "name",
|
||||
uid: "uid",
|
||||
addressesAPI: tc.addrAPI,
|
||||
healthChecksAPI: tc.healthAPI,
|
||||
backendServicesAPI: tc.backendAPI,
|
||||
forwardingRulesAPI: tc.forwardAPI,
|
||||
operationRegionAPI: tc.opRegionAPI,
|
||||
loadbalancerIPname: "loadbalancerIPid",
|
||||
loadbalancers: []*loadBalancer{
|
||||
newRunningLB(),
|
||||
newRunningLB(),
|
||||
newRunningLB(),
|
||||
},
|
||||
}
|
||||
|
||||
err := client.TerminateLoadBalancers(ctx)
|
||||
|
||||
if tc.wantErr {
|
||||
assert.Error(err)
|
||||
} else {
|
||||
assert.NoError(err)
|
||||
assert.Empty(client.loadbalancerIPname)
|
||||
assert.Nil(client.loadbalancers)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestTerminateLoadBalancer(t *testing.T) {
|
||||
someErr := errors.New("failed")
|
||||
notFoundErr := &googleapi.Error{Code: http.StatusNotFound}
|
||||
newRunningLB := func() *loadBalancer {
|
||||
return &loadBalancer{
|
||||
name: "name",
|
||||
hasHealthCheck: true,
|
||||
hasBackendService: true,
|
||||
hasForwardingRules: true,
|
||||
}
|
||||
}
|
||||
|
||||
testCases := map[string]struct {
|
||||
lb *loadBalancer
|
||||
opRegionAPI operationRegionAPI
|
||||
healthChecksAPI healthChecksAPI
|
||||
backendServicesAPI backendServicesAPI
|
||||
forwardingRulesAPI forwardingRulesAPI
|
||||
wantErr bool
|
||||
wantLB *loadBalancer
|
||||
}{
|
||||
"successful terminate": {
|
||||
lb: newRunningLB(),
|
||||
healthChecksAPI: stubHealthChecksAPI{},
|
||||
backendServicesAPI: stubBackendServicesAPI{},
|
||||
forwardingRulesAPI: stubForwardingRulesAPI{},
|
||||
opRegionAPI: stubOperationRegionAPI{},
|
||||
wantLB: &loadBalancer{},
|
||||
},
|
||||
"terminate partially created loadbalancer": {
|
||||
lb: &loadBalancer{
|
||||
name: "name",
|
||||
hasHealthCheck: true,
|
||||
hasBackendService: true,
|
||||
hasForwardingRules: false,
|
||||
},
|
||||
healthChecksAPI: stubHealthChecksAPI{},
|
||||
backendServicesAPI: stubBackendServicesAPI{},
|
||||
forwardingRulesAPI: stubForwardingRulesAPI{deleteErr: someErr},
|
||||
opRegionAPI: stubOperationRegionAPI{},
|
||||
wantLB: &loadBalancer{},
|
||||
},
|
||||
"terminate partially created loadbalancer 2": {
|
||||
lb: &loadBalancer{
|
||||
name: "name",
|
||||
hasHealthCheck: true,
|
||||
hasBackendService: false,
|
||||
hasForwardingRules: false,
|
||||
},
|
||||
healthChecksAPI: stubHealthChecksAPI{},
|
||||
backendServicesAPI: stubBackendServicesAPI{deleteErr: someErr},
|
||||
forwardingRulesAPI: stubForwardingRulesAPI{deleteErr: someErr},
|
||||
opRegionAPI: stubOperationRegionAPI{},
|
||||
wantLB: &loadBalancer{},
|
||||
},
|
||||
"no-op for nil loadbalancer": {
|
||||
lb: nil,
|
||||
},
|
||||
"health check not found": {
|
||||
lb: newRunningLB(),
|
||||
healthChecksAPI: stubHealthChecksAPI{deleteErr: notFoundErr},
|
||||
backendServicesAPI: stubBackendServicesAPI{},
|
||||
forwardingRulesAPI: stubForwardingRulesAPI{},
|
||||
opRegionAPI: stubOperationRegionAPI{},
|
||||
wantLB: &loadBalancer{},
|
||||
},
|
||||
"backend service not found": {
|
||||
lb: newRunningLB(),
|
||||
healthChecksAPI: stubHealthChecksAPI{},
|
||||
backendServicesAPI: stubBackendServicesAPI{deleteErr: notFoundErr},
|
||||
forwardingRulesAPI: stubForwardingRulesAPI{},
|
||||
opRegionAPI: stubOperationRegionAPI{},
|
||||
wantLB: &loadBalancer{},
|
||||
},
|
||||
"forwarding rules not found": {
|
||||
lb: newRunningLB(),
|
||||
healthChecksAPI: stubHealthChecksAPI{},
|
||||
backendServicesAPI: stubBackendServicesAPI{},
|
||||
forwardingRulesAPI: stubForwardingRulesAPI{deleteErr: notFoundErr},
|
||||
opRegionAPI: stubOperationRegionAPI{},
|
||||
wantLB: &loadBalancer{},
|
||||
},
|
||||
"fails for loadbalancer without name": {
|
||||
lb: &loadBalancer{},
|
||||
wantErr: true,
|
||||
wantLB: &loadBalancer{},
|
||||
},
|
||||
"fails when deleting health check": {
|
||||
lb: newRunningLB(),
|
||||
healthChecksAPI: stubHealthChecksAPI{deleteErr: someErr},
|
||||
backendServicesAPI: stubBackendServicesAPI{},
|
||||
forwardingRulesAPI: stubForwardingRulesAPI{},
|
||||
opRegionAPI: stubOperationRegionAPI{},
|
||||
wantErr: true,
|
||||
wantLB: &loadBalancer{
|
||||
name: "name",
|
||||
hasHealthCheck: true,
|
||||
hasBackendService: false,
|
||||
hasForwardingRules: false,
|
||||
},
|
||||
},
|
||||
"fails when deleting backend service": {
|
||||
lb: newRunningLB(),
|
||||
healthChecksAPI: stubHealthChecksAPI{},
|
||||
backendServicesAPI: stubBackendServicesAPI{deleteErr: someErr},
|
||||
forwardingRulesAPI: stubForwardingRulesAPI{},
|
||||
opRegionAPI: stubOperationRegionAPI{},
|
||||
wantErr: true,
|
||||
wantLB: &loadBalancer{
|
||||
name: "name",
|
||||
hasHealthCheck: true,
|
||||
hasBackendService: true,
|
||||
hasForwardingRules: false,
|
||||
},
|
||||
},
|
||||
"fails when deleting forwarding rule": {
|
||||
lb: newRunningLB(),
|
||||
healthChecksAPI: stubHealthChecksAPI{},
|
||||
backendServicesAPI: stubBackendServicesAPI{},
|
||||
forwardingRulesAPI: stubForwardingRulesAPI{deleteErr: someErr},
|
||||
opRegionAPI: stubOperationRegionAPI{},
|
||||
wantErr: true,
|
||||
wantLB: &loadBalancer{
|
||||
name: "name",
|
||||
hasHealthCheck: true,
|
||||
hasBackendService: true,
|
||||
hasForwardingRules: true,
|
||||
},
|
||||
},
|
||||
"fails when waiting on operation": {
|
||||
lb: newRunningLB(),
|
||||
healthChecksAPI: stubHealthChecksAPI{},
|
||||
backendServicesAPI: stubBackendServicesAPI{},
|
||||
forwardingRulesAPI: stubForwardingRulesAPI{},
|
||||
opRegionAPI: stubOperationRegionAPI{waitErr: someErr},
|
||||
wantErr: true,
|
||||
wantLB: &loadBalancer{
|
||||
name: "name",
|
||||
hasHealthCheck: true,
|
||||
hasBackendService: true,
|
||||
hasForwardingRules: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for name, tc := range testCases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
ctx := context.Background()
|
||||
client := Client{
|
||||
project: "project",
|
||||
zone: "zone",
|
||||
name: "name",
|
||||
uid: "uid",
|
||||
backendServicesAPI: tc.backendServicesAPI,
|
||||
forwardingRulesAPI: tc.forwardingRulesAPI,
|
||||
healthChecksAPI: tc.healthChecksAPI,
|
||||
operationRegionAPI: tc.opRegionAPI,
|
||||
}
|
||||
|
||||
err := client.terminateLoadBalancer(ctx, tc.lb)
|
||||
|
||||
if tc.wantErr {
|
||||
assert.Error(err)
|
||||
assert.Equal(tc.wantLB, tc.lb)
|
||||
} else {
|
||||
assert.NoError(err)
|
||||
assert.Equal(tc.wantLB, tc.lb)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateIPAddr(t *testing.T) {
|
||||
someErr := errors.New("failed")
|
||||
|
||||
testCases := map[string]struct {
|
||||
addrAPI addressesAPI
|
||||
opAPI operationRegionAPI
|
||||
wantErr bool
|
||||
}{
|
||||
"successful create": {
|
||||
addrAPI: stubAddressesAPI{getAddr: proto.String("test-ip")},
|
||||
opAPI: stubOperationRegionAPI{},
|
||||
},
|
||||
"insert fails": {
|
||||
addrAPI: stubAddressesAPI{insertErr: someErr},
|
||||
opAPI: stubOperationRegionAPI{},
|
||||
wantErr: true,
|
||||
},
|
||||
"get fails": {
|
||||
addrAPI: stubAddressesAPI{getErr: someErr},
|
||||
opAPI: stubOperationRegionAPI{},
|
||||
wantErr: true,
|
||||
},
|
||||
"get address nil": {
|
||||
addrAPI: stubAddressesAPI{getAddr: nil},
|
||||
opAPI: stubOperationRegionAPI{},
|
||||
wantErr: true,
|
||||
},
|
||||
"wait fails": {
|
||||
addrAPI: stubAddressesAPI{},
|
||||
opAPI: stubOperationRegionAPI{waitErr: someErr},
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for name, tc := range testCases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
ctx := context.Background()
|
||||
client := Client{
|
||||
project: "project",
|
||||
zone: "zone",
|
||||
name: "name",
|
||||
uid: "uid",
|
||||
addressesAPI: tc.addrAPI,
|
||||
operationRegionAPI: tc.opAPI,
|
||||
}
|
||||
|
||||
err := client.createIPAddr(ctx)
|
||||
|
||||
if tc.wantErr {
|
||||
assert.Error(err)
|
||||
} else {
|
||||
assert.NoError(err)
|
||||
assert.Equal("test-ip", client.loadbalancerIP)
|
||||
assert.Equal("name-uid", client.loadbalancerIPname)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeleteIPAddr(t *testing.T) {
|
||||
someErr := errors.New("failed")
|
||||
notFoundErr := &googleapi.Error{Code: http.StatusNotFound}
|
||||
|
||||
testCases := map[string]struct {
|
||||
addrAPI addressesAPI
|
||||
opAPI operationRegionAPI
|
||||
addrID string
|
||||
wantErr bool
|
||||
}{
|
||||
"successful delete": {
|
||||
addrAPI: stubAddressesAPI{},
|
||||
opAPI: stubOperationRegionAPI{},
|
||||
addrID: "name",
|
||||
},
|
||||
"not found": {
|
||||
addrAPI: stubAddressesAPI{deleteErr: notFoundErr},
|
||||
opAPI: stubOperationRegionAPI{},
|
||||
addrID: "name",
|
||||
},
|
||||
"empty is no-op": {
|
||||
addrAPI: stubAddressesAPI{},
|
||||
opAPI: stubOperationRegionAPI{},
|
||||
},
|
||||
"delete fails": {
|
||||
addrAPI: stubAddressesAPI{deleteErr: someErr},
|
||||
opAPI: stubOperationRegionAPI{},
|
||||
addrID: "name",
|
||||
wantErr: true,
|
||||
},
|
||||
"wait fails": {
|
||||
addrAPI: stubAddressesAPI{},
|
||||
opAPI: stubOperationRegionAPI{waitErr: someErr},
|
||||
addrID: "name",
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for name, tc := range testCases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
ctx := context.Background()
|
||||
client := Client{
|
||||
project: "project",
|
||||
zone: "zone",
|
||||
name: "name",
|
||||
uid: "uid",
|
||||
addressesAPI: tc.addrAPI,
|
||||
operationRegionAPI: tc.opAPI,
|
||||
loadbalancerIPname: tc.addrID,
|
||||
}
|
||||
|
||||
err := client.deleteIPAddr(ctx)
|
||||
|
||||
if tc.wantErr {
|
||||
assert.Error(err)
|
||||
} else {
|
||||
assert.NoError(err)
|
||||
assert.Empty(client.loadbalancerIPname)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -3,11 +3,8 @@ package client
|
|||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"github.com/edgelesssys/constellation/internal/cloud/cloudtypes"
|
||||
"github.com/edgelesssys/constellation/internal/constants"
|
||||
computepb "google.golang.org/genproto/googleapis/cloud/compute/v1"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
@ -226,148 +223,3 @@ func (c *Client) terminateSubnet(ctx context.Context) error {
|
|||
c.subnetwork = ""
|
||||
return nil
|
||||
}
|
||||
|
||||
// CreateLoadBalancer creates a load balancer.
|
||||
func (c *Client) CreateLoadBalancer(ctx context.Context) error {
|
||||
c.healthCheck = c.name + "-" + c.uid
|
||||
resp, err := c.healthChecksAPI.Insert(ctx, &computepb.InsertRegionHealthCheckRequest{
|
||||
Project: c.project,
|
||||
Region: c.region,
|
||||
HealthCheckResource: &computepb.HealthCheck{
|
||||
Name: proto.String(c.healthCheck),
|
||||
Type: proto.String(computepb.HealthCheck_Type_name[int32(computepb.HealthCheck_HTTPS)]),
|
||||
CheckIntervalSec: proto.Int32(1),
|
||||
TimeoutSec: proto.Int32(1),
|
||||
HttpsHealthCheck: &computepb.HTTPSHealthCheck{
|
||||
Host: proto.String(""),
|
||||
Port: proto.Int32(6443),
|
||||
RequestPath: proto.String("/readyz"),
|
||||
},
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := c.waitForOperations(ctx, []Operation{resp}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c.backendService = c.name + "-" + c.uid
|
||||
resp, err = c.backendServicesAPI.Insert(ctx, &computepb.InsertRegionBackendServiceRequest{
|
||||
Project: c.project,
|
||||
Region: c.region,
|
||||
BackendServiceResource: &computepb.BackendService{
|
||||
Name: proto.String(c.backendService),
|
||||
Protocol: proto.String(computepb.BackendService_Protocol_name[int32(computepb.BackendService_TCP)]),
|
||||
LoadBalancingScheme: proto.String(computepb.BackendService_LoadBalancingScheme_name[int32(computepb.BackendService_EXTERNAL)]),
|
||||
TimeoutSec: proto.Int32(10),
|
||||
HealthChecks: []string{"https://www.googleapis.com/compute/v1/projects/" + c.project + "/regions/" + c.region + "/healthChecks/" + c.healthCheck},
|
||||
Backends: []*computepb.Backend{
|
||||
{
|
||||
BalancingMode: proto.String(computepb.Backend_BalancingMode_name[int32(computepb.Backend_CONNECTION)]),
|
||||
Group: proto.String("https://www.googleapis.com/compute/v1/projects/" + c.project + "/zones/" + c.zone + "/instanceGroups/" + c.controlPlaneInstanceGroup),
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := c.waitForOperations(ctx, []Operation{resp}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c.forwardingRule = c.name + "-" + c.uid
|
||||
resp, err = c.forwardingRulesAPI.Insert(ctx, &computepb.InsertForwardingRuleRequest{
|
||||
Project: c.project,
|
||||
Region: c.region,
|
||||
ForwardingRuleResource: &computepb.ForwardingRule{
|
||||
Name: proto.String(c.forwardingRule),
|
||||
IPProtocol: proto.String(computepb.ForwardingRule_IPProtocolEnum_name[int32(computepb.ForwardingRule_TCP)]),
|
||||
LoadBalancingScheme: proto.String(computepb.ForwardingRule_LoadBalancingScheme_name[int32(computepb.ForwardingRule_EXTERNAL)]),
|
||||
Ports: []string{"6443", "9000", strconv.Itoa(constants.VerifyServiceNodePortGRPC)},
|
||||
BackendService: proto.String("https://www.googleapis.com/compute/v1/projects/" + c.project + "/regions/" + c.region + "/backendServices/" + c.backendService),
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := c.waitForOperations(ctx, []Operation{resp}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
forwardingRule, err := c.forwardingRulesAPI.Get(ctx, &computepb.GetForwardingRuleRequest{
|
||||
Project: c.project,
|
||||
Region: c.region,
|
||||
ForwardingRule: c.forwardingRule,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if forwardingRule.LabelFingerprint == nil {
|
||||
return fmt.Errorf("forwarding rule %s has no label fingerprint", c.forwardingRule)
|
||||
}
|
||||
|
||||
resp, err = c.forwardingRulesAPI.SetLabels(ctx, &computepb.SetLabelsForwardingRuleRequest{
|
||||
Project: c.project,
|
||||
Region: c.region,
|
||||
Resource: c.forwardingRule,
|
||||
RegionSetLabelsRequestResource: &computepb.RegionSetLabelsRequest{
|
||||
Labels: map[string]string{"constellation-uid": c.uid},
|
||||
LabelFingerprint: forwardingRule.LabelFingerprint,
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return c.waitForOperations(ctx, []Operation{resp})
|
||||
}
|
||||
|
||||
// TerminateLoadBalancer removes the load balancer and its associated resources.
|
||||
func (c *Client) TerminateLoadBalancer(ctx context.Context) error {
|
||||
resp, err := c.forwardingRulesAPI.Delete(ctx, &computepb.DeleteForwardingRuleRequest{
|
||||
Project: c.project,
|
||||
Region: c.region,
|
||||
ForwardingRule: c.forwardingRule,
|
||||
})
|
||||
if err != nil && !isNotFoundError(err) {
|
||||
return err
|
||||
}
|
||||
if err == nil {
|
||||
if err := c.waitForOperations(ctx, []Operation{resp}); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
resp, err = c.backendServicesAPI.Delete(ctx, &computepb.DeleteRegionBackendServiceRequest{
|
||||
Project: c.project,
|
||||
Region: c.region,
|
||||
BackendService: c.backendService,
|
||||
})
|
||||
if err != nil && !isNotFoundError(err) {
|
||||
return err
|
||||
}
|
||||
if err == nil {
|
||||
if err := c.waitForOperations(ctx, []Operation{resp}); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
resp, err = c.healthChecksAPI.Delete(ctx, &computepb.DeleteRegionHealthCheckRequest{
|
||||
Project: c.project,
|
||||
Region: c.region,
|
||||
HealthCheck: c.healthCheck,
|
||||
})
|
||||
if err != nil && !isNotFoundError(err) {
|
||||
return err
|
||||
}
|
||||
|
||||
if err == nil {
|
||||
if err := c.waitForOperations(ctx, []Operation{resp}); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -9,8 +9,6 @@ import (
|
|||
"github.com/edgelesssys/constellation/internal/cloud/cloudtypes"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"google.golang.org/api/googleapi"
|
||||
"google.golang.org/genproto/googleapis/cloud/compute/v1"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
func TestCreateVPCs(t *testing.T) {
|
||||
|
@ -351,183 +349,3 @@ func TestTerminateFirewall(t *testing.T) {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateLoadBalancer(t *testing.T) {
|
||||
someErr := errors.New("failed")
|
||||
testCases := map[string]struct {
|
||||
operationRegionAPI operationRegionAPI
|
||||
healthChecksAPI healthChecksAPI
|
||||
backendServicesAPI backendServicesAPI
|
||||
forwardingRulesAPI forwardingRulesAPI
|
||||
wantErr bool
|
||||
}{
|
||||
"successful create": {
|
||||
healthChecksAPI: stubHealthChecksAPI{},
|
||||
backendServicesAPI: stubBackendServicesAPI{},
|
||||
forwardingRulesAPI: stubForwardingRulesAPI{forwardingRule: &compute.ForwardingRule{LabelFingerprint: proto.String("fingerprint")}},
|
||||
operationRegionAPI: stubOperationRegionAPI{},
|
||||
},
|
||||
"CreateLoadBalancer fails when getting forwarding rule": {
|
||||
healthChecksAPI: stubHealthChecksAPI{},
|
||||
backendServicesAPI: stubBackendServicesAPI{},
|
||||
forwardingRulesAPI: stubForwardingRulesAPI{getErr: someErr},
|
||||
operationRegionAPI: stubOperationRegionAPI{},
|
||||
wantErr: true,
|
||||
},
|
||||
"CreateLoadBalancer fails when label fingerprint is missing": {
|
||||
healthChecksAPI: stubHealthChecksAPI{},
|
||||
backendServicesAPI: stubBackendServicesAPI{},
|
||||
forwardingRulesAPI: stubForwardingRulesAPI{forwardingRule: &compute.ForwardingRule{}},
|
||||
operationRegionAPI: stubOperationRegionAPI{},
|
||||
wantErr: true,
|
||||
},
|
||||
"CreateLoadBalancer fails when creating health check": {
|
||||
healthChecksAPI: stubHealthChecksAPI{insertErr: someErr},
|
||||
backendServicesAPI: stubBackendServicesAPI{},
|
||||
forwardingRulesAPI: stubForwardingRulesAPI{forwardingRule: &compute.ForwardingRule{LabelFingerprint: proto.String("fingerprint")}},
|
||||
operationRegionAPI: stubOperationRegionAPI{},
|
||||
wantErr: true,
|
||||
},
|
||||
"CreateLoadBalancer fails when creating backend service": {
|
||||
healthChecksAPI: stubHealthChecksAPI{},
|
||||
backendServicesAPI: stubBackendServicesAPI{insertErr: someErr},
|
||||
forwardingRulesAPI: stubForwardingRulesAPI{},
|
||||
operationRegionAPI: stubOperationRegionAPI{},
|
||||
wantErr: true,
|
||||
},
|
||||
"CreateLoadBalancer fails when creating forwarding rule": {
|
||||
healthChecksAPI: stubHealthChecksAPI{},
|
||||
backendServicesAPI: stubBackendServicesAPI{},
|
||||
forwardingRulesAPI: stubForwardingRulesAPI{insertErr: someErr},
|
||||
operationRegionAPI: stubOperationRegionAPI{},
|
||||
wantErr: true,
|
||||
},
|
||||
"CreateLoadBalancer fails when waiting on operation": {
|
||||
healthChecksAPI: stubHealthChecksAPI{},
|
||||
backendServicesAPI: stubBackendServicesAPI{},
|
||||
forwardingRulesAPI: stubForwardingRulesAPI{forwardingRule: &compute.ForwardingRule{LabelFingerprint: proto.String("fingerprint")}},
|
||||
operationRegionAPI: stubOperationRegionAPI{waitErr: someErr},
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for name, tc := range testCases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
ctx := context.Background()
|
||||
client := Client{
|
||||
project: "project",
|
||||
zone: "zone",
|
||||
name: "name",
|
||||
uid: "uid",
|
||||
backendServicesAPI: tc.backendServicesAPI,
|
||||
forwardingRulesAPI: tc.forwardingRulesAPI,
|
||||
healthChecksAPI: tc.healthChecksAPI,
|
||||
operationRegionAPI: tc.operationRegionAPI,
|
||||
}
|
||||
|
||||
if tc.wantErr {
|
||||
assert.Error(client.CreateLoadBalancer(ctx))
|
||||
} else {
|
||||
assert.NoError(client.CreateLoadBalancer(ctx))
|
||||
assert.NotEmpty(client.healthCheck)
|
||||
assert.NotEmpty(client.backendService)
|
||||
assert.NotEmpty(client.forwardingRule)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestTerminateLoadBalancer(t *testing.T) {
|
||||
someErr := errors.New("failed")
|
||||
notFoundErr := &googleapi.Error{Code: http.StatusNotFound}
|
||||
|
||||
testCases := map[string]struct {
|
||||
operationRegionAPI operationRegionAPI
|
||||
healthChecksAPI healthChecksAPI
|
||||
backendServicesAPI backendServicesAPI
|
||||
forwardingRulesAPI forwardingRulesAPI
|
||||
wantErr bool
|
||||
}{
|
||||
"successful terminate": {
|
||||
healthChecksAPI: stubHealthChecksAPI{},
|
||||
backendServicesAPI: stubBackendServicesAPI{},
|
||||
forwardingRulesAPI: stubForwardingRulesAPI{},
|
||||
operationRegionAPI: stubOperationRegionAPI{},
|
||||
},
|
||||
"successful terminate when health check not found": {
|
||||
healthChecksAPI: stubHealthChecksAPI{deleteErr: notFoundErr},
|
||||
backendServicesAPI: stubBackendServicesAPI{},
|
||||
forwardingRulesAPI: stubForwardingRulesAPI{},
|
||||
operationRegionAPI: stubOperationRegionAPI{},
|
||||
},
|
||||
"successful terminate when backend service not found": {
|
||||
healthChecksAPI: stubHealthChecksAPI{},
|
||||
backendServicesAPI: stubBackendServicesAPI{deleteErr: notFoundErr},
|
||||
forwardingRulesAPI: stubForwardingRulesAPI{},
|
||||
operationRegionAPI: stubOperationRegionAPI{},
|
||||
},
|
||||
"successful terminate when forwarding rule not found": {
|
||||
healthChecksAPI: stubHealthChecksAPI{},
|
||||
backendServicesAPI: stubBackendServicesAPI{},
|
||||
forwardingRulesAPI: stubForwardingRulesAPI{deleteErr: notFoundErr},
|
||||
operationRegionAPI: stubOperationRegionAPI{},
|
||||
},
|
||||
"TerminateLoadBalancer fails when deleting health check": {
|
||||
healthChecksAPI: stubHealthChecksAPI{deleteErr: someErr},
|
||||
backendServicesAPI: stubBackendServicesAPI{},
|
||||
forwardingRulesAPI: stubForwardingRulesAPI{},
|
||||
operationRegionAPI: stubOperationRegionAPI{},
|
||||
wantErr: true,
|
||||
},
|
||||
"TerminateLoadBalancer fails when deleting backend service": {
|
||||
healthChecksAPI: stubHealthChecksAPI{},
|
||||
backendServicesAPI: stubBackendServicesAPI{deleteErr: someErr},
|
||||
forwardingRulesAPI: stubForwardingRulesAPI{},
|
||||
operationRegionAPI: stubOperationRegionAPI{},
|
||||
wantErr: true,
|
||||
},
|
||||
"TerminateLoadBalancer fails when deleting forwarding rule": {
|
||||
healthChecksAPI: stubHealthChecksAPI{},
|
||||
backendServicesAPI: stubBackendServicesAPI{},
|
||||
forwardingRulesAPI: stubForwardingRulesAPI{deleteErr: someErr},
|
||||
operationRegionAPI: stubOperationRegionAPI{},
|
||||
wantErr: true,
|
||||
},
|
||||
"TerminateLoadBalancer fails when waiting on operation": {
|
||||
healthChecksAPI: stubHealthChecksAPI{},
|
||||
backendServicesAPI: stubBackendServicesAPI{},
|
||||
forwardingRulesAPI: stubForwardingRulesAPI{},
|
||||
operationRegionAPI: stubOperationRegionAPI{waitErr: someErr},
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for name, tc := range testCases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
ctx := context.Background()
|
||||
client := Client{
|
||||
project: "project",
|
||||
zone: "zone",
|
||||
name: "name",
|
||||
uid: "uid",
|
||||
backendServicesAPI: tc.backendServicesAPI,
|
||||
forwardingRulesAPI: tc.forwardingRulesAPI,
|
||||
healthChecksAPI: tc.healthChecksAPI,
|
||||
operationRegionAPI: tc.operationRegionAPI,
|
||||
}
|
||||
|
||||
if tc.wantErr {
|
||||
assert.Error(client.TerminateLoadBalancer(ctx))
|
||||
} else {
|
||||
assert.NoError(client.TerminateLoadBalancer(ctx))
|
||||
assert.Empty(client.healthCheck)
|
||||
assert.Empty(client.backendService)
|
||||
assert.Empty(client.forwardingRule)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -37,6 +37,9 @@ func (c *Client) waitForGlobalOperation(ctx context.Context, op Operation) error
|
|||
if err := ctx.Err(); err != nil {
|
||||
return err
|
||||
}
|
||||
if op.Proto().Name == nil {
|
||||
return errors.New("operation name is nil")
|
||||
}
|
||||
waitReq := &computepb.WaitGlobalOperationRequest{
|
||||
Operation: *op.Proto().Name,
|
||||
Project: c.project,
|
||||
|
@ -59,6 +62,9 @@ func (c *Client) waitForZoneOperation(ctx context.Context, op Operation) error {
|
|||
if err := ctx.Err(); err != nil {
|
||||
return err
|
||||
}
|
||||
if op.Proto().Name == nil {
|
||||
return errors.New("operation name is nil")
|
||||
}
|
||||
waitReq := &computepb.WaitZoneOperationRequest{
|
||||
Operation: *op.Proto().Name,
|
||||
Project: c.project,
|
||||
|
@ -82,6 +88,9 @@ func (c *Client) waitForRegionOperation(ctx context.Context, op Operation) error
|
|||
if err := ctx.Err(); err != nil {
|
||||
return err
|
||||
}
|
||||
if op.Proto().Name == nil {
|
||||
return errors.New("operation name is nil")
|
||||
}
|
||||
waitReq := &computepb.WaitRegionOperationRequest{
|
||||
Operation: *op.Proto().Name,
|
||||
Project: c.project,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue