Refactor provider metadata

This commit is contained in:
katexochen 2022-06-28 16:08:05 +02:00 committed by Paul Meyer
parent 32f1f5fd3e
commit 09e86e6c5d
36 changed files with 198 additions and 1340 deletions

View File

@ -1,40 +0,0 @@
package aws
import (
"github.com/edgelesssys/constellation/coordinator/cloudprovider/cloudtypes"
"github.com/edgelesssys/constellation/coordinator/kubernetes/k8sapi/resources"
k8s "k8s.io/api/core/v1"
)
// Autoscaler holds the AWS cluster-autoscaler configuration.
type Autoscaler struct{}
// Name returns the cloud-provider name as used by k8s cluster-autoscaler.
func (a Autoscaler) Name() string {
return "aws"
}
// Secrets returns a list of secrets to deploy together with the k8s cluster-autoscaler.
func (a Autoscaler) Secrets(instance cloudtypes.Instance, cloudServiceAccountURI string) (resources.Secrets, error) {
return resources.Secrets{}, nil
}
// Volumes returns a list of volumes to deploy together with the k8s cluster-autoscaler.
func (a Autoscaler) Volumes() []k8s.Volume {
return []k8s.Volume{}
}
// VolumeMounts returns a list of volume mounts to deploy together with the k8s cluster-autoscaler.
func (a Autoscaler) VolumeMounts() []k8s.VolumeMount {
return []k8s.VolumeMount{}
}
// Env returns a list of k8s environment key-value pairs to deploy together with the k8s cluster-autoscaler.
func (a Autoscaler) Env() []k8s.EnvVar {
return []k8s.EnvVar{}
}
// Supported is used to determine if we support autoscaling for the cloud provider.
func (a Autoscaler) Supported() bool {
return false
}

View File

@ -1,66 +0,0 @@
package aws
import (
"context"
"github.com/edgelesssys/constellation/coordinator/cloudprovider"
"github.com/edgelesssys/constellation/coordinator/cloudprovider/cloudtypes"
"github.com/edgelesssys/constellation/coordinator/kubernetes/k8sapi/resources"
k8s "k8s.io/api/core/v1"
)
// CloudControllerManager holds the AWS cloud-controller-manager configuration.
type CloudControllerManager struct{}
// Image returns the container image used to provide cloud-controller-manager for the cloud-provider.
func (c CloudControllerManager) Image() string {
return cloudprovider.CloudControllerManagerImageAWS
}
// Path returns the path used by cloud-controller-manager executable within the container image.
func (c CloudControllerManager) Path() string {
return "/aws-cloud-controller-manager"
}
// Name returns the cloud-provider name as used by k8s cloud-controller-manager (k8s.gcr.io/cloud-controller-manager).
func (c CloudControllerManager) Name() string {
return "aws"
}
// ExtraArgs returns a list of arguments to append to the cloud-controller-manager command.
func (c CloudControllerManager) ExtraArgs() []string {
return []string{}
}
// ConfigMaps returns a list of ConfigMaps to deploy together with the k8s cloud-controller-manager
// Reference: https://kubernetes.io/docs/concepts/configuration/configmap/ .
func (c CloudControllerManager) ConfigMaps(instance cloudtypes.Instance) (resources.ConfigMaps, error) {
return resources.ConfigMaps{}, nil
}
// Secrets returns a list of secrets to deploy together with the k8s cloud-controller-manager.
// Reference: https://kubernetes.io/docs/concepts/configuration/secret/ .
func (c CloudControllerManager) Secrets(ctx context.Context, instance cloudtypes.Instance, cloudServiceAccountURI string) (resources.Secrets, error) {
return resources.Secrets{}, nil
}
// Volumes returns a list of volumes to deploy together with the k8s cloud-controller-manager.
// Reference: https://kubernetes.io/docs/concepts/storage/volumes/ .
func (c CloudControllerManager) Volumes() []k8s.Volume {
return []k8s.Volume{}
}
// VolumeMounts a list of of volume mounts to deploy together with the k8s cloud-controller-manager.
func (c CloudControllerManager) VolumeMounts() []k8s.VolumeMount {
return []k8s.VolumeMount{}
}
// Env returns a list of k8s environment key-value pairs to deploy together with the k8s cloud-controller-manager.
func (c CloudControllerManager) Env() []k8s.EnvVar {
return []k8s.EnvVar{}
}
// Supported is used to determine if cloud controller manager is implemented for this cloud provider.
func (c CloudControllerManager) Supported() bool {
return false
}

View File

@ -1,27 +0,0 @@
package aws
// CloudNodeManager holds the AWS cloud-node-manager configuration.
type CloudNodeManager struct{}
// Image returns the container image used to provide cloud-node-manager for the cloud-provider.
// Not used on AWS.
func (c *CloudNodeManager) Image() string {
return ""
}
// Path returns the path used by cloud-node-manager executable within the container image.
// Not used on AWS.
func (c *CloudNodeManager) Path() string {
return ""
}
// ExtraArgs returns a list of arguments to append to the cloud-node-manager command.
// Not used on AWS.
func (c *CloudNodeManager) ExtraArgs() []string {
return []string{}
}
// Supported is used to determine if cloud node manager is implemented for this cloud provider.
func (c *CloudNodeManager) Supported() bool {
return false
}

View File

@ -1,79 +0,0 @@
package aws
import (
"context"
"fmt"
"github.com/aws/aws-sdk-go-v2/config"
"github.com/aws/aws-sdk-go-v2/feature/ec2/imds"
"github.com/edgelesssys/constellation/coordinator/cloudprovider/cloudtypes"
"github.com/edgelesssys/constellation/coordinator/role"
)
// Metadata implements core.ProviderMetadata interface.
type Metadata struct{}
// List retrieves all instances belonging to the current constellation.
func (m Metadata) List(ctx context.Context) ([]cloudtypes.Instance, error) {
// TODO: implement using https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/service/ec2#Client.DescribeInstances
// And using AWS ec2 instance tags
panic("function *Metadata.List not implemented")
}
// Self retrieves the current instance.
func (m Metadata) Self(ctx context.Context) (cloudtypes.Instance, error) {
identityDocument, err := retrieveIdentityDocument(ctx)
if err != nil {
return cloudtypes.Instance{}, err
}
// TODO: implement metadata using AWS ec2 instance tags
return cloudtypes.Instance{
Name: identityDocument.InstanceID,
ProviderID: providerID(identityDocument),
PrivateIPs: []string{
identityDocument.PrivateIP,
},
}, nil
}
// GetInstance retrieves an instance using its providerID.
func (m Metadata) GetInstance(ctx context.Context, providerID string) (cloudtypes.Instance, error) {
// TODO: implement using https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/service/ec2#DescribeInstancesAPIClient.DescribeInstances
// And using AWS ec2 instance tags
// Filter request to only return info on this instance
panic("function *Metadata.GetInstance not implemented")
}
// SignalRole signals the constellation role via cloud provider metadata (if supported by the CSP and deployment type, otherwise does nothing).
func (m Metadata) SignalRole(ctx context.Context, role role.Role) error {
panic("function *Metadata.SignalRole not implemented")
}
// SetVPNIP stores the internally used VPN IP in cloud provider metadata (if supported and required for autoscaling by the CSP, otherwise does nothing).
func (m Metadata) SetVPNIP(ctx context.Context, vpnIP string) error {
panic("function *Metadata.SetVPNIP not implemented")
}
// Supported is used to determine if metadata API is implemented for this cloud provider.
func (m Metadata) Supported() bool {
return false
}
// retrieveIdentityDocument retrieves an AWS instance identity document.
func retrieveIdentityDocument(ctx context.Context) (*imds.GetInstanceIdentityDocumentOutput, error) {
cfg, err := config.LoadDefaultConfig(ctx)
if err != nil {
return nil, fmt.Errorf("loading default AWS configuration: %w", err)
}
client := imds.NewFromConfig(cfg)
identityDocument, err := client.GetInstanceIdentityDocument(ctx, &imds.GetInstanceIdentityDocumentInput{})
if err != nil {
return nil, fmt.Errorf("retrieving AWS instance identity document: %w", err)
}
return identityDocument, nil
}
func providerID(identityDocument *imds.GetInstanceIdentityDocumentOutput) string {
// On AWS, the ProviderID has the form "aws:///<AVAILABILITY_ZONE>/<EC2_INSTANCE_ID>"
return fmt.Sprintf("aws://%v/%v", identityDocument.AvailabilityZone, identityDocument.InstanceID)
}

View File

@ -50,16 +50,6 @@ type publicIPAddressesAPI interface {
options *armnetwork.PublicIPAddressesClientGetOptions) (armnetwork.PublicIPAddressesClientGetResponse, error) options *armnetwork.PublicIPAddressesClientGetOptions) (armnetwork.PublicIPAddressesClientGetResponse, error)
} }
type virtualMachinesAPI interface {
Get(ctx context.Context, resourceGroupName string, vmName string, options *armcompute.VirtualMachinesClientGetOptions) (armcompute.VirtualMachinesClientGetResponse, error)
List(resourceGroupName string, options *armcompute.VirtualMachinesClientListOptions) virtualMachinesClientListPager
}
type virtualMachinesClientListPager interface {
NextPage(ctx context.Context) bool
PageResponse() armcompute.VirtualMachinesClientListResponse
}
type virtualMachineScaleSetVMsAPI interface { type virtualMachineScaleSetVMsAPI interface {
Get(ctx context.Context, resourceGroupName string, vmScaleSetName string, instanceID string, options *armcompute.VirtualMachineScaleSetVMsClientGetOptions) (armcompute.VirtualMachineScaleSetVMsClientGetResponse, error) Get(ctx context.Context, resourceGroupName string, vmScaleSetName string, instanceID string, options *armcompute.VirtualMachineScaleSetVMsClientGetOptions) (armcompute.VirtualMachineScaleSetVMsClientGetResponse, error)
List(resourceGroupName string, virtualMachineScaleSetName string, options *armcompute.VirtualMachineScaleSetVMsClientListOptions) virtualMachineScaleSetVMsClientListPager List(resourceGroupName string, virtualMachineScaleSetName string, options *armcompute.VirtualMachineScaleSetVMsClientListOptions) virtualMachineScaleSetVMsClientListPager

View File

@ -52,49 +52,6 @@ func (a *stubNetworkInterfacesAPI) Get(ctx context.Context, resourceGroupName st
}, a.getErr }, a.getErr
} }
type stubVirtualMachinesClientListPager struct {
pagesCounter int
pages [][]*armcompute.VirtualMachine
}
func (p *stubVirtualMachinesClientListPager) NextPage(ctx context.Context) bool {
return p.pagesCounter < len(p.pages)
}
func (p *stubVirtualMachinesClientListPager) PageResponse() armcompute.VirtualMachinesClientListResponse {
if p.pagesCounter >= len(p.pages) {
return armcompute.VirtualMachinesClientListResponse{}
}
p.pagesCounter = p.pagesCounter + 1
return armcompute.VirtualMachinesClientListResponse{
VirtualMachinesClientListResult: armcompute.VirtualMachinesClientListResult{
VirtualMachineListResult: armcompute.VirtualMachineListResult{
Value: p.pages[p.pagesCounter-1],
},
},
}
}
type stubVirtualMachinesAPI struct {
getVM armcompute.VirtualMachine
getErr error
listPages [][]*armcompute.VirtualMachine
}
func (a *stubVirtualMachinesAPI) Get(ctx context.Context, resourceGroupName string, vmName string, options *armcompute.VirtualMachinesClientGetOptions) (armcompute.VirtualMachinesClientGetResponse, error) {
return armcompute.VirtualMachinesClientGetResponse{
VirtualMachinesClientGetResult: armcompute.VirtualMachinesClientGetResult{
VirtualMachine: a.getVM,
},
}, a.getErr
}
func (a *stubVirtualMachinesAPI) List(resourceGroupName string, options *armcompute.VirtualMachinesClientListOptions) virtualMachinesClientListPager {
return &stubVirtualMachinesClientListPager{
pages: a.listPages,
}
}
type stubVirtualMachineScaleSetVMsClientListPager struct { type stubVirtualMachineScaleSetVMsClientListPager struct {
pagesCounter int pagesCounter int
pages [][]*armcompute.VirtualMachineScaleSetVM pages [][]*armcompute.VirtualMachineScaleSetVM

View File

@ -1,7 +1,6 @@
package azure package azure
import ( import (
"github.com/edgelesssys/constellation/coordinator/cloudprovider/cloudtypes"
"github.com/edgelesssys/constellation/coordinator/kubernetes/k8sapi/resources" "github.com/edgelesssys/constellation/coordinator/kubernetes/k8sapi/resources"
"github.com/edgelesssys/constellation/internal/azureshared" "github.com/edgelesssys/constellation/internal/azureshared"
k8s "k8s.io/api/core/v1" k8s "k8s.io/api/core/v1"
@ -17,8 +16,8 @@ func (a *Autoscaler) Name() string {
} }
// Secrets returns a list of secrets to deploy together with the k8s cluster-autoscaler. // Secrets returns a list of secrets to deploy together with the k8s cluster-autoscaler.
func (a *Autoscaler) Secrets(instance cloudtypes.Instance, cloudServiceAccountURI string) (resources.Secrets, error) { func (a *Autoscaler) Secrets(providerID string, cloudServiceAccountURI string) (resources.Secrets, error) {
subscriptionID, resourceGroup, err := azureshared.BasicsFromProviderID(instance.ProviderID) subscriptionID, resourceGroup, err := azureshared.BasicsFromProviderID(providerID)
if err != nil { if err != nil {
return resources.Secrets{}, err return resources.Secrets{}, err
} }

View File

@ -3,7 +3,6 @@ package azure
import ( import (
"testing" "testing"
"github.com/edgelesssys/constellation/coordinator/cloudprovider/cloudtypes"
"github.com/edgelesssys/constellation/coordinator/kubernetes/k8sapi/resources" "github.com/edgelesssys/constellation/coordinator/kubernetes/k8sapi/resources"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
@ -13,13 +12,13 @@ import (
func TestAutoscalerSecrets(t *testing.T) { func TestAutoscalerSecrets(t *testing.T) {
testCases := map[string]struct { testCases := map[string]struct {
instance cloudtypes.Instance providerID string
cloudServiceAccountURI string cloudServiceAccountURI string
wantSecrets resources.Secrets wantSecrets resources.Secrets
wantErr bool wantErr bool
}{ }{
"Secrets works": { "Secrets works": {
instance: cloudtypes.Instance{ProviderID: "azure:///subscriptions/subscription-id/resourceGroups/resource-group/providers/Microsoft.Compute/virtualMachines/instance-name"}, providerID: "azure:///subscriptions/subscription-id/resourceGroups/resource-group/providers/Microsoft.Compute/virtualMachines/instance-name",
cloudServiceAccountURI: "serviceaccount://azure?tenant_id=tenant-id&client_id=client-id&client_secret=client-secret", cloudServiceAccountURI: "serviceaccount://azure?tenant_id=tenant-id&client_id=client-id&client_secret=client-secret",
wantSecrets: resources.Secrets{ wantSecrets: resources.Secrets{
&k8s.Secret{ &k8s.Secret{
@ -43,11 +42,11 @@ func TestAutoscalerSecrets(t *testing.T) {
}, },
}, },
"invalid providerID fails": { "invalid providerID fails": {
instance: cloudtypes.Instance{ProviderID: "invalid"}, providerID: "invalid",
wantErr: true, wantErr: true,
}, },
"invalid cloudServiceAccountURI fails": { "invalid cloudServiceAccountURI fails": {
instance: cloudtypes.Instance{ProviderID: "azure:///subscriptions/subscription-id/resourceGroups/resource-group/providers/Microsoft.Compute/virtualMachines/instance-name"}, providerID: "azure:///subscriptions/subscription-id/resourceGroups/resource-group/providers/Microsoft.Compute/virtualMachines/instance-name",
cloudServiceAccountURI: "invalid", cloudServiceAccountURI: "invalid",
wantErr: true, wantErr: true,
}, },
@ -59,7 +58,7 @@ func TestAutoscalerSecrets(t *testing.T) {
require := require.New(t) require := require.New(t)
autoscaler := Autoscaler{} autoscaler := Autoscaler{}
secrets, err := autoscaler.Secrets(tc.instance, tc.cloudServiceAccountURI) secrets, err := autoscaler.Secrets(tc.providerID, tc.cloudServiceAccountURI)
if tc.wantErr { if tc.wantErr {
assert.Error(err) assert.Error(err)
return return

View File

@ -5,9 +5,9 @@ import (
"encoding/json" "encoding/json"
"github.com/edgelesssys/constellation/coordinator/cloudprovider" "github.com/edgelesssys/constellation/coordinator/cloudprovider"
"github.com/edgelesssys/constellation/coordinator/cloudprovider/cloudtypes"
"github.com/edgelesssys/constellation/coordinator/kubernetes/k8sapi/resources" "github.com/edgelesssys/constellation/coordinator/kubernetes/k8sapi/resources"
"github.com/edgelesssys/constellation/internal/azureshared" "github.com/edgelesssys/constellation/internal/azureshared"
"github.com/edgelesssys/constellation/internal/cloud/metadata"
k8s "k8s.io/api/core/v1" k8s "k8s.io/api/core/v1"
meta "k8s.io/apimachinery/pkg/apis/meta/v1" meta "k8s.io/apimachinery/pkg/apis/meta/v1"
) )
@ -55,17 +55,17 @@ func (c *CloudControllerManager) ExtraArgs() []string {
// ConfigMaps returns a list of ConfigMaps to deploy together with the k8s cloud-controller-manager // ConfigMaps returns a list of ConfigMaps to deploy together with the k8s cloud-controller-manager
// Reference: https://kubernetes.io/docs/concepts/configuration/configmap/ . // Reference: https://kubernetes.io/docs/concepts/configuration/configmap/ .
func (c *CloudControllerManager) ConfigMaps(instance cloudtypes.Instance) (resources.ConfigMaps, error) { func (c *CloudControllerManager) ConfigMaps(instance metadata.InstanceMetadata) (resources.ConfigMaps, error) {
return resources.ConfigMaps{}, nil return resources.ConfigMaps{}, nil
} }
// Secrets returns a list of secrets to deploy together with the k8s cloud-controller-manager. // Secrets returns a list of secrets to deploy together with the k8s cloud-controller-manager.
// Reference: https://kubernetes.io/docs/concepts/configuration/secret/ . // Reference: https://kubernetes.io/docs/concepts/configuration/secret/ .
func (c *CloudControllerManager) Secrets(ctx context.Context, instance cloudtypes.Instance, cloudServiceAccountURI string) (resources.Secrets, error) { func (c *CloudControllerManager) Secrets(ctx context.Context, providerID string, cloudServiceAccountURI string) (resources.Secrets, error) {
// Azure CCM expects cloud provider config to contain cluster configuration and service principal client secrets // Azure CCM expects cloud provider config to contain cluster configuration and service principal client secrets
// reference: https://kubernetes-sigs.github.io/cloud-provider-azure/install/configs/ // reference: https://kubernetes-sigs.github.io/cloud-provider-azure/install/configs/
subscriptionID, resourceGroup, err := azureshared.BasicsFromProviderID(instance.ProviderID) subscriptionID, resourceGroup, err := azureshared.BasicsFromProviderID(providerID)
if err != nil { if err != nil {
return resources.Secrets{}, err return resources.Secrets{}, err
} }
@ -75,7 +75,7 @@ func (c *CloudControllerManager) Secrets(ctx context.Context, instance cloudtype
} }
vmType := "standard" vmType := "standard"
if _, _, _, _, err := azureshared.ScaleSetInformationFromProviderID(instance.ProviderID); err == nil { if _, _, _, _, err := azureshared.ScaleSetInformationFromProviderID(providerID); err == nil {
vmType = "vmss" vmType = "vmss"
} }

View File

@ -5,8 +5,8 @@ import (
"errors" "errors"
"testing" "testing"
"github.com/edgelesssys/constellation/coordinator/cloudprovider/cloudtypes"
"github.com/edgelesssys/constellation/coordinator/kubernetes/k8sapi/resources" "github.com/edgelesssys/constellation/coordinator/kubernetes/k8sapi/resources"
"github.com/edgelesssys/constellation/internal/cloud/metadata"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
k8s "k8s.io/api/core/v1" k8s "k8s.io/api/core/v1"
@ -16,14 +16,14 @@ import (
func TestSecrets(t *testing.T) { func TestSecrets(t *testing.T) {
someErr := errors.New("some error") someErr := errors.New("some error")
testCases := map[string]struct { testCases := map[string]struct {
instance cloudtypes.Instance providerID string
metadata ccmMetadata metadata ccmMetadata
cloudServiceAccountURI string cloudServiceAccountURI string
wantSecrets resources.Secrets wantSecrets resources.Secrets
wantErr bool wantErr bool
}{ }{
"Secrets works": { "Secrets works": {
instance: cloudtypes.Instance{ProviderID: "azure:///subscriptions/subscription-id/resourceGroups/resource-group/providers/Microsoft.Compute/virtualMachines/instance-name"}, providerID: "azure:///subscriptions/subscription-id/resourceGroups/resource-group/providers/Microsoft.Compute/virtualMachines/instance-name",
cloudServiceAccountURI: "serviceaccount://azure?tenant_id=tenant-id&client_id=client-id&client_secret=client-secret&location=location", cloudServiceAccountURI: "serviceaccount://azure?tenant_id=tenant-id&client_id=client-id&client_secret=client-secret&location=location",
metadata: &ccmMetadataStub{loadBalancerName: "load-balancer-name", networkSecurityGroupName: "network-security-group-name"}, metadata: &ccmMetadataStub{loadBalancerName: "load-balancer-name", networkSecurityGroupName: "network-security-group-name"},
wantSecrets: resources.Secrets{ wantSecrets: resources.Secrets{
@ -43,7 +43,7 @@ func TestSecrets(t *testing.T) {
}, },
}, },
"Secrets works for scale sets": { "Secrets works for scale sets": {
instance: cloudtypes.Instance{ProviderID: "azure:///subscriptions/subscription-id/resourceGroups/resource-group/providers/Microsoft.Compute/virtualMachineScaleSets/scale-set-name/virtualMachines/instance-id"}, providerID: "azure:///subscriptions/subscription-id/resourceGroups/resource-group/providers/Microsoft.Compute/virtualMachineScaleSets/scale-set-name/virtualMachines/instance-id",
cloudServiceAccountURI: "serviceaccount://azure?tenant_id=tenant-id&client_id=client-id&client_secret=client-secret&location=location", cloudServiceAccountURI: "serviceaccount://azure?tenant_id=tenant-id&client_id=client-id&client_secret=client-secret&location=location",
metadata: &ccmMetadataStub{loadBalancerName: "load-balancer-name", networkSecurityGroupName: "network-security-group-name"}, metadata: &ccmMetadataStub{loadBalancerName: "load-balancer-name", networkSecurityGroupName: "network-security-group-name"},
wantSecrets: resources.Secrets{ wantSecrets: resources.Secrets{
@ -63,24 +63,24 @@ func TestSecrets(t *testing.T) {
}, },
}, },
"cannot get load balancer Name": { "cannot get load balancer Name": {
instance: cloudtypes.Instance{ProviderID: "azure:///subscriptions/subscription-id/resourceGroups/resource-group/providers/Microsoft.Compute/virtualMachineScaleSets/scale-set-name/virtualMachines/instance-id"}, providerID: "azure:///subscriptions/subscription-id/resourceGroups/resource-group/providers/Microsoft.Compute/virtualMachineScaleSets/scale-set-name/virtualMachines/instance-id",
cloudServiceAccountURI: "serviceaccount://azure?tenant_id=tenant-id&client_id=client-id&client_secret=client-secret&location=location", cloudServiceAccountURI: "serviceaccount://azure?tenant_id=tenant-id&client_id=client-id&client_secret=client-secret&location=location",
metadata: &ccmMetadataStub{getLoadBalancerNameErr: someErr}, metadata: &ccmMetadataStub{getLoadBalancerNameErr: someErr},
wantErr: true, wantErr: true,
}, },
"cannot get network security group name": { "cannot get network security group name": {
instance: cloudtypes.Instance{ProviderID: "azure:///subscriptions/subscription-id/resourceGroups/resource-group/providers/Microsoft.Compute/virtualMachineScaleSets/scale-set-name/virtualMachines/instance-id"}, providerID: "azure:///subscriptions/subscription-id/resourceGroups/resource-group/providers/Microsoft.Compute/virtualMachineScaleSets/scale-set-name/virtualMachines/instance-id",
cloudServiceAccountURI: "serviceaccount://azure?tenant_id=tenant-id&client_id=client-id&client_secret=client-secret&location=location", cloudServiceAccountURI: "serviceaccount://azure?tenant_id=tenant-id&client_id=client-id&client_secret=client-secret&location=location",
metadata: &ccmMetadataStub{getNetworkSecurityGroupNameErr: someErr}, metadata: &ccmMetadataStub{getNetworkSecurityGroupNameErr: someErr},
wantErr: true, wantErr: true,
}, },
"invalid providerID fails": { "invalid providerID fails": {
instance: cloudtypes.Instance{ProviderID: "invalid"}, providerID: "invalid",
metadata: &ccmMetadataStub{}, metadata: &ccmMetadataStub{},
wantErr: true, wantErr: true,
}, },
"invalid cloudServiceAccountURI fails": { "invalid cloudServiceAccountURI fails": {
instance: cloudtypes.Instance{ProviderID: "azure:///subscriptions/subscription-id/resourceGroups/resource-group/providers/Microsoft.Compute/virtualMachines/instance-name"}, providerID: "azure:///subscriptions/subscription-id/resourceGroups/resource-group/providers/Microsoft.Compute/virtualMachines/instance-name",
metadata: &ccmMetadataStub{}, metadata: &ccmMetadataStub{},
cloudServiceAccountURI: "invalid", cloudServiceAccountURI: "invalid",
wantErr: true, wantErr: true,
@ -93,7 +93,7 @@ func TestSecrets(t *testing.T) {
require := require.New(t) require := require.New(t)
cloud := NewCloudControllerManager(tc.metadata) cloud := NewCloudControllerManager(tc.metadata)
secrets, err := cloud.Secrets(context.Background(), tc.instance, tc.cloudServiceAccountURI) secrets, err := cloud.Secrets(context.Background(), tc.providerID, tc.cloudServiceAccountURI)
if tc.wantErr { if tc.wantErr {
assert.Error(err) assert.Error(err)
return return
@ -112,7 +112,7 @@ func TestTrivialCCMFunctions(t *testing.T) {
assert.NotEmpty(cloud.Path()) assert.NotEmpty(cloud.Path())
assert.NotEmpty(cloud.Name()) assert.NotEmpty(cloud.Name())
assert.NotEmpty(cloud.ExtraArgs()) assert.NotEmpty(cloud.ExtraArgs())
assert.Empty(cloud.ConfigMaps(cloudtypes.Instance{})) assert.Empty(cloud.ConfigMaps(metadata.InstanceMetadata{}))
assert.NotEmpty(cloud.Volumes()) assert.NotEmpty(cloud.Volumes())
assert.NotEmpty(cloud.VolumeMounts()) assert.NotEmpty(cloud.VolumeMounts())
assert.Empty(cloud.Env()) assert.Empty(cloud.Env())

View File

@ -11,10 +11,8 @@ import (
"github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute" "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute"
"github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork" "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork"
"github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources" "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources"
"github.com/edgelesssys/constellation/coordinator/cloudprovider/cloudtypes"
"github.com/edgelesssys/constellation/coordinator/core"
"github.com/edgelesssys/constellation/coordinator/role"
"github.com/edgelesssys/constellation/internal/azureshared" "github.com/edgelesssys/constellation/internal/azureshared"
"github.com/edgelesssys/constellation/internal/cloud/metadata"
) )
var ( var (
@ -31,7 +29,6 @@ type Metadata struct {
publicIPAddressesAPI publicIPAddressesAPI
scaleSetsAPI scaleSetsAPI
loadBalancerAPI loadBalancerAPI
virtualMachinesAPI
virtualMachineScaleSetVMsAPI virtualMachineScaleSetVMsAPI
tagsAPI tagsAPI
applicationInsightsAPI applicationInsightsAPI
@ -63,7 +60,6 @@ func NewMetadata(ctx context.Context) (*Metadata, error) {
securityGroupsAPI := armnetwork.NewSecurityGroupsClient(subscriptionID, cred, nil) securityGroupsAPI := armnetwork.NewSecurityGroupsClient(subscriptionID, cred, nil)
scaleSetsAPI := armcompute.NewVirtualMachineScaleSetsClient(subscriptionID, cred, nil) scaleSetsAPI := armcompute.NewVirtualMachineScaleSetsClient(subscriptionID, cred, nil)
loadBalancerAPI := armnetwork.NewLoadBalancersClient(subscriptionID, cred, nil) loadBalancerAPI := armnetwork.NewLoadBalancersClient(subscriptionID, cred, nil)
virtualMachinesAPI := armcompute.NewVirtualMachinesClient(subscriptionID, cred, nil)
virtualMachineScaleSetVMsAPI := armcompute.NewVirtualMachineScaleSetVMsClient(subscriptionID, cred, nil) virtualMachineScaleSetVMsAPI := armcompute.NewVirtualMachineScaleSetVMsClient(subscriptionID, cred, nil)
tagsAPI := armresources.NewTagsClient(subscriptionID, cred, nil) tagsAPI := armresources.NewTagsClient(subscriptionID, cred, nil)
applicationInsightsAPI := armapplicationinsights.NewComponentsClient(subscriptionID, cred, nil) applicationInsightsAPI := armapplicationinsights.NewComponentsClient(subscriptionID, cred, nil)
@ -76,7 +72,6 @@ func NewMetadata(ctx context.Context) (*Metadata, error) {
publicIPAddressesAPI: &publicIPAddressesClient{publicIPAddressesAPI}, publicIPAddressesAPI: &publicIPAddressesClient{publicIPAddressesAPI},
loadBalancerAPI: &loadBalancersClient{loadBalancerAPI}, loadBalancerAPI: &loadBalancersClient{loadBalancerAPI},
scaleSetsAPI: &scaleSetsClient{scaleSetsAPI}, scaleSetsAPI: &scaleSetsClient{scaleSetsAPI},
virtualMachinesAPI: &virtualMachinesClient{virtualMachinesAPI},
virtualMachineScaleSetVMsAPI: &virtualMachineScaleSetVMsClient{virtualMachineScaleSetVMsAPI}, virtualMachineScaleSetVMsAPI: &virtualMachineScaleSetVMsClient{virtualMachineScaleSetVMsAPI},
tagsAPI: &tagsClient{tagsAPI}, tagsAPI: &tagsClient{tagsAPI},
applicationInsightsAPI: &applicationInsightsClient{applicationInsightsAPI}, applicationInsightsAPI: &applicationInsightsClient{applicationInsightsAPI},
@ -84,7 +79,7 @@ func NewMetadata(ctx context.Context) (*Metadata, error) {
} }
// List retrieves all instances belonging to the current constellation. // List retrieves all instances belonging to the current constellation.
func (m *Metadata) List(ctx context.Context) ([]cloudtypes.Instance, error) { func (m *Metadata) List(ctx context.Context) ([]metadata.InstanceMetadata, error) {
providerID, err := m.providerID(ctx) providerID, err := m.providerID(ctx)
if err != nil { if err != nil {
return nil, err return nil, err
@ -93,54 +88,29 @@ func (m *Metadata) List(ctx context.Context) ([]cloudtypes.Instance, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
singleInstances, err := m.listVMs(ctx, resourceGroup)
if err != nil {
return nil, err
}
scaleSetInstances, err := m.listScaleSetVMs(ctx, resourceGroup) scaleSetInstances, err := m.listScaleSetVMs(ctx, resourceGroup)
if err != nil { if err != nil {
return nil, err return nil, err
} }
instances := make([]cloudtypes.Instance, 0, len(singleInstances)+len(scaleSetInstances)) return scaleSetInstances, nil
instances = append(instances, singleInstances...)
instances = append(instances, scaleSetInstances...)
return instances, nil
} }
// Self retrieves the current instance. // Self retrieves the current instance.
func (m *Metadata) Self(ctx context.Context) (cloudtypes.Instance, error) { func (m *Metadata) Self(ctx context.Context) (metadata.InstanceMetadata, error) {
providerID, err := m.providerID(ctx) providerID, err := m.providerID(ctx)
if err != nil { if err != nil {
return cloudtypes.Instance{}, err return metadata.InstanceMetadata{}, err
} }
return m.GetInstance(ctx, providerID) return m.GetInstance(ctx, providerID)
} }
// GetInstance retrieves an instance using its providerID. // GetInstance retrieves an instance using its providerID.
func (m *Metadata) GetInstance(ctx context.Context, providerID string) (cloudtypes.Instance, error) { func (m *Metadata) GetInstance(ctx context.Context, providerID string) (metadata.InstanceMetadata, error) {
instance, singleErr := m.getVM(ctx, providerID)
if singleErr == nil {
return instance, nil
}
instance, scaleSetErr := m.getScaleSetVM(ctx, providerID) instance, scaleSetErr := m.getScaleSetVM(ctx, providerID)
if scaleSetErr == nil { if scaleSetErr == nil {
return instance, nil return instance, nil
} }
return cloudtypes.Instance{}, fmt.Errorf("retrieving instance given providerID %v as either single VM or scale set VM: %v; %v", providerID, singleErr, scaleSetErr) return metadata.InstanceMetadata{}, fmt.Errorf("retrieving instance given providerID %v: %w", providerID, scaleSetErr)
}
// SignalRole signals the constellation role via cloud provider metadata.
// On single VMs, the role is stored in tags, on scale set VMs, the role is inferred from the scale set and not signalied explicitly.
func (m *Metadata) SignalRole(ctx context.Context, role role.Role) error {
providerID, err := m.providerID(ctx)
if err != nil {
return err
}
if _, _, _, _, err := azureshared.ScaleSetInformationFromProviderID(providerID); err == nil {
// scale set instances cannot store tags and role can be inferred from scale set name.
return nil
}
return m.setTag(ctx, core.RoleMetadataKey, role.String())
} }
// GetNetworkSecurityGroupName returns the security group name of the resource group. // GetNetworkSecurityGroupName returns the security group name of the resource group.

View File

@ -8,20 +8,13 @@ import (
"github.com/Azure/azure-sdk-for-go/sdk/azcore/to" "github.com/Azure/azure-sdk-for-go/sdk/azcore/to"
"github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute" "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute"
"github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork" "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork"
"github.com/edgelesssys/constellation/coordinator/cloudprovider/cloudtypes" "github.com/edgelesssys/constellation/internal/cloud/metadata"
"github.com/edgelesssys/constellation/coordinator/role"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
func TestList(t *testing.T) { func TestList(t *testing.T) {
wantInstances := []cloudtypes.Instance{ wantInstances := []metadata.InstanceMetadata{
{
Name: "instance-name",
ProviderID: "azure:///subscriptions/subscription-id/resourceGroups/resource-group/providers/Microsoft.Compute/virtualMachines/instance-name",
PrivateIPs: []string{"192.0.2.0"},
SSHKeys: map[string][]string{"user": {"key-data"}},
},
{ {
Name: "scale-set-name-instance-id", Name: "scale-set-name-instance-id",
ProviderID: "azure:///subscriptions/subscription-id/resourceGroups/resource-group/providers/Microsoft.Compute/virtualMachineScaleSets/scale-set-name/virtualMachines/instance-id", ProviderID: "azure:///subscriptions/subscription-id/resourceGroups/resource-group/providers/Microsoft.Compute/virtualMachineScaleSets/scale-set-name/virtualMachines/instance-id",
@ -33,17 +26,15 @@ func TestList(t *testing.T) {
imdsAPI imdsAPI imdsAPI imdsAPI
networkInterfacesAPI networkInterfacesAPI networkInterfacesAPI networkInterfacesAPI
scaleSetsAPI scaleSetsAPI scaleSetsAPI scaleSetsAPI
virtualMachinesAPI virtualMachinesAPI
virtualMachineScaleSetVMsAPI virtualMachineScaleSetVMsAPI virtualMachineScaleSetVMsAPI virtualMachineScaleSetVMsAPI
tagsAPI tagsAPI tagsAPI tagsAPI
wantErr bool wantErr bool
wantInstances []cloudtypes.Instance wantInstances []metadata.InstanceMetadata
}{ }{
"List works": { "List works": {
imdsAPI: newIMDSStub(), imdsAPI: newIMDSStub(),
networkInterfacesAPI: newNetworkInterfacesStub(), networkInterfacesAPI: newNetworkInterfacesStub(),
scaleSetsAPI: newScaleSetsStub(), scaleSetsAPI: newScaleSetsStub(),
virtualMachinesAPI: newVirtualMachinesStub(),
virtualMachineScaleSetVMsAPI: newVirtualMachineScaleSetsVMsStub(), virtualMachineScaleSetVMsAPI: newVirtualMachineScaleSetsVMsStub(),
tagsAPI: newTagsStub(), tagsAPI: newTagsStub(),
wantInstances: wantInstances, wantInstances: wantInstances,
@ -56,16 +47,10 @@ func TestList(t *testing.T) {
imdsAPI: newInvalidIMDSStub(), imdsAPI: newInvalidIMDSStub(),
wantErr: true, wantErr: true,
}, },
"listVMs fails": {
imdsAPI: newIMDSStub(),
virtualMachinesAPI: newFailingListsVirtualMachinesStub(),
wantErr: true,
},
"listScaleSetVMs fails": { "listScaleSetVMs fails": {
imdsAPI: newIMDSStub(), imdsAPI: newIMDSStub(),
networkInterfacesAPI: newNetworkInterfacesStub(), networkInterfacesAPI: newNetworkInterfacesStub(),
scaleSetsAPI: newScaleSetsStub(), scaleSetsAPI: newScaleSetsStub(),
virtualMachinesAPI: newVirtualMachinesStub(),
virtualMachineScaleSetVMsAPI: newFailingListsVirtualMachineScaleSetsVMsStub(), virtualMachineScaleSetVMsAPI: newFailingListsVirtualMachineScaleSetsVMsStub(),
tagsAPI: newTagsStub(), tagsAPI: newTagsStub(),
wantErr: true, wantErr: true,
@ -77,15 +62,14 @@ func TestList(t *testing.T) {
assert := assert.New(t) assert := assert.New(t)
require := require.New(t) require := require.New(t)
metadata := Metadata{ azureMetadata := Metadata{
imdsAPI: tc.imdsAPI, imdsAPI: tc.imdsAPI,
networkInterfacesAPI: tc.networkInterfacesAPI, networkInterfacesAPI: tc.networkInterfacesAPI,
scaleSetsAPI: tc.scaleSetsAPI, scaleSetsAPI: tc.scaleSetsAPI,
virtualMachinesAPI: tc.virtualMachinesAPI,
virtualMachineScaleSetVMsAPI: tc.virtualMachineScaleSetVMsAPI, virtualMachineScaleSetVMsAPI: tc.virtualMachineScaleSetVMsAPI,
tagsAPI: tc.tagsAPI, tagsAPI: tc.tagsAPI,
} }
instances, err := metadata.List(context.Background()) instances, err := azureMetadata.List(context.Background())
if tc.wantErr { if tc.wantErr {
assert.Error(err) assert.Error(err)
@ -98,13 +82,13 @@ func TestList(t *testing.T) {
} }
func TestSelf(t *testing.T) { func TestSelf(t *testing.T) {
wantVMInstance := cloudtypes.Instance{ wantVMInstance := metadata.InstanceMetadata{
Name: "instance-name", Name: "instance-name",
ProviderID: "azure:///subscriptions/subscription-id/resourceGroups/resource-group/providers/Microsoft.Compute/virtualMachines/instance-name", ProviderID: "azure:///subscriptions/subscription-id/resourceGroups/resource-group/providers/Microsoft.Compute/virtualMachines/instance-name",
PrivateIPs: []string{"192.0.2.0"}, PrivateIPs: []string{"192.0.2.0"},
SSHKeys: map[string][]string{"user": {"key-data"}}, SSHKeys: map[string][]string{"user": {"key-data"}},
} }
wantScaleSetInstance := cloudtypes.Instance{ wantScaleSetInstance := metadata.InstanceMetadata{
Name: "scale-set-name-instance-id", Name: "scale-set-name-instance-id",
ProviderID: "azure:///subscriptions/subscription-id/resourceGroups/resource-group/providers/Microsoft.Compute/virtualMachineScaleSets/scale-set-name/virtualMachines/instance-id", ProviderID: "azure:///subscriptions/subscription-id/resourceGroups/resource-group/providers/Microsoft.Compute/virtualMachineScaleSets/scale-set-name/virtualMachines/instance-id",
PrivateIPs: []string{"192.0.2.0"}, PrivateIPs: []string{"192.0.2.0"},
@ -113,22 +97,19 @@ func TestSelf(t *testing.T) {
testCases := map[string]struct { testCases := map[string]struct {
imdsAPI imdsAPI imdsAPI imdsAPI
networkInterfacesAPI networkInterfacesAPI networkInterfacesAPI networkInterfacesAPI
virtualMachinesAPI virtualMachinesAPI
virtualMachineScaleSetVMsAPI virtualMachineScaleSetVMsAPI virtualMachineScaleSetVMsAPI virtualMachineScaleSetVMsAPI
wantErr bool wantErr bool
wantInstance cloudtypes.Instance wantInstance metadata.InstanceMetadata
}{ }{
"self for individual instance works": { "self for individual instance works": {
imdsAPI: newIMDSStub(), imdsAPI: newIMDSStub(),
networkInterfacesAPI: newNetworkInterfacesStub(), networkInterfacesAPI: newNetworkInterfacesStub(),
virtualMachinesAPI: newVirtualMachinesStub(),
virtualMachineScaleSetVMsAPI: newVirtualMachineScaleSetsVMsStub(), virtualMachineScaleSetVMsAPI: newVirtualMachineScaleSetsVMsStub(),
wantInstance: wantVMInstance, wantInstance: wantVMInstance,
}, },
"self for scale set instance works": { "self for scale set instance works": {
imdsAPI: newScaleSetIMDSStub(), imdsAPI: newScaleSetIMDSStub(),
networkInterfacesAPI: newNetworkInterfacesStub(), networkInterfacesAPI: newNetworkInterfacesStub(),
virtualMachinesAPI: newVirtualMachinesStub(),
virtualMachineScaleSetVMsAPI: newVirtualMachineScaleSetsVMsStub(), virtualMachineScaleSetVMsAPI: newVirtualMachineScaleSetsVMsStub(),
wantInstance: wantScaleSetInstance, wantInstance: wantScaleSetInstance,
}, },
@ -137,9 +118,8 @@ func TestSelf(t *testing.T) {
wantErr: true, wantErr: true,
}, },
"GetInstance fails": { "GetInstance fails": {
imdsAPI: newIMDSStub(), imdsAPI: newIMDSStub(),
virtualMachinesAPI: newFailingGetVirtualMachinesStub(), wantErr: true,
wantErr: true,
}, },
} }
@ -151,7 +131,6 @@ func TestSelf(t *testing.T) {
metadata := Metadata{ metadata := Metadata{
imdsAPI: tc.imdsAPI, imdsAPI: tc.imdsAPI,
networkInterfacesAPI: tc.networkInterfacesAPI, networkInterfacesAPI: tc.networkInterfacesAPI,
virtualMachinesAPI: tc.virtualMachinesAPI,
virtualMachineScaleSetVMsAPI: tc.virtualMachineScaleSetVMsAPI, virtualMachineScaleSetVMsAPI: tc.virtualMachineScaleSetVMsAPI,
} }
instance, err := metadata.Self(context.Background()) instance, err := metadata.Self(context.Background())
@ -166,50 +145,6 @@ func TestSelf(t *testing.T) {
} }
} }
func TestSignalRole(t *testing.T) {
testCases := map[string]struct {
imdsAPI imdsAPI
tagsAPI tagsAPI
wantErr bool
}{
"SignalRole works": {
imdsAPI: newIMDSStub(),
tagsAPI: newTagsStub(),
},
"SignalRole is not attempted on scale set vm": {
imdsAPI: newScaleSetIMDSStub(),
},
"providerID cannot be retrieved": {
imdsAPI: &stubIMDSAPI{retrieveErr: errors.New("imds err")},
wantErr: true,
},
"setting tag fails": {
imdsAPI: newIMDSStub(),
tagsAPI: newFailingTagsStub(),
wantErr: true,
},
}
for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
assert := assert.New(t)
require := require.New(t)
metadata := Metadata{
imdsAPI: tc.imdsAPI,
tagsAPI: tc.tagsAPI,
}
err := metadata.SignalRole(context.Background(), role.Coordinator)
if tc.wantErr {
assert.Error(err)
return
}
require.NoError(err)
})
}
}
func TestGetNetworkSecurityGroupName(t *testing.T) { func TestGetNetworkSecurityGroupName(t *testing.T) {
name := "network-security-group-name" name := "network-security-group-name"
testCases := map[string]struct { testCases := map[string]struct {
@ -719,12 +654,6 @@ func newInvalidIMDSStub() *stubIMDSAPI {
} }
} }
func newFailingIMDSStub() *stubIMDSAPI {
return &stubIMDSAPI{
retrieveErr: errors.New("imds retrieve error"),
}
}
func newNetworkInterfacesStub() *stubNetworkInterfacesAPI { func newNetworkInterfacesStub() *stubNetworkInterfacesAPI {
return &stubNetworkInterfacesAPI{ return &stubNetworkInterfacesAPI{
getInterface: armnetwork.Interface{ getInterface: armnetwork.Interface{
@ -754,81 +683,6 @@ func newScaleSetsStub() *stubScaleSetsAPI {
} }
} }
func newVirtualMachinesStub() *stubVirtualMachinesAPI {
return &stubVirtualMachinesAPI{
getVM: armcompute.VirtualMachine{
Name: to.StringPtr("instance-name"),
ID: to.StringPtr("/subscriptions/subscription-id/resourceGroups/resource-group/providers/Microsoft.Compute/virtualMachines/instance-name"),
Properties: &armcompute.VirtualMachineProperties{
NetworkProfile: &armcompute.NetworkProfile{
NetworkInterfaces: []*armcompute.NetworkInterfaceReference{
{
ID: to.StringPtr("/subscriptions/subscription/resourceGroups/resource-group/providers/Microsoft.Network/networkInterfaces/interface-name"),
},
},
},
OSProfile: &armcompute.OSProfile{
LinuxConfiguration: &armcompute.LinuxConfiguration{
SSH: &armcompute.SSHConfiguration{
PublicKeys: []*armcompute.SSHPublicKey{
{
KeyData: to.StringPtr("key-data"),
Path: to.StringPtr("/home/user/.ssh/authorized_keys"),
},
},
},
},
},
},
},
listPages: [][]*armcompute.VirtualMachine{
{
{
Name: to.StringPtr("instance-name"),
ID: to.StringPtr("/subscriptions/subscription-id/resourceGroups/resource-group/providers/Microsoft.Compute/virtualMachines/instance-name"),
Properties: &armcompute.VirtualMachineProperties{
NetworkProfile: &armcompute.NetworkProfile{
NetworkInterfaces: []*armcompute.NetworkInterfaceReference{
{
ID: to.StringPtr("/subscriptions/subscription-id/resourceGroups/resource-group/providers/Microsoft.Network/networkInterfaces/interface-name"),
},
},
},
OSProfile: &armcompute.OSProfile{
LinuxConfiguration: &armcompute.LinuxConfiguration{
SSH: &armcompute.SSHConfiguration{
PublicKeys: []*armcompute.SSHPublicKey{
{
KeyData: to.StringPtr("key-data"),
Path: to.StringPtr("/home/user/.ssh/authorized_keys"),
},
},
},
},
},
},
},
},
},
}
}
func newFailingListsVirtualMachinesStub() *stubVirtualMachinesAPI {
return &stubVirtualMachinesAPI{
listPages: [][]*armcompute.VirtualMachine{
{
{},
},
},
}
}
func newFailingGetVirtualMachinesStub() *stubVirtualMachinesAPI {
return &stubVirtualMachinesAPI{
getErr: errors.New("get err"),
}
}
func newVirtualMachineScaleSetsVMsStub() *stubVirtualMachineScaleSetVMsAPI { func newVirtualMachineScaleSetsVMsStub() *stubVirtualMachineScaleSetVMsAPI {
return &stubVirtualMachineScaleSetVMsAPI{ return &stubVirtualMachineScaleSetVMsAPI{
getVM: armcompute.VirtualMachineScaleSetVM{ getVM: armcompute.VirtualMachineScaleSetVM{
@ -907,10 +761,3 @@ func newFailingListsVirtualMachineScaleSetsVMsStub() *stubVirtualMachineScaleSet
func newTagsStub() *stubTagsAPI { func newTagsStub() *stubTagsAPI {
return &stubTagsAPI{} return &stubTagsAPI{}
} }
func newFailingTagsStub() *stubTagsAPI {
return &stubTagsAPI{
createOrUpdateAtScopeErr: errors.New("createOrUpdateErr"),
updateAtScopeErr: errors.New("updateErr"),
}
}

View File

@ -7,9 +7,9 @@ import (
"github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute" "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute"
"github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork" "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork"
"github.com/edgelesssys/constellation/coordinator/cloudprovider/cloudtypes"
"github.com/edgelesssys/constellation/coordinator/role" "github.com/edgelesssys/constellation/coordinator/role"
"github.com/edgelesssys/constellation/internal/azureshared" "github.com/edgelesssys/constellation/internal/azureshared"
"github.com/edgelesssys/constellation/internal/cloud/metadata"
) )
var ( var (
@ -18,30 +18,30 @@ var (
) )
// getScaleSetVM tries to get an azure vm belonging to a scale set. // getScaleSetVM tries to get an azure vm belonging to a scale set.
func (m *Metadata) getScaleSetVM(ctx context.Context, providerID string) (cloudtypes.Instance, error) { func (m *Metadata) getScaleSetVM(ctx context.Context, providerID string) (metadata.InstanceMetadata, error) {
_, resourceGroup, scaleSet, instanceID, err := azureshared.ScaleSetInformationFromProviderID(providerID) _, resourceGroup, scaleSet, instanceID, err := azureshared.ScaleSetInformationFromProviderID(providerID)
if err != nil { if err != nil {
return cloudtypes.Instance{}, err return metadata.InstanceMetadata{}, err
} }
vmResp, err := m.virtualMachineScaleSetVMsAPI.Get(ctx, resourceGroup, scaleSet, instanceID, nil) vmResp, err := m.virtualMachineScaleSetVMsAPI.Get(ctx, resourceGroup, scaleSet, instanceID, nil)
if err != nil { if err != nil {
return cloudtypes.Instance{}, err return metadata.InstanceMetadata{}, err
} }
networkInterfaces, err := m.getScaleSetVMInterfaces(ctx, vmResp.VirtualMachineScaleSetVM, resourceGroup, scaleSet, instanceID) networkInterfaces, err := m.getScaleSetVMInterfaces(ctx, vmResp.VirtualMachineScaleSetVM, resourceGroup, scaleSet, instanceID)
if err != nil { if err != nil {
return cloudtypes.Instance{}, err return metadata.InstanceMetadata{}, err
} }
publicIPAddresses, err := m.getScaleSetVMPublicIPAddresses(ctx, resourceGroup, scaleSet, instanceID, networkInterfaces) publicIPAddresses, err := m.getScaleSetVMPublicIPAddresses(ctx, resourceGroup, scaleSet, instanceID, networkInterfaces)
if err != nil { if err != nil {
return cloudtypes.Instance{}, err return metadata.InstanceMetadata{}, err
} }
return convertScaleSetVMToCoreInstance(scaleSet, vmResp.VirtualMachineScaleSetVM, networkInterfaces, publicIPAddresses) return convertScaleSetVMToCoreInstance(scaleSet, vmResp.VirtualMachineScaleSetVM, networkInterfaces, publicIPAddresses)
} }
// listScaleSetVMs lists all scale set VMs in the current resource group. // listScaleSetVMs lists all scale set VMs in the current resource group.
func (m *Metadata) listScaleSetVMs(ctx context.Context, resourceGroup string) ([]cloudtypes.Instance, error) { func (m *Metadata) listScaleSetVMs(ctx context.Context, resourceGroup string) ([]metadata.InstanceMetadata, error) {
instances := []cloudtypes.Instance{} instances := []metadata.InstanceMetadata{}
scaleSetPager := m.scaleSetsAPI.List(resourceGroup, nil) scaleSetPager := m.scaleSetsAPI.List(resourceGroup, nil)
for scaleSetPager.NextPage(ctx) { for scaleSetPager.NextPage(ctx) {
for _, scaleSet := range scaleSetPager.PageResponse().Value { for _, scaleSet := range scaleSetPager.PageResponse().Value {
@ -71,12 +71,12 @@ func (m *Metadata) listScaleSetVMs(ctx context.Context, resourceGroup string) ([
} }
// convertScaleSetVMToCoreInstance converts an azure scale set virtual machine with interface configurations into a core.Instance. // convertScaleSetVMToCoreInstance converts an azure scale set virtual machine with interface configurations into a core.Instance.
func convertScaleSetVMToCoreInstance(scaleSet string, vm armcompute.VirtualMachineScaleSetVM, networkInterfaces []armnetwork.Interface, publicIPAddresses []string) (cloudtypes.Instance, error) { func convertScaleSetVMToCoreInstance(scaleSet string, vm armcompute.VirtualMachineScaleSetVM, networkInterfaces []armnetwork.Interface, publicIPAddresses []string) (metadata.InstanceMetadata, error) {
if vm.ID == nil { if vm.ID == nil {
return cloudtypes.Instance{}, errors.New("retrieving instance from armcompute API client returned no instance ID") return metadata.InstanceMetadata{}, errors.New("retrieving instance from armcompute API client returned no instance ID")
} }
if vm.Properties == nil || vm.Properties.OSProfile == nil || vm.Properties.OSProfile.ComputerName == nil { if vm.Properties == nil || vm.Properties.OSProfile == nil || vm.Properties.OSProfile.ComputerName == nil {
return cloudtypes.Instance{}, errors.New("retrieving instance from armcompute API client returned no computer name") return metadata.InstanceMetadata{}, errors.New("retrieving instance from armcompute API client returned no computer name")
} }
var sshKeys map[string][]string var sshKeys map[string][]string
if vm.Properties.OSProfile.LinuxConfiguration == nil || vm.Properties.OSProfile.LinuxConfiguration.SSH == nil { if vm.Properties.OSProfile.LinuxConfiguration == nil || vm.Properties.OSProfile.LinuxConfiguration.SSH == nil {
@ -84,7 +84,7 @@ func convertScaleSetVMToCoreInstance(scaleSet string, vm armcompute.VirtualMachi
} else { } else {
sshKeys = extractSSHKeys(*vm.Properties.OSProfile.LinuxConfiguration.SSH) sshKeys = extractSSHKeys(*vm.Properties.OSProfile.LinuxConfiguration.SSH)
} }
return cloudtypes.Instance{ return metadata.InstanceMetadata{
Name: *vm.Properties.OSProfile.ComputerName, Name: *vm.Properties.OSProfile.ComputerName,
ProviderID: "azure://" + *vm.ID, ProviderID: "azure://" + *vm.ID,
Role: extractScaleSetVMRole(scaleSet), Role: extractScaleSetVMRole(scaleSet),

View File

@ -8,14 +8,14 @@ import (
"github.com/Azure/azure-sdk-for-go/sdk/azcore/to" "github.com/Azure/azure-sdk-for-go/sdk/azcore/to"
"github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute" "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute"
"github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork" "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork"
"github.com/edgelesssys/constellation/coordinator/cloudprovider/cloudtypes"
"github.com/edgelesssys/constellation/coordinator/role" "github.com/edgelesssys/constellation/coordinator/role"
"github.com/edgelesssys/constellation/internal/cloud/metadata"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
func TestGetScaleSetVM(t *testing.T) { func TestGetScaleSetVM(t *testing.T) {
wantInstance := cloudtypes.Instance{ wantInstance := metadata.InstanceMetadata{
Name: "scale-set-name-instance-id", Name: "scale-set-name-instance-id",
ProviderID: "azure:///subscriptions/subscription-id/resourceGroups/resource-group/providers/Microsoft.Compute/virtualMachineScaleSets/scale-set-name/virtualMachines/instance-id", ProviderID: "azure:///subscriptions/subscription-id/resourceGroups/resource-group/providers/Microsoft.Compute/virtualMachineScaleSets/scale-set-name/virtualMachines/instance-id",
PrivateIPs: []string{"192.0.2.0"}, PrivateIPs: []string{"192.0.2.0"},
@ -26,7 +26,7 @@ func TestGetScaleSetVM(t *testing.T) {
networkInterfacesAPI networkInterfacesAPI networkInterfacesAPI networkInterfacesAPI
virtualMachineScaleSetVMsAPI virtualMachineScaleSetVMsAPI virtualMachineScaleSetVMsAPI virtualMachineScaleSetVMsAPI
wantErr bool wantErr bool
wantInstance cloudtypes.Instance wantInstance metadata.InstanceMetadata
}{ }{
"getVM for scale set instance works": { "getVM for scale set instance works": {
providerID: "azure:///subscriptions/subscription-id/resourceGroups/resource-group/providers/Microsoft.Compute/virtualMachineScaleSets/scale-set-name/virtualMachines/instance-id", providerID: "azure:///subscriptions/subscription-id/resourceGroups/resource-group/providers/Microsoft.Compute/virtualMachineScaleSets/scale-set-name/virtualMachines/instance-id",
@ -43,12 +43,6 @@ func TestGetScaleSetVM(t *testing.T) {
virtualMachineScaleSetVMsAPI: newFailingGetScaleSetVirtualMachinesStub(), virtualMachineScaleSetVMsAPI: newFailingGetScaleSetVirtualMachinesStub(),
wantErr: true, wantErr: true,
}, },
"retrieving interfaces fails": {
providerID: "azure:///subscriptions/subscription-id/resourceGroups/resource-group/providers/Microsoft.Compute/virtualMachineScaleSets/scale-set-name/virtualMachines/instance-id",
virtualMachineScaleSetVMsAPI: newVirtualMachineScaleSetsVMsStub(),
networkInterfacesAPI: newFailingNetworkInterfacesStub(),
wantErr: true,
},
"conversion fails": { "conversion fails": {
providerID: "azure:///subscriptions/subscription-id/resourceGroups/resource-group/providers/Microsoft.Compute/virtualMachineScaleSets/scale-set-name/virtualMachines/instance-id", providerID: "azure:///subscriptions/subscription-id/resourceGroups/resource-group/providers/Microsoft.Compute/virtualMachineScaleSets/scale-set-name/virtualMachines/instance-id",
virtualMachineScaleSetVMsAPI: newGetInvalidScaleSetVirtualMachinesStub(), virtualMachineScaleSetVMsAPI: newGetInvalidScaleSetVirtualMachinesStub(),
@ -79,7 +73,7 @@ func TestGetScaleSetVM(t *testing.T) {
} }
func TestListScaleSetVMs(t *testing.T) { func TestListScaleSetVMs(t *testing.T) {
wantInstances := []cloudtypes.Instance{ wantInstances := []metadata.InstanceMetadata{
{ {
Name: "scale-set-name-instance-id", Name: "scale-set-name-instance-id",
ProviderID: "azure:///subscriptions/subscription-id/resourceGroups/resource-group/providers/Microsoft.Compute/virtualMachineScaleSets/scale-set-name/virtualMachines/instance-id", ProviderID: "azure:///subscriptions/subscription-id/resourceGroups/resource-group/providers/Microsoft.Compute/virtualMachineScaleSets/scale-set-name/virtualMachines/instance-id",
@ -93,7 +87,7 @@ func TestListScaleSetVMs(t *testing.T) {
virtualMachineScaleSetVMsAPI virtualMachineScaleSetVMsAPI virtualMachineScaleSetVMsAPI virtualMachineScaleSetVMsAPI
scaleSetsAPI scaleSetsAPI scaleSetsAPI scaleSetsAPI
wantErr bool wantErr bool
wantInstances []cloudtypes.Instance wantInstances []metadata.InstanceMetadata
}{ }{
"listVMs works": { "listVMs works": {
imdsAPI: newIMDSStub(), imdsAPI: newIMDSStub(),
@ -114,7 +108,7 @@ func TestListScaleSetVMs(t *testing.T) {
networkInterfacesAPI: newNetworkInterfacesStub(), networkInterfacesAPI: newNetworkInterfacesStub(),
virtualMachineScaleSetVMsAPI: &stubVirtualMachineScaleSetVMsAPI{}, virtualMachineScaleSetVMsAPI: &stubVirtualMachineScaleSetVMsAPI{},
scaleSetsAPI: newScaleSetsStub(), scaleSetsAPI: newScaleSetsStub(),
wantInstances: []cloudtypes.Instance{}, wantInstances: []metadata.InstanceMetadata{},
}, },
"can skip nil in VM list": { "can skip nil in VM list": {
imdsAPI: newIMDSStub(), imdsAPI: newIMDSStub(),
@ -123,13 +117,6 @@ func TestListScaleSetVMs(t *testing.T) {
scaleSetsAPI: newScaleSetsStub(), scaleSetsAPI: newScaleSetsStub(),
wantInstances: wantInstances, wantInstances: wantInstances,
}, },
"retrieving network interfaces fails": {
imdsAPI: newIMDSStub(),
networkInterfacesAPI: newFailingNetworkInterfacesStub(),
virtualMachineScaleSetVMsAPI: newVirtualMachineScaleSetsVMsStub(),
scaleSetsAPI: newScaleSetsStub(),
wantErr: true,
},
"converting instance fails": { "converting instance fails": {
imdsAPI: newIMDSStub(), imdsAPI: newIMDSStub(),
networkInterfacesAPI: newNetworkInterfacesStub(), networkInterfacesAPI: newNetworkInterfacesStub(),
@ -168,7 +155,7 @@ func TestConvertScaleSetVMToCoreInstance(t *testing.T) {
inInterface []armnetwork.Interface inInterface []armnetwork.Interface
inPublicIPs []string inPublicIPs []string
wantErr bool wantErr bool
wantInstance cloudtypes.Instance wantInstance metadata.InstanceMetadata
}{ }{
"conversion works": { "conversion works": {
inVM: armcompute.VirtualMachineScaleSetVM{ inVM: armcompute.VirtualMachineScaleSetVM{
@ -197,7 +184,7 @@ func TestConvertScaleSetVMToCoreInstance(t *testing.T) {
}, },
}, },
inPublicIPs: []string{"192.0.2.100", "192.0.2.101"}, inPublicIPs: []string{"192.0.2.100", "192.0.2.101"},
wantInstance: cloudtypes.Instance{ wantInstance: metadata.InstanceMetadata{
Name: "scale-set-name-instance-id", Name: "scale-set-name-instance-id",
ProviderID: "azure:///subscriptions/subscription-id/resourceGroups/resource-group/providers/Microsoft.Compute/virtualMachineScaleSets/scale-set-name/virtualMachines/instance-id", ProviderID: "azure:///subscriptions/subscription-id/resourceGroups/resource-group/providers/Microsoft.Compute/virtualMachineScaleSets/scale-set-name/virtualMachines/instance-id",
PrivateIPs: []string{"192.0.2.0"}, PrivateIPs: []string{"192.0.2.0"},

View File

@ -1,91 +0,0 @@
package azure
import (
"context"
"fmt"
"github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute"
"github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork"
"github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources"
"github.com/Azure/go-autorest/autorest/to"
"github.com/edgelesssys/constellation/coordinator/cloudprovider"
"github.com/edgelesssys/constellation/coordinator/cloudprovider/cloudtypes"
"github.com/edgelesssys/constellation/internal/azureshared"
)
func (m *Metadata) getVM(ctx context.Context, providerID string) (cloudtypes.Instance, error) {
_, resourceGroup, instanceName, err := azureshared.VMInformationFromProviderID(providerID)
if err != nil {
return cloudtypes.Instance{}, err
}
vmResp, err := m.virtualMachinesAPI.Get(ctx, resourceGroup, instanceName, nil)
if err != nil {
return cloudtypes.Instance{}, err
}
interfaceIPConfigurations, err := m.getVMInterfaces(ctx, vmResp.VirtualMachine, resourceGroup)
if err != nil {
return cloudtypes.Instance{}, err
}
return convertVMToCoreInstance(vmResp.VirtualMachine, interfaceIPConfigurations)
}
// listVMs lists all individual VMs in the current resource group.
func (m *Metadata) listVMs(ctx context.Context, resourceGroup string) ([]cloudtypes.Instance, error) {
instances := []cloudtypes.Instance{}
pager := m.virtualMachinesAPI.List(resourceGroup, nil)
for pager.NextPage(ctx) {
for _, vm := range pager.PageResponse().Value {
if vm == nil {
continue
}
interfaces, err := m.getVMInterfaces(ctx, *vm, resourceGroup)
if err != nil {
return nil, err
}
instance, err := convertVMToCoreInstance(*vm, interfaces)
if err != nil {
return nil, err
}
instances = append(instances, instance)
}
}
return instances, nil
}
// setTag merges key-value pair into VM tags.
func (m *Metadata) setTag(ctx context.Context, key, value string) error {
instanceMetadata, err := m.imdsAPI.Retrieve(ctx)
if err != nil {
return err
}
_, err = m.tagsAPI.UpdateAtScope(ctx, instanceMetadata.Compute.ResourceID, armresources.TagsPatchResource{
Operation: armresources.TagsPatchOperationMerge.ToPtr(),
Properties: &armresources.Tags{
Tags: map[string]*string{
key: to.StringPtr(value),
},
},
}, nil)
return err
}
// convertVMToCoreInstance converts an azure virtual machine with interface configurations into a cloudtypes.Instance.
func convertVMToCoreInstance(vm armcompute.VirtualMachine, networkInterfaces []armnetwork.Interface) (cloudtypes.Instance, error) {
if vm.Name == nil || vm.ID == nil {
return cloudtypes.Instance{}, fmt.Errorf("retrieving instance from armcompute API client returned invalid instance Name (%v) or ID (%v)", vm.Name, vm.ID)
}
var sshKeys map[string][]string
if vm.Properties == nil || vm.Properties.OSProfile == nil || vm.Properties.OSProfile.LinuxConfiguration == nil || vm.Properties.OSProfile.LinuxConfiguration.SSH == nil {
sshKeys = map[string][]string{}
} else {
sshKeys = extractSSHKeys(*vm.Properties.OSProfile.LinuxConfiguration.SSH)
}
metadata := extractInstanceTags(vm.Tags)
return cloudtypes.Instance{
Name: *vm.Name,
ProviderID: "azure://" + *vm.ID,
Role: cloudprovider.ExtractRole(metadata),
PrivateIPs: extractPrivateIPs(networkInterfaces),
SSHKeys: sshKeys,
}, nil
}

View File

@ -1,374 +0,0 @@
package azure
import (
"context"
"errors"
"testing"
"github.com/Azure/azure-sdk-for-go/sdk/azcore/to"
"github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute"
"github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork"
"github.com/edgelesssys/constellation/coordinator/cloudprovider/cloudtypes"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestGetVM(t *testing.T) {
wantInstance := cloudtypes.Instance{
Name: "instance-name",
ProviderID: "azure:///subscriptions/subscription-id/resourceGroups/resource-group/providers/Microsoft.Compute/virtualMachines/instance-name",
PrivateIPs: []string{"192.0.2.0"},
SSHKeys: map[string][]string{"user": {"key-data"}},
}
testCases := map[string]struct {
providerID string
networkInterfacesAPI networkInterfacesAPI
virtualMachinesAPI virtualMachinesAPI
wantErr bool
wantInstance cloudtypes.Instance
}{
"getVM for individual instance works": {
providerID: "azure:///subscriptions/subscription-id/resourceGroups/resource-group/providers/Microsoft.Compute/virtualMachines/instance-name",
networkInterfacesAPI: newNetworkInterfacesStub(),
virtualMachinesAPI: newVirtualMachinesStub(),
wantInstance: wantInstance,
},
"getVM for scale set instance must fail": {
providerID: "azure:///subscriptions/subscription-id/resourceGroups/resource-group/providers/Microsoft.Compute/virtualMachineScaleSets/scale-set-name/virtualMachines/instance-id",
wantErr: true,
},
"Get fails": {
providerID: "azure:///subscriptions/subscription-id/resourceGroups/resource-group/providers/Microsoft.Compute/virtualMachines/instance-name",
virtualMachinesAPI: newFailingGetVirtualMachinesStub(),
wantErr: true,
},
"retrieving interfaces fails": {
providerID: "azure:///subscriptions/subscription-id/resourceGroups/resource-group/providers/Microsoft.Compute/virtualMachines/instance-name",
virtualMachinesAPI: newVirtualMachinesStub(),
networkInterfacesAPI: newFailingNetworkInterfacesStub(),
wantErr: true,
},
"conversion fails": {
providerID: "azure:///subscriptions/subscription-id/resourceGroups/resource-group/providers/Microsoft.Compute/virtualMachines/instance-name",
virtualMachinesAPI: newGetInvalidVirtualMachinesStub(),
networkInterfacesAPI: newNetworkInterfacesStub(),
wantErr: true,
},
}
for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
assert := assert.New(t)
require := require.New(t)
metadata := Metadata{
networkInterfacesAPI: tc.networkInterfacesAPI,
virtualMachinesAPI: tc.virtualMachinesAPI,
}
instance, err := metadata.getVM(context.Background(), tc.providerID)
if tc.wantErr {
assert.Error(err)
return
}
require.NoError(err)
assert.Equal(tc.wantInstance, instance)
})
}
}
func TestListVMs(t *testing.T) {
wantInstances := []cloudtypes.Instance{
{
Name: "instance-name",
ProviderID: "azure:///subscriptions/subscription-id/resourceGroups/resource-group/providers/Microsoft.Compute/virtualMachines/instance-name",
PrivateIPs: []string{"192.0.2.0"},
SSHKeys: map[string][]string{"user": {"key-data"}},
},
}
testCases := map[string]struct {
imdsAPI imdsAPI
networkInterfacesAPI networkInterfacesAPI
virtualMachinesAPI virtualMachinesAPI
wantErr bool
wantInstances []cloudtypes.Instance
}{
"listVMs works": {
imdsAPI: newIMDSStub(),
networkInterfacesAPI: newNetworkInterfacesStub(),
virtualMachinesAPI: newVirtualMachinesStub(),
wantInstances: wantInstances,
},
"listVMs can return 0 VMs": {
imdsAPI: newIMDSStub(),
networkInterfacesAPI: newNetworkInterfacesStub(),
virtualMachinesAPI: &stubVirtualMachinesAPI{},
wantInstances: []cloudtypes.Instance{},
},
"can skip nil in VM list": {
imdsAPI: newIMDSStub(),
networkInterfacesAPI: newNetworkInterfacesStub(),
virtualMachinesAPI: newListContainingNilVirtualMachinesStub(),
wantInstances: wantInstances,
},
"retrieving network interfaces fails": {
imdsAPI: newIMDSStub(),
networkInterfacesAPI: newFailingNetworkInterfacesStub(),
virtualMachinesAPI: newVirtualMachinesStub(),
wantErr: true,
},
"converting instance fails": {
imdsAPI: newIMDSStub(),
networkInterfacesAPI: newNetworkInterfacesStub(),
virtualMachinesAPI: newListContainingInvalidVirtualMachinesStub(),
wantErr: true,
},
}
for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
assert := assert.New(t)
require := require.New(t)
metadata := Metadata{
imdsAPI: tc.imdsAPI,
networkInterfacesAPI: tc.networkInterfacesAPI,
virtualMachinesAPI: tc.virtualMachinesAPI,
}
instances, err := metadata.listVMs(context.Background(), "resource-group")
if tc.wantErr {
assert.Error(err)
return
}
require.NoError(err)
assert.ElementsMatch(tc.wantInstances, instances)
})
}
}
func TestSetTag(t *testing.T) {
testCases := map[string]struct {
imdsAPI imdsAPI
tagsAPI tagsAPI
wantErr bool
}{
"setTag works": {
imdsAPI: newIMDSStub(),
tagsAPI: newTagsStub(),
},
"retrieving resource ID fails": {
imdsAPI: newFailingIMDSStub(),
tagsAPI: newTagsStub(),
wantErr: true,
},
"updating tags fails": {
imdsAPI: newIMDSStub(),
tagsAPI: newFailingTagsStub(),
wantErr: true,
},
}
for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
assert := assert.New(t)
require := require.New(t)
metadata := Metadata{
imdsAPI: tc.imdsAPI,
tagsAPI: tc.tagsAPI,
}
err := metadata.setTag(context.Background(), "key", "value")
if tc.wantErr {
assert.Error(err)
return
}
require.NoError(err)
})
}
}
func TestConvertVMToCoreInstance(t *testing.T) {
testCases := map[string]struct {
inVM armcompute.VirtualMachine
inInterface []armnetwork.Interface
wantErr bool
wantInstance cloudtypes.Instance
}{
"conversion works": {
inVM: armcompute.VirtualMachine{
Name: to.StringPtr("instance-name"),
ID: to.StringPtr("/subscriptions/subscription-id/resourceGroups/resource-group/providers/Microsoft.Compute/virtualMachines/instance-name"),
Tags: map[string]*string{"tag-key": to.StringPtr("tag-value")},
Properties: &armcompute.VirtualMachineProperties{
OSProfile: &armcompute.OSProfile{
LinuxConfiguration: &armcompute.LinuxConfiguration{
SSH: &armcompute.SSHConfiguration{
PublicKeys: []*armcompute.SSHPublicKey{
{
Path: to.StringPtr("/home/user/.ssh/authorized_keys"),
KeyData: to.StringPtr("key-data"),
},
},
},
},
},
},
},
inInterface: []armnetwork.Interface{
{
Name: to.StringPtr("interface-name"),
ID: to.StringPtr("/subscriptions/subscription-id/resourceGroups/resource-group/providers/Microsoft.Network/networkInterfaces/interface-name"),
Properties: &armnetwork.InterfacePropertiesFormat{
IPConfigurations: []*armnetwork.InterfaceIPConfiguration{
{
Properties: &armnetwork.InterfaceIPConfigurationPropertiesFormat{
PrivateIPAddress: to.StringPtr("192.0.2.0"),
},
},
},
},
},
},
wantInstance: cloudtypes.Instance{
Name: "instance-name",
ProviderID: "azure:///subscriptions/subscription-id/resourceGroups/resource-group/providers/Microsoft.Compute/virtualMachines/instance-name",
PrivateIPs: []string{"192.0.2.0"},
SSHKeys: map[string][]string{"user": {"key-data"}},
},
},
"conversion without SSH keys works": {
inVM: armcompute.VirtualMachine{
Name: to.StringPtr("instance-name"),
ID: to.StringPtr("/subscriptions/subscription-id/resourceGroups/resource-group/providers/Microsoft.Compute/virtualMachines/instance-name"),
Tags: map[string]*string{"tag-key": to.StringPtr("tag-value")},
},
inInterface: []armnetwork.Interface{
{
Name: to.StringPtr("interface-name"),
ID: to.StringPtr("/subscriptions/subscription-id/resourceGroups/resource-group/providers/Microsoft.Network/networkInterfaces/interface-name"),
Properties: &armnetwork.InterfacePropertiesFormat{
IPConfigurations: []*armnetwork.InterfaceIPConfiguration{
{
Properties: &armnetwork.InterfaceIPConfigurationPropertiesFormat{
PrivateIPAddress: to.StringPtr("192.0.2.0"),
},
},
},
},
},
},
wantInstance: cloudtypes.Instance{
Name: "instance-name",
ProviderID: "azure:///subscriptions/subscription-id/resourceGroups/resource-group/providers/Microsoft.Compute/virtualMachines/instance-name",
PrivateIPs: []string{"192.0.2.0"},
SSHKeys: map[string][]string{},
},
},
"invalid instance": {
inVM: armcompute.VirtualMachine{},
wantErr: true,
},
}
for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
assert := assert.New(t)
require := require.New(t)
instance, err := convertVMToCoreInstance(tc.inVM, tc.inInterface)
if tc.wantErr {
assert.Error(err)
return
}
require.NoError(err)
assert.Equal(tc.wantInstance, instance)
})
}
}
func newFailingNetworkInterfacesStub() *stubNetworkInterfacesAPI {
return &stubNetworkInterfacesAPI{
getErr: errors.New("get err"),
}
}
func newGetInvalidVirtualMachinesStub() *stubVirtualMachinesAPI {
return &stubVirtualMachinesAPI{
getVM: armcompute.VirtualMachine{},
}
}
func newListContainingNilVirtualMachinesStub() *stubVirtualMachinesAPI {
return &stubVirtualMachinesAPI{
listPages: [][]*armcompute.VirtualMachine{
{
nil,
{
Name: to.StringPtr("instance-name"),
ID: to.StringPtr("/subscriptions/subscription-id/resourceGroups/resource-group/providers/Microsoft.Compute/virtualMachines/instance-name"),
Tags: map[string]*string{
"tag-key": to.StringPtr("tag-value"),
},
Properties: &armcompute.VirtualMachineProperties{
NetworkProfile: &armcompute.NetworkProfile{
NetworkInterfaces: []*armcompute.NetworkInterfaceReference{
{
ID: to.StringPtr("/subscriptions/subscription-id/resourceGroups/resource-group/providers/Microsoft.Network/networkInterfaces/interface-name"),
},
},
},
OSProfile: &armcompute.OSProfile{
LinuxConfiguration: &armcompute.LinuxConfiguration{
SSH: &armcompute.SSHConfiguration{
PublicKeys: []*armcompute.SSHPublicKey{
{
KeyData: to.StringPtr("key-data"),
Path: to.StringPtr("/home/user/.ssh/authorized_keys"),
},
},
},
},
},
},
},
},
},
}
}
func newListContainingInvalidVirtualMachinesStub() *stubVirtualMachinesAPI {
return &stubVirtualMachinesAPI{
listPages: [][]*armcompute.VirtualMachine{
{
{
Name: nil,
ID: nil,
Properties: &armcompute.VirtualMachineProperties{
NetworkProfile: &armcompute.NetworkProfile{
NetworkInterfaces: []*armcompute.NetworkInterfaceReference{
{
ID: to.StringPtr("/subscriptions/subscription-id/resourceGroups/resource-group/providers/Microsoft.Network/networkInterfaces/interface-name"),
},
},
},
OSProfile: &armcompute.OSProfile{
LinuxConfiguration: &armcompute.LinuxConfiguration{
SSH: &armcompute.SSHConfiguration{
PublicKeys: []*armcompute.SSHPublicKey{
{
KeyData: to.StringPtr("key-data"),
Path: to.StringPtr("/home/user/.ssh/authorized_keys"),
},
},
},
},
},
},
},
},
},
}
}

View File

@ -68,18 +68,6 @@ func (c *loadBalancersClient) List(resourceGroupName string, options *armnetwork
return c.LoadBalancersClient.List(resourceGroupName, options) return c.LoadBalancersClient.List(resourceGroupName, options)
} }
type virtualMachinesClient struct {
*armcompute.VirtualMachinesClient
}
func (c *virtualMachinesClient) Get(ctx context.Context, resourceGroupName, vmName string, options *armcompute.VirtualMachinesClientGetOptions) (armcompute.VirtualMachinesClientGetResponse, error) {
return c.VirtualMachinesClient.Get(ctx, resourceGroupName, vmName, options)
}
func (c *virtualMachinesClient) List(resourceGroupName string, options *armcompute.VirtualMachinesClientListOptions) virtualMachinesClientListPager {
return c.VirtualMachinesClient.List(resourceGroupName, options)
}
type virtualMachineScaleSetVMsClient struct { type virtualMachineScaleSetVMsClient struct {
*armcompute.VirtualMachineScaleSetVMsClient *armcompute.VirtualMachineScaleSetVMsClient
} }

View File

@ -1,15 +0,0 @@
package cloudtypes
import "github.com/edgelesssys/constellation/coordinator/role"
// Instance describes metadata of a peer.
type Instance struct {
Name string
ProviderID string
Role role.Role
PrivateIPs []string
PublicIPs []string
AliasIPRanges []string
// SSHKeys maps usernames to ssh public keys.
SSHKeys map[string][]string
}

View File

@ -1,8 +1,8 @@
package gcp package gcp
import ( import (
"github.com/edgelesssys/constellation/coordinator/cloudprovider/cloudtypes"
"github.com/edgelesssys/constellation/coordinator/kubernetes/k8sapi/resources" "github.com/edgelesssys/constellation/coordinator/kubernetes/k8sapi/resources"
"github.com/edgelesssys/constellation/internal/cloud/metadata"
k8s "k8s.io/api/core/v1" k8s "k8s.io/api/core/v1"
) )
@ -15,7 +15,7 @@ func (a *Autoscaler) Name() string {
} }
// Secrets returns a list of secrets to deploy together with the k8s cluster-autoscaler. // Secrets returns a list of secrets to deploy together with the k8s cluster-autoscaler.
func (a *Autoscaler) Secrets(instance cloudtypes.Instance, cloudServiceAccountURI string) (resources.Secrets, error) { func (a *Autoscaler) Secrets(instance metadata.InstanceMetadata, cloudServiceAccountURI string) (resources.Secrets, error) {
return resources.Secrets{}, nil return resources.Secrets{}, nil
} }

View File

@ -3,7 +3,7 @@ package gcp
import ( import (
"testing" "testing"
"github.com/edgelesssys/constellation/coordinator/cloudprovider/cloudtypes" "github.com/edgelesssys/constellation/internal/cloud/metadata"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
@ -12,7 +12,7 @@ func TestTrivialAutoscalerFunctions(t *testing.T) {
autoscaler := Autoscaler{} autoscaler := Autoscaler{}
assert.NotEmpty(autoscaler.Name()) assert.NotEmpty(autoscaler.Name())
assert.Empty(autoscaler.Secrets(cloudtypes.Instance{}, "")) assert.Empty(autoscaler.Secrets(metadata.InstanceMetadata{}, ""))
assert.NotEmpty(autoscaler.Volumes()) assert.NotEmpty(autoscaler.Volumes())
assert.NotEmpty(autoscaler.VolumeMounts()) assert.NotEmpty(autoscaler.VolumeMounts())
assert.NotEmpty(autoscaler.Env()) assert.NotEmpty(autoscaler.Env())

View File

@ -7,8 +7,8 @@ import (
"strings" "strings"
"github.com/edgelesssys/constellation/coordinator/cloudprovider" "github.com/edgelesssys/constellation/coordinator/cloudprovider"
"github.com/edgelesssys/constellation/coordinator/cloudprovider/cloudtypes"
"github.com/edgelesssys/constellation/coordinator/kubernetes/k8sapi/resources" "github.com/edgelesssys/constellation/coordinator/kubernetes/k8sapi/resources"
"github.com/edgelesssys/constellation/internal/cloud/metadata"
"github.com/edgelesssys/constellation/internal/gcpshared" "github.com/edgelesssys/constellation/internal/gcpshared"
k8s "k8s.io/api/core/v1" k8s "k8s.io/api/core/v1"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1" v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@ -46,7 +46,7 @@ func (c *CloudControllerManager) ExtraArgs() []string {
// ConfigMaps returns a list of ConfigMaps to deploy together with the k8s cloud-controller-manager // ConfigMaps returns a list of ConfigMaps to deploy together with the k8s cloud-controller-manager
// Reference: https://kubernetes.io/docs/concepts/configuration/configmap/ . // Reference: https://kubernetes.io/docs/concepts/configuration/configmap/ .
func (c *CloudControllerManager) ConfigMaps(instance cloudtypes.Instance) (resources.ConfigMaps, error) { func (c *CloudControllerManager) ConfigMaps(instance metadata.InstanceMetadata) (resources.ConfigMaps, error) {
// GCP CCM expects cloud config to contain the GCP project-id and other configuration. // GCP CCM expects cloud config to contain the GCP project-id and other configuration.
// reference: https://github.com/kubernetes/cloud-provider-gcp/blob/master/cluster/gce/gci/configure-helper.sh#L791-L892 // reference: https://github.com/kubernetes/cloud-provider-gcp/blob/master/cluster/gce/gci/configure-helper.sh#L791-L892
var config strings.Builder var config strings.Builder
@ -80,7 +80,7 @@ func (c *CloudControllerManager) ConfigMaps(instance cloudtypes.Instance) (resou
// Secrets returns a list of secrets to deploy together with the k8s cloud-controller-manager. // Secrets returns a list of secrets to deploy together with the k8s cloud-controller-manager.
// Reference: https://kubernetes.io/docs/concepts/configuration/secret/ . // Reference: https://kubernetes.io/docs/concepts/configuration/secret/ .
func (c *CloudControllerManager) Secrets(ctx context.Context, instance cloudtypes.Instance, cloudServiceAccountURI string) (resources.Secrets, error) { func (c *CloudControllerManager) Secrets(ctx context.Context, _ string, cloudServiceAccountURI string) (resources.Secrets, error) {
serviceAccountKey, err := gcpshared.ServiceAccountKeyFromURI(cloudServiceAccountURI) serviceAccountKey, err := gcpshared.ServiceAccountKeyFromURI(cloudServiceAccountURI)
if err != nil { if err != nil {
return resources.Secrets{}, err return resources.Secrets{}, err

View File

@ -5,8 +5,8 @@ import (
"encoding/json" "encoding/json"
"testing" "testing"
"github.com/edgelesssys/constellation/coordinator/cloudprovider/cloudtypes"
"github.com/edgelesssys/constellation/coordinator/kubernetes/k8sapi/resources" "github.com/edgelesssys/constellation/coordinator/kubernetes/k8sapi/resources"
"github.com/edgelesssys/constellation/internal/cloud/metadata"
"github.com/edgelesssys/constellation/internal/gcpshared" "github.com/edgelesssys/constellation/internal/gcpshared"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
@ -16,12 +16,12 @@ import (
func TestConfigMaps(t *testing.T) { func TestConfigMaps(t *testing.T) {
testCases := map[string]struct { testCases := map[string]struct {
instance cloudtypes.Instance instance metadata.InstanceMetadata
wantConfigMaps resources.ConfigMaps wantConfigMaps resources.ConfigMaps
wantErr bool wantErr bool
}{ }{
"ConfigMaps works": { "ConfigMaps works": {
instance: cloudtypes.Instance{ProviderID: "gce://project-id/zone/instanceName-UID-0", Name: "instanceName-UID-0"}, instance: metadata.InstanceMetadata{ProviderID: "gce://project-id/zone/instanceName-UID-0", Name: "instanceName-UID-0"},
wantConfigMaps: resources.ConfigMaps{ wantConfigMaps: resources.ConfigMaps{
&k8s.ConfigMap{ &k8s.ConfigMap{
TypeMeta: v1.TypeMeta{ TypeMeta: v1.TypeMeta{
@ -43,7 +43,7 @@ node-tags = constellation-UID
}, },
}, },
"invalid providerID fails": { "invalid providerID fails": {
instance: cloudtypes.Instance{ProviderID: "invalid"}, instance: metadata.InstanceMetadata{ProviderID: "invalid"},
wantErr: true, wantErr: true,
}, },
} }
@ -82,7 +82,7 @@ func TestSecrets(t *testing.T) {
rawKey, err := json.Marshal(serviceAccountKey) rawKey, err := json.Marshal(serviceAccountKey)
require.NoError(t, err) require.NoError(t, err)
testCases := map[string]struct { testCases := map[string]struct {
instance cloudtypes.Instance instance metadata.InstanceMetadata
cloudServiceAccountURI string cloudServiceAccountURI string
wantSecrets resources.Secrets wantSecrets resources.Secrets
wantErr bool wantErr bool
@ -117,7 +117,7 @@ func TestSecrets(t *testing.T) {
require := require.New(t) require := require.New(t)
cloud := CloudControllerManager{} cloud := CloudControllerManager{}
secrets, err := cloud.Secrets(context.Background(), tc.instance, tc.cloudServiceAccountURI) secrets, err := cloud.Secrets(context.Background(), tc.instance.ProviderID, tc.cloudServiceAccountURI)
if tc.wantErr { if tc.wantErr {
assert.Error(err) assert.Error(err)
return return

View File

@ -7,16 +7,17 @@ import (
"strings" "strings"
compute "cloud.google.com/go/compute/apiv1" compute "cloud.google.com/go/compute/apiv1"
"github.com/edgelesssys/constellation/coordinator/cloudprovider" "github.com/edgelesssys/constellation/internal/cloud/metadata"
"github.com/edgelesssys/constellation/coordinator/cloudprovider/cloudtypes"
"github.com/edgelesssys/constellation/coordinator/core"
"github.com/edgelesssys/constellation/internal/gcpshared" "github.com/edgelesssys/constellation/internal/gcpshared"
"google.golang.org/api/iterator" "google.golang.org/api/iterator"
computepb "google.golang.org/genproto/googleapis/cloud/compute/v1" computepb "google.golang.org/genproto/googleapis/cloud/compute/v1"
"google.golang.org/protobuf/proto" "google.golang.org/protobuf/proto"
) )
const gcpSSHMetadataKey = "ssh-keys" const (
gcpSSHMetadataKey = "ssh-keys"
constellationUIDMetadataKey = "constellation-uid"
)
var zoneFromRegionRegex = regexp.MustCompile("([a-z]*-[a-z]*[0-9])") var zoneFromRegionRegex = regexp.MustCompile("([a-z]*-[a-z]*[0-9])")
@ -51,7 +52,7 @@ func NewClient(ctx context.Context) (*Client, error) {
} }
// RetrieveInstances returns list of instances including their ips and metadata. // RetrieveInstances returns list of instances including their ips and metadata.
func (c *Client) RetrieveInstances(ctx context.Context, project, zone string) ([]cloudtypes.Instance, error) { func (c *Client) RetrieveInstances(ctx context.Context, project, zone string) ([]metadata.InstanceMetadata, error) {
uid, err := c.uid() uid, err := c.uid()
if err != nil { if err != nil {
return nil, err return nil, err
@ -62,7 +63,7 @@ func (c *Client) RetrieveInstances(ctx context.Context, project, zone string) ([
} }
instanceIterator := c.instanceAPI.List(ctx, req) instanceIterator := c.instanceAPI.List(ctx, req)
instances := []cloudtypes.Instance{} instances := []metadata.InstanceMetadata{}
for { for {
resp, err := instanceIterator.Next() resp, err := instanceIterator.Next()
if err == iterator.Done { if err == iterator.Done {
@ -73,7 +74,7 @@ func (c *Client) RetrieveInstances(ctx context.Context, project, zone string) ([
} }
metadata := extractInstanceMetadata(resp.Metadata, "", false) metadata := extractInstanceMetadata(resp.Metadata, "", false)
// skip instances not belonging to the current constellation // skip instances not belonging to the current constellation
if instanceUID, ok := metadata[core.ConstellationUIDMetadataKey]; !ok || instanceUID != uid { if instanceUID, ok := metadata[constellationUIDMetadataKey]; !ok || instanceUID != uid {
continue continue
} }
instance, err := convertToCoreInstance(resp, project, zone) instance, err := convertToCoreInstance(resp, project, zone)
@ -87,10 +88,10 @@ func (c *Client) RetrieveInstances(ctx context.Context, project, zone string) ([
} }
// RetrieveInstance returns a an instance including ips and metadata. // RetrieveInstance returns a an instance including ips and metadata.
func (c *Client) RetrieveInstance(ctx context.Context, project, zone, instanceName string) (cloudtypes.Instance, error) { func (c *Client) RetrieveInstance(ctx context.Context, project, zone, instanceName string) (metadata.InstanceMetadata, error) {
instance, err := c.getComputeInstance(ctx, project, zone, instanceName) instance, err := c.getComputeInstance(ctx, project, zone, instanceName)
if err != nil { if err != nil {
return cloudtypes.Instance{}, err return metadata.InstanceMetadata{}, err
} }
return convertToCoreInstance(instance, project, zone) return convertToCoreInstance(instance, project, zone)
@ -286,7 +287,7 @@ func (c *Client) updateInstanceMetadata(ctx context.Context, project, zone, inst
// uid retrieves the current instances uid. // uid retrieves the current instances uid.
func (c *Client) uid() (string, error) { func (c *Client) uid() (string, error) {
// API endpoint: http://metadata.google.internal/computeMetadata/v1/instance/attributes/constellation-uid // API endpoint: http://metadata.google.internal/computeMetadata/v1/instance/attributes/constellation-uid
uid, err := c.RetrieveInstanceMetadata(core.ConstellationUIDMetadataKey) uid, err := c.RetrieveInstanceMetadata(constellationUIDMetadataKey)
if err != nil { if err != nil {
return "", fmt.Errorf("retrieving constellation uid: %w", err) return "", fmt.Errorf("retrieving constellation uid: %w", err)
} }
@ -367,19 +368,19 @@ func extractSSHKeys(metadata map[string]string) map[string][]string {
} }
// convertToCoreInstance converts a *computepb.Instance to a core.Instance. // convertToCoreInstance converts a *computepb.Instance to a core.Instance.
func convertToCoreInstance(in *computepb.Instance, project string, zone string) (cloudtypes.Instance, error) { func convertToCoreInstance(in *computepb.Instance, project string, zone string) (metadata.InstanceMetadata, error) {
if in.Name == nil { if in.Name == nil {
return cloudtypes.Instance{}, fmt.Errorf("retrieving instance from compute API client returned invalid instance Name: %v", in.Name) return metadata.InstanceMetadata{}, fmt.Errorf("retrieving instance from compute API client returned invalid instance Name: %v", in.Name)
} }
metadata := extractInstanceMetadata(in.Metadata, "", false) mdata := extractInstanceMetadata(in.Metadata, "", false)
return cloudtypes.Instance{ return metadata.InstanceMetadata{
Name: *in.Name, Name: *in.Name,
ProviderID: gcpshared.JoinProviderID(project, zone, *in.Name), ProviderID: gcpshared.JoinProviderID(project, zone, *in.Name),
Role: cloudprovider.ExtractRole(metadata), Role: extractRole(mdata),
PrivateIPs: extractPrivateIPs(in.NetworkInterfaces), PrivateIPs: extractPrivateIPs(in.NetworkInterfaces),
PublicIPs: extractPublicIPs(in.NetworkInterfaces), PublicIPs: extractPublicIPs(in.NetworkInterfaces),
AliasIPRanges: extractAliasIPRanges(in.NetworkInterfaces), AliasIPRanges: extractAliasIPRanges(in.NetworkInterfaces),
SSHKeys: extractSSHKeys(metadata), SSHKeys: extractSSHKeys(mdata),
}, nil }, nil
} }

View File

@ -6,9 +6,8 @@ import (
"testing" "testing"
compute "cloud.google.com/go/compute/apiv1" compute "cloud.google.com/go/compute/apiv1"
"github.com/edgelesssys/constellation/coordinator/cloudprovider/cloudtypes"
"github.com/edgelesssys/constellation/coordinator/core"
"github.com/edgelesssys/constellation/coordinator/role" "github.com/edgelesssys/constellation/coordinator/role"
"github.com/edgelesssys/constellation/internal/cloud/metadata"
gax "github.com/googleapis/gax-go/v2" gax "github.com/googleapis/gax-go/v2"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
@ -44,11 +43,11 @@ func TestRetrieveInstances(t *testing.T) {
Value: proto.String("value-2"), Value: proto.String("value-2"),
}, },
{ {
Key: proto.String(core.ConstellationUIDMetadataKey), Key: proto.String(constellationUIDMetadataKey),
Value: proto.String(uid), Value: proto.String(uid),
}, },
{ {
Key: proto.String(core.RoleMetadataKey), Key: proto.String(roleMetadataKey),
Value: proto.String(role.Coordinator.String()), Value: proto.String(role.Coordinator.String()),
}, },
}, },
@ -70,14 +69,14 @@ func TestRetrieveInstances(t *testing.T) {
metadata stubMetadataClient metadata stubMetadataClient
instanceIter *stubInstanceIterator instanceIter *stubInstanceIterator
instanceIterMutator func(*stubInstanceIterator) instanceIterMutator func(*stubInstanceIterator)
wantInstances []cloudtypes.Instance wantInstances []metadata.InstanceMetadata
wantErr bool wantErr bool
}{ }{
"retrieve works": { "retrieve works": {
client: stubInstancesClient{}, client: stubInstancesClient{},
metadata: stubMetadataClient{InstanceValue: uid}, metadata: stubMetadataClient{InstanceValue: uid},
instanceIter: newTestIter(), instanceIter: newTestIter(),
wantInstances: []cloudtypes.Instance{ wantInstances: []metadata.InstanceMetadata{
{ {
Name: "someInstance", Name: "someInstance",
ProviderID: "gce://someProject/someZone/someInstance", ProviderID: "gce://someProject/someZone/someInstance",
@ -101,7 +100,7 @@ func TestRetrieveInstances(t *testing.T) {
metadata: stubMetadataClient{InstanceValue: uid}, metadata: stubMetadataClient{InstanceValue: uid},
instanceIter: newTestIter(), instanceIter: newTestIter(),
instanceIterMutator: func(sii *stubInstanceIterator) { sii.instances[0].NetworkInterfaces = nil }, instanceIterMutator: func(sii *stubInstanceIterator) { sii.instances[0].NetworkInterfaces = nil },
wantInstances: []cloudtypes.Instance{ wantInstances: []metadata.InstanceMetadata{
{ {
Name: "someInstance", Name: "someInstance",
ProviderID: "gce://someProject/someZone/someInstance", ProviderID: "gce://someProject/someZone/someInstance",
@ -118,7 +117,7 @@ func TestRetrieveInstances(t *testing.T) {
metadata: stubMetadataClient{InstanceValue: uid}, metadata: stubMetadataClient{InstanceValue: uid},
instanceIter: newTestIter(), instanceIter: newTestIter(),
instanceIterMutator: func(sii *stubInstanceIterator) { sii.instances[0].NetworkInterfaces[0].NetworkIP = nil }, instanceIterMutator: func(sii *stubInstanceIterator) { sii.instances[0].NetworkInterfaces[0].NetworkIP = nil },
wantInstances: []cloudtypes.Instance{ wantInstances: []metadata.InstanceMetadata{
{ {
Name: "someInstance", Name: "someInstance",
ProviderID: "gce://someProject/someZone/someInstance", ProviderID: "gce://someProject/someZone/someInstance",
@ -135,7 +134,7 @@ func TestRetrieveInstances(t *testing.T) {
metadata: stubMetadataClient{InstanceValue: uid}, metadata: stubMetadataClient{InstanceValue: uid},
instanceIter: newTestIter(), instanceIter: newTestIter(),
instanceIterMutator: func(sii *stubInstanceIterator) { sii.instances[0].Metadata.Items[2].Key = proto.String("") }, instanceIterMutator: func(sii *stubInstanceIterator) { sii.instances[0].Metadata.Items[2].Key = proto.String("") },
wantInstances: []cloudtypes.Instance{}, wantInstances: []metadata.InstanceMetadata{},
}, },
"constellation retrieval fails": { "constellation retrieval fails": {
client: stubInstancesClient{}, client: stubInstancesClient{},
@ -148,7 +147,7 @@ func TestRetrieveInstances(t *testing.T) {
metadata: stubMetadataClient{InstanceValue: uid}, metadata: stubMetadataClient{InstanceValue: uid},
instanceIter: newTestIter(), instanceIter: newTestIter(),
instanceIterMutator: func(sii *stubInstanceIterator) { sii.instances[0].Metadata.Items[3].Key = proto.String("") }, instanceIterMutator: func(sii *stubInstanceIterator) { sii.instances[0].Metadata.Items[3].Key = proto.String("") },
wantInstances: []cloudtypes.Instance{ wantInstances: []metadata.InstanceMetadata{
{ {
Name: "someInstance", Name: "someInstance",
ProviderID: "gce://someProject/someZone/someInstance", ProviderID: "gce://someProject/someZone/someInstance",
@ -224,13 +223,13 @@ func TestRetrieveInstance(t *testing.T) {
client stubInstancesClient client stubInstancesClient
clientInstance *computepb.Instance clientInstance *computepb.Instance
clientInstanceMutator func(*computepb.Instance) clientInstanceMutator func(*computepb.Instance)
wantInstance cloudtypes.Instance wantInstance metadata.InstanceMetadata
wantErr bool wantErr bool
}{ }{
"retrieve works": { "retrieve works": {
client: stubInstancesClient{}, client: stubInstancesClient{},
clientInstance: newTestInstance(), clientInstance: newTestInstance(),
wantInstance: cloudtypes.Instance{ wantInstance: metadata.InstanceMetadata{
Name: "someInstance", Name: "someInstance",
ProviderID: "gce://someProject/someZone/someInstance", ProviderID: "gce://someProject/someZone/someInstance",
AliasIPRanges: []string{"192.0.2.0/16"}, AliasIPRanges: []string{"192.0.2.0/16"},
@ -246,7 +245,7 @@ func TestRetrieveInstance(t *testing.T) {
i.Metadata.Items[0].Key = proto.String("ssh-keys") i.Metadata.Items[0].Key = proto.String("ssh-keys")
i.Metadata.Items[0].Value = proto.String("bob:ssh-rsa bobskey") i.Metadata.Items[0].Value = proto.String("bob:ssh-rsa bobskey")
}, },
wantInstance: cloudtypes.Instance{ wantInstance: metadata.InstanceMetadata{
Name: "someInstance", Name: "someInstance",
ProviderID: "gce://someProject/someZone/someInstance", ProviderID: "gce://someProject/someZone/someInstance",
AliasIPRanges: []string{"192.0.2.0/16"}, AliasIPRanges: []string{"192.0.2.0/16"},
@ -259,10 +258,10 @@ func TestRetrieveInstance(t *testing.T) {
client: stubInstancesClient{}, client: stubInstancesClient{},
clientInstance: newTestInstance(), clientInstance: newTestInstance(),
clientInstanceMutator: func(i *computepb.Instance) { clientInstanceMutator: func(i *computepb.Instance) {
i.Metadata.Items[0].Key = proto.String(core.RoleMetadataKey) i.Metadata.Items[0].Key = proto.String(roleMetadataKey)
i.Metadata.Items[0].Value = proto.String(role.Coordinator.String()) i.Metadata.Items[0].Value = proto.String(role.Coordinator.String())
}, },
wantInstance: cloudtypes.Instance{ wantInstance: metadata.InstanceMetadata{
Name: "someInstance", Name: "someInstance",
ProviderID: "gce://someProject/someZone/someInstance", ProviderID: "gce://someProject/someZone/someInstance",
AliasIPRanges: []string{"192.0.2.0/16"}, AliasIPRanges: []string{"192.0.2.0/16"},
@ -283,7 +282,7 @@ func TestRetrieveInstance(t *testing.T) {
client: stubInstancesClient{}, client: stubInstancesClient{},
clientInstance: newTestInstance(), clientInstance: newTestInstance(),
clientInstanceMutator: func(i *computepb.Instance) { i.Metadata.Items[0] = nil }, clientInstanceMutator: func(i *computepb.Instance) { i.Metadata.Items[0] = nil },
wantInstance: cloudtypes.Instance{ wantInstance: metadata.InstanceMetadata{
Name: "someInstance", Name: "someInstance",
ProviderID: "gce://someProject/someZone/someInstance", ProviderID: "gce://someProject/someZone/someInstance",
AliasIPRanges: []string{"192.0.2.0/16"}, AliasIPRanges: []string{"192.0.2.0/16"},
@ -296,7 +295,7 @@ func TestRetrieveInstance(t *testing.T) {
client: stubInstancesClient{}, client: stubInstancesClient{},
clientInstance: newTestInstance(), clientInstance: newTestInstance(),
clientInstanceMutator: func(i *computepb.Instance) { i.Metadata.Items[0].Key = nil }, clientInstanceMutator: func(i *computepb.Instance) { i.Metadata.Items[0].Key = nil },
wantInstance: cloudtypes.Instance{ wantInstance: metadata.InstanceMetadata{
Name: "someInstance", Name: "someInstance",
ProviderID: "gce://someProject/someZone/someInstance", ProviderID: "gce://someProject/someZone/someInstance",
AliasIPRanges: []string{"192.0.2.0/16"}, AliasIPRanges: []string{"192.0.2.0/16"},
@ -309,7 +308,7 @@ func TestRetrieveInstance(t *testing.T) {
client: stubInstancesClient{}, client: stubInstancesClient{},
clientInstance: newTestInstance(), clientInstance: newTestInstance(),
clientInstanceMutator: func(i *computepb.Instance) { i.Metadata.Items[0].Value = nil }, clientInstanceMutator: func(i *computepb.Instance) { i.Metadata.Items[0].Value = nil },
wantInstance: cloudtypes.Instance{ wantInstance: metadata.InstanceMetadata{
Name: "someInstance", Name: "someInstance",
ProviderID: "gce://someProject/someZone/someInstance", ProviderID: "gce://someProject/someZone/someInstance",
AliasIPRanges: []string{"192.0.2.0/16"}, AliasIPRanges: []string{"192.0.2.0/16"},
@ -322,7 +321,7 @@ func TestRetrieveInstance(t *testing.T) {
client: stubInstancesClient{}, client: stubInstancesClient{},
clientInstance: newTestInstance(), clientInstance: newTestInstance(),
clientInstanceMutator: func(i *computepb.Instance) { i.NetworkInterfaces[0] = nil }, clientInstanceMutator: func(i *computepb.Instance) { i.NetworkInterfaces[0] = nil },
wantInstance: cloudtypes.Instance{ wantInstance: metadata.InstanceMetadata{
Name: "someInstance", Name: "someInstance",
ProviderID: "gce://someProject/someZone/someInstance", ProviderID: "gce://someProject/someZone/someInstance",
AliasIPRanges: []string{}, AliasIPRanges: []string{},
@ -335,7 +334,7 @@ func TestRetrieveInstance(t *testing.T) {
client: stubInstancesClient{}, client: stubInstancesClient{},
clientInstance: newTestInstance(), clientInstance: newTestInstance(),
clientInstanceMutator: func(i *computepb.Instance) { i.NetworkInterfaces[0].NetworkIP = nil }, clientInstanceMutator: func(i *computepb.Instance) { i.NetworkInterfaces[0].NetworkIP = nil },
wantInstance: cloudtypes.Instance{ wantInstance: metadata.InstanceMetadata{
Name: "someInstance", Name: "someInstance",
ProviderID: "gce://someProject/someZone/someInstance", ProviderID: "gce://someProject/someZone/someInstance",
AliasIPRanges: []string{"192.0.2.0/16"}, AliasIPRanges: []string{"192.0.2.0/16"},
@ -348,7 +347,7 @@ func TestRetrieveInstance(t *testing.T) {
client: stubInstancesClient{}, client: stubInstancesClient{},
clientInstance: newTestInstance(), clientInstance: newTestInstance(),
clientInstanceMutator: func(i *computepb.Instance) { i.NetworkInterfaces[0].AliasIpRanges[0].IpCidrRange = nil }, clientInstanceMutator: func(i *computepb.Instance) { i.NetworkInterfaces[0].AliasIpRanges[0].IpCidrRange = nil },
wantInstance: cloudtypes.Instance{ wantInstance: metadata.InstanceMetadata{
Name: "someInstance", Name: "someInstance",
ProviderID: "gce://someProject/someZone/someInstance", ProviderID: "gce://someProject/someZone/someInstance",
AliasIPRanges: []string{}, AliasIPRanges: []string{},
@ -361,7 +360,7 @@ func TestRetrieveInstance(t *testing.T) {
client: stubInstancesClient{}, client: stubInstancesClient{},
clientInstance: newTestInstance(), clientInstance: newTestInstance(),
clientInstanceMutator: func(i *computepb.Instance) { i.NetworkInterfaces[0].AccessConfigs[0].NatIP = nil }, clientInstanceMutator: func(i *computepb.Instance) { i.NetworkInterfaces[0].AccessConfigs[0].NatIP = nil },
wantInstance: cloudtypes.Instance{ wantInstance: metadata.InstanceMetadata{
Name: "someInstance", Name: "someInstance",
ProviderID: "gce://someProject/someZone/someInstance", ProviderID: "gce://someProject/someZone/someInstance",
AliasIPRanges: []string{"192.0.2.0/16"}, AliasIPRanges: []string{"192.0.2.0/16"},

View File

@ -4,18 +4,16 @@ import (
"context" "context"
"fmt" "fmt"
"github.com/edgelesssys/constellation/coordinator/cloudprovider/cloudtypes" "github.com/edgelesssys/constellation/internal/cloud/metadata"
"github.com/edgelesssys/constellation/coordinator/core"
"github.com/edgelesssys/constellation/coordinator/role"
"github.com/edgelesssys/constellation/internal/gcpshared" "github.com/edgelesssys/constellation/internal/gcpshared"
) )
// API handles all GCP API requests. // API handles all GCP API requests.
type API interface { type API interface {
// RetrieveInstances retrieves a list of all accessible GCP instances with their metadata. // RetrieveInstances retrieves a list of all accessible GCP instances with their metadata.
RetrieveInstances(ctx context.Context, project, zone string) ([]cloudtypes.Instance, error) RetrieveInstances(ctx context.Context, project, zone string) ([]metadata.InstanceMetadata, error)
// RetrieveInstances retrieves a single GCP instances with its metadata. // RetrieveInstances retrieves a single GCP instances with its metadata.
RetrieveInstance(ctx context.Context, project, zone, instanceName string) (cloudtypes.Instance, error) RetrieveInstance(ctx context.Context, project, zone, instanceName string) (metadata.InstanceMetadata, error)
// RetrieveInstanceMetadata retrieves the GCP instance metadata of the current instance. // RetrieveInstanceMetadata retrieves the GCP instance metadata of the current instance.
RetrieveInstanceMetadata(attr string) (string, error) RetrieveInstanceMetadata(attr string) (string, error)
// RetrieveProjectID retrieves the GCP projectID containing the current instance. // RetrieveProjectID retrieves the GCP projectID containing the current instance.
@ -47,7 +45,7 @@ func New(api API) *Metadata {
} }
// List retrieves all instances belonging to the current constellation. // List retrieves all instances belonging to the current constellation.
func (m *Metadata) List(ctx context.Context) ([]cloudtypes.Instance, error) { func (m *Metadata) List(ctx context.Context) ([]metadata.InstanceMetadata, error) {
project, err := m.api.RetrieveProjectID() project, err := m.api.RetrieveProjectID()
if err != nil { if err != nil {
return nil, err return nil, err
@ -64,65 +62,31 @@ func (m *Metadata) List(ctx context.Context) ([]cloudtypes.Instance, error) {
} }
// Self retrieves the current instance. // Self retrieves the current instance.
func (m *Metadata) Self(ctx context.Context) (cloudtypes.Instance, error) { func (m *Metadata) Self(ctx context.Context) (metadata.InstanceMetadata, error) {
project, err := m.api.RetrieveProjectID() project, err := m.api.RetrieveProjectID()
if err != nil { if err != nil {
return cloudtypes.Instance{}, err return metadata.InstanceMetadata{}, err
} }
zone, err := m.api.RetrieveZone() zone, err := m.api.RetrieveZone()
if err != nil { if err != nil {
return cloudtypes.Instance{}, err return metadata.InstanceMetadata{}, err
} }
instanceName, err := m.api.RetrieveInstanceName() instanceName, err := m.api.RetrieveInstanceName()
if err != nil { if err != nil {
return cloudtypes.Instance{}, err return metadata.InstanceMetadata{}, err
} }
return m.api.RetrieveInstance(ctx, project, zone, instanceName) return m.api.RetrieveInstance(ctx, project, zone, instanceName)
} }
// GetInstance retrieves an instance using its providerID. // GetInstance retrieves an instance using its providerID.
func (m *Metadata) GetInstance(ctx context.Context, providerID string) (cloudtypes.Instance, error) { func (m *Metadata) GetInstance(ctx context.Context, providerID string) (metadata.InstanceMetadata, error) {
project, zone, instanceName, err := gcpshared.SplitProviderID(providerID) project, zone, instanceName, err := gcpshared.SplitProviderID(providerID)
if err != nil { if err != nil {
return cloudtypes.Instance{}, fmt.Errorf("invalid providerID: %w", err) return metadata.InstanceMetadata{}, fmt.Errorf("invalid providerID: %w", err)
} }
return m.api.RetrieveInstance(ctx, project, zone, instanceName) return m.api.RetrieveInstance(ctx, project, zone, instanceName)
} }
// SignalRole signals the constellation role via cloud provider metadata.
func (m *Metadata) SignalRole(ctx context.Context, role role.Role) error {
project, err := m.api.RetrieveProjectID()
if err != nil {
return err
}
zone, err := m.api.RetrieveZone()
if err != nil {
return err
}
instanceName, err := m.api.RetrieveInstanceName()
if err != nil {
return err
}
return m.api.SetInstanceMetadata(ctx, project, zone, instanceName, core.RoleMetadataKey, role.String())
}
// SetVPNIP stores the internally used VPN IP in cloud provider metadata.
func (m *Metadata) SetVPNIP(ctx context.Context, vpnIP string) error {
project, err := m.api.RetrieveProjectID()
if err != nil {
return err
}
zone, err := m.api.RetrieveZone()
if err != nil {
return err
}
instanceName, err := m.api.RetrieveInstanceName()
if err != nil {
return err
}
return m.api.SetInstanceMetadata(ctx, project, zone, instanceName, core.VPNIPMetadataKey, vpnIP)
}
// GetSubnetworkCIDR returns the subnetwork CIDR of the current instance. // GetSubnetworkCIDR returns the subnetwork CIDR of the current instance.
func (m *Metadata) GetSubnetworkCIDR(ctx context.Context) (string, error) { func (m *Metadata) GetSubnetworkCIDR(ctx context.Context) (string, error) {
project, err := m.api.RetrieveProjectID() project, err := m.api.RetrieveProjectID()
@ -140,11 +104,6 @@ func (m *Metadata) GetSubnetworkCIDR(ctx context.Context) (string, error) {
return m.api.RetrieveSubnetworkAliasCIDR(ctx, project, zone, instanceName) return m.api.RetrieveSubnetworkAliasCIDR(ctx, project, zone, instanceName)
} }
// SupportsLoadBalancer returns true if the cloud provider supports load balancers.
func (m *Metadata) SupportsLoadBalancer() bool {
return true
}
// GetLoadBalancerIP returns the IP of the load balancer. // GetLoadBalancerIP returns the IP of the load balancer.
func (m *Metadata) GetLoadBalancerIP(ctx context.Context) (string, error) { func (m *Metadata) GetLoadBalancerIP(ctx context.Context) (string, error) {
project, err := m.api.RetrieveProjectID() project, err := m.api.RetrieveProjectID()
@ -157,8 +116,3 @@ func (m *Metadata) GetLoadBalancerIP(ctx context.Context) (string, error) {
} }
return m.api.RetrieveLoadBalancerIP(ctx, project, zone) return m.api.RetrieveLoadBalancerIP(ctx, project, zone)
} }
// Supported is used to determine if metadata API is implemented for this cloud provider.
func (m *Metadata) Supported() bool {
return true
}

View File

@ -5,9 +5,7 @@ import (
"errors" "errors"
"testing" "testing"
"github.com/edgelesssys/constellation/coordinator/cloudprovider/cloudtypes" "github.com/edgelesssys/constellation/internal/cloud/metadata"
"github.com/edgelesssys/constellation/coordinator/core"
"github.com/edgelesssys/constellation/coordinator/role"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
@ -15,8 +13,8 @@ import (
func TestList(t *testing.T) { func TestList(t *testing.T) {
err := errors.New("some err") err := errors.New("some err")
uid := "1234" uid := "1234"
instancesGenerator := func() *[]cloudtypes.Instance { instancesGenerator := func() *[]metadata.InstanceMetadata {
return &[]cloudtypes.Instance{ return &[]metadata.InstanceMetadata{
{ {
Name: "someInstance", Name: "someInstance",
ProviderID: "gce://someProject/someZone/someInstance", ProviderID: "gce://someProject/someZone/someInstance",
@ -27,10 +25,10 @@ func TestList(t *testing.T) {
testCases := map[string]struct { testCases := map[string]struct {
client stubGCPClient client stubGCPClient
instancesGenerator func() *[]cloudtypes.Instance instancesGenerator func() *[]metadata.InstanceMetadata
instancesMutator func(*[]cloudtypes.Instance) instancesMutator func(*[]metadata.InstanceMetadata)
wantErr bool wantErr bool
wantInstances []cloudtypes.Instance wantInstances []metadata.InstanceMetadata
}{ }{
"retrieve works": { "retrieve works": {
client: stubGCPClient{ client: stubGCPClient{
@ -41,7 +39,7 @@ func TestList(t *testing.T) {
}, },
}, },
instancesGenerator: instancesGenerator, instancesGenerator: instancesGenerator,
wantInstances: []cloudtypes.Instance{ wantInstances: []metadata.InstanceMetadata{
{ {
Name: "someInstance", Name: "someInstance",
ProviderID: "gce://someProject/someZone/someInstance", ProviderID: "gce://someProject/someZone/someInstance",
@ -106,19 +104,19 @@ func TestSelf(t *testing.T) {
testCases := map[string]struct { testCases := map[string]struct {
client stubGCPClient client stubGCPClient
wantErr bool wantErr bool
wantInstance cloudtypes.Instance wantInstance metadata.InstanceMetadata
}{ }{
"retrieve works": { "retrieve works": {
client: stubGCPClient{ client: stubGCPClient{
projectID: "someProjectID", projectID: "someProjectID",
zone: "someZone", zone: "someZone",
retrieveInstanceValue: cloudtypes.Instance{ retrieveInstanceValue: metadata.InstanceMetadata{
Name: "someInstance", Name: "someInstance",
ProviderID: "gce://someProject/someZone/someInstance", ProviderID: "gce://someProject/someZone/someInstance",
PrivateIPs: []string{"192.0.2.0"}, PrivateIPs: []string{"192.0.2.0"},
}, },
}, },
wantInstance: cloudtypes.Instance{ wantInstance: metadata.InstanceMetadata{
Name: "someInstance", Name: "someInstance",
ProviderID: "gce://someProject/someZone/someInstance", ProviderID: "gce://someProject/someZone/someInstance",
PrivateIPs: []string{"192.0.2.0"}, PrivateIPs: []string{"192.0.2.0"},
@ -180,18 +178,18 @@ func TestGetInstance(t *testing.T) {
providerID string providerID string
client stubGCPClient client stubGCPClient
wantErr bool wantErr bool
wantInstance cloudtypes.Instance wantInstance metadata.InstanceMetadata
}{ }{
"retrieve works": { "retrieve works": {
providerID: "gce://someProject/someZone/someInstance", providerID: "gce://someProject/someZone/someInstance",
client: stubGCPClient{ client: stubGCPClient{
retrieveInstanceValue: cloudtypes.Instance{ retrieveInstanceValue: metadata.InstanceMetadata{
Name: "someInstance", Name: "someInstance",
ProviderID: "gce://someProject/someZone/someInstance", ProviderID: "gce://someProject/someZone/someInstance",
PrivateIPs: []string{"192.0.2.0"}, PrivateIPs: []string{"192.0.2.0"},
}, },
}, },
wantInstance: cloudtypes.Instance{ wantInstance: metadata.InstanceMetadata{
Name: "someInstance", Name: "someInstance",
ProviderID: "gce://someProject/someZone/someInstance", ProviderID: "gce://someProject/someZone/someInstance",
PrivateIPs: []string{"192.0.2.0"}, PrivateIPs: []string{"192.0.2.0"},
@ -232,135 +230,10 @@ func TestGetInstance(t *testing.T) {
} }
} }
func TestSignalRole(t *testing.T) {
err := errors.New("some err")
testCases := map[string]struct {
client stubGCPClient
wantErr bool
wantRole role.Role
}{
"signaling role works": {
client: stubGCPClient{
projectID: "someProjectID",
zone: "someZone",
instanceName: "someName",
},
wantRole: role.Coordinator,
},
"project metadata retrieval error is detected": {
client: stubGCPClient{
retrieveProjectIDErr: err,
},
wantErr: true,
},
"instance zone retrieval error is detected": {
client: stubGCPClient{
retrieveZoneErr: err,
},
wantErr: true,
},
"instance name retrieval error is detected": {
client: stubGCPClient{
retrieveInstanceNameErr: err,
},
wantErr: true,
},
}
for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
assert := assert.New(t)
require := require.New(t)
cloud := New(&tc.client)
err := cloud.SignalRole(context.Background(), tc.wantRole)
if tc.wantErr {
assert.Error(err)
return
}
require.NoError(err)
assert.ElementsMatch([]string{"someProjectID"}, tc.client.instanceMetadataProjects)
assert.ElementsMatch([]string{"someZone"}, tc.client.instanceMetadataZones)
assert.ElementsMatch([]string{"someName"}, tc.client.instanceMetadataInstanceNames)
assert.ElementsMatch([]string{core.RoleMetadataKey}, tc.client.instanceMetadataKeys)
assert.ElementsMatch([]string{tc.wantRole.String()}, tc.client.instanceMetadataValues)
})
}
}
func TestSetVPNIP(t *testing.T) {
err := errors.New("some err")
testCases := map[string]struct {
client stubGCPClient
wantErr bool
wantVPNIP string
}{
"signaling role works": {
client: stubGCPClient{
projectID: "someProjectID",
zone: "someZone",
instanceName: "someName",
},
wantVPNIP: "192.0.2.0",
},
"project metadata retrieval error is detected": {
client: stubGCPClient{
retrieveProjectIDErr: err,
},
wantErr: true,
},
"instance zone retrieval error is detected": {
client: stubGCPClient{
retrieveZoneErr: err,
},
wantErr: true,
},
"instance name retrieval error is detected": {
client: stubGCPClient{
retrieveInstanceNameErr: err,
},
wantErr: true,
},
}
for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
assert := assert.New(t)
require := require.New(t)
cloud := New(&tc.client)
err := cloud.SetVPNIP(context.Background(), tc.wantVPNIP)
if tc.wantErr {
assert.Error(err)
return
}
require.NoError(err)
assert.ElementsMatch([]string{"someProjectID"}, tc.client.instanceMetadataProjects)
assert.ElementsMatch([]string{"someZone"}, tc.client.instanceMetadataZones)
assert.ElementsMatch([]string{"someName"}, tc.client.instanceMetadataInstanceNames)
assert.ElementsMatch([]string{core.VPNIPMetadataKey}, tc.client.instanceMetadataKeys)
assert.ElementsMatch([]string{tc.wantVPNIP}, tc.client.instanceMetadataValues)
})
}
}
func TestTrivialMetadataFunctions(t *testing.T) {
assert := assert.New(t)
metadata := Metadata{}
assert.True(metadata.Supported())
}
type stubGCPClient struct { type stubGCPClient struct {
retrieveInstanceValue cloudtypes.Instance retrieveInstanceValue metadata.InstanceMetadata
retrieveInstanceErr error retrieveInstanceErr error
retrieveInstancesValues []cloudtypes.Instance retrieveInstancesValues []metadata.InstanceMetadata
retrieveInstancesErr error retrieveInstancesErr error
retrieveInstanceMetadaValues map[string]string retrieveInstanceMetadaValues map[string]string
retrieveInstanceMetadataErr error retrieveInstanceMetadataErr error
@ -388,11 +261,11 @@ type stubGCPClient struct {
unsetMetadataKeys []string unsetMetadataKeys []string
} }
func (s *stubGCPClient) RetrieveInstances(ctx context.Context, project, zone string) ([]cloudtypes.Instance, error) { func (s *stubGCPClient) RetrieveInstances(ctx context.Context, project, zone string) ([]metadata.InstanceMetadata, error) {
return s.retrieveInstancesValues, s.retrieveInstancesErr return s.retrieveInstancesValues, s.retrieveInstancesErr
} }
func (s *stubGCPClient) RetrieveInstance(ctx context.Context, project, zone string, instanceName string) (cloudtypes.Instance, error) { func (s *stubGCPClient) RetrieveInstance(ctx context.Context, project, zone string, instanceName string) (metadata.InstanceMetadata, error) {
return s.retrieveInstanceValue, s.retrieveInstanceErr return s.retrieveInstanceValue, s.retrieveInstanceErr
} }

View File

@ -0,0 +1,19 @@
package gcp
import (
"github.com/edgelesssys/constellation/coordinator/role"
)
const roleMetadataKey = "constellation-role"
// extractRole extracts role from cloud provider metadata.
func extractRole(metadata map[string]string) role.Role {
switch metadata[roleMetadataKey] {
case role.Coordinator.String():
return role.Coordinator
case role.Node.String():
return role.Node
default:
return role.Unknown
}
}

View File

@ -1,9 +1,8 @@
package cloudprovider package gcp
import ( import (
"testing" "testing"
"github.com/edgelesssys/constellation/coordinator/core"
"github.com/edgelesssys/constellation/coordinator/role" "github.com/edgelesssys/constellation/coordinator/role"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"go.uber.org/goleak" "go.uber.org/goleak"
@ -23,19 +22,19 @@ func TestExtractRole(t *testing.T) {
}{ }{
"coordinator role": { "coordinator role": {
metadata: map[string]string{ metadata: map[string]string{
core.RoleMetadataKey: role.Coordinator.String(), roleMetadataKey: role.Coordinator.String(),
}, },
wantRole: role.Coordinator, wantRole: role.Coordinator,
}, },
"node role": { "node role": {
metadata: map[string]string{ metadata: map[string]string{
core.RoleMetadataKey: role.Node.String(), roleMetadataKey: role.Node.String(),
}, },
wantRole: role.Node, wantRole: role.Node,
}, },
"unknown role": { "unknown role": {
metadata: map[string]string{ metadata: map[string]string{
core.RoleMetadataKey: "some-unknown-role", roleMetadataKey: "some-unknown-role",
}, },
wantRole: role.Unknown, wantRole: role.Unknown,
}, },
@ -48,7 +47,7 @@ func TestExtractRole(t *testing.T) {
t.Run(name, func(t *testing.T) { t.Run(name, func(t *testing.T) {
assert := assert.New(t) assert := assert.New(t)
role := ExtractRole(tc.metadata) role := extractRole(tc.metadata)
assert.Equal(tc.wantRole, role) assert.Equal(tc.wantRole, role)
}) })

View File

@ -1,8 +1,8 @@
package qemu package qemu
import ( import (
"github.com/edgelesssys/constellation/coordinator/cloudprovider/cloudtypes"
"github.com/edgelesssys/constellation/coordinator/kubernetes/k8sapi/resources" "github.com/edgelesssys/constellation/coordinator/kubernetes/k8sapi/resources"
"github.com/edgelesssys/constellation/internal/cloud/metadata"
k8s "k8s.io/api/core/v1" k8s "k8s.io/api/core/v1"
) )
@ -15,7 +15,7 @@ func (a Autoscaler) Name() string {
} }
// Secrets returns a list of secrets to deploy together with the k8s cluster-autoscaler. // Secrets returns a list of secrets to deploy together with the k8s cluster-autoscaler.
func (a Autoscaler) Secrets(instance cloudtypes.Instance, cloudServiceAccountURI string) (resources.Secrets, error) { func (a Autoscaler) Secrets(instance metadata.InstanceMetadata, cloudServiceAccountURI string) (resources.Secrets, error) {
return resources.Secrets{}, nil return resources.Secrets{}, nil
} }

View File

@ -3,8 +3,8 @@ package qemu
import ( import (
"context" "context"
"github.com/edgelesssys/constellation/coordinator/cloudprovider/cloudtypes"
"github.com/edgelesssys/constellation/coordinator/kubernetes/k8sapi/resources" "github.com/edgelesssys/constellation/coordinator/kubernetes/k8sapi/resources"
"github.com/edgelesssys/constellation/internal/cloud/metadata"
k8s "k8s.io/api/core/v1" k8s "k8s.io/api/core/v1"
) )
@ -33,13 +33,13 @@ func (c CloudControllerManager) ExtraArgs() []string {
// ConfigMaps returns a list of ConfigMaps to deploy together with the k8s cloud-controller-manager // ConfigMaps returns a list of ConfigMaps to deploy together with the k8s cloud-controller-manager
// Reference: https://kubernetes.io/docs/concepts/configuration/configmap/ . // Reference: https://kubernetes.io/docs/concepts/configuration/configmap/ .
func (c CloudControllerManager) ConfigMaps(instance cloudtypes.Instance) (resources.ConfigMaps, error) { func (c CloudControllerManager) ConfigMaps(instance metadata.InstanceMetadata) (resources.ConfigMaps, error) {
return resources.ConfigMaps{}, nil return resources.ConfigMaps{}, nil
} }
// Secrets returns a list of secrets to deploy together with the k8s cloud-controller-manager. // Secrets returns a list of secrets to deploy together with the k8s cloud-controller-manager.
// Reference: https://kubernetes.io/docs/concepts/configuration/secret/ . // Reference: https://kubernetes.io/docs/concepts/configuration/secret/ .
func (c CloudControllerManager) Secrets(ctx context.Context, instance cloudtypes.Instance, cloudServiceAccountURI string) (resources.Secrets, error) { func (c CloudControllerManager) Secrets(ctx context.Context, instance metadata.InstanceMetadata, cloudServiceAccountURI string) (resources.Secrets, error) {
return resources.Secrets{}, nil return resources.Secrets{}, nil
} }
@ -61,7 +61,7 @@ func (c CloudControllerManager) Env() []k8s.EnvVar {
// PrepareInstance is called on every instance before deploying the cloud-controller-manager. // PrepareInstance is called on every instance before deploying the cloud-controller-manager.
// Allows for cloud-provider specific hooks. // Allows for cloud-provider specific hooks.
func (c CloudControllerManager) PrepareInstance(instance cloudtypes.Instance, vpnIP string) error { func (c CloudControllerManager) PrepareInstance(instance metadata.InstanceMetadata, vpnIP string) error {
// no specific hook required. // no specific hook required.
return nil return nil
} }

View File

@ -8,8 +8,8 @@ import (
"net/http" "net/http"
"net/url" "net/url"
"github.com/edgelesssys/constellation/coordinator/cloudprovider/cloudtypes"
"github.com/edgelesssys/constellation/coordinator/role" "github.com/edgelesssys/constellation/coordinator/role"
"github.com/edgelesssys/constellation/internal/cloud/metadata"
) )
const qemuMetadataEndpoint = "10.42.0.1:8080" const qemuMetadataEndpoint = "10.42.0.1:8080"
@ -23,34 +23,34 @@ func (m *Metadata) Supported() bool {
} }
// List retrieves all instances belonging to the current constellation. // List retrieves all instances belonging to the current constellation.
func (m *Metadata) List(ctx context.Context) ([]cloudtypes.Instance, error) { func (m *Metadata) List(ctx context.Context) ([]metadata.InstanceMetadata, error) {
instancesRaw, err := m.retrieveMetadata(ctx, "/peers") instancesRaw, err := m.retrieveMetadata(ctx, "/peers")
if err != nil { if err != nil {
return nil, err return nil, err
} }
var instances []cloudtypes.Instance var instances []metadata.InstanceMetadata
err = json.Unmarshal(instancesRaw, &instances) err = json.Unmarshal(instancesRaw, &instances)
return instances, err return instances, err
} }
// Self retrieves the current instance. // Self retrieves the current instance.
func (m *Metadata) Self(ctx context.Context) (cloudtypes.Instance, error) { func (m *Metadata) Self(ctx context.Context) (metadata.InstanceMetadata, error) {
instanceRaw, err := m.retrieveMetadata(ctx, "/self") instanceRaw, err := m.retrieveMetadata(ctx, "/self")
if err != nil { if err != nil {
return cloudtypes.Instance{}, err return metadata.InstanceMetadata{}, err
} }
var instance cloudtypes.Instance var instance metadata.InstanceMetadata
err = json.Unmarshal(instanceRaw, &instance) err = json.Unmarshal(instanceRaw, &instance)
return instance, err return instance, err
} }
// GetInstance retrieves an instance using its providerID. // GetInstance retrieves an instance using its providerID.
func (m Metadata) GetInstance(ctx context.Context, providerID string) (cloudtypes.Instance, error) { func (m Metadata) GetInstance(ctx context.Context, providerID string) (metadata.InstanceMetadata, error) {
instances, err := m.List(ctx) instances, err := m.List(ctx)
if err != nil { if err != nil {
return cloudtypes.Instance{}, err return metadata.InstanceMetadata{}, err
} }
for _, instance := range instances { for _, instance := range instances {
@ -58,7 +58,7 @@ func (m Metadata) GetInstance(ctx context.Context, providerID string) (cloudtype
return instance, nil return instance, nil
} }
} }
return cloudtypes.Instance{}, errors.New("instance not found") return metadata.InstanceMetadata{}, errors.New("instance not found")
} }
// SignalRole signals the constellation role via cloud provider metadata (if supported by the CSP and deployment type, otherwise does nothing). // SignalRole signals the constellation role via cloud provider metadata (if supported by the CSP and deployment type, otherwise does nothing).

View File

@ -1,18 +0,0 @@
package cloudprovider
import (
"github.com/edgelesssys/constellation/coordinator/core"
"github.com/edgelesssys/constellation/coordinator/role"
)
// ExtractRole extracts role from cloud provider metadata.
func ExtractRole(metadata map[string]string) role.Role {
switch metadata[core.RoleMetadataKey] {
case role.Coordinator.String():
return role.Coordinator
case role.Node.String():
return role.Node
default:
return role.Unknown
}
}

View File

@ -282,7 +282,7 @@ func (k *KubeWrapper) setupCCM(ctx context.Context, subnetworkPodCIDR, cloudServ
if err != nil { if err != nil {
return fmt.Errorf("defining ConfigMaps for CCM failed: %w", err) return fmt.Errorf("defining ConfigMaps for CCM failed: %w", err)
} }
ccmSecrets, err := k.cloudControllerManager.Secrets(ctx, instance, cloudServiceAccountURI) ccmSecrets, err := k.cloudControllerManager.Secrets(ctx, instance.ProviderID, cloudServiceAccountURI)
if err != nil { if err != nil {
return fmt.Errorf("defining Secrets for CCM failed: %w", err) return fmt.Errorf("defining Secrets for CCM failed: %w", err)
} }
@ -316,7 +316,7 @@ func (k *KubeWrapper) setupClusterAutoscaler(instance cloudtypes.Instance, cloud
if !k.clusterAutoscaler.Supported() { if !k.clusterAutoscaler.Supported() {
return nil return nil
} }
caSecrets, err := k.clusterAutoscaler.Secrets(instance, cloudServiceAccountURI) caSecrets, err := k.clusterAutoscaler.Secrets(instance.ProviderID, cloudServiceAccountURI)
if err != nil { if err != nil {
return fmt.Errorf("defining Secrets for cluster-autoscaler failed: %w", err) return fmt.Errorf("defining Secrets for cluster-autoscaler failed: %w", err)
} }

View File

@ -7,17 +7,10 @@ import (
"strings" "strings"
) )
var ( var azureVMSSProviderIDRegexp = regexp.MustCompile(`^azure:///subscriptions/([^/]+)/resourceGroups/([^/]+)/providers/Microsoft.Compute/virtualMachineScaleSets/([^/]+)/virtualMachines/([^/]+)$`)
azureVMProviderIDRegexp = regexp.MustCompile(`^azure:///subscriptions/([^/]+)/resourceGroups/([^/]+)/providers/Microsoft.Compute/virtualMachines/([^/]+)$`)
azureVMSSProviderIDRegexp = regexp.MustCompile(`^azure:///subscriptions/([^/]+)/resourceGroups/([^/]+)/providers/Microsoft.Compute/virtualMachineScaleSets/([^/]+)/virtualMachines/([^/]+)$`)
)
// BasicsFromProviderID extracts subscriptionID and resourceGroup from both types of valid azure providerID. // BasicsFromProviderID extracts subscriptionID and resourceGroup from both types of valid azure providerID.
func BasicsFromProviderID(providerID string) (subscriptionID, resourceGroup string, err error) { func BasicsFromProviderID(providerID string) (subscriptionID, resourceGroup string, err error) {
subscriptionID, resourceGroup, _, err = VMInformationFromProviderID(providerID)
if err == nil {
return subscriptionID, resourceGroup, nil
}
subscriptionID, resourceGroup, _, _, err = ScaleSetInformationFromProviderID(providerID) subscriptionID, resourceGroup, _, _, err = ScaleSetInformationFromProviderID(providerID)
if err == nil { if err == nil {
return subscriptionID, resourceGroup, nil return subscriptionID, resourceGroup, nil
@ -29,7 +22,7 @@ func BasicsFromProviderID(providerID string) (subscriptionID, resourceGroup stri
// suffix at the resource group, e.g., resource-group-J18dB // suffix at the resource group, e.g., resource-group-J18dB
// J18dB is the UID. // J18dB is the UID.
func UIDFromProviderID(providerID string) (string, error) { func UIDFromProviderID(providerID string) (string, error) {
_, resourceGroup, err := BasicsFromProviderID(providerID) _, resourceGroup, _, _, err := ScaleSetInformationFromProviderID(providerID)
if err != nil { if err != nil {
return "", err return "", err
} }
@ -38,17 +31,6 @@ func UIDFromProviderID(providerID string) (string, error) {
return parts[len(parts)-1], nil return parts[len(parts)-1], nil
} }
// VMInformationFromProviderID splits a provider's id belonging to a single azure instance into core components.
// A providerID for individual VMs is build after the following schema:
// - 'azure:///subscriptions/<subscription-id>/resourceGroups/<resource-group>/providers/Microsoft.Compute/virtualMachines/<instance-name>'
func VMInformationFromProviderID(providerID string) (subscriptionID, resourceGroup, instanceName string, err error) {
matches := azureVMProviderIDRegexp.FindStringSubmatch(providerID)
if len(matches) != 4 {
return "", "", "", errors.New("error splitting providerID")
}
return matches[1], matches[2], matches[3], nil
}
// ScaleSetInformationFromProviderID splits a provider's id belonging to an azure scaleset into core components. // ScaleSetInformationFromProviderID splits a provider's id belonging to an azure scaleset into core components.
// A providerID for scale set VMs is build after the following schema: // A providerID for scale set VMs is build after the following schema:
// - 'azure:///subscriptions/<subscription-id>/resourceGroups/<resource-group>/providers/Microsoft.Compute/virtualMachineScaleSets/<scale-set-name>/virtualMachines/<instance-id>' // - 'azure:///subscriptions/<subscription-id>/resourceGroups/<resource-group>/providers/Microsoft.Compute/virtualMachineScaleSets/<scale-set-name>/virtualMachines/<instance-id>'

View File

@ -2,7 +2,6 @@ package metadata
import ( import (
"context" "context"
"errors"
"fmt" "fmt"
"net" "net"
"strconv" "strconv"
@ -36,24 +35,30 @@ type metadataAPI interface {
Supported() bool Supported() bool
} }
// TODO(katexochen): Rename to InitEndpoints type InstanceSelfer interface {
func CoordinatorEndpoints(ctx context.Context, api metadataAPI) ([]string, error) { // Self retrieves the current instance.
if !api.Supported() { Self(ctx context.Context) (InstanceMetadata, error)
return nil, errors.New("retrieving instances list from cloud provider is not yet supported") }
}
instances, err := api.List(ctx) type InstanceLister interface {
// List retrieves all instances belonging to the current constellation.
List(ctx context.Context) ([]InstanceMetadata, error)
}
func InitServerEndpoints(ctx context.Context, lister InstanceLister) ([]string, error) {
instances, err := lister.List(ctx)
if err != nil { if err != nil {
return nil, fmt.Errorf("retrieving instances list from cloud provider: %w", err) return nil, fmt.Errorf("retrieving instances list from cloud provider: %w", err)
} }
coordinatorEndpoints := []string{} initServerEndpoints := []string{}
for _, instance := range instances { for _, instance := range instances {
// check if role of instance is "Coordinator" // check if role of instance is "Coordinator"
if instance.Role == role.Coordinator { if instance.Role == role.Coordinator {
for _, ip := range instance.PrivateIPs { for _, ip := range instance.PrivateIPs {
coordinatorEndpoints = append(coordinatorEndpoints, net.JoinHostPort(ip, strconv.Itoa(constants.CoordinatorPort))) initServerEndpoints = append(initServerEndpoints, net.JoinHostPort(ip, strconv.Itoa(constants.CoordinatorPort)))
} }
} }
} }
return coordinatorEndpoints, nil return initServerEndpoints, nil
} }

View File

@ -50,7 +50,6 @@ const (
// //
// Filenames. // Filenames.
// //
StateFilename = "constellation-state.json" StateFilename = "constellation-state.json"
ClusterIDsFileName = "constellation-id.json" ClusterIDsFileName = "constellation-id.json"
ConfigFilename = "constellation-conf.yaml" ConfigFilename = "constellation-conf.yaml"