Remove GCP client from CLI

This commit is contained in:
katexochen 2022-09-27 09:26:06 +02:00 committed by Paul Meyer
parent d973740b03
commit feffe40987
18 changed files with 0 additions and 4693 deletions

View File

@ -1,13 +0,0 @@
/*
Copyright (c) Edgeless Systems GmbH
SPDX-License-Identifier: AGPL-3.0-only
*/
package gcp
import "fmt"
func AutoscalingNodeGroup(project string, zone string, nodeInstanceGroup string, min int, max int) string {
return fmt.Sprintf("%d:%d:https://www.googleapis.com/compute/v1/projects/%s/zones/%s/instanceGroups/%s", min, max, project, zone, nodeInstanceGroup)
}

View File

@ -1,25 +0,0 @@
/*
Copyright (c) Edgeless Systems GmbH
SPDX-License-Identifier: AGPL-3.0-only
*/
package gcp
import (
"testing"
"github.com/stretchr/testify/assert"
"go.uber.org/goleak"
)
func TestMain(m *testing.M) {
goleak.VerifyTestMain(m)
}
func TestAutoscalingNodeGroup(t *testing.T) {
assert := assert.New(t)
nodeGroups := AutoscalingNodeGroup("some-project", "some-zone", "some-group", 0, 100)
wantNodeGroups := "0:100:https://www.googleapis.com/compute/v1/projects/some-project/zones/some-zone/instanceGroups/some-group"
assert.Equal(wantNodeGroups, nodeGroups)
}

View File

@ -1,158 +0,0 @@
/*
Copyright (c) Edgeless Systems GmbH
SPDX-License-Identifier: AGPL-3.0-only
*/
package client
import (
"context"
"github.com/googleapis/gax-go/v2"
computepb "google.golang.org/genproto/googleapis/cloud/compute/v1"
adminpb "google.golang.org/genproto/googleapis/iam/admin/v1"
iampb "google.golang.org/genproto/googleapis/iam/v1"
)
type instanceAPI interface {
Close() error
List(ctx context.Context, req *computepb.ListInstancesRequest,
opts ...gax.CallOption) InstanceIterator
}
type operationRegionAPI interface {
Close() error
Wait(ctx context.Context, req *computepb.WaitRegionOperationRequest,
opts ...gax.CallOption) (*computepb.Operation, error)
}
type operationZoneAPI interface {
Close() error
Wait(ctx context.Context, req *computepb.WaitZoneOperationRequest,
opts ...gax.CallOption) (*computepb.Operation, error)
}
type operationGlobalAPI interface {
Close() error
Wait(ctx context.Context, req *computepb.WaitGlobalOperationRequest,
opts ...gax.CallOption) (*computepb.Operation, error)
}
type firewallsAPI interface {
Close() error
Delete(ctx context.Context, req *computepb.DeleteFirewallRequest,
opts ...gax.CallOption) (Operation, error)
Insert(ctx context.Context, req *computepb.InsertFirewallRequest,
opts ...gax.CallOption) (Operation, error)
}
type forwardingRulesAPI interface {
Close() error
Delete(ctx context.Context, req *computepb.DeleteGlobalForwardingRuleRequest,
opts ...gax.CallOption) (Operation, error)
Insert(ctx context.Context, req *computepb.InsertGlobalForwardingRuleRequest,
opts ...gax.CallOption) (Operation, error)
Get(ctx context.Context, req *computepb.GetGlobalForwardingRuleRequest,
opts ...gax.CallOption) (*computepb.ForwardingRule, error)
SetLabels(ctx context.Context, req *computepb.SetLabelsGlobalForwardingRuleRequest,
opts ...gax.CallOption) (Operation, error)
}
type backendServicesAPI interface {
Close() error
Delete(ctx context.Context, req *computepb.DeleteBackendServiceRequest,
opts ...gax.CallOption) (Operation, error)
Insert(ctx context.Context, req *computepb.InsertBackendServiceRequest,
opts ...gax.CallOption) (Operation, error)
}
type healthChecksAPI interface {
Close() error
Delete(ctx context.Context, req *computepb.DeleteHealthCheckRequest,
opts ...gax.CallOption) (Operation, error)
Insert(ctx context.Context, req *computepb.InsertHealthCheckRequest,
opts ...gax.CallOption) (Operation, error)
}
type targetTCPProxiesAPI interface {
Close() error
Delete(ctx context.Context, req *computepb.DeleteTargetTcpProxyRequest,
opts ...gax.CallOption) (Operation, error)
Insert(ctx context.Context, req *computepb.InsertTargetTcpProxyRequest,
opts ...gax.CallOption) (Operation, error)
}
type networksAPI interface {
Close() error
Delete(ctx context.Context, req *computepb.DeleteNetworkRequest,
opts ...gax.CallOption) (Operation, error)
Insert(ctx context.Context, req *computepb.InsertNetworkRequest,
opts ...gax.CallOption) (Operation, error)
}
type subnetworksAPI interface {
Close() error
Delete(ctx context.Context, req *computepb.DeleteSubnetworkRequest,
opts ...gax.CallOption) (Operation, error)
Insert(ctx context.Context, req *computepb.InsertSubnetworkRequest,
opts ...gax.CallOption) (Operation, error)
}
type instanceTemplateAPI interface {
Close() error
Delete(ctx context.Context, req *computepb.DeleteInstanceTemplateRequest,
opts ...gax.CallOption) (Operation, error)
Insert(ctx context.Context, req *computepb.InsertInstanceTemplateRequest,
opts ...gax.CallOption) (Operation, error)
}
type instanceGroupManagersAPI interface {
Close() error
Delete(ctx context.Context, req *computepb.DeleteInstanceGroupManagerRequest,
opts ...gax.CallOption) (Operation, error)
Insert(ctx context.Context, req *computepb.InsertInstanceGroupManagerRequest,
opts ...gax.CallOption) (Operation, error)
ListManagedInstances(ctx context.Context, req *computepb.ListManagedInstancesInstanceGroupManagersRequest,
opts ...gax.CallOption) ManagedInstanceIterator
}
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
}
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)
}
type addressesAPI interface {
Close() error
Insert(ctx context.Context, req *computepb.InsertGlobalAddressRequest,
opts ...gax.CallOption) (Operation, error)
Get(ctx context.Context, req *computepb.GetGlobalAddressRequest,
opts ...gax.CallOption) (*computepb.Address, error)
Delete(ctx context.Context, req *computepb.DeleteGlobalAddressRequest,
opts ...gax.CallOption) (Operation, error)
}
type Operation interface {
Proto() *computepb.Operation
}
type ManagedInstanceIterator interface {
Next() (*computepb.ManagedInstance, error)
}
type InstanceIterator interface {
Next() (*computepb.Instance, error)
}

View File

@ -1,576 +0,0 @@
/*
Copyright (c) Edgeless Systems GmbH
SPDX-License-Identifier: AGPL-3.0-only
*/
package client
import (
"context"
"github.com/googleapis/gax-go/v2"
"google.golang.org/api/iterator"
computepb "google.golang.org/genproto/googleapis/cloud/compute/v1"
iampb "google.golang.org/genproto/googleapis/iam/v1"
"google.golang.org/protobuf/proto"
)
type stubOperation struct {
*computepb.Operation
}
func (o *stubOperation) Proto() *computepb.Operation {
return o.Operation
}
type stubInstanceAPI struct {
listIterator *stubInstanceIterator
}
func (a stubInstanceAPI) Close() error {
return nil
}
func (a stubInstanceAPI) List(ctx context.Context, req *computepb.ListInstancesRequest,
opts ...gax.CallOption,
) InstanceIterator {
return a.listIterator
}
type stubInstanceIterator struct {
instances []*computepb.Instance
nextErr error
internalCounter int
}
func (i *stubInstanceIterator) Next() (*computepb.Instance, error) {
if i.nextErr != nil {
return nil, i.nextErr
}
if i.internalCounter >= len(i.instances) {
i.internalCounter = 0
return nil, iterator.Done
}
resp := i.instances[i.internalCounter]
i.internalCounter++
return resp, nil
}
type stubOperationZoneAPI struct {
waitErr error
}
func (a stubOperationZoneAPI) Close() error {
return nil
}
func (a stubOperationZoneAPI) Wait(ctx context.Context, req *computepb.WaitZoneOperationRequest,
opts ...gax.CallOption,
) (*computepb.Operation, error) {
if a.waitErr != nil {
return nil, a.waitErr
}
return &computepb.Operation{
Status: computepb.Operation_DONE.Enum(),
}, nil
}
type stubOperationRegionAPI struct {
waitErr error
}
func (a stubOperationRegionAPI) Close() error {
return nil
}
func (a stubOperationRegionAPI) Wait(ctx context.Context, req *computepb.WaitRegionOperationRequest,
opts ...gax.CallOption,
) (*computepb.Operation, error) {
if a.waitErr != nil {
return nil, a.waitErr
}
return &computepb.Operation{
Status: computepb.Operation_DONE.Enum(),
}, nil
}
type stubOperationGlobalAPI struct {
waitErr error
}
func (a stubOperationGlobalAPI) Close() error {
return nil
}
func (a stubOperationGlobalAPI) Wait(ctx context.Context, req *computepb.WaitGlobalOperationRequest,
opts ...gax.CallOption,
) (*computepb.Operation, error) {
if a.waitErr != nil {
return nil, a.waitErr
}
return &computepb.Operation{
Status: computepb.Operation_DONE.Enum(),
}, nil
}
type stubFirewallsAPI struct {
deleteErr error
insertErr error
}
func (a stubFirewallsAPI) Close() error {
return nil
}
func (a stubFirewallsAPI) Delete(ctx context.Context, req *computepb.DeleteFirewallRequest,
opts ...gax.CallOption,
) (Operation, error) {
if a.deleteErr != nil {
return nil, a.deleteErr
}
return &stubOperation{
&computepb.Operation{
Name: proto.String("name"),
},
}, nil
}
func (a stubFirewallsAPI) Insert(ctx context.Context, req *computepb.InsertFirewallRequest,
opts ...gax.CallOption,
) (Operation, error) {
if a.insertErr != nil {
return nil, a.insertErr
}
return &stubOperation{
&computepb.Operation{
Name: proto.String("name"),
},
}, nil
}
type stubNetworksAPI struct {
insertErr error
deleteErr error
}
func (a stubNetworksAPI) Close() error {
return nil
}
func (a stubNetworksAPI) Insert(ctx context.Context, req *computepb.InsertNetworkRequest,
opts ...gax.CallOption,
) (Operation, error) {
if a.insertErr != nil {
return nil, a.insertErr
}
return &stubOperation{
&computepb.Operation{
Name: proto.String("name"),
},
}, nil
}
func (a stubNetworksAPI) Delete(ctx context.Context, req *computepb.DeleteNetworkRequest,
opts ...gax.CallOption,
) (Operation, error) {
if a.deleteErr != nil {
return nil, a.deleteErr
}
return &stubOperation{
&computepb.Operation{
Name: proto.String("name"),
},
}, nil
}
type stubSubnetworksAPI struct {
insertErr error
deleteErr error
}
func (a stubSubnetworksAPI) Close() error {
return nil
}
func (a stubSubnetworksAPI) Insert(ctx context.Context, req *computepb.InsertSubnetworkRequest,
opts ...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 stubSubnetworksAPI) Delete(ctx context.Context, req *computepb.DeleteSubnetworkRequest,
opts ...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
}
type stubBackendServicesAPI struct {
insertErr error
deleteErr error
}
func (a stubBackendServicesAPI) Close() error {
return nil
}
func (a stubBackendServicesAPI) Insert(ctx context.Context, req *computepb.InsertBackendServiceRequest,
opts ...gax.CallOption,
) (Operation, error) {
if a.insertErr != nil {
return nil, a.insertErr
}
return &stubOperation{
&computepb.Operation{
Name: proto.String("name"),
},
}, nil
}
func (a stubBackendServicesAPI) Delete(ctx context.Context, req *computepb.DeleteBackendServiceRequest,
opts ...gax.CallOption,
) (Operation, error) {
if a.deleteErr != nil {
return nil, a.deleteErr
}
return &stubOperation{
&computepb.Operation{
Name: proto.String("name"),
},
}, nil
}
type stubTargetTCPProxiesAPI struct {
insertErr error
deleteErr error
}
func (a stubTargetTCPProxiesAPI) Close() error {
return nil
}
func (a stubTargetTCPProxiesAPI) Insert(ctx context.Context, req *computepb.InsertTargetTcpProxyRequest,
opts ...gax.CallOption,
) (Operation, error) {
if a.insertErr != nil {
return nil, a.insertErr
}
return &stubOperation{
&computepb.Operation{
Name: proto.String("name"),
},
}, nil
}
func (a stubTargetTCPProxiesAPI) Delete(ctx context.Context, req *computepb.DeleteTargetTcpProxyRequest,
opts ...gax.CallOption,
) (Operation, error) {
if a.deleteErr != nil {
return nil, a.deleteErr
}
return &stubOperation{
&computepb.Operation{
Name: proto.String("name"),
},
}, nil
}
type stubForwardingRulesAPI struct {
insertErr error
deleteErr error
getErr error
setLabelErr error
forwardingRule *computepb.ForwardingRule
}
func (a stubForwardingRulesAPI) Close() error {
return nil
}
func (a stubForwardingRulesAPI) Insert(ctx context.Context, req *computepb.InsertGlobalForwardingRuleRequest,
opts ...gax.CallOption,
) (Operation, error) {
if a.insertErr != nil {
return nil, a.insertErr
}
return &stubOperation{
&computepb.Operation{
Name: proto.String("name"),
},
}, nil
}
func (a stubForwardingRulesAPI) Delete(ctx context.Context, req *computepb.DeleteGlobalForwardingRuleRequest,
opts ...gax.CallOption,
) (Operation, error) {
if a.deleteErr != nil {
return nil, a.deleteErr
}
return &stubOperation{
&computepb.Operation{
Name: proto.String("name"),
},
}, nil
}
func (a stubForwardingRulesAPI) Get(ctx context.Context, req *computepb.GetGlobalForwardingRuleRequest,
opts ...gax.CallOption,
) (*computepb.ForwardingRule, error) {
if a.getErr != nil {
return nil, a.getErr
}
return a.forwardingRule, nil
}
func (a stubForwardingRulesAPI) SetLabels(ctx context.Context, req *computepb.SetLabelsGlobalForwardingRuleRequest,
opts ...gax.CallOption,
) (Operation, error) {
if a.deleteErr != nil {
return nil, a.setLabelErr
}
return &stubOperation{
&computepb.Operation{
Name: proto.String("name"),
},
}, nil
}
type stubHealthChecksAPI struct {
insertErr error
deleteErr error
}
func (a stubHealthChecksAPI) Close() error {
return nil
}
func (a stubHealthChecksAPI) Insert(ctx context.Context, req *computepb.InsertHealthCheckRequest,
opts ...gax.CallOption,
) (Operation, error) {
if a.insertErr != nil {
return nil, a.insertErr
}
return &stubOperation{
&computepb.Operation{
Name: proto.String("name"),
},
}, nil
}
func (a stubHealthChecksAPI) Delete(ctx context.Context, req *computepb.DeleteHealthCheckRequest,
opts ...gax.CallOption,
) (Operation, error) {
if a.deleteErr != nil {
return nil, a.deleteErr
}
return &stubOperation{
&computepb.Operation{
Name: proto.String("name"),
},
}, nil
}
type stubInstanceTemplateAPI struct {
deleteErr error
insertErr error
}
func (a stubInstanceTemplateAPI) Close() error {
return nil
}
func (a stubInstanceTemplateAPI) Delete(ctx context.Context, req *computepb.DeleteInstanceTemplateRequest,
opts ...gax.CallOption,
) (Operation, error) {
if a.deleteErr != nil {
return nil, a.deleteErr
}
return &stubOperation{
&computepb.Operation{
Name: proto.String("name"),
},
}, nil
}
func (a stubInstanceTemplateAPI) Insert(ctx context.Context, req *computepb.InsertInstanceTemplateRequest,
opts ...gax.CallOption,
) (Operation, error) {
if a.insertErr != nil {
return nil, a.insertErr
}
return &stubOperation{
&computepb.Operation{
Name: proto.String("name"),
},
}, nil
}
type stubInstanceGroupManagersAPI struct {
listIterator *stubManagedInstanceIterator
deleteErr error
insertErr error
}
func (a stubInstanceGroupManagersAPI) Close() error {
return nil
}
func (a stubInstanceGroupManagersAPI) Delete(ctx context.Context, req *computepb.DeleteInstanceGroupManagerRequest,
opts ...gax.CallOption,
) (Operation, error) {
if a.deleteErr != nil {
return nil, a.deleteErr
}
return &stubOperation{
&computepb.Operation{
Zone: proto.String("zone"),
Name: proto.String("name"),
},
}, nil
}
func (a stubInstanceGroupManagersAPI) Insert(ctx context.Context, req *computepb.InsertInstanceGroupManagerRequest,
opts ...gax.CallOption,
) (Operation, error) {
if a.insertErr != nil {
return nil, a.insertErr
}
return &stubOperation{
&computepb.Operation{
Zone: proto.String("zone"),
Name: proto.String("name"),
},
}, nil
}
func (a stubInstanceGroupManagersAPI) ListManagedInstances(ctx context.Context, req *computepb.ListManagedInstancesInstanceGroupManagersRequest,
opts ...gax.CallOption,
) ManagedInstanceIterator {
return a.listIterator
}
type stubProjectsAPI struct {
getPolicyErr error
setPolicyErr error
}
func (a stubProjectsAPI) Close() error {
return nil
}
func (a stubProjectsAPI) GetIamPolicy(ctx context.Context, req *iampb.GetIamPolicyRequest, opts ...gax.CallOption) (*iampb.Policy, error) {
if a.getPolicyErr != nil {
return nil, a.getPolicyErr
}
return &iampb.Policy{
Version: 3,
Bindings: []*iampb.Binding{
{
Role: "role",
Members: []string{
"member",
},
},
},
Etag: []byte("etag"),
}, nil
}
func (a stubProjectsAPI) SetIamPolicy(ctx context.Context, req *iampb.SetIamPolicyRequest, opts ...gax.CallOption) (*iampb.Policy, error) {
if a.setPolicyErr != nil {
return nil, a.setPolicyErr
}
return &iampb.Policy{
Version: 3,
Bindings: []*iampb.Binding{
{
Role: "role",
Members: []string{
"member",
},
},
},
Etag: []byte("etag"),
}, nil
}
type stubManagedInstanceIterator struct {
instances []*computepb.ManagedInstance
nextErr error
internalCounter int
}
func (i *stubManagedInstanceIterator) Next() (*computepb.ManagedInstance, error) {
if i.nextErr != nil {
return nil, i.nextErr
}
if i.internalCounter >= len(i.instances) {
i.internalCounter = 0
return nil, iterator.Done
}
resp := i.instances[i.internalCounter]
i.internalCounter++
return resp, nil
}
type stubAddressesAPI struct {
insertErr error
getAddr *string
getErr error
deleteErr error
}
func (a stubAddressesAPI) Insert(context.Context, *computepb.InsertGlobalAddressRequest,
...gax.CallOption,
) (Operation, error) {
if a.insertErr != nil {
return nil, a.insertErr
}
return &stubOperation{
&computepb.Operation{
Name: proto.String("name"),
},
}, nil
}
func (a stubAddressesAPI) Get(ctx context.Context, req *computepb.GetGlobalAddressRequest,
opts ...gax.CallOption,
) (*computepb.Address, error) {
return &computepb.Address{Address: a.getAddr}, a.getErr
}
func (a stubAddressesAPI) Delete(context.Context, *computepb.DeleteGlobalAddressRequest,
...gax.CallOption,
) (Operation, error) {
if a.deleteErr != nil {
return nil, a.deleteErr
}
return &stubOperation{
&computepb.Operation{
Name: proto.String("name"),
},
}, nil
}
func (a stubAddressesAPI) Close() error {
return nil
}

View File

@ -1,394 +0,0 @@
/*
Copyright (c) Edgeless Systems GmbH
SPDX-License-Identifier: AGPL-3.0-only
*/
package client
import (
"context"
"crypto/rand"
"errors"
"math/big"
"net/http"
"strings"
compute "cloud.google.com/go/compute/apiv1"
admin "cloud.google.com/go/iam/admin/apiv1"
resourcemanager "cloud.google.com/go/resourcemanager/apiv3"
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudtypes"
"github.com/edgelesssys/constellation/v2/internal/state"
"go.uber.org/multierr"
"google.golang.org/api/googleapi"
)
// Client is a client for the Google Compute Engine.
type Client struct {
instanceAPI
operationRegionAPI
operationZoneAPI
operationGlobalAPI
networksAPI
subnetworksAPI
firewallsAPI
forwardingRulesAPI
backendServicesAPI
healthChecksAPI
targetTCPProxiesAPI
instanceTemplateAPI
instanceGroupManagersAPI
iamAPI
projectsAPI
addressesAPI
workers cloudtypes.Instances
controlPlanes cloudtypes.Instances
workerInstanceGroup string
controlPlaneInstanceGroup string
controlPlaneTemplate string
workerTemplate string
network string
subnetwork string
secondarySubnetworkRange string
firewalls []string
name string
project string
uid string
zone string
region string
// loadbalancer
loadbalancerIP string
loadbalancerIPname string
loadbalancers []*loadBalancer
}
// NewFromDefault creates an uninitialized client.
func NewFromDefault(ctx context.Context) (*Client, error) {
var closers []closer
insAPI, err := compute.NewInstancesRESTClient(ctx)
if err != nil {
return nil, err
}
closers = append(closers, insAPI)
opZoneAPI, err := compute.NewZoneOperationsRESTClient(ctx)
if err != nil {
_ = closeAll(closers)
return nil, err
}
closers = append(closers, opZoneAPI)
opRegionAPI, err := compute.NewRegionOperationsRESTClient(ctx)
if err != nil {
_ = closeAll(closers)
return nil, err
}
closers = append(closers, opRegionAPI)
opGlobalAPI, err := compute.NewGlobalOperationsRESTClient(ctx)
if err != nil {
_ = closeAll(closers)
return nil, err
}
closers = append(closers, opGlobalAPI)
netAPI, err := compute.NewNetworksRESTClient(ctx)
if err != nil {
_ = closeAll(closers)
return nil, err
}
closers = append(closers, netAPI)
subnetAPI, err := compute.NewSubnetworksRESTClient(ctx)
if err != nil {
_ = closeAll(closers)
return nil, err
}
closers = append(closers, subnetAPI)
fwAPI, err := compute.NewFirewallsRESTClient(ctx)
if err != nil {
_ = closeAll(closers)
return nil, err
}
closers = append(closers, subnetAPI)
forwardingRulesAPI, err := compute.NewGlobalForwardingRulesRESTClient(ctx)
if err != nil {
_ = closeAll(closers)
return nil, err
}
closers = append(closers, forwardingRulesAPI)
backendServicesAPI, err := compute.NewBackendServicesRESTClient(ctx)
if err != nil {
_ = closeAll(closers)
return nil, err
}
closers = append(closers, backendServicesAPI)
targetTCPProxiesAPI, err := compute.NewTargetTcpProxiesRESTClient(ctx)
if err != nil {
_ = closeAll(closers)
return nil, err
}
closers = append(closers, targetTCPProxiesAPI)
targetPoolsAPI, err := compute.NewTargetPoolsRESTClient(ctx)
if err != nil {
_ = closeAll(closers)
return nil, err
}
closers = append(closers, targetPoolsAPI)
healthChecksAPI, err := compute.NewHealthChecksRESTClient(ctx)
if err != nil {
_ = closeAll(closers)
return nil, err
}
closers = append(closers, healthChecksAPI)
templAPI, err := compute.NewInstanceTemplatesRESTClient(ctx)
if err != nil {
_ = closeAll(closers)
return nil, err
}
closers = append(closers, templAPI)
groupAPI, err := compute.NewInstanceGroupManagersRESTClient(ctx)
if err != nil {
_ = closeAll(closers)
return nil, err
}
closers = append(closers, groupAPI)
iamAPI, err := admin.NewIamClient(ctx)
if err != nil {
_ = closeAll(closers)
return nil, err
}
closers = append(closers, iamAPI)
projectsAPI, err := resourcemanager.NewProjectsClient(ctx)
if err != nil {
_ = closeAll(closers)
return nil, err
}
closers = append(closers, projectsAPI)
addressesAPI, err := compute.NewGlobalAddressesRESTClient(ctx)
if err != nil {
_ = closeAll(closers)
return nil, err
}
return &Client{
instanceAPI: &instanceClient{insAPI},
operationRegionAPI: opRegionAPI,
operationZoneAPI: opZoneAPI,
operationGlobalAPI: opGlobalAPI,
networksAPI: &networksClient{netAPI},
subnetworksAPI: &subnetworksClient{subnetAPI},
firewallsAPI: &firewallsClient{fwAPI},
forwardingRulesAPI: &forwardingRulesClient{forwardingRulesAPI},
backendServicesAPI: &backendServicesClient{backendServicesAPI},
targetTCPProxiesAPI: &targetTCPProxiesClient{targetTCPProxiesAPI},
healthChecksAPI: &healthChecksClient{healthChecksAPI},
instanceTemplateAPI: &instanceTemplateClient{templAPI},
instanceGroupManagersAPI: &instanceGroupManagersClient{groupAPI},
iamAPI: &iamClient{iamAPI},
projectsAPI: &projectsClient{projectsAPI},
addressesAPI: &addressesClient{addressesAPI},
workers: make(cloudtypes.Instances),
controlPlanes: make(cloudtypes.Instances),
}, nil
}
// NewInitialized creates an initialized client.
func NewInitialized(ctx context.Context, project, zone, region, name string) (*Client, error) {
client, err := NewFromDefault(ctx)
if err != nil {
return nil, err
}
err = client.init(project, zone, region, name)
return client, err
}
// Close closes the client's connection.
func (c *Client) Close() error {
closers := []closer{
c.instanceAPI,
c.operationRegionAPI,
c.operationZoneAPI,
c.operationGlobalAPI,
c.networksAPI,
c.subnetworksAPI,
c.firewallsAPI,
c.forwardingRulesAPI,
c.backendServicesAPI,
c.healthChecksAPI,
c.instanceTemplateAPI,
c.instanceGroupManagersAPI,
c.iamAPI,
c.projectsAPI,
c.addressesAPI,
}
return closeAll(closers)
}
// init initializes the client.
func (c *Client) init(project, zone, region, name string) error {
c.project = project
c.zone = zone
c.name = name
c.region = region
uid, err := c.generateUID()
if err != nil {
return err
}
c.uid = uid
return nil
}
// GetState returns the state of the client as ConstellationState.
func (c *Client) GetState() state.ConstellationState {
stat := state.ConstellationState{
Name: c.name,
UID: c.uid,
CloudProvider: cloudprovider.GCP.String(),
LoadBalancerIP: c.loadbalancerIP,
GCPProject: c.project,
GCPZone: c.zone,
GCPRegion: c.region,
GCPWorkerInstances: c.workers,
GCPWorkerInstanceGroup: c.workerInstanceGroup,
GCPWorkerInstanceTemplate: c.workerTemplate,
GCPControlPlaneInstances: c.controlPlanes,
GCPControlPlaneInstanceGroup: c.controlPlaneInstanceGroup,
GCPControlPlaneInstanceTemplate: c.controlPlaneTemplate,
GCPFirewalls: c.firewalls,
GCPNetwork: c.network,
GCPSubnetwork: c.subnetwork,
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.
func (c *Client) SetState(stat state.ConstellationState) {
c.workers = stat.GCPWorkerInstances
c.controlPlanes = stat.GCPControlPlaneInstances
c.workerInstanceGroup = stat.GCPWorkerInstanceGroup
c.controlPlaneInstanceGroup = stat.GCPControlPlaneInstanceGroup
c.project = stat.GCPProject
c.zone = stat.GCPZone
c.region = stat.GCPRegion
c.name = stat.Name
c.uid = stat.UID
c.firewalls = stat.GCPFirewalls
c.network = stat.GCPNetwork
c.subnetwork = stat.GCPSubnetwork
c.workerTemplate = stat.GCPWorkerInstanceTemplate
c.controlPlaneTemplate = stat.GCPControlPlaneInstanceTemplate
c.loadbalancerIPname = stat.GCPLoadbalancerIPname
c.loadbalancerIP = stat.LoadBalancerIP
for _, lbName := range stat.GCPLoadbalancers {
lb := &loadBalancer{
name: lbName,
hasForwardingRules: true,
hasBackendService: true,
hasHealthCheck: true,
hasTargetTCPProxy: true,
}
c.loadbalancers = append(c.loadbalancers, lb)
}
}
func (c *Client) generateUID() (string, error) {
letters := []byte("abcdefghijklmnopqrstuvwxyz0123456789")
const uidLen = 5
uid := make([]byte, uidLen)
for i := 0; i < uidLen; i++ {
n, err := rand.Int(rand.Reader, big.NewInt(int64(len(letters))))
if err != nil {
return "", err
}
uid[i] = letters[n.Int64()]
}
return string(uid), nil
}
// buildInstanceName returns a formatted name string.
// The names are joined with a '-'.
// If names is empty, the returned value is c.name + "-" + c.uid.
func (c *Client) buildResourceName(names ...string) string {
builder := strings.Builder{}
builder.WriteString(c.name)
builder.WriteRune('-')
for _, name := range names {
builder.WriteString(name)
builder.WriteRune('-')
}
builder.WriteString(c.uid)
return builder.String()
}
func (c *Client) resourceURI(scope resourceScope, resourceType, resourceName string) string {
const baseURI = "https://www.googleapis.com/compute/v1/projects/"
builder := strings.Builder{}
builder.WriteString(baseURI)
builder.WriteString(c.project)
switch scope {
case scopeGlobal:
builder.WriteString("/global/")
case scopeRegion:
builder.WriteString("/regions/")
builder.WriteString(c.region)
builder.WriteRune('/')
case scopeZone:
builder.WriteString("/zones/")
builder.WriteString(c.zone)
builder.WriteRune('/')
default:
panic("unknown scope")
}
builder.WriteString(resourceType)
builder.WriteRune('/')
builder.WriteString(resourceName)
return builder.String()
}
type resourceScope string
const (
scopeGlobal resourceScope = "global"
scopeRegion resourceScope = "region"
scopeZone resourceScope = "zone"
)
type closer interface {
Close() error
}
// closeAll closes all closers, even if an error occurs.
//
// Errors are collected and a composed error is returned.
func closeAll(closers []closer) error {
// Since this function is intended to be deferred, it will always call all
// close operations, even if a previous operation failed. The if multiple
// errors occur, the returned error will be composed of the error messages
// of those errors.
var err error
for _, closer := range closers {
err = multierr.Append(err, closer.Close())
}
return err
}
func isNotFoundError(err error) bool {
var gAPIErr *googleapi.Error
if !errors.As(err, &gAPIErr) {
return false
}
return gAPIErr.Code == http.StatusNotFound
}

View File

@ -1,259 +0,0 @@
/*
Copyright (c) Edgeless Systems GmbH
SPDX-License-Identifier: AGPL-3.0-only
*/
package client
import (
"errors"
"net/http"
"testing"
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudtypes"
"github.com/edgelesssys/constellation/v2/internal/state"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.uber.org/goleak"
"google.golang.org/api/googleapi"
)
func TestMain(m *testing.M) {
goleak.VerifyTestMain(m,
// https://github.com/census-instrumentation/opencensus-go/issues/1262
goleak.IgnoreTopFunction("go.opencensus.io/stats/view.(*worker).start"),
)
}
func TestSetGetState(t *testing.T) {
state := state.ConstellationState{
CloudProvider: cloudprovider.GCP.String(),
GCPWorkerInstances: cloudtypes.Instances{
"id-1": {
PublicIP: "ip1",
PrivateIP: "ip2",
},
},
GCPControlPlaneInstances: cloudtypes.Instances{
"id-1": {
PublicIP: "ip3",
PrivateIP: "ip4",
},
},
GCPWorkerInstanceGroup: "group-id",
GCPControlPlaneInstanceGroup: "group-id",
GCPProject: "proj-id",
GCPZone: "zone-id",
GCPRegion: "region-id",
Name: "name",
UID: "uid",
LoadBalancerIP: "ip5",
GCPNetwork: "net-id",
GCPSubnetwork: "subnet-id",
GCPFirewalls: []string{"fw-1", "fw-2"},
GCPWorkerInstanceTemplate: "temp-id",
GCPControlPlaneInstanceTemplate: "temp-id",
GCPLoadbalancers: []string{"lb-1", "lb-2"},
}
t.Run("SetState", func(t *testing.T) {
assert := assert.New(t)
client := Client{}
client.SetState(state)
assert.Equal(state.GCPWorkerInstances, client.workers)
assert.Equal(state.GCPControlPlaneInstances, client.controlPlanes)
assert.Equal(state.GCPWorkerInstanceGroup, client.workerInstanceGroup)
assert.Equal(state.GCPControlPlaneInstanceGroup, client.controlPlaneInstanceGroup)
assert.Equal(state.GCPProject, client.project)
assert.Equal(state.GCPZone, client.zone)
assert.Equal(state.Name, client.name)
assert.Equal(state.UID, client.uid)
assert.Equal(state.GCPNetwork, client.network)
assert.Equal(state.GCPFirewalls, client.firewalls)
assert.Equal(state.GCPControlPlaneInstanceTemplate, client.controlPlaneTemplate)
assert.Equal(state.GCPWorkerInstanceTemplate, client.workerTemplate)
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) {
assert := assert.New(t)
client := Client{
workers: state.GCPWorkerInstances,
controlPlanes: state.GCPControlPlaneInstances,
workerInstanceGroup: state.GCPWorkerInstanceGroup,
controlPlaneInstanceGroup: state.GCPControlPlaneInstanceGroup,
project: state.GCPProject,
zone: state.GCPZone,
region: state.GCPRegion,
name: state.Name,
uid: state.UID,
network: state.GCPNetwork,
subnetwork: state.GCPSubnetwork,
firewalls: state.GCPFirewalls,
workerTemplate: state.GCPWorkerInstanceTemplate,
controlPlaneTemplate: state.GCPControlPlaneInstanceTemplate,
loadbalancerIP: state.LoadBalancerIP,
loadbalancerIPname: state.GCPLoadbalancerIPname,
}
for _, lbName := range state.GCPLoadbalancers {
client.loadbalancers = append(client.loadbalancers, &loadBalancer{name: lbName})
}
stat := client.GetState()
assert.Equal(state, stat)
})
}
func TestBuildResourceName(t *testing.T) {
testCases := map[string]struct {
clientUID string
clientName string
names []string
wantName string
}{
"no names": {
clientUID: "uid",
clientName: "name",
wantName: "name-uid",
},
"one name": {
clientUID: "uid",
clientName: "name",
names: []string{"foo"},
wantName: "name-foo-uid",
},
"two names": {
clientUID: "uid",
clientName: "name",
names: []string{"foo", "bar"},
wantName: "name-foo-bar-uid",
},
}
for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
assert := assert.New(t)
client := Client{
name: tc.clientName,
uid: tc.clientUID,
}
name := client.buildResourceName(tc.names...)
assert.Equal(tc.wantName, name)
})
}
}
func TestResourceURI(t *testing.T) {
testCases := map[string]struct {
scope resourceScope
resourceType string
resourceName string
wantURI string
}{
"global resource": {
scope: scopeGlobal,
resourceType: "healthChecks",
resourceName: "name",
wantURI: "https://www.googleapis.com/compute/v1/projects/project/global/healthChecks/name",
},
"regional resource": {
scope: scopeRegion,
resourceType: "healthChecks",
resourceName: "name",
wantURI: "https://www.googleapis.com/compute/v1/projects/project/regions/region/healthChecks/name",
},
"zonal resource": {
scope: scopeZone,
resourceType: "instanceGroups",
resourceName: "name",
wantURI: "https://www.googleapis.com/compute/v1/projects/project/zones/zone/instanceGroups/name",
},
}
for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
assert := assert.New(t)
client := Client{
project: "project",
zone: "zone",
region: "region",
}
uri := client.resourceURI(tc.scope, tc.resourceType, tc.resourceName)
assert.Equal(tc.wantURI, uri)
})
}
}
func TestInit(t *testing.T) {
assert := assert.New(t)
require := require.New(t)
client := Client{}
require.NoError(client.init("project", "zone", "region", "name"))
assert.Equal("project", client.project)
assert.Equal("zone", client.zone)
assert.Equal("region", client.region)
assert.Equal("name", client.name)
}
func TestCloseAll(t *testing.T) {
assert := assert.New(t)
closers := []closer{&someCloser{}, &someCloser{}, &someCloser{}}
assert.NoError(closeAll(closers))
for _, c := range closers {
assert.True(c.(*someCloser).closed)
}
someErr := errors.New("failed")
closers = []closer{&someCloser{}, &someCloser{closeErr: someErr}, &someCloser{}}
assert.Error(closeAll(closers))
for _, c := range closers {
assert.True(c.(*someCloser).closed)
}
}
type someCloser struct {
closeErr error
closed bool
}
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))
})
}
}

View File

@ -1,297 +0,0 @@
/*
Copyright (c) Edgeless Systems GmbH
SPDX-License-Identifier: AGPL-3.0-only
*/
package client
import (
"context"
compute "cloud.google.com/go/compute/apiv1"
admin "cloud.google.com/go/iam/admin/apiv1"
resourcemanager "cloud.google.com/go/resourcemanager/apiv3"
"github.com/googleapis/gax-go/v2"
computepb "google.golang.org/genproto/googleapis/cloud/compute/v1"
adminpb "google.golang.org/genproto/googleapis/iam/admin/v1"
iampb "google.golang.org/genproto/googleapis/iam/v1"
)
type instanceClient struct {
*compute.InstancesClient
}
func (c *instanceClient) Close() error {
return c.InstancesClient.Close()
}
func (c *instanceClient) List(ctx context.Context, req *computepb.ListInstancesRequest,
opts ...gax.CallOption,
) InstanceIterator {
return c.InstancesClient.List(ctx, req)
}
type firewallsClient struct {
*compute.FirewallsClient
}
func (c *firewallsClient) Close() error {
return c.FirewallsClient.Close()
}
func (c *firewallsClient) Delete(ctx context.Context, req *computepb.DeleteFirewallRequest,
opts ...gax.CallOption,
) (Operation, error) {
return c.FirewallsClient.Delete(ctx, req)
}
func (c *firewallsClient) Insert(ctx context.Context, req *computepb.InsertFirewallRequest,
opts ...gax.CallOption,
) (Operation, error) {
return c.FirewallsClient.Insert(ctx, req)
}
type forwardingRulesClient struct {
*compute.GlobalForwardingRulesClient
}
func (c *forwardingRulesClient) Close() error {
return c.GlobalForwardingRulesClient.Close()
}
func (c *forwardingRulesClient) Delete(ctx context.Context, req *computepb.DeleteGlobalForwardingRuleRequest,
opts ...gax.CallOption,
) (Operation, error) {
return c.GlobalForwardingRulesClient.Delete(ctx, req)
}
func (c *forwardingRulesClient) Insert(ctx context.Context, req *computepb.InsertGlobalForwardingRuleRequest,
opts ...gax.CallOption,
) (Operation, error) {
return c.GlobalForwardingRulesClient.Insert(ctx, req)
}
func (c *forwardingRulesClient) Get(ctx context.Context, req *computepb.GetGlobalForwardingRuleRequest,
opts ...gax.CallOption,
) (*computepb.ForwardingRule, error) {
return c.GlobalForwardingRulesClient.Get(ctx, req)
}
func (c *forwardingRulesClient) SetLabels(ctx context.Context, req *computepb.SetLabelsGlobalForwardingRuleRequest,
opts ...gax.CallOption,
) (Operation, error) {
return c.GlobalForwardingRulesClient.SetLabels(ctx, req)
}
type backendServicesClient struct {
*compute.BackendServicesClient
}
func (c *backendServicesClient) Close() error {
return c.BackendServicesClient.Close()
}
func (c *backendServicesClient) Insert(ctx context.Context, req *computepb.InsertBackendServiceRequest,
opts ...gax.CallOption,
) (Operation, error) {
return c.BackendServicesClient.Insert(ctx, req)
}
func (c *backendServicesClient) Delete(ctx context.Context, req *computepb.DeleteBackendServiceRequest,
opts ...gax.CallOption,
) (Operation, error) {
return c.BackendServicesClient.Delete(ctx, req)
}
type targetTCPProxiesClient struct {
*compute.TargetTcpProxiesClient
}
func (c *targetTCPProxiesClient) Close() error {
return c.TargetTcpProxiesClient.Close()
}
func (c *targetTCPProxiesClient) Delete(ctx context.Context, req *computepb.DeleteTargetTcpProxyRequest,
opts ...gax.CallOption,
) (Operation, error) {
return c.TargetTcpProxiesClient.Delete(ctx, req)
}
func (c *targetTCPProxiesClient) Insert(ctx context.Context, req *computepb.InsertTargetTcpProxyRequest,
opts ...gax.CallOption,
) (Operation, error) {
return c.TargetTcpProxiesClient.Insert(ctx, req)
}
type healthChecksClient struct {
*compute.HealthChecksClient
}
func (c *healthChecksClient) Close() error {
return c.HealthChecksClient.Close()
}
func (c *healthChecksClient) Delete(ctx context.Context, req *computepb.DeleteHealthCheckRequest,
opts ...gax.CallOption,
) (Operation, error) {
return c.HealthChecksClient.Delete(ctx, req)
}
func (c *healthChecksClient) Insert(ctx context.Context, req *computepb.InsertHealthCheckRequest,
opts ...gax.CallOption,
) (Operation, error) {
return c.HealthChecksClient.Insert(ctx, req)
}
type networksClient struct {
*compute.NetworksClient
}
func (c *networksClient) Close() error {
return c.NetworksClient.Close()
}
func (c *networksClient) Insert(ctx context.Context, req *computepb.InsertNetworkRequest,
opts ...gax.CallOption,
) (Operation, error) {
return c.NetworksClient.Insert(ctx, req)
}
func (c *networksClient) Delete(ctx context.Context, req *computepb.DeleteNetworkRequest,
opts ...gax.CallOption,
) (Operation, error) {
return c.NetworksClient.Delete(ctx, req)
}
type subnetworksClient struct {
*compute.SubnetworksClient
}
func (c *subnetworksClient) Close() error {
return c.SubnetworksClient.Close()
}
func (c *subnetworksClient) Insert(ctx context.Context, req *computepb.InsertSubnetworkRequest,
opts ...gax.CallOption,
) (Operation, error) {
return c.SubnetworksClient.Insert(ctx, req)
}
func (c *subnetworksClient) Delete(ctx context.Context, req *computepb.DeleteSubnetworkRequest,
opts ...gax.CallOption,
) (Operation, error) {
return c.SubnetworksClient.Delete(ctx, req)
}
type instanceTemplateClient struct {
*compute.InstanceTemplatesClient
}
func (c *instanceTemplateClient) Close() error {
return c.InstanceTemplatesClient.Close()
}
func (c *instanceTemplateClient) Delete(ctx context.Context, req *computepb.DeleteInstanceTemplateRequest,
opts ...gax.CallOption,
) (Operation, error) {
return c.InstanceTemplatesClient.Delete(ctx, req)
}
func (c *instanceTemplateClient) Insert(ctx context.Context, req *computepb.InsertInstanceTemplateRequest,
opts ...gax.CallOption,
) (Operation, error) {
return c.InstanceTemplatesClient.Insert(ctx, req)
}
type instanceGroupManagersClient struct {
*compute.InstanceGroupManagersClient
}
func (c *instanceGroupManagersClient) Close() error {
return c.InstanceGroupManagersClient.Close()
}
func (c *instanceGroupManagersClient) Delete(ctx context.Context, req *computepb.DeleteInstanceGroupManagerRequest,
opts ...gax.CallOption,
) (Operation, error) {
return c.InstanceGroupManagersClient.Delete(ctx, req)
}
func (c *instanceGroupManagersClient) Insert(ctx context.Context, req *computepb.InsertInstanceGroupManagerRequest,
opts ...gax.CallOption,
) (Operation, error) {
return c.InstanceGroupManagersClient.Insert(ctx, req)
}
func (c *instanceGroupManagersClient) ListManagedInstances(ctx context.Context, req *computepb.ListManagedInstancesInstanceGroupManagersRequest,
opts ...gax.CallOption,
) ManagedInstanceIterator {
return c.InstanceGroupManagersClient.ListManagedInstances(ctx, req)
}
type iamClient struct {
*admin.IamClient
}
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) {
return c.IamClient.CreateServiceAccount(ctx, req)
}
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 {
return c.IamClient.DeleteServiceAccount(ctx, req)
}
type projectsClient struct {
*resourcemanager.ProjectsClient
}
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) {
return c.ProjectsClient.GetIamPolicy(ctx, req)
}
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.GlobalAddressesClient
}
func (c *addressesClient) Insert(ctx context.Context, req *computepb.InsertGlobalAddressRequest,
opts ...gax.CallOption,
) (Operation, error) {
return c.GlobalAddressesClient.Insert(ctx, req)
}
func (c *addressesClient) Delete(ctx context.Context, req *computepb.DeleteGlobalAddressRequest,
opts ...gax.CallOption,
) (Operation, error) {
return c.GlobalAddressesClient.Delete(ctx, req)
}
func (c *addressesClient) Close() error {
return c.GlobalAddressesClient.Close()
}

View File

@ -1,454 +0,0 @@
/*
Copyright (c) Edgeless Systems GmbH
SPDX-License-Identifier: AGPL-3.0-only
*/
package client
import (
"context"
"errors"
"fmt"
"strconv"
"strings"
"time"
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudtypes"
"github.com/edgelesssys/constellation/v2/internal/constants"
"github.com/edgelesssys/constellation/v2/internal/role"
"google.golang.org/api/iterator"
computepb "google.golang.org/genproto/googleapis/cloud/compute/v1"
"google.golang.org/protobuf/proto"
)
// CreateInstances creates instances (virtual machines) on Google Compute Engine.
//
// A separate managed instance group is created for control planes and workers, the function
// waits until the instances are up and stores the public and private IPs of the instances
// in the client. If the client's network must be set before instances can be created.
func (c *Client) CreateInstances(ctx context.Context, input CreateInstancesInput) error {
if c.network == "" {
return errors.New("client has no network")
}
ops := []Operation{}
enableSerialConsole := strconv.FormatBool(input.EnableSerialConsole)
workerTemplateInput := insertInstanceTemplateInput{
Name: c.buildResourceName("worker"),
Network: c.network,
SecondarySubnetworkRangeName: c.secondarySubnetworkRange,
Subnetwork: c.subnetwork,
EnableSerialConsole: enableSerialConsole,
ImageID: input.ImageID,
InstanceType: input.InstanceType,
StateDiskSizeGB: int64(input.StateDiskSizeGB),
StateDiskType: input.StateDiskType,
Role: role.Worker.String(),
KubeEnv: input.KubeEnv,
Project: c.project,
Zone: c.zone,
Region: c.region,
UID: c.uid,
}
op, err := c.insertInstanceTemplate(ctx, workerTemplateInput)
if err != nil {
return fmt.Errorf("inserting instanceTemplate: %w", err)
}
ops = append(ops, op)
c.workerTemplate = workerTemplateInput.Name
controlPlaneTemplateInput := insertInstanceTemplateInput{
Name: c.buildResourceName("control-plane"),
Network: c.network,
Subnetwork: c.subnetwork,
SecondarySubnetworkRangeName: c.secondarySubnetworkRange,
EnableSerialConsole: enableSerialConsole,
ImageID: input.ImageID,
InstanceType: input.InstanceType,
StateDiskSizeGB: int64(input.StateDiskSizeGB),
StateDiskType: input.StateDiskType,
Role: role.ControlPlane.String(),
KubeEnv: input.KubeEnv,
Project: c.project,
Zone: c.zone,
Region: c.region,
UID: c.uid,
}
op, err = c.insertInstanceTemplate(ctx, controlPlaneTemplateInput)
if err != nil {
return fmt.Errorf("inserting instanceTemplate: %w", err)
}
ops = append(ops, op)
c.controlPlaneTemplate = controlPlaneTemplateInput.Name
if err := c.waitForOperations(ctx, ops); err != nil {
return err
}
ops = []Operation{}
controlPlaneGroupInput := instanceGroupManagerInput{
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)},
{Name: proto.String("konnectivity"), Port: proto.Int32(constants.KonnectivityPort)},
{Name: proto.String("recovery"), Port: proto.Int32(constants.RecoveryPort)},
},
Template: c.controlPlaneTemplate,
UID: c.uid,
Project: c.project,
Zone: c.zone,
}
op, err = c.insertInstanceGroupManger(ctx, controlPlaneGroupInput)
if err != nil {
return fmt.Errorf("inserting instanceGroupManager: %w", err)
}
ops = append(ops, op)
c.controlPlaneInstanceGroup = controlPlaneGroupInput.Name
workerGroupInput := instanceGroupManagerInput{
Count: input.CountWorkers,
Name: strings.Join([]string{c.name, "worker", c.uid}, "-"),
Template: c.workerTemplate,
UID: c.uid,
Project: c.project,
Zone: c.zone,
}
op, err = c.insertInstanceGroupManger(ctx, workerGroupInput)
if err != nil {
return fmt.Errorf("inserting instanceGroupManager: %w", err)
}
ops = append(ops, op)
c.workerInstanceGroup = workerGroupInput.Name
if err := c.waitForOperations(ctx, ops); err != nil {
return err
}
if err := c.waitForInstanceGroupScaling(ctx, c.workerInstanceGroup); err != nil {
return fmt.Errorf("waiting for instanceGroupScaling: %w", err)
}
if err := c.waitForInstanceGroupScaling(ctx, c.controlPlaneInstanceGroup); err != nil {
return fmt.Errorf("waiting for instanceGroupScaling: %w", err)
}
if err := c.getInstanceIPs(ctx, c.workerInstanceGroup, c.workers); err != nil {
return fmt.Errorf("getting instanceIPs: %w", err)
}
if err := c.getInstanceIPs(ctx, c.controlPlaneInstanceGroup, c.controlPlanes); err != nil {
return fmt.Errorf("getting instanceIPs: %w", err)
}
return nil
}
// TerminateInstances terminates the clients instances.
func (c *Client) TerminateInstances(ctx context.Context) error {
ops := []Operation{}
if c.workerInstanceGroup != "" {
op, err := c.deleteInstanceGroupManager(ctx, c.workerInstanceGroup)
if err != nil && !isNotFoundError(err) {
return fmt.Errorf("deleting instanceGroupManager '%s': %w", c.workerInstanceGroup, err)
}
if err == nil {
ops = append(ops, op)
}
c.workerInstanceGroup = ""
c.workers = make(cloudtypes.Instances)
}
if c.controlPlaneInstanceGroup != "" {
op, err := c.deleteInstanceGroupManager(ctx, c.controlPlaneInstanceGroup)
if err != nil && !isNotFoundError(err) {
return fmt.Errorf("deleting instanceGroupManager '%s': %w", c.controlPlaneInstanceGroup, err)
}
if err == nil {
ops = append(ops, op)
}
c.controlPlaneInstanceGroup = ""
c.controlPlanes = make(cloudtypes.Instances)
}
if err := c.waitForOperations(ctx, ops); err != nil {
return err
}
ops = []Operation{}
if c.workerTemplate != "" {
op, err := c.deleteInstanceTemplate(ctx, c.workerTemplate)
if err != nil && !isNotFoundError(err) {
return fmt.Errorf("deleting instanceTemplate: %w", err)
}
if err == nil {
ops = append(ops, op)
}
c.workerTemplate = ""
}
if c.controlPlaneTemplate != "" {
op, err := c.deleteInstanceTemplate(ctx, c.controlPlaneTemplate)
if err != nil && !isNotFoundError(err) {
return fmt.Errorf("deleting instanceTemplate: %w", err)
}
if err == nil {
ops = append(ops, op)
}
c.controlPlaneTemplate = ""
}
return c.waitForOperations(ctx, ops)
}
func (c *Client) insertInstanceTemplate(ctx context.Context, input insertInstanceTemplateInput) (Operation, error) {
req := input.insertInstanceTemplateRequest()
return c.instanceTemplateAPI.Insert(ctx, req)
}
func (c *Client) deleteInstanceTemplate(ctx context.Context, name string) (Operation, error) {
req := &computepb.DeleteInstanceTemplateRequest{
InstanceTemplate: name,
Project: c.project,
}
return c.instanceTemplateAPI.Delete(ctx, req)
}
func (c *Client) insertInstanceGroupManger(ctx context.Context, input instanceGroupManagerInput) (Operation, error) {
req := input.InsertInstanceGroupManagerRequest()
return c.instanceGroupManagersAPI.Insert(ctx, &req)
}
func (c *Client) deleteInstanceGroupManager(ctx context.Context, instanceGroupManagerName string) (Operation, error) {
req := &computepb.DeleteInstanceGroupManagerRequest{
InstanceGroupManager: instanceGroupManagerName,
Project: c.project,
Zone: c.zone,
}
return c.instanceGroupManagersAPI.Delete(ctx, req)
}
func (c *Client) waitForInstanceGroupScaling(ctx context.Context, groupID string) error {
for {
if err := ctx.Err(); err != nil {
return err
}
listReq := &computepb.ListManagedInstancesInstanceGroupManagersRequest{
InstanceGroupManager: groupID,
Project: c.project,
Zone: c.zone,
}
it := c.instanceGroupManagersAPI.ListManagedInstances(ctx, listReq)
for {
resp, err := it.Next()
if errors.Is(err, iterator.Done) {
return nil
}
if err != nil {
return err
}
if resp.CurrentAction == nil {
return errors.New("currentAction is nil")
}
if *resp.CurrentAction != computepb.ManagedInstance_NONE.String() {
time.Sleep(5 * time.Second)
break
}
}
}
}
// getInstanceIPs requests the IPs of the client's instances.
func (c *Client) getInstanceIPs(ctx context.Context, groupID string, list cloudtypes.Instances) error {
req := &computepb.ListInstancesRequest{
Filter: proto.String("name=" + groupID + "*"),
Project: c.project,
Zone: c.zone,
}
it := c.instanceAPI.List(ctx, req)
for {
resp, err := it.Next()
if errors.Is(err, iterator.Done) {
return nil
}
if err != nil {
return err
}
if resp.Name == nil {
return errors.New("instance name is nil pointer")
}
if len(resp.NetworkInterfaces) == 0 {
return errors.New("network interface is empty")
}
if resp.NetworkInterfaces[0].NetworkIP == nil {
return errors.New("networkIP is nil")
}
if len(resp.NetworkInterfaces[0].AccessConfigs) == 0 {
return errors.New("access configs is empty")
}
if resp.NetworkInterfaces[0].AccessConfigs[0].NatIP == nil {
return errors.New("natIP is nil")
}
instance := cloudtypes.Instance{
PrivateIP: *resp.NetworkInterfaces[0].NetworkIP,
PublicIP: *resp.NetworkInterfaces[0].AccessConfigs[0].NatIP,
}
list[*resp.Name] = instance
}
}
type instanceGroupManagerInput struct {
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)),
},
Project: i.Project,
Zone: i.Zone,
}
}
// CreateInstancesInput is the input for a CreatInstances operation.
type CreateInstancesInput struct {
EnableSerialConsole bool
CountWorkers int
CountControlPlanes int
ImageID string
InstanceType string
StateDiskSizeGB int
StateDiskType string
KubeEnv string
}
type insertInstanceTemplateInput struct {
Name string
Network string
Subnetwork string
SecondarySubnetworkRangeName string
EnableSerialConsole string
ImageID string
InstanceType string
StateDiskSizeGB int64
StateDiskType string
Role string
KubeEnv string
Project string
Zone string
Region string
UID string
}
func (i insertInstanceTemplateInput) insertInstanceTemplateRequest() *computepb.InsertInstanceTemplateRequest {
req := computepb.InsertInstanceTemplateRequest{
InstanceTemplateResource: &computepb.InstanceTemplate{
Description: proto.String("This instance belongs to a Constellation cluster."),
Name: proto.String(i.Name),
Properties: &computepb.InstanceProperties{
ConfidentialInstanceConfig: &computepb.ConfidentialInstanceConfig{
EnableConfidentialCompute: proto.Bool(true),
},
Description: proto.String("This instance belongs to a Constellation cluster."),
Disks: []*computepb.AttachedDisk{
{
InitializeParams: &computepb.AttachedDiskInitializeParams{
DiskSizeGb: proto.Int64(10),
SourceImage: proto.String(i.ImageID),
},
AutoDelete: proto.Bool(true),
Boot: proto.Bool(true),
Mode: proto.String(computepb.AttachedDisk_READ_WRITE.String()),
},
{
InitializeParams: &computepb.AttachedDiskInitializeParams{
DiskSizeGb: proto.Int64(i.StateDiskSizeGB),
DiskType: proto.String(i.StateDiskType),
},
AutoDelete: proto.Bool(true),
DeviceName: proto.String("state-disk"),
Mode: proto.String(computepb.AttachedDisk_READ_WRITE.String()),
Type: proto.String(computepb.AttachedDisk_PERSISTENT.String()),
},
},
MachineType: proto.String(i.InstanceType),
Metadata: &computepb.Metadata{
Items: []*computepb.Items{
{
Key: proto.String("kube-env"),
Value: proto.String(i.KubeEnv),
},
{
Key: proto.String("constellation-uid"),
Value: proto.String(i.UID),
},
{
Key: proto.String("constellation-role"),
Value: proto.String(i.Role),
},
{
Key: proto.String("serial-port-enable"),
Value: proto.String(i.EnableSerialConsole),
},
},
},
NetworkInterfaces: []*computepb.NetworkInterface{
{
Network: proto.String("projects/" + i.Project + "/global/networks/" + i.Network),
Subnetwork: proto.String("regions/" + i.Region + "/subnetworks/" + i.Subnetwork),
AccessConfigs: []*computepb.AccessConfig{
{Type: proto.String(computepb.AccessConfig_ONE_TO_ONE_NAT.String())},
},
},
},
Scheduling: &computepb.Scheduling{
OnHostMaintenance: proto.String(computepb.Scheduling_TERMINATE.String()),
},
ServiceAccounts: []*computepb.ServiceAccount{
{
Scopes: []string{
"https://www.googleapis.com/auth/compute",
"https://www.googleapis.com/auth/servicecontrol",
"https://www.googleapis.com/auth/service.management.readonly",
"https://www.googleapis.com/auth/devstorage.read_only",
"https://www.googleapis.com/auth/logging.write",
"https://www.googleapis.com/auth/monitoring.write",
"https://www.googleapis.com/auth/trace.append",
},
},
},
ShieldedInstanceConfig: &computepb.ShieldedInstanceConfig{
EnableIntegrityMonitoring: proto.Bool(true),
EnableSecureBoot: proto.Bool(true),
EnableVtpm: proto.Bool(true),
},
Tags: &computepb.Tags{
Items: []string{"constellation-" + i.UID},
},
},
},
Project: i.Project,
}
// if there is an secondary IP range defined, we use it as an alias IP range
if i.SecondarySubnetworkRangeName != "" {
req.InstanceTemplateResource.Properties.NetworkInterfaces[0].AliasIpRanges = []*computepb.AliasIpRange{
{
IpCidrRange: proto.String("/24"),
SubnetworkRangeName: proto.String(i.SecondarySubnetworkRangeName),
},
}
}
return &req
}

View File

@ -1,285 +0,0 @@
/*
Copyright (c) Edgeless Systems GmbH
SPDX-License-Identifier: AGPL-3.0-only
*/
package client
import (
"context"
"errors"
"net/http"
"testing"
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudtypes"
"github.com/stretchr/testify/assert"
"google.golang.org/api/googleapi"
computepb "google.golang.org/genproto/googleapis/cloud/compute/v1"
"google.golang.org/protobuf/proto"
)
func TestCreateInstances(t *testing.T) {
testInstances := []*computepb.Instance{
{
Name: proto.String("instance-name-1"),
NetworkInterfaces: []*computepb.NetworkInterface{
{
AccessConfigs: []*computepb.AccessConfig{
{NatIP: proto.String("public-ip")},
},
NetworkIP: proto.String("private-ip"),
},
},
},
{
Name: proto.String("instance-name-2"),
NetworkInterfaces: []*computepb.NetworkInterface{
{
AccessConfigs: []*computepb.AccessConfig{
{NatIP: proto.String("public-ip")},
},
NetworkIP: proto.String("private-ip"),
},
},
},
}
testManagedInstances := []*computepb.ManagedInstance{
{CurrentAction: proto.String(computepb.ManagedInstance_NONE.String())},
{CurrentAction: proto.String(computepb.ManagedInstance_NONE.String())},
}
testInput := CreateInstancesInput{
CountControlPlanes: 3,
CountWorkers: 4,
ImageID: "img",
InstanceType: "n2d-standard-4",
KubeEnv: "kube-env",
}
someErr := errors.New("failed")
testCases := map[string]struct {
instanceAPI instanceAPI
operationZoneAPI operationZoneAPI
operationGlobalAPI operationGlobalAPI
instanceTemplateAPI instanceTemplateAPI
instanceGroupManagersAPI instanceGroupManagersAPI
input CreateInstancesInput
network string
wantErr bool
}{
"successful create": {
instanceAPI: stubInstanceAPI{listIterator: &stubInstanceIterator{instances: testInstances}},
operationZoneAPI: stubOperationZoneAPI{},
operationGlobalAPI: stubOperationGlobalAPI{},
instanceTemplateAPI: stubInstanceTemplateAPI{},
instanceGroupManagersAPI: stubInstanceGroupManagersAPI{listIterator: &stubManagedInstanceIterator{instances: testManagedInstances}},
network: "network",
input: testInput,
},
"failed no network": {
instanceAPI: stubInstanceAPI{listIterator: &stubInstanceIterator{instances: testInstances}},
operationZoneAPI: stubOperationZoneAPI{waitErr: someErr},
operationGlobalAPI: stubOperationGlobalAPI{},
instanceTemplateAPI: stubInstanceTemplateAPI{},
instanceGroupManagersAPI: stubInstanceGroupManagersAPI{listIterator: &stubManagedInstanceIterator{instances: testManagedInstances}},
input: testInput,
wantErr: true,
},
"failed wait zonal op": {
instanceAPI: stubInstanceAPI{listIterator: &stubInstanceIterator{instances: testInstances}},
operationZoneAPI: stubOperationZoneAPI{waitErr: someErr},
operationGlobalAPI: stubOperationGlobalAPI{},
instanceTemplateAPI: stubInstanceTemplateAPI{},
instanceGroupManagersAPI: stubInstanceGroupManagersAPI{listIterator: &stubManagedInstanceIterator{instances: testManagedInstances}},
network: "network",
input: testInput,
wantErr: true,
},
"failed wait global op": {
instanceAPI: stubInstanceAPI{listIterator: &stubInstanceIterator{instances: testInstances}},
operationZoneAPI: stubOperationZoneAPI{},
operationGlobalAPI: stubOperationGlobalAPI{waitErr: someErr},
instanceTemplateAPI: stubInstanceTemplateAPI{},
instanceGroupManagersAPI: stubInstanceGroupManagersAPI{listIterator: &stubManagedInstanceIterator{instances: testManagedInstances}},
network: "network",
input: testInput,
wantErr: true,
},
"failed insert template": {
instanceAPI: stubInstanceAPI{listIterator: &stubInstanceIterator{instances: testInstances}},
operationZoneAPI: stubOperationZoneAPI{},
operationGlobalAPI: stubOperationGlobalAPI{},
instanceTemplateAPI: stubInstanceTemplateAPI{insertErr: someErr},
instanceGroupManagersAPI: stubInstanceGroupManagersAPI{listIterator: &stubManagedInstanceIterator{instances: testManagedInstances}},
input: testInput,
network: "network",
wantErr: true,
},
"failed insert instanceGroupManager": {
instanceAPI: stubInstanceAPI{listIterator: &stubInstanceIterator{instances: testInstances}},
operationZoneAPI: stubOperationZoneAPI{},
operationGlobalAPI: stubOperationGlobalAPI{},
instanceTemplateAPI: stubInstanceTemplateAPI{},
instanceGroupManagersAPI: stubInstanceGroupManagersAPI{insertErr: someErr},
network: "network",
input: testInput,
wantErr: true,
},
"failed instanceGroupManager iterator": {
instanceAPI: stubInstanceAPI{listIterator: &stubInstanceIterator{instances: testInstances}},
operationZoneAPI: stubOperationZoneAPI{},
operationGlobalAPI: stubOperationGlobalAPI{},
instanceTemplateAPI: stubInstanceTemplateAPI{},
instanceGroupManagersAPI: stubInstanceGroupManagersAPI{listIterator: &stubManagedInstanceIterator{nextErr: someErr}},
network: "network",
input: testInput,
wantErr: true,
},
"failed instance iterator": {
instanceAPI: stubInstanceAPI{listIterator: &stubInstanceIterator{nextErr: someErr}},
operationZoneAPI: stubOperationZoneAPI{},
operationGlobalAPI: stubOperationGlobalAPI{},
instanceTemplateAPI: stubInstanceTemplateAPI{},
instanceGroupManagersAPI: stubInstanceGroupManagersAPI{listIterator: &stubManagedInstanceIterator{instances: testManagedInstances}},
network: "network",
input: testInput,
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",
network: tc.network,
subnetwork: "subnetwork",
secondarySubnetworkRange: "secondary-range",
instanceAPI: tc.instanceAPI,
operationZoneAPI: tc.operationZoneAPI,
operationGlobalAPI: tc.operationGlobalAPI,
instanceTemplateAPI: tc.instanceTemplateAPI,
instanceGroupManagersAPI: tc.instanceGroupManagersAPI,
workers: make(cloudtypes.Instances),
controlPlanes: make(cloudtypes.Instances),
}
if tc.wantErr {
assert.Error(client.CreateInstances(ctx, tc.input))
} else {
assert.NoError(client.CreateInstances(ctx, tc.input))
assert.Equal([]string{"public-ip", "public-ip"}, client.workers.PublicIPs())
assert.Equal([]string{"private-ip", "private-ip"}, client.workers.PrivateIPs())
assert.Equal([]string{"public-ip", "public-ip"}, client.controlPlanes.PublicIPs())
assert.Equal([]string{"private-ip", "private-ip"}, client.controlPlanes.PrivateIPs())
assert.NotNil(client.workerInstanceGroup)
assert.NotNil(client.controlPlaneInstanceGroup)
assert.NotNil(client.controlPlaneTemplate)
assert.NotNil(client.workerTemplate)
}
})
}
}
func TestTerminateInstances(t *testing.T) {
someErr := errors.New("failed")
notFoundErr := &googleapi.Error{Code: http.StatusNotFound}
testCases := map[string]struct {
operationZoneAPI operationZoneAPI
operationGlobalAPI operationGlobalAPI
instanceTemplateAPI instanceTemplateAPI
instanceGroupManagersAPI instanceGroupManagersAPI
missingWorkerInstanceGroup bool
wantErr bool
}{
"successful terminate": {
operationZoneAPI: stubOperationZoneAPI{},
operationGlobalAPI: stubOperationGlobalAPI{},
instanceTemplateAPI: stubInstanceTemplateAPI{},
instanceGroupManagersAPI: stubInstanceGroupManagersAPI{},
},
"successful terminate with missing worker instance group": {
operationZoneAPI: stubOperationZoneAPI{},
operationGlobalAPI: stubOperationGlobalAPI{},
instanceTemplateAPI: stubInstanceTemplateAPI{},
instanceGroupManagersAPI: stubInstanceGroupManagersAPI{},
missingWorkerInstanceGroup: true,
},
"instances not found": {
operationZoneAPI: stubOperationZoneAPI{},
operationGlobalAPI: stubOperationGlobalAPI{},
instanceTemplateAPI: stubInstanceTemplateAPI{},
instanceGroupManagersAPI: stubInstanceGroupManagersAPI{deleteErr: notFoundErr},
},
"templates not found": {
operationZoneAPI: stubOperationZoneAPI{},
operationGlobalAPI: stubOperationGlobalAPI{},
instanceTemplateAPI: stubInstanceTemplateAPI{deleteErr: notFoundErr},
instanceGroupManagersAPI: stubInstanceGroupManagersAPI{},
},
"fail delete instanceGroupManager": {
operationZoneAPI: stubOperationZoneAPI{},
operationGlobalAPI: stubOperationGlobalAPI{},
instanceTemplateAPI: stubInstanceTemplateAPI{},
instanceGroupManagersAPI: stubInstanceGroupManagersAPI{deleteErr: someErr},
wantErr: true,
},
"fail delete instanceTemplate": {
operationZoneAPI: stubOperationZoneAPI{},
operationGlobalAPI: stubOperationGlobalAPI{},
instanceTemplateAPI: stubInstanceTemplateAPI{deleteErr: someErr},
instanceGroupManagersAPI: stubInstanceGroupManagersAPI{},
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",
operationZoneAPI: tc.operationZoneAPI,
operationGlobalAPI: tc.operationGlobalAPI,
instanceTemplateAPI: tc.instanceTemplateAPI,
instanceGroupManagersAPI: tc.instanceGroupManagersAPI,
workers: cloudtypes.Instances{"worker-id-1": cloudtypes.Instance{}, "worker-id-2": cloudtypes.Instance{}},
controlPlanes: cloudtypes.Instances{"controlplane-id-1": cloudtypes.Instance{}},
firewalls: []string{"firewall-1", "firewall-2"},
network: "network-id-1",
workerInstanceGroup: "workerInstanceGroup-id-1",
controlPlaneInstanceGroup: "controlplaneInstanceGroup-id-1",
workerTemplate: "template-id-1",
controlPlaneTemplate: "template-id-1",
}
if tc.missingWorkerInstanceGroup {
client.workerInstanceGroup = ""
client.workers = cloudtypes.Instances{}
}
if tc.wantErr {
assert.Error(client.TerminateInstances(ctx))
} else {
assert.NoError(client.TerminateInstances(ctx))
assert.Nil(client.workers.PublicIPs())
assert.Nil(client.workers.PrivateIPs())
assert.Nil(client.controlPlanes.PublicIPs())
assert.Nil(client.controlPlanes.PrivateIPs())
assert.Empty(client.workerInstanceGroup)
assert.Empty(client.controlPlaneInstanceGroup)
assert.Empty(client.controlPlaneTemplate)
assert.Empty(client.workerTemplate)
}
})
}
}

View File

@ -1,493 +0,0 @@
/*
Copyright (c) Edgeless Systems GmbH
SPDX-License-Identifier: AGPL-3.0-only
*/
package client
import (
"context"
"errors"
"fmt"
"strconv"
"github.com/edgelesssys/constellation/v2/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
hasTargetTCPProxy bool
}
// CreateLoadBalancers creates all necessary load balancers.
func (c *Client) CreateLoadBalancers(ctx context.Context, isDebugCluster bool) error {
if err := c.createIPAddr(ctx); err != nil {
return fmt.Errorf("creating load balancer IP address: %w", err)
}
//
// LoadBalancer definitions.
//
// LoadBalancers added here also need to be referenced in instances.go:*Client.CreateInstances
c.loadbalancers = append(c.loadbalancers, &loadBalancer{
name: c.buildResourceName("kube"),
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.buildResourceName("boot"),
ip: c.loadbalancerIPname,
frontendPort: constants.BootstrapperPort,
backendPortName: "bootstrapper",
healthCheck: computepb.HealthCheck_TCP,
})
c.loadbalancers = append(c.loadbalancers, &loadBalancer{
name: c.buildResourceName("verify"),
ip: c.loadbalancerIPname,
frontendPort: constants.VerifyServiceNodePortGRPC,
backendPortName: "verify",
healthCheck: computepb.HealthCheck_TCP,
})
c.loadbalancers = append(c.loadbalancers, &loadBalancer{
name: c.buildResourceName("konnectivity"),
ip: c.loadbalancerIPname,
frontendPort: constants.KonnectivityPort,
backendPortName: "konnectivity",
healthCheck: computepb.HealthCheck_TCP,
})
c.loadbalancers = append(c.loadbalancers, &loadBalancer{
name: c.buildResourceName("recovery"),
ip: c.loadbalancerIPname,
frontendPort: constants.RecoveryPort,
backendPortName: "recovery",
healthCheck: computepb.HealthCheck_TCP,
})
// Only create when the debug cluster flag is set in the Constellation config
if isDebugCluster {
c.loadbalancers = append(c.loadbalancers, &loadBalancer{
name: c.buildResourceName("debugd"),
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.createTargetTCPProxy(ctx, lb); err != nil {
return fmt.Errorf("creating target TCP proxies: %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.InsertHealthCheckRequest{
Project: c.project,
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.InsertBackendServiceRequest{
Project: c.project,
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{c.resourceURI(scopeGlobal, "healthChecks", lb.name)},
PortName: proto.String(lb.backendPortName),
Backends: []*computepb.Backend{
{
BalancingMode: proto.String(computepb.Backend_BalancingMode_name[int32(computepb.Backend_UTILIZATION)]),
Group: proto.String(c.resourceURI(scopeZone, "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.InsertGlobalForwardingRuleRequest{
Project: c.project,
ForwardingRuleResource: &computepb.ForwardingRule{
Name: proto.String(lb.name),
IPAddress: proto.String(c.resourceURI(scopeGlobal, "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)]),
PortRange: proto.String(strconv.Itoa(lb.frontendPort)),
Target: proto.String(c.resourceURI(scopeGlobal, "targetTcpProxies", 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.GetGlobalForwardingRuleRequest{
Project: c.project,
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.SetLabelsGlobalForwardingRuleRequest{
Project: c.project,
Resource: name,
GlobalSetLabelsRequestResource: &computepb.GlobalSetLabelsRequest{
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})
}
func (c *Client) createTargetTCPProxy(ctx context.Context, lb *loadBalancer) error {
req := &computepb.InsertTargetTcpProxyRequest{
Project: c.project,
TargetTcpProxyResource: &computepb.TargetTcpProxy{
Name: proto.String(lb.name),
Service: proto.String(c.resourceURI(scopeGlobal, "backendServices", lb.name)),
},
}
resp, err := c.targetTCPProxiesAPI.Insert(ctx, req)
if err != nil {
return fmt.Errorf("inserting target tcp proxy: %w", err)
}
if err := c.waitForOperations(ctx, []Operation{resp}); err != nil {
return err
}
lb.hasTargetTCPProxy = true
return nil
}
// 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.hasTargetTCPProxy {
if err := c.terminateTargetTCPProxy(ctx, lb); err != nil {
return fmt.Errorf("terminating target tcp proxy: %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.DeleteGlobalForwardingRuleRequest{
Project: c.project,
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) terminateTargetTCPProxy(ctx context.Context, lb *loadBalancer) error {
resp, err := c.targetTCPProxiesAPI.Delete(ctx, &computepb.DeleteTargetTcpProxyRequest{
Project: c.project,
TargetTcpProxy: lb.name,
})
if isNotFoundError(err) {
lb.hasTargetTCPProxy = false
return nil
}
if err != nil {
return fmt.Errorf("deleting target tcp proxy: %w", err)
}
if err := c.waitForOperations(ctx, []Operation{resp}); err != nil {
return err
}
lb.hasTargetTCPProxy = false
return nil
}
func (c *Client) terminateBackendService(ctx context.Context, lb *loadBalancer) error {
resp, err := c.backendServicesAPI.Delete(ctx, &computepb.DeleteBackendServiceRequest{
Project: c.project,
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.DeleteHealthCheckRequest{
Project: c.project,
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.buildResourceName()
insertReq := &computepb.InsertGlobalAddressRequest{
Project: c.project,
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 = ipName
getReq := &computepb.GetGlobalAddressRequest{
Project: c.project,
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.DeleteGlobalAddressRequest{
Project: c.project,
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)),
}
}

View File

@ -1,750 +0,0 @@
/*
Copyright (c) Edgeless Systems GmbH
SPDX-License-Identifier: AGPL-3.0-only
*/
package client
import (
"context"
"errors"
"fmt"
"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
proxyAPI targetTCPProxiesAPI
forwardAPI forwardingRulesAPI
operationAPI operationGlobalAPI
isDebugCluster bool
wantErr bool
}{
"successful create": {
addrAPI: &stubAddressesAPI{getAddr: proto.String("192.0.2.1")},
healthAPI: &stubHealthChecksAPI{},
backendAPI: &stubBackendServicesAPI{},
proxyAPI: &stubTargetTCPProxiesAPI{},
forwardAPI: &stubForwardingRulesAPI{forwardingRule: forwardingRule},
operationAPI: stubOperationGlobalAPI{},
},
"successful create (debug cluster)": {
addrAPI: &stubAddressesAPI{getAddr: proto.String("192.0.2.1")},
healthAPI: &stubHealthChecksAPI{},
backendAPI: &stubBackendServicesAPI{},
proxyAPI: &stubTargetTCPProxiesAPI{},
forwardAPI: &stubForwardingRulesAPI{forwardingRule: forwardingRule},
operationAPI: stubOperationGlobalAPI{},
isDebugCluster: true,
},
"createIPAddr fails": {
addrAPI: &stubAddressesAPI{insertErr: someErr},
healthAPI: &stubHealthChecksAPI{},
backendAPI: &stubBackendServicesAPI{},
proxyAPI: &stubTargetTCPProxiesAPI{},
forwardAPI: &stubForwardingRulesAPI{forwardingRule: forwardingRule},
operationAPI: stubOperationGlobalAPI{},
wantErr: true,
},
"createLB fails": {
addrAPI: &stubAddressesAPI{},
healthAPI: &stubHealthChecksAPI{},
backendAPI: &stubBackendServicesAPI{insertErr: someErr},
proxyAPI: &stubTargetTCPProxiesAPI{},
forwardAPI: &stubForwardingRulesAPI{forwardingRule: forwardingRule},
operationAPI: stubOperationGlobalAPI{},
wantErr: true,
},
"createTcpProxy fails": {
addrAPI: &stubAddressesAPI{getAddr: proto.String("192.0.2.1")},
healthAPI: &stubHealthChecksAPI{},
backendAPI: &stubBackendServicesAPI{},
proxyAPI: &stubTargetTCPProxiesAPI{insertErr: someErr},
forwardAPI: &stubForwardingRulesAPI{forwardingRule: forwardingRule},
operationAPI: stubOperationGlobalAPI{},
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,
targetTCPProxiesAPI: tc.proxyAPI,
healthChecksAPI: tc.healthAPI,
backendServicesAPI: tc.backendAPI,
forwardingRulesAPI: tc.forwardAPI,
operationGlobalAPI: tc.operationAPI,
}
err := client.CreateLoadBalancers(ctx, tc.isDebugCluster)
// In case we expect an error, check for the error and continue otherwise.
if tc.wantErr {
assert.Error(err)
return
}
// If we don't expect an error, check if the resources have been successfully created.
assert.NoError(err)
assert.NotEmpty(client.loadbalancerIPname)
var foundDebugdLB bool
for _, lb := range client.loadbalancers {
// Expect load balancer name to have the format of "name-serviceName-uid" which is what buildResourceName does currently.
if lb.name == fmt.Sprintf("%s-debugd-%s", client.name, client.uid) {
foundDebugdLB = true
break
}
}
if tc.isDebugCluster {
assert.Equal(6, len(client.loadbalancers))
assert.True(foundDebugdLB, "debugd loadbalancer not found in debug-mode")
} else {
assert.Equal(5, len(client.loadbalancers))
assert.False(foundDebugdLB, "debugd loadbalancer found in non-debug mode")
}
})
}
}
func TestCreateLoadBalancer(t *testing.T) {
someErr := errors.New("failed")
testCases := map[string]struct {
operationGlobalAPI operationGlobalAPI
healthChecksAPI healthChecksAPI
backendServicesAPI backendServicesAPI
forwardingRulesAPI forwardingRulesAPI
targetTCPProxiesAPI targetTCPProxiesAPI
wantErr bool
wantLB *loadBalancer
}{
"successful create": {
healthChecksAPI: stubHealthChecksAPI{},
backendServicesAPI: stubBackendServicesAPI{},
targetTCPProxiesAPI: stubTargetTCPProxiesAPI{},
forwardingRulesAPI: stubForwardingRulesAPI{forwardingRule: &compute.ForwardingRule{LabelFingerprint: proto.String("fingerprint")}},
operationGlobalAPI: stubOperationGlobalAPI{},
wantLB: &loadBalancer{
name: "name",
frontendPort: 1234,
backendPortName: "testport",
hasHealthCheck: true,
hasTargetTCPProxy: true,
hasBackendService: true,
hasForwardingRules: true,
},
},
"successful create with label": {
healthChecksAPI: stubHealthChecksAPI{},
backendServicesAPI: stubBackendServicesAPI{},
targetTCPProxiesAPI: stubTargetTCPProxiesAPI{},
forwardingRulesAPI: stubForwardingRulesAPI{forwardingRule: &compute.ForwardingRule{LabelFingerprint: proto.String("fingerprint")}},
operationGlobalAPI: stubOperationGlobalAPI{},
wantLB: &loadBalancer{
name: "name",
frontendPort: 1234,
backendPortName: "testport",
label: true,
hasHealthCheck: true,
hasTargetTCPProxy: true,
hasBackendService: true,
hasForwardingRules: true,
},
},
"CreateLoadBalancer fails when getting forwarding rule": {
healthChecksAPI: stubHealthChecksAPI{},
backendServicesAPI: stubBackendServicesAPI{},
targetTCPProxiesAPI: stubTargetTCPProxiesAPI{},
forwardingRulesAPI: stubForwardingRulesAPI{getErr: someErr},
operationGlobalAPI: stubOperationGlobalAPI{},
wantErr: true,
wantLB: &loadBalancer{
name: "name",
frontendPort: 1234,
backendPortName: "testport",
label: true,
hasHealthCheck: true,
hasTargetTCPProxy: true,
hasBackendService: true,
hasForwardingRules: true,
},
},
"CreateLoadBalancer fails when label fingerprint is missing": {
healthChecksAPI: stubHealthChecksAPI{},
backendServicesAPI: stubBackendServicesAPI{},
targetTCPProxiesAPI: stubTargetTCPProxiesAPI{},
forwardingRulesAPI: stubForwardingRulesAPI{forwardingRule: &compute.ForwardingRule{}},
operationGlobalAPI: stubOperationGlobalAPI{},
wantErr: true,
wantLB: &loadBalancer{
name: "name",
frontendPort: 1234,
backendPortName: "testport",
label: true,
hasHealthCheck: true,
hasTargetTCPProxy: true,
hasBackendService: true,
hasForwardingRules: true,
},
},
"CreateLoadBalancer fails when creating health check": {
healthChecksAPI: stubHealthChecksAPI{insertErr: someErr},
backendServicesAPI: stubBackendServicesAPI{},
targetTCPProxiesAPI: stubTargetTCPProxiesAPI{},
forwardingRulesAPI: stubForwardingRulesAPI{forwardingRule: &compute.ForwardingRule{LabelFingerprint: proto.String("fingerprint")}},
operationGlobalAPI: stubOperationGlobalAPI{},
wantErr: true,
wantLB: &loadBalancer{
name: "name",
frontendPort: 1234,
backendPortName: "testport",
hasHealthCheck: false,
hasTargetTCPProxy: false,
hasBackendService: false,
hasForwardingRules: false,
},
},
"CreateLoadBalancer fails when creating backend service": {
healthChecksAPI: stubHealthChecksAPI{},
targetTCPProxiesAPI: stubTargetTCPProxiesAPI{},
backendServicesAPI: stubBackendServicesAPI{insertErr: someErr},
forwardingRulesAPI: stubForwardingRulesAPI{},
operationGlobalAPI: stubOperationGlobalAPI{},
wantErr: true,
wantLB: &loadBalancer{
name: "name",
frontendPort: 1234,
backendPortName: "testport",
hasHealthCheck: true,
hasBackendService: false,
hasTargetTCPProxy: false,
hasForwardingRules: false,
},
},
"CreateLoadBalancer fails when creating forwarding rule": {
healthChecksAPI: stubHealthChecksAPI{},
backendServicesAPI: stubBackendServicesAPI{},
targetTCPProxiesAPI: stubTargetTCPProxiesAPI{},
forwardingRulesAPI: stubForwardingRulesAPI{insertErr: someErr},
operationGlobalAPI: stubOperationGlobalAPI{},
wantErr: true,
wantLB: &loadBalancer{
name: "name",
frontendPort: 1234,
backendPortName: "testport",
hasHealthCheck: true,
hasBackendService: true,
hasTargetTCPProxy: true,
hasForwardingRules: false,
},
},
"CreateLoadBalancer fails when creating target proxy rule": {
healthChecksAPI: stubHealthChecksAPI{},
backendServicesAPI: stubBackendServicesAPI{},
targetTCPProxiesAPI: stubTargetTCPProxiesAPI{insertErr: someErr},
forwardingRulesAPI: stubForwardingRulesAPI{},
operationGlobalAPI: stubOperationGlobalAPI{},
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{},
targetTCPProxiesAPI: stubTargetTCPProxiesAPI{},
forwardingRulesAPI: stubForwardingRulesAPI{forwardingRule: &compute.ForwardingRule{LabelFingerprint: proto.String("fingerprint")}},
operationGlobalAPI: stubOperationGlobalAPI{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,
targetTCPProxiesAPI: tc.targetTCPProxiesAPI,
healthChecksAPI: tc.healthChecksAPI,
operationGlobalAPI: tc.operationGlobalAPI,
}
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,
hasTargetTCPProxy: true,
hasForwardingRules: true,
}
}
testCases := map[string]struct {
addrAPI addressesAPI
healthAPI healthChecksAPI
backendAPI backendServicesAPI
targetAPI targetTCPProxiesAPI
forwardAPI forwardingRulesAPI
opGlobalAPI operationGlobalAPI
wantErr bool
}{
"successful terminate": {
addrAPI: &stubAddressesAPI{},
healthAPI: &stubHealthChecksAPI{},
backendAPI: &stubBackendServicesAPI{},
targetAPI: &stubTargetTCPProxiesAPI{},
forwardAPI: &stubForwardingRulesAPI{},
opGlobalAPI: stubOperationGlobalAPI{},
},
"deleteIPAddr fails": {
addrAPI: &stubAddressesAPI{deleteErr: someErr},
healthAPI: &stubHealthChecksAPI{},
backendAPI: &stubBackendServicesAPI{},
targetAPI: &stubTargetTCPProxiesAPI{},
forwardAPI: &stubForwardingRulesAPI{},
opGlobalAPI: stubOperationGlobalAPI{},
wantErr: true,
},
"deleteLB fails": {
addrAPI: &stubAddressesAPI{},
healthAPI: &stubHealthChecksAPI{},
backendAPI: &stubBackendServicesAPI{deleteErr: someErr},
targetAPI: &stubTargetTCPProxiesAPI{},
forwardAPI: &stubForwardingRulesAPI{},
opGlobalAPI: stubOperationGlobalAPI{},
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,
targetTCPProxiesAPI: tc.targetAPI,
forwardingRulesAPI: tc.forwardAPI,
operationGlobalAPI: tc.opGlobalAPI,
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,
hasTargetTCPProxy: true,
hasBackendService: true,
hasForwardingRules: true,
}
}
testCases := map[string]struct {
lb *loadBalancer
opGlobalAPI operationGlobalAPI
healthChecksAPI healthChecksAPI
backendServicesAPI backendServicesAPI
targetTCPProxiesAPI targetTCPProxiesAPI
forwardingRulesAPI forwardingRulesAPI
wantErr bool
wantLB *loadBalancer
}{
"successful terminate": {
lb: newRunningLB(),
healthChecksAPI: stubHealthChecksAPI{},
backendServicesAPI: stubBackendServicesAPI{},
forwardingRulesAPI: stubForwardingRulesAPI{},
targetTCPProxiesAPI: stubTargetTCPProxiesAPI{},
opGlobalAPI: stubOperationGlobalAPI{},
wantLB: &loadBalancer{},
},
"terminate partially created loadbalancer": {
lb: &loadBalancer{
name: "name",
hasHealthCheck: true,
hasBackendService: true,
hasForwardingRules: false,
},
healthChecksAPI: stubHealthChecksAPI{},
backendServicesAPI: stubBackendServicesAPI{},
forwardingRulesAPI: stubForwardingRulesAPI{deleteErr: someErr},
targetTCPProxiesAPI: stubTargetTCPProxiesAPI{},
opGlobalAPI: stubOperationGlobalAPI{},
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},
targetTCPProxiesAPI: stubTargetTCPProxiesAPI{},
opGlobalAPI: stubOperationGlobalAPI{},
wantLB: &loadBalancer{},
},
"no-op for nil loadbalancer": {
lb: nil,
},
"health check not found": {
lb: newRunningLB(),
healthChecksAPI: stubHealthChecksAPI{deleteErr: notFoundErr},
backendServicesAPI: stubBackendServicesAPI{},
forwardingRulesAPI: stubForwardingRulesAPI{},
targetTCPProxiesAPI: stubTargetTCPProxiesAPI{},
opGlobalAPI: stubOperationGlobalAPI{},
wantLB: &loadBalancer{},
},
"backend service not found": {
lb: newRunningLB(),
healthChecksAPI: stubHealthChecksAPI{},
backendServicesAPI: stubBackendServicesAPI{deleteErr: notFoundErr},
forwardingRulesAPI: stubForwardingRulesAPI{},
targetTCPProxiesAPI: stubTargetTCPProxiesAPI{},
opGlobalAPI: stubOperationGlobalAPI{},
wantLB: &loadBalancer{},
},
"forwarding rules not found": {
lb: newRunningLB(),
healthChecksAPI: stubHealthChecksAPI{},
backendServicesAPI: stubBackendServicesAPI{},
forwardingRulesAPI: stubForwardingRulesAPI{deleteErr: notFoundErr},
targetTCPProxiesAPI: stubTargetTCPProxiesAPI{},
opGlobalAPI: stubOperationGlobalAPI{},
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{},
targetTCPProxiesAPI: stubTargetTCPProxiesAPI{},
opGlobalAPI: stubOperationGlobalAPI{},
wantErr: true,
wantLB: &loadBalancer{
name: "name",
hasHealthCheck: true,
hasBackendService: false,
hasForwardingRules: false,
hasTargetTCPProxy: false,
},
},
"fails when deleting backend service": {
lb: newRunningLB(),
healthChecksAPI: stubHealthChecksAPI{},
backendServicesAPI: stubBackendServicesAPI{deleteErr: someErr},
forwardingRulesAPI: stubForwardingRulesAPI{},
targetTCPProxiesAPI: stubTargetTCPProxiesAPI{},
opGlobalAPI: stubOperationGlobalAPI{},
wantErr: true,
wantLB: &loadBalancer{
name: "name",
hasHealthCheck: true,
hasBackendService: true,
hasForwardingRules: false,
hasTargetTCPProxy: false,
},
},
"fails when deleting forwarding rule": {
lb: newRunningLB(),
healthChecksAPI: stubHealthChecksAPI{},
backendServicesAPI: stubBackendServicesAPI{},
forwardingRulesAPI: stubForwardingRulesAPI{deleteErr: someErr},
targetTCPProxiesAPI: stubTargetTCPProxiesAPI{},
opGlobalAPI: stubOperationGlobalAPI{},
wantErr: true,
wantLB: &loadBalancer{
name: "name",
hasHealthCheck: true,
hasBackendService: true,
hasForwardingRules: true,
hasTargetTCPProxy: true,
},
},
"fails when deleting tcp proxy rule": {
lb: newRunningLB(),
healthChecksAPI: stubHealthChecksAPI{},
backendServicesAPI: stubBackendServicesAPI{},
forwardingRulesAPI: stubForwardingRulesAPI{},
targetTCPProxiesAPI: stubTargetTCPProxiesAPI{deleteErr: someErr},
opGlobalAPI: stubOperationGlobalAPI{},
wantErr: true,
wantLB: &loadBalancer{
name: "name",
hasHealthCheck: true,
hasBackendService: true,
hasForwardingRules: false,
hasTargetTCPProxy: true,
},
},
"fails when waiting on operation": {
lb: newRunningLB(),
healthChecksAPI: stubHealthChecksAPI{},
backendServicesAPI: stubBackendServicesAPI{},
forwardingRulesAPI: stubForwardingRulesAPI{},
targetTCPProxiesAPI: stubTargetTCPProxiesAPI{},
opGlobalAPI: stubOperationGlobalAPI{waitErr: someErr},
wantErr: true,
wantLB: &loadBalancer{
name: "name",
hasHealthCheck: true,
hasBackendService: true,
hasForwardingRules: true,
hasTargetTCPProxy: 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,
targetTCPProxiesAPI: tc.targetTCPProxiesAPI,
operationGlobalAPI: tc.opGlobalAPI,
}
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 operationGlobalAPI
wantErr bool
}{
"successful create": {
addrAPI: stubAddressesAPI{getAddr: proto.String("test-ip")},
opAPI: stubOperationGlobalAPI{},
},
"insert fails": {
addrAPI: stubAddressesAPI{insertErr: someErr},
opAPI: stubOperationGlobalAPI{},
wantErr: true,
},
"get fails": {
addrAPI: stubAddressesAPI{getErr: someErr},
opAPI: stubOperationGlobalAPI{},
wantErr: true,
},
"get address nil": {
addrAPI: stubAddressesAPI{getAddr: nil},
opAPI: stubOperationGlobalAPI{},
wantErr: true,
},
"wait fails": {
addrAPI: stubAddressesAPI{},
opAPI: stubOperationGlobalAPI{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,
operationGlobalAPI: 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 operationGlobalAPI
addrID string
wantErr bool
}{
"successful delete": {
addrAPI: stubAddressesAPI{},
opAPI: stubOperationGlobalAPI{},
addrID: "name",
},
"not found": {
addrAPI: stubAddressesAPI{deleteErr: notFoundErr},
opAPI: stubOperationGlobalAPI{},
addrID: "name",
},
"empty is no-op": {
addrAPI: stubAddressesAPI{},
opAPI: stubOperationGlobalAPI{},
},
"delete fails": {
addrAPI: stubAddressesAPI{deleteErr: someErr},
opAPI: stubOperationGlobalAPI{},
addrID: "name",
wantErr: true,
},
"wait fails": {
addrAPI: stubAddressesAPI{},
opAPI: stubOperationGlobalAPI{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,
operationGlobalAPI: tc.opAPI,
loadbalancerIPname: tc.addrID,
}
err := client.deleteIPAddr(ctx)
if tc.wantErr {
assert.Error(err)
} else {
assert.NoError(err)
assert.Empty(client.loadbalancerIPname)
}
})
}
}

View File

@ -1,231 +0,0 @@
/*
Copyright (c) Edgeless Systems GmbH
SPDX-License-Identifier: AGPL-3.0-only
*/
package client
import (
"context"
"errors"
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudtypes"
computepb "google.golang.org/genproto/googleapis/cloud/compute/v1"
"google.golang.org/protobuf/proto"
)
const (
SubnetCIDR = "192.168.178.0/24"
SubnetExtCIDR = "10.10.0.0/16"
)
// CreateFirewall creates a set of firewall rules for the client's network.
//
// The client must have a VPC network to set firewall rules.
func (c *Client) CreateFirewall(ctx context.Context, input FirewallInput) error {
if c.network == "" {
return errors.New("client has not network")
}
firewallRules, err := input.Ingress.GCP()
if err != nil {
return err
}
var ops []Operation
for _, rule := range firewallRules {
c.firewalls = append(c.firewalls, rule.GetName())
rule.Network = proto.String("global/networks/" + c.network)
rule.Name = proto.String(rule.GetName() + "-" + c.uid)
req := &computepb.InsertFirewallRequest{
FirewallResource: rule,
Project: c.project,
}
resp, err := c.firewallsAPI.Insert(ctx, req)
if err != nil {
return err
}
if resp.Proto().Name == nil {
return errors.New("operation name is nil")
}
ops = append(ops, resp)
}
return c.waitForOperations(ctx, ops)
}
// TerminateFirewall deletes firewall rules from the client's network.
//
// The client must have a VPC network to set firewall rules.
func (c *Client) TerminateFirewall(ctx context.Context) error {
if len(c.firewalls) == 0 {
return nil
}
var ops []Operation
for _, name := range c.firewalls {
ruleName := name + "-" + c.uid
req := &computepb.DeleteFirewallRequest{
Firewall: ruleName,
Project: c.project,
}
resp, err := c.firewallsAPI.Delete(ctx, req)
if isNotFoundError(err) {
continue
}
if err != nil {
return err
}
if resp.Proto().Name == nil {
return errors.New("operation name is nil")
}
ops = append(ops, resp)
}
if err := c.waitForOperations(ctx, ops); err != nil {
return err
}
c.firewalls = []string{}
return nil
}
// FirewallInput defines firewall rules to be set.
type FirewallInput struct {
Ingress cloudtypes.Firewall
Egress cloudtypes.Firewall
}
// CreateVPCs creates all necessary VPC networks.
func (c *Client) CreateVPCs(ctx context.Context) error {
c.network = c.buildResourceName()
op, err := c.createVPC(ctx, c.network)
if err != nil {
return err
}
if err := c.waitForOperations(ctx, []Operation{op}); err != nil {
return err
}
if err := c.createSubnets(ctx); err != nil {
return err
}
return nil
}
// createVPC creates a VPC network.
func (c *Client) createVPC(ctx context.Context, name string) (Operation, error) {
req := &computepb.InsertNetworkRequest{
NetworkResource: &computepb.Network{
AutoCreateSubnetworks: proto.Bool(false),
Description: proto.String("Constellation VPC"),
Name: proto.String(name),
},
Project: c.project,
}
return c.networksAPI.Insert(ctx, req)
}
// TerminateVPCs terminates all VPC networks.
//
// If the any network has firewall rules, these must be terminated first.
func (c *Client) TerminateVPCs(ctx context.Context) error {
if len(c.firewalls) != 0 {
return errors.New("client has firewalls, which must be deleted first")
}
if err := c.terminateSubnet(ctx); err != nil {
return err
}
if err := c.terminateVPC(ctx); err != nil {
return err
}
return nil
}
// terminateVPC terminates a VPC network.
//
// If the network has firewall rules, these must be terminated first.
func (c *Client) terminateVPC(ctx context.Context) error {
if c.network == "" {
return nil
}
req := &computepb.DeleteNetworkRequest{
Project: c.project,
Network: c.network,
}
op, err := c.networksAPI.Delete(ctx, req)
if err != nil && !isNotFoundError(err) {
return err
}
if isNotFoundError(err) {
c.network = ""
return nil
}
if err := c.waitForOperations(ctx, []Operation{op}); err != nil {
return err
}
c.network = ""
return nil
}
func (c *Client) createSubnets(ctx context.Context) error {
c.subnetwork = "node-net-" + c.uid
c.secondarySubnetworkRange = "net-ext" + c.uid
op, err := c.createSubnet(ctx, c.subnetwork, c.network, c.secondarySubnetworkRange)
if err != nil {
return err
}
return c.waitForOperations(ctx, []Operation{op})
}
func (c *Client) createSubnet(ctx context.Context, name, network, secondaryRangeName string) (Operation, error) {
req := &computepb.InsertSubnetworkRequest{
Project: c.project,
Region: c.region,
SubnetworkResource: &computepb.Subnetwork{
IpCidrRange: proto.String(SubnetCIDR),
Name: proto.String(name),
Network: proto.String("projects/" + c.project + "/global/networks/" + network),
SecondaryIpRanges: []*computepb.SubnetworkSecondaryRange{
{
RangeName: proto.String(secondaryRangeName),
IpCidrRange: proto.String(SubnetExtCIDR),
},
},
},
}
return c.subnetworksAPI.Insert(ctx, req)
}
func (c *Client) terminateSubnet(ctx context.Context) error {
if c.subnetwork == "" {
return nil
}
req := &computepb.DeleteSubnetworkRequest{
Project: c.project,
Region: c.region,
Subnetwork: c.subnetwork,
}
op, err := c.subnetworksAPI.Delete(ctx, req)
if err != nil && !isNotFoundError(err) {
return err
}
if isNotFoundError(err) {
c.subnetwork = ""
return nil
}
if err := c.waitForOperations(ctx, []Operation{op}); err != nil {
return err
}
c.subnetwork = ""
return nil
}

View File

@ -1,357 +0,0 @@
/*
Copyright (c) Edgeless Systems GmbH
SPDX-License-Identifier: AGPL-3.0-only
*/
package client
import (
"context"
"errors"
"net/http"
"testing"
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudtypes"
"github.com/stretchr/testify/assert"
"google.golang.org/api/googleapi"
)
func TestCreateVPCs(t *testing.T) {
someErr := errors.New("failed")
testCases := map[string]struct {
operationGlobalAPI operationGlobalAPI
operationRegionAPI operationRegionAPI
networksAPI networksAPI
subnetworksAPI subnetworksAPI
wantErr bool
}{
"successful create": {
operationGlobalAPI: stubOperationGlobalAPI{},
operationRegionAPI: stubOperationRegionAPI{},
networksAPI: stubNetworksAPI{},
subnetworksAPI: stubSubnetworksAPI{},
},
"failed wait global op": {
operationGlobalAPI: stubOperationGlobalAPI{waitErr: someErr},
operationRegionAPI: stubOperationRegionAPI{},
networksAPI: stubNetworksAPI{},
subnetworksAPI: stubSubnetworksAPI{},
wantErr: true,
},
"failed wait region op": {
operationGlobalAPI: stubOperationGlobalAPI{},
operationRegionAPI: stubOperationRegionAPI{waitErr: someErr},
networksAPI: stubNetworksAPI{},
subnetworksAPI: stubSubnetworksAPI{},
wantErr: true,
},
"failed insert networks": {
operationGlobalAPI: stubOperationGlobalAPI{},
operationRegionAPI: stubOperationRegionAPI{},
networksAPI: stubNetworksAPI{insertErr: someErr},
subnetworksAPI: stubSubnetworksAPI{},
wantErr: true,
},
"failed insert subnetworks": {
operationGlobalAPI: stubOperationGlobalAPI{},
operationRegionAPI: stubOperationRegionAPI{},
networksAPI: stubNetworksAPI{},
subnetworksAPI: stubSubnetworksAPI{insertErr: 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",
operationGlobalAPI: tc.operationGlobalAPI,
operationRegionAPI: tc.operationRegionAPI,
networksAPI: tc.networksAPI,
subnetworksAPI: tc.subnetworksAPI,
workers: make(cloudtypes.Instances),
controlPlanes: make(cloudtypes.Instances),
}
if tc.wantErr {
assert.Error(client.CreateVPCs(ctx))
} else {
assert.NoError(client.CreateVPCs(ctx))
assert.NotNil(client.network)
}
})
}
}
func TestTerminateVPCs(t *testing.T) {
someErr := errors.New("failed")
notFoundErr := &googleapi.Error{Code: http.StatusNotFound}
testCases := map[string]struct {
operationGlobalAPI operationGlobalAPI
operationRegionAPI operationRegionAPI
networksAPI networksAPI
subnetworksAPI subnetworksAPI
firewalls []string
subnetwork string
network string
wantErr bool
}{
"successful terminate": {
operationGlobalAPI: stubOperationGlobalAPI{},
operationRegionAPI: stubOperationRegionAPI{},
networksAPI: stubNetworksAPI{},
subnetworksAPI: stubSubnetworksAPI{},
subnetwork: "subnetwork-id-1",
network: "network-id-1",
},
"subnetwork empty": {
operationGlobalAPI: stubOperationGlobalAPI{},
operationRegionAPI: stubOperationRegionAPI{},
networksAPI: stubNetworksAPI{},
subnetworksAPI: stubSubnetworksAPI{},
subnetwork: "",
network: "network-id-1",
},
"network empty": {
operationGlobalAPI: stubOperationGlobalAPI{},
operationRegionAPI: stubOperationRegionAPI{},
networksAPI: stubNetworksAPI{},
subnetworksAPI: stubSubnetworksAPI{},
subnetwork: "subnetwork-id-1",
network: "",
},
"subnetwork not found": {
operationGlobalAPI: stubOperationGlobalAPI{},
operationRegionAPI: stubOperationRegionAPI{},
networksAPI: stubNetworksAPI{},
subnetworksAPI: stubSubnetworksAPI{deleteErr: notFoundErr},
subnetwork: "subnetwork-id-1",
network: "network-id-1",
},
"network not found": {
operationGlobalAPI: stubOperationGlobalAPI{},
operationRegionAPI: stubOperationRegionAPI{},
networksAPI: stubNetworksAPI{deleteErr: notFoundErr},
subnetworksAPI: stubSubnetworksAPI{},
subnetwork: "subnetwork-id-1",
network: "network-id-1",
},
"failed wait global op": {
operationGlobalAPI: stubOperationGlobalAPI{waitErr: someErr},
operationRegionAPI: stubOperationRegionAPI{},
networksAPI: stubNetworksAPI{},
subnetworksAPI: stubSubnetworksAPI{},
subnetwork: "subnetwork-id-1",
network: "network-id-1",
wantErr: true,
},
"failed delete networks": {
operationGlobalAPI: stubOperationGlobalAPI{},
operationRegionAPI: stubOperationRegionAPI{},
networksAPI: stubNetworksAPI{deleteErr: someErr},
subnetworksAPI: stubSubnetworksAPI{},
subnetwork: "subnetwork-id-1",
network: "network-id-1",
wantErr: true,
},
"failed delete subnetworks": {
operationGlobalAPI: stubOperationGlobalAPI{},
operationRegionAPI: stubOperationRegionAPI{},
networksAPI: stubNetworksAPI{},
subnetworksAPI: stubSubnetworksAPI{deleteErr: someErr},
subnetwork: "subnetwork-id-1",
network: "network-id-1",
wantErr: true,
},
"must delete firewalls first": {
firewalls: []string{"firewall-1", "firewall-2"},
operationRegionAPI: stubOperationRegionAPI{},
operationGlobalAPI: stubOperationGlobalAPI{},
networksAPI: stubNetworksAPI{},
subnetworksAPI: stubSubnetworksAPI{},
subnetwork: "subnetwork-id-1",
network: "network-id-1",
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",
operationGlobalAPI: tc.operationGlobalAPI,
operationRegionAPI: tc.operationRegionAPI,
networksAPI: tc.networksAPI,
subnetworksAPI: tc.subnetworksAPI,
firewalls: tc.firewalls,
network: tc.network,
subnetwork: tc.subnetwork,
}
if tc.wantErr {
assert.Error(client.TerminateVPCs(ctx))
} else {
assert.NoError(client.TerminateVPCs(ctx))
assert.Empty(client.network)
assert.Empty(client.subnetwork)
}
})
}
}
func TestCreateFirewall(t *testing.T) {
someErr := errors.New("failed")
testFirewallInput := FirewallInput{
Ingress: cloudtypes.Firewall{
cloudtypes.FirewallRule{
Name: "test-1",
Description: "test-1 description",
Protocol: "tcp",
IPRange: "192.0.2.0/24",
FromPort: 9000,
},
cloudtypes.FirewallRule{
Name: "test-2",
Description: "test-2 description",
Protocol: "udp",
IPRange: "192.0.2.0/24",
FromPort: 51820,
},
},
Egress: cloudtypes.Firewall{},
}
testCases := map[string]struct {
network string
operationGlobalAPI operationGlobalAPI
firewallsAPI firewallsAPI
firewallInput FirewallInput
wantErr bool
}{
"successful create": {
network: "network",
operationGlobalAPI: stubOperationGlobalAPI{},
firewallsAPI: stubFirewallsAPI{},
},
"failed wait global op": {
network: "network",
operationGlobalAPI: stubOperationGlobalAPI{waitErr: someErr},
firewallsAPI: stubFirewallsAPI{},
wantErr: true,
},
"failed insert networks": {
network: "network",
operationGlobalAPI: stubOperationGlobalAPI{},
firewallsAPI: stubFirewallsAPI{insertErr: someErr},
wantErr: true,
},
"no network set": {
operationGlobalAPI: stubOperationGlobalAPI{},
firewallsAPI: stubFirewallsAPI{},
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",
network: tc.network,
operationGlobalAPI: tc.operationGlobalAPI,
firewallsAPI: tc.firewallsAPI,
}
if tc.wantErr {
assert.Error(client.CreateFirewall(ctx, testFirewallInput))
} else {
assert.NoError(client.CreateFirewall(ctx, testFirewallInput))
assert.ElementsMatch([]string{"test-1", "test-2"}, client.firewalls)
}
})
}
}
func TestTerminateFirewall(t *testing.T) {
someErr := errors.New("failed")
notFoundErr := &googleapi.Error{Code: http.StatusNotFound}
testCases := map[string]struct {
operationGlobalAPI operationGlobalAPI
firewallsAPI firewallsAPI
firewalls []string
wantErr bool
}{
"successful terminate": {
operationGlobalAPI: stubOperationGlobalAPI{},
firewallsAPI: stubFirewallsAPI{},
firewalls: []string{"firewall-1", "firewall-2"},
},
"successful terminate when no firewall exists": {
operationGlobalAPI: stubOperationGlobalAPI{},
firewallsAPI: stubFirewallsAPI{},
firewalls: []string{},
},
"successful terminate when firewall not found": {
operationGlobalAPI: stubOperationGlobalAPI{},
firewallsAPI: stubFirewallsAPI{deleteErr: notFoundErr},
firewalls: []string{"firewall-1", "firewall-2"},
},
"failed to wait on global operation": {
operationGlobalAPI: stubOperationGlobalAPI{waitErr: someErr},
firewallsAPI: stubFirewallsAPI{},
firewalls: []string{"firewall-1", "firewall-2"},
wantErr: true,
},
"failed to delete firewalls": {
operationGlobalAPI: stubOperationGlobalAPI{},
firewallsAPI: stubFirewallsAPI{deleteErr: someErr},
firewalls: []string{"firewall-1", "firewall-2"},
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",
firewalls: tc.firewalls,
operationGlobalAPI: tc.operationGlobalAPI,
firewallsAPI: tc.firewallsAPI,
}
if tc.wantErr {
assert.Error(client.TerminateFirewall(ctx))
} else {
assert.NoError(client.TerminateFirewall(ctx))
assert.Empty(client.firewalls)
}
})
}
}

View File

@ -1,116 +0,0 @@
/*
Copyright (c) Edgeless Systems GmbH
SPDX-License-Identifier: AGPL-3.0-only
*/
package client
import (
"context"
"errors"
"fmt"
computepb "google.golang.org/genproto/googleapis/cloud/compute/v1"
)
// waitForOperations waits until every operation in the opIDs slice is
// done or returns the first occurring error.
func (c *Client) waitForOperations(ctx context.Context, ops []Operation) error {
for _, op := range ops {
switch {
case op.Proto() == nil:
return errors.New("proto of operation is nil")
case op.Proto().Zone != nil:
if err := c.waitForZoneOperation(ctx, op); err != nil {
return err
}
case op.Proto().Region != nil:
if err := c.waitForRegionOperation(ctx, op); err != nil {
return err
}
default:
if err := c.waitForGlobalOperation(ctx, op); err != nil {
return err
}
}
}
return nil
}
func (c *Client) waitForGlobalOperation(ctx context.Context, op Operation) error {
for {
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,
}
zoneOp, err := c.operationGlobalAPI.Wait(ctx, waitReq)
if err != nil {
return fmt.Errorf("unable to wait for the operation: %w", err)
}
if *zoneOp.Status.Enum() == computepb.Operation_DONE {
if opErr := zoneOp.Error; opErr != nil {
return fmt.Errorf("operation failed: %s", opErr.String())
}
return nil
}
}
}
func (c *Client) waitForZoneOperation(ctx context.Context, op Operation) error {
for {
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,
Zone: c.zone,
}
zoneOp, err := c.operationZoneAPI.Wait(ctx, waitReq)
if err != nil {
return fmt.Errorf("unable to wait for the operation: %w", err)
}
if *zoneOp.Status.Enum() == computepb.Operation_DONE {
if opErr := zoneOp.Error; opErr != nil {
return fmt.Errorf("operation failed: %s", opErr.String())
}
return nil
}
}
}
func (c *Client) waitForRegionOperation(ctx context.Context, op Operation) error {
for {
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,
Region: c.region,
}
regionOp, err := c.operationRegionAPI.Wait(ctx, waitReq)
if err != nil {
return fmt.Errorf("unable to wait for the operation: %w", err)
}
if *regionOp.Status.Enum() == computepb.Operation_DONE {
if opErr := regionOp.Error; opErr != nil {
return fmt.Errorf("operation failed: %s", opErr.String())
}
return nil
}
}
}

View File

@ -1,77 +0,0 @@
/*
Copyright (c) Edgeless Systems GmbH
SPDX-License-Identifier: AGPL-3.0-only
*/
package client
import (
"context"
"fmt"
iampb "google.golang.org/genproto/googleapis/iam/v1"
)
// addIAMPolicyBindings adds a GCP service account to roles specified in the input.
func (c *Client) addIAMPolicyBindings(ctx context.Context, input AddIAMPolicyBindingInput) error {
getReq := &iampb.GetIamPolicyRequest{
Resource: "projects/" + c.project,
}
policy, err := c.projectsAPI.GetIamPolicy(ctx, getReq)
if err != nil {
return fmt.Errorf("retrieving current iam policy: %w", err)
}
for _, binding := range input.Bindings {
addIAMPolicy(policy, binding)
}
setReq := &iampb.SetIamPolicyRequest{
Resource: "projects/" + c.project,
Policy: policy,
}
if _, err := c.projectsAPI.SetIamPolicy(ctx, setReq); err != nil {
return fmt.Errorf("setting new iam policy: %w", err)
}
return nil
}
// PolicyBinding is a GCP IAM policy binding.
type PolicyBinding struct {
ServiceAccount string
Role string
}
// addIAMPolicy inserts policy binding for service account and role to an existing iam policy.
func addIAMPolicy(policy *iampb.Policy, policyBinding PolicyBinding) {
var binding *iampb.Binding
for _, existingBinding := range policy.Bindings {
if existingBinding.Role == policyBinding.Role && existingBinding.Condition == nil {
binding = existingBinding
break
}
}
if binding == nil {
binding = &iampb.Binding{
Role: policyBinding.Role,
}
policy.Bindings = append(policy.Bindings, binding)
}
// add service account to role, if not already a member
member := "serviceAccount:" + policyBinding.ServiceAccount
var alreadyMember bool
for _, existingMember := range binding.Members {
if member == existingMember {
alreadyMember = true
break
}
}
if !alreadyMember {
binding.Members = append(binding.Members, member)
}
}
// AddIAMPolicyBindingInput is the input for an AddIAMPolicyBinding operation.
type AddIAMPolicyBindingInput struct {
Bindings []PolicyBinding
}

View File

@ -1,183 +0,0 @@
/*
Copyright (c) Edgeless Systems GmbH
SPDX-License-Identifier: AGPL-3.0-only
*/
package client
import (
"context"
"errors"
"testing"
"github.com/stretchr/testify/assert"
iampb "google.golang.org/genproto/googleapis/iam/v1"
"google.golang.org/protobuf/proto"
)
func TestAddIAMPolicyBindings(t *testing.T) {
someErr := errors.New("someErr")
testCases := map[string]struct {
projectsAPI stubProjectsAPI
input AddIAMPolicyBindingInput
wantErr bool
}{
"successful set without new bindings": {
input: AddIAMPolicyBindingInput{
Bindings: []PolicyBinding{},
},
},
"successful set with bindings": {
input: AddIAMPolicyBindingInput{
Bindings: []PolicyBinding{
{
ServiceAccount: "service-account",
Role: "role",
},
},
},
},
"retrieving iam policy fails": {
projectsAPI: stubProjectsAPI{
getPolicyErr: someErr,
},
wantErr: true,
},
"setting iam policy fails": {
projectsAPI: stubProjectsAPI{
setPolicyErr: 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",
projectsAPI: tc.projectsAPI,
}
err := client.addIAMPolicyBindings(ctx, tc.input)
if tc.wantErr {
assert.Error(err)
} else {
assert.NoError(err)
}
})
}
}
func TestAddIAMPolicy(t *testing.T) {
testCases := map[string]struct {
binding PolicyBinding
policy *iampb.Policy
wantErr bool
wantPolicy *iampb.Policy
}{
"successful on empty policy": {
binding: PolicyBinding{
ServiceAccount: "service-account",
Role: "role",
},
policy: &iampb.Policy{
Bindings: []*iampb.Binding{},
},
wantPolicy: &iampb.Policy{
Bindings: []*iampb.Binding{
{
Role: "role",
Members: []string{"serviceAccount:service-account"},
},
},
},
},
"successful on existing policy with different role": {
binding: PolicyBinding{
ServiceAccount: "service-account",
Role: "role",
},
policy: &iampb.Policy{
Bindings: []*iampb.Binding{
{
Role: "other-role",
Members: []string{"other-member"},
},
},
},
wantPolicy: &iampb.Policy{
Bindings: []*iampb.Binding{
{
Role: "other-role",
Members: []string{"other-member"},
},
{
Role: "role",
Members: []string{"serviceAccount:service-account"},
},
},
},
},
"successful on existing policy with existing role": {
binding: PolicyBinding{
ServiceAccount: "service-account",
Role: "role",
},
policy: &iampb.Policy{
Bindings: []*iampb.Binding{
{
Role: "role",
Members: []string{"other-member"},
},
},
},
wantPolicy: &iampb.Policy{
Bindings: []*iampb.Binding{
{
Role: "role",
Members: []string{"other-member", "serviceAccount:service-account"},
},
},
},
},
"already a member": {
binding: PolicyBinding{
ServiceAccount: "service-account",
Role: "role",
},
policy: &iampb.Policy{
Bindings: []*iampb.Binding{
{
Role: "role",
Members: []string{"serviceAccount:service-account"},
},
},
},
wantPolicy: &iampb.Policy{
Bindings: []*iampb.Binding{
{
Role: "role",
Members: []string{"serviceAccount:service-account"},
},
},
},
},
}
for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
assert := assert.New(t)
addIAMPolicy(tc.policy, tc.binding)
assert.True(proto.Equal(tc.wantPolicy, tc.policy))
})
}
}

View File

@ -1,10 +0,0 @@
/*
Copyright (c) Edgeless Systems GmbH
SPDX-License-Identifier: AGPL-3.0-only
*/
package gcp
// KubeEnv contains placeholder values required by cluster-autoscaler.
var KubeEnv = `AUTOSCALER_ENV_VARS: kube_reserved=cpu=1060m,memory=1019Mi,ephemeral-storage=41Gi;node_labels=;os=linux;os_distribution=cos;evictionHard=`

View File

@ -17,21 +17,6 @@ type ConstellationState struct {
CloudProvider string `json:"cloudprovider,omitempty"`
LoadBalancerIP string `json:"bootstrapperhost,omitempty"`
GCPWorkerInstances cloudtypes.Instances `json:"gcpworkers,omitempty"`
GCPControlPlaneInstances cloudtypes.Instances `json:"gcpcontrolplanes,omitempty"`
GCPWorkerInstanceGroup string `json:"gcpworkerinstancegroup,omitempty"`
GCPControlPlaneInstanceGroup string `json:"gcpcontrolplaneinstancegroup,omitempty"`
GCPWorkerInstanceTemplate string `json:"gcpworkerinstancetemplate,omitempty"`
GCPControlPlaneInstanceTemplate string `json:"gcpcontrolplaneinstancetemplate,omitempty"`
GCPNetwork string `json:"gcpnetwork,omitempty"`
GCPSubnetwork string `json:"gcpsubnetwork,omitempty"`
GCPFirewalls []string `json:"gcpfirewalls,omitempty"`
GCPLoadbalancerIPname string `json:"gcploadbalanceripid,omitempty"`
GCPLoadbalancers []string `json:"gcploadbalancers,omitempty"`
GCPProject string `json:"gcpproject,omitempty"`
GCPZone string `json:"gcpzone,omitempty"`
GCPRegion string `json:"gcpregion,omitempty"`
AzureWorkerInstances cloudtypes.Instances `json:"azureworkers,omitempty"`
AzureControlPlaneInstances cloudtypes.Instances `json:"azurecontrolplanes,omitempty"`
AzureResourceGroup string `json:"azureresourcegroup,omitempty"`