mirror of
https://github.com/edgelesssys/constellation.git
synced 2024-09-20 00:06:21 +00:00
Remove GCP client from CLI
This commit is contained in:
parent
d973740b03
commit
feffe40987
@ -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)
|
|
||||||
}
|
|
@ -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)
|
|
||||||
}
|
|
@ -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)
|
|
||||||
}
|
|
@ -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
|
|
||||||
}
|
|
@ -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
|
|
||||||
}
|
|
@ -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))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
@ -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()
|
|
||||||
}
|
|
@ -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
|
|
||||||
}
|
|
@ -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)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
@ -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)),
|
|
||||||
}
|
|
||||||
}
|
|
@ -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)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
@ -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
|
|
||||||
}
|
|
@ -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)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -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
|
|
||||||
}
|
|
@ -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))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
@ -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=`
|
|
@ -17,21 +17,6 @@ type ConstellationState struct {
|
|||||||
CloudProvider string `json:"cloudprovider,omitempty"`
|
CloudProvider string `json:"cloudprovider,omitempty"`
|
||||||
LoadBalancerIP string `json:"bootstrapperhost,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"`
|
AzureWorkerInstances cloudtypes.Instances `json:"azureworkers,omitempty"`
|
||||||
AzureControlPlaneInstances cloudtypes.Instances `json:"azurecontrolplanes,omitempty"`
|
AzureControlPlaneInstances cloudtypes.Instances `json:"azurecontrolplanes,omitempty"`
|
||||||
AzureResourceGroup string `json:"azureresourcegroup,omitempty"`
|
AzureResourceGroup string `json:"azureresourcegroup,omitempty"`
|
||||||
|
Loading…
Reference in New Issue
Block a user