mirror of
https://github.com/edgelesssys/constellation.git
synced 2024-12-24 06:59:40 -05:00
Add autoscaling and cluster upgrade support for AWS (#1758)
* aws: autoscaling and upgrades * docs: update scaling and upgrades for AWS * deps: pin vuln check against release
This commit is contained in:
parent
12ccfea543
commit
964775c4c2
8
.github/workflows/e2e-test-release.yml
vendored
8
.github/workflows/e2e-test-release.yml
vendored
@ -127,6 +127,10 @@ jobs:
|
||||
provider: "azure"
|
||||
kubernetes-version: "v1.27"
|
||||
runner: "ubuntu-22.04"
|
||||
- test: "autoscaling"
|
||||
provider: "aws"
|
||||
kubernetes-version: "v1.27"
|
||||
runner: "ubuntu-22.04"
|
||||
|
||||
# perf-bench test on latest k8s version, not supported on AWS
|
||||
- test: "perf-bench"
|
||||
@ -238,8 +242,8 @@ jobs:
|
||||
fail-fast: false
|
||||
max-parallel: 1
|
||||
matrix:
|
||||
fromVersion: ["v2.6.0"]
|
||||
cloudProvider: ["gcp", "azure"]
|
||||
fromVersion: ["v2.7.1"]
|
||||
cloudProvider: ["gcp", "azure", "aws"]
|
||||
name: Run upgrade tests
|
||||
secrets: inherit
|
||||
permissions:
|
||||
|
4
.github/workflows/e2e-test-weekly.yml
vendored
4
.github/workflows/e2e-test-weekly.yml
vendored
@ -253,8 +253,8 @@ jobs:
|
||||
fail-fast: false
|
||||
max-parallel: 1
|
||||
matrix:
|
||||
fromVersion: ["v2.6.0"]
|
||||
cloudProvider: ["gcp", "azure"]
|
||||
fromVersion: ["v2.7.1"]
|
||||
cloudProvider: ["gcp", "azure", "aws"]
|
||||
name: Run upgrade tests
|
||||
secrets: inherit
|
||||
permissions:
|
||||
|
1
.github/workflows/e2e-upgrade.yml
vendored
1
.github/workflows/e2e-upgrade.yml
vendored
@ -9,6 +9,7 @@ on:
|
||||
options:
|
||||
- "gcp"
|
||||
- "azure"
|
||||
- "aws"
|
||||
default: "azure"
|
||||
workerNodesCount:
|
||||
description: "Number of worker nodes to spawn."
|
||||
|
@ -363,8 +363,8 @@ def go_dependencies():
|
||||
build_file_generation = "on",
|
||||
build_file_proto_mode = "disable_global",
|
||||
importpath = "github.com/aws/aws-sdk-go-v2",
|
||||
sum = "h1:GMupCNNI7FARX27L7GjCJM8NgivWbRgpjNI/hOQjFS8=",
|
||||
version = "v1.17.8",
|
||||
sum = "h1:882kkTpSFhdgYRKVZ/VCgf7sd0ru57p2JCxz4/oN5RY=",
|
||||
version = "v1.18.0",
|
||||
)
|
||||
go_repository(
|
||||
name = "com_github_aws_aws_sdk_go_v2_aws_protocol_eventstream",
|
||||
@ -390,6 +390,7 @@ def go_dependencies():
|
||||
sum = "h1:oZCEFcrMppP/CNiS8myzv9JgOzq2s0d3v3MXYil/mxQ=",
|
||||
version = "v1.13.20",
|
||||
)
|
||||
|
||||
go_repository(
|
||||
name = "com_github_aws_aws_sdk_go_v2_feature_ec2_imds",
|
||||
build_file_generation = "on",
|
||||
@ -411,16 +412,16 @@ def go_dependencies():
|
||||
build_file_generation = "on",
|
||||
build_file_proto_mode = "disable_global",
|
||||
importpath = "github.com/aws/aws-sdk-go-v2/internal/configsources",
|
||||
sum = "h1:dpbVNUjczQ8Ae3QKHbpHBpfvaVkRdesxpTOe9pTouhU=",
|
||||
version = "v1.1.32",
|
||||
sum = "h1:kG5eQilShqmJbv11XL1VpyDbaEJzWxd4zRiCG30GSn4=",
|
||||
version = "v1.1.33",
|
||||
)
|
||||
go_repository(
|
||||
name = "com_github_aws_aws_sdk_go_v2_internal_endpoints_v2",
|
||||
build_file_generation = "on",
|
||||
build_file_proto_mode = "disable_global",
|
||||
importpath = "github.com/aws/aws-sdk-go-v2/internal/endpoints/v2",
|
||||
sum = "h1:QH2kOS3Ht7x+u0gHCh06CXL/h6G8LQJFpZfFBYBNboo=",
|
||||
version = "v2.4.26",
|
||||
sum = "h1:vFQlirhuM8lLlpI7imKOMsjdQLuN9CPi+k44F/OFVsk=",
|
||||
version = "v2.4.27",
|
||||
)
|
||||
go_repository(
|
||||
name = "com_github_aws_aws_sdk_go_v2_internal_ini",
|
||||
@ -438,6 +439,14 @@ def go_dependencies():
|
||||
sum = "h1:DWYZIsyqagnWL00f8M/SOr9fN063OEQWn9LLTbdYXsk=",
|
||||
version = "v1.0.23",
|
||||
)
|
||||
go_repository(
|
||||
name = "com_github_aws_aws_sdk_go_v2_service_autoscaling",
|
||||
build_file_generation = "on",
|
||||
build_file_proto_mode = "disable_global",
|
||||
importpath = "github.com/aws/aws-sdk-go-v2/service/autoscaling",
|
||||
sum = "h1:OpzahvFZn/B+TNWLZf0ARovZoQB0Q2MvM+y13gdL+WY=",
|
||||
version = "v1.28.6",
|
||||
)
|
||||
|
||||
go_repository(
|
||||
name = "com_github_aws_aws_sdk_go_v2_service_cloudfront",
|
||||
@ -576,6 +585,7 @@ def go_dependencies():
|
||||
sum = "h1:hgz0X/DX0dGqTYpGALqXJoRKRj5oQ7150i5FdTePzO8=",
|
||||
version = "v1.13.5",
|
||||
)
|
||||
|
||||
go_repository(
|
||||
name = "com_github_aybabtme_rgbterm",
|
||||
build_file_generation = "on",
|
||||
@ -5824,8 +5834,8 @@ def go_dependencies():
|
||||
build_file_generation = "on",
|
||||
build_file_proto_mode = "disable_global",
|
||||
importpath = "github.com/rogpeppe/go-internal",
|
||||
sum = "h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=",
|
||||
version = "v1.9.0",
|
||||
sum = "h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=",
|
||||
version = "v1.10.0",
|
||||
)
|
||||
go_repository(
|
||||
name = "com_github_rs_cors",
|
||||
@ -9165,8 +9175,8 @@ def go_dependencies():
|
||||
build_file_generation = "on",
|
||||
build_file_proto_mode = "disable_global",
|
||||
importpath = "golang.org/x/vuln",
|
||||
sum = "h1:SJ0lK20LZB3cfTHvYOXH2m7DCIEaFdSlXtICBRv5bYU=",
|
||||
version = "v0.0.0-20230411201117-aaaefcd264f6",
|
||||
sum = "h1:9GRdj6wAIkDrsMevuolY+SXERPjQPp2P1ysYA0jpZe0=",
|
||||
version = "v0.1.0",
|
||||
)
|
||||
|
||||
go_repository(
|
||||
|
@ -189,7 +189,7 @@ func (k *KubeWrapper) InitCluster(
|
||||
|
||||
log.Infof("Waiting for Cilium to become healthy")
|
||||
timeToStartWaiting := time.Now()
|
||||
// TODO(Nirusu): Reduce the timeout when we switched the package repository - this is only this high because I once
|
||||
// TODO(3u13r): Reduce the timeout when we switched the package repository - this is only this high because we once
|
||||
// saw polling times of ~16 minutes when hitting a slow PoP from Fastly (GitHub's / ghcr.io CDN).
|
||||
waitCtx, cancel = context.WithTimeout(ctx, 20*time.Minute)
|
||||
defer cancel()
|
||||
|
@ -94,7 +94,7 @@ func (u *upgradeApplyCmd) upgradeApply(cmd *cobra.Command, fileHandler file.Hand
|
||||
return fmt.Errorf("upgrading measurements: %w", err)
|
||||
}
|
||||
|
||||
if conf.GetProvider() == cloudprovider.Azure || conf.GetProvider() == cloudprovider.GCP {
|
||||
if conf.GetProvider() == cloudprovider.Azure || conf.GetProvider() == cloudprovider.GCP || conf.GetProvider() == cloudprovider.AWS {
|
||||
err = u.handleServiceUpgrade(cmd, conf, flags)
|
||||
upgradeErr := &compatibility.InvalidUpgradeError{}
|
||||
switch {
|
||||
@ -114,7 +114,7 @@ func (u *upgradeApplyCmd) upgradeApply(cmd *cobra.Command, fileHandler file.Hand
|
||||
return fmt.Errorf("upgrading NodeVersion: %w", err)
|
||||
}
|
||||
} else {
|
||||
cmd.PrintErrln("WARNING: Skipping service and image upgrades, which are currently only supported for Azure and GCP.")
|
||||
cmd.PrintErrln("WARNING: Skipping service and image upgrades, which are currently only supported for AWS, Azure, and GCP.")
|
||||
}
|
||||
|
||||
return nil
|
||||
|
@ -112,7 +112,13 @@ resource "aws_iam_policy" "control_plane_policy" {
|
||||
"logs:DescribeLogGroups",
|
||||
"logs:ListTagsLogGroup",
|
||||
"logs:PutLogEvents",
|
||||
"tag:GetResources"
|
||||
"tag:GetResources",
|
||||
"ec2:DescribeLaunchTemplateVersions",
|
||||
"autoscaling:SetDesiredCapacity",
|
||||
"autoscaling:TerminateInstanceInAutoScalingGroup",
|
||||
"ec2:DescribeInstanceStatus",
|
||||
"ec2:CreateLaunchTemplateVersion",
|
||||
"ec2:ModifyLaunchTemplate"
|
||||
],
|
||||
"Resource": [
|
||||
"*"
|
||||
|
@ -66,11 +66,9 @@ Alternatively, you can manually scale your cluster up or down:
|
||||
</tabItem>
|
||||
<tabItem value="aws" label="AWS">
|
||||
|
||||
:::caution
|
||||
|
||||
Scaling isn't yet implemented for AWS. If you require this feature, [let us know](https://github.com/edgelesssys/constellation/issues/new?assignees=&labels=&template=feature_request.md)!
|
||||
|
||||
:::
|
||||
1. Go to Auto Scaling Groups and select the worker ASG to scale up.
|
||||
2. Click **Edit**
|
||||
3. Set the new (increased) **Desired capacity** and **Update**.
|
||||
|
||||
</tabItem>
|
||||
</tabs>
|
||||
@ -100,11 +98,9 @@ To increase the number of control-plane nodes, follow these steps:
|
||||
</tabItem>
|
||||
<tabItem value="aws" label="AWS">
|
||||
|
||||
:::caution
|
||||
|
||||
Scaling isn't yet implemented for AWS. If you require this feature, [let us know](https://github.com/edgelesssys/constellation/issues/new?assignees=&labels=&template=feature_request.md)!
|
||||
|
||||
:::
|
||||
1. Go to Auto Scaling Groups and select the control-plane ASG to scale up.
|
||||
2. Click **Edit**
|
||||
3. Set the new (increased) **Desired capacity** and **Update**.
|
||||
|
||||
</tabItem>
|
||||
</tabs>
|
||||
|
@ -6,12 +6,6 @@ You configure the desired versions in your local Constellation configuration and
|
||||
To learn about available versions you use the `upgrade check` command.
|
||||
Which versions are available depends on the CLI version you are using.
|
||||
|
||||
:::caution
|
||||
|
||||
Upgrades aren't yet implemented for AWS. If you require this feature, [let us know](https://github.com/edgelesssys/constellation/issues/new?assignees=&labels=&template=feature_request.md)!
|
||||
|
||||
:::
|
||||
|
||||
## Update the CLI
|
||||
|
||||
Each CLI comes with a set of supported microservice and Kubernetes versions.
|
||||
|
@ -5,8 +5,8 @@ go 1.20
|
||||
require (
|
||||
github.com/google/go-licenses v1.6.0
|
||||
github.com/katexochen/sh/v3 v3.6.0
|
||||
golang.org/x/tools v0.9.1
|
||||
golang.org/x/vuln v0.0.0-20230411201117-aaaefcd264f6
|
||||
golang.org/x/tools v0.8.1-0.20230421161920-b9619ee54b47
|
||||
golang.org/x/vuln v0.1.0
|
||||
)
|
||||
|
||||
require (
|
||||
|
@ -572,10 +572,10 @@ golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.5.0/go.mod h1:N+Kgy78s5I24c24dU8OfWNEotWjutIs8SnJvn5IDq+k=
|
||||
golang.org/x/tools v0.9.1 h1:8WMNJAz3zrtPmnYC7ISf5dEn3MT0gY7jBJfw27yrrLo=
|
||||
golang.org/x/tools v0.9.1/go.mod h1:owI94Op576fPu3cIGQeHs3joujW/2Oc6MtlxbF5dfNc=
|
||||
golang.org/x/vuln v0.0.0-20230411201117-aaaefcd264f6 h1:SJ0lK20LZB3cfTHvYOXH2m7DCIEaFdSlXtICBRv5bYU=
|
||||
golang.org/x/vuln v0.0.0-20230411201117-aaaefcd264f6/go.mod h1:64LpnL2PuSMzFYeCmJjYiRbroOUG9aCZYznINnF5PHE=
|
||||
golang.org/x/tools v0.8.1-0.20230421161920-b9619ee54b47 h1:fQlOhMJ24apqitZX8S4hbCbHU1Z9AvyWkN3BYI55Le4=
|
||||
golang.org/x/tools v0.8.1-0.20230421161920-b9619ee54b47/go.mod h1:JxBZ99ISMI5ViVkT1tr6tdNmXeTrcpVSD3vZ1RsRdN4=
|
||||
golang.org/x/vuln v0.1.0 h1:9GRdj6wAIkDrsMevuolY+SXERPjQPp2P1ysYA0jpZe0=
|
||||
golang.org/x/vuln v0.1.0/go.mod h1:/YuzZYjGbwB8y19CisAppfyw3uTZnuCz3r+qgx/QRzU=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
|
@ -13,6 +13,7 @@ go_library(
|
||||
"//3rdparty/node-maintenance-operator/api/v1beta1",
|
||||
"//operators/constellation-node-operator/api/v1alpha1",
|
||||
"//operators/constellation-node-operator/controllers",
|
||||
"//operators/constellation-node-operator/internal/cloud/aws/client",
|
||||
"//operators/constellation-node-operator/internal/cloud/azure/client",
|
||||
"//operators/constellation-node-operator/internal/cloud/fake/client",
|
||||
"//operators/constellation-node-operator/internal/cloud/gcp/client",
|
||||
|
@ -217,6 +217,7 @@ func (r *NodeVersionReconciler) Reconcile(ctx context.Context, req ctrl.Request)
|
||||
|
||||
newNodeConfig := newNodeConfig{desiredNodeVersion, groups.Outdated, pendingNodeList.Items, scalingGroupByID, newNodesBudget}
|
||||
if err := r.createNewNodes(ctx, newNodeConfig); err != nil {
|
||||
logr.Error(err, "Creating new nodes")
|
||||
return ctrl.Result{Requeue: shouldRequeue}, nil
|
||||
}
|
||||
// cleanup obsolete nodes
|
||||
|
@ -63,6 +63,7 @@ func NewPendingNodeReconciler(nodeStateGetter nodeStateGetter, client client.Cli
|
||||
// If the node is trying to join the cluster and fails to join within the deadline referenced in the PendingNode spec, the node is deleted.
|
||||
func (r *PendingNodeReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
|
||||
logr := log.FromContext(ctx)
|
||||
logr.Info("Reconciling PendingNode", "pendingNode", req.NamespacedName)
|
||||
|
||||
var pendingNode updatev1alpha1.PendingNode
|
||||
if err := r.Get(ctx, req.NamespacedName, &pendingNode); err != nil {
|
||||
|
@ -13,6 +13,9 @@ require (
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.6.0
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.2.2
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute/v4 v4.2.1
|
||||
github.com/aws/aws-sdk-go-v2/config v1.18.21
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.2
|
||||
github.com/aws/aws-sdk-go-v2/service/ec2 v1.92.1
|
||||
github.com/edgelesssys/constellation/v2 v2.6.0
|
||||
github.com/edgelesssys/constellation/v2/3rdparty/node-maintenance-operator v0.0.0
|
||||
github.com/edgelesssys/constellation/v2/operators/constellation-node-operator/v2/api v0.0.0
|
||||
@ -35,14 +38,26 @@ require (
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/aws/aws-sdk-go-v2 v1.18.0 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.13.20 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.33 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.27 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.3.33 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.26 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.12.8 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.14.8 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.18.9 // indirect
|
||||
github.com/aws/smithy-go v1.13.5 // indirect
|
||||
github.com/google/s2a-go v0.1.2 // indirect
|
||||
github.com/rogpeppe/go-internal v1.9.0 // indirect
|
||||
github.com/jmespath/go-jmespath v0.4.0 // indirect
|
||||
github.com/rogpeppe/go-internal v1.10.0 // indirect
|
||||
)
|
||||
|
||||
require (
|
||||
cloud.google.com/go/compute/metadata v0.2.3 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/internal v1.3.0 // indirect
|
||||
github.com/AzureAD/microsoft-authentication-library-for-go v0.9.0 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/autoscaling v1.28.6
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.2.0 // indirect
|
||||
github.com/coreos/go-semver v0.3.1 // indirect
|
||||
|
@ -59,6 +59,41 @@ github.com/AzureAD/microsoft-authentication-library-for-go v0.9.0/go.mod h1:kgDm
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
|
||||
github.com/aws/aws-sdk-go-v2 v1.17.7/go.mod h1:uzbQtefpm44goOPmdKyAlXSNcwlRgF3ePWVW6EtJvvw=
|
||||
github.com/aws/aws-sdk-go-v2 v1.17.8/go.mod h1:uzbQtefpm44goOPmdKyAlXSNcwlRgF3ePWVW6EtJvvw=
|
||||
github.com/aws/aws-sdk-go-v2 v1.18.0 h1:882kkTpSFhdgYRKVZ/VCgf7sd0ru57p2JCxz4/oN5RY=
|
||||
github.com/aws/aws-sdk-go-v2 v1.18.0/go.mod h1:uzbQtefpm44goOPmdKyAlXSNcwlRgF3ePWVW6EtJvvw=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.18.21 h1:ENTXWKwE8b9YXgQCsruGLhvA9bhg+RqAsL9XEMEsa2c=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.18.21/go.mod h1:+jPQiVPz1diRnjj6VGqWcLK6EzNmQ42l7J3OqGTLsSY=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.13.20 h1:oZCEFcrMppP/CNiS8myzv9JgOzq2s0d3v3MXYil/mxQ=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.13.20/go.mod h1:xtZnXErtbZ8YGXC3+8WfajpMBn5Ga/3ojZdxHq6iI8o=
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.2 h1:jOzQAesnBFDmz93feqKnsTHsXrlwWORNZMFHMV+WLFU=
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.2/go.mod h1:cDh1p6XkSGSwSRIArWRc6+UqAQ7x4alQ0QfpVR6f+co=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.31/go.mod h1:QT0BqUvX1Bh2ABdTGnjqEjvjzrCfIniM9Sc8zn9Yndo=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.32/go.mod h1:RudqOgadTWdcS3t/erPQo24pcVEoYyqj/kKW5Vya21I=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.33 h1:kG5eQilShqmJbv11XL1VpyDbaEJzWxd4zRiCG30GSn4=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.33/go.mod h1:7i0PF1ME/2eUPFcjkVIwq+DOygHEoK92t5cDqNgYbIw=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.25/go.mod h1:zBHOPwhBc3FlQjQJE/D3IfPWiWaQmT06Vq9aNukDo0k=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.26/go.mod h1:vq86l7956VgFr0/FWQ2BWnK07QC3WYsepKzy33qqY5U=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.27 h1:vFQlirhuM8lLlpI7imKOMsjdQLuN9CPi+k44F/OFVsk=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.27/go.mod h1:UrHnn3QV/d0pBZ6QBAEQcqFLf8FAzLmoUfPVIueOvoM=
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.3.33 h1:HbH1VjUgrCdLJ+4lnnuLI4iVNRvBbBELGaJ5f69ClA8=
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.3.33/go.mod h1:zG2FcwjQarWaqXSCGpgcr3RSjZ6dHGguZSppUL0XR7Q=
|
||||
github.com/aws/aws-sdk-go-v2/service/autoscaling v1.28.6 h1:OpzahvFZn/B+TNWLZf0ARovZoQB0Q2MvM+y13gdL+WY=
|
||||
github.com/aws/aws-sdk-go-v2/service/autoscaling v1.28.6/go.mod h1:cQ05ETcKMluA1/g1/jMQTD/qv9E1WeYCyHmqErEoHBk=
|
||||
github.com/aws/aws-sdk-go-v2/service/ec2 v1.92.1 h1:xn5CI639mnWvdiweqoRx/H221Ia9Asx9XxfIRhe0MPo=
|
||||
github.com/aws/aws-sdk-go-v2/service/ec2 v1.92.1/go.mod h1:ZZLfkd1Y7fjXujjMg1CFqNmaTl314eCbShlHQO7VTWo=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.25/go.mod h1:/95IA+0lMnzW6XzqYJRpjjsAbKEORVeO0anQqjd2CNU=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.26 h1:uUt4XctZLhl9wBE1L8lobU3bVN8SNUP7T+olb0bWBO4=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.26/go.mod h1:Bd4C/4PkVGubtNe5iMXu5BNnaBi/9t/UsFspPt4ram8=
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.12.8 h1:5cb3D6xb006bPTqEfCNaEA6PPEfBXxxy4NNeX/44kGk=
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.12.8/go.mod h1:GNIveDnP+aE3jujyUSH5aZ/rktsTM5EvtKnCqBZawdw=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.14.8 h1:NZaj0ngZMzsubWZbrEFSB4rgSQRbFq38Sd6KBxHuOIU=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.14.8/go.mod h1:44qFP1g7pfd+U+sQHLPalAPKnyfTZjJsYR4xIwsJy5o=
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.18.9 h1:Qf1aWwnsNkyAoqDqmdM3nHwN78XQjec27LjM6b9vyfI=
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.18.9/go.mod h1:yyW88BEPXA2fGFyI2KCcZC3dNpiT0CZAHaF+i656/tQ=
|
||||
github.com/aws/smithy-go v1.13.5 h1:hgz0X/DX0dGqTYpGALqXJoRKRj5oQ7150i5FdTePzO8=
|
||||
github.com/aws/smithy-go v1.13.5/go.mod h1:Tg+OJXh4MB2R/uN61Ko2f6hTZwB/ZYGOtib8J3gBHzA=
|
||||
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
|
||||
github.com/benbjohnson/clock v1.3.0 h1:ip6w0uFQkncKQ979AypyG0ER7mqUSBdKLOgAle/AT8A=
|
||||
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||
@ -173,6 +208,7 @@ github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
|
||||
github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
||||
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
@ -213,6 +249,10 @@ github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:
|
||||
github.com/imdario/mergo v0.3.13 h1:lFzP57bqS/wsqKssCGmtLAb8A0wKjLGrve2q3PPVcBk=
|
||||
github.com/imdario/mergo v0.3.13/go.mod h1:4lJ1jqUDcsbIECGy0RUJAXNIhg+6ocWgb1ALK2O4oXg=
|
||||
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
|
||||
github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg=
|
||||
github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
|
||||
github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8=
|
||||
github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
|
||||
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
|
||||
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
|
||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||
@ -275,8 +315,8 @@ github.com/prometheus/procfs v0.9.0 h1:wzCHvIvM5SxWqYvwgVL7yJY8Lz3PKn49KQtpgMYJf
|
||||
github.com/prometheus/procfs v0.9.0/go.mod h1:+pB4zwohETzFnmlpe6yd2lSc+0/46IYZRB/chUwxUZY=
|
||||
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
|
||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
|
||||
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
|
||||
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
|
||||
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
|
||||
github.com/spf13/afero v1.9.5 h1:stMpOSZFs//0Lv29HduCmli3GUfpFoF3Y1Q/aXj/wVM=
|
||||
github.com/spf13/afero v1.9.5/go.mod h1:UBogFpq8E9Hx+xc5CNTTEpTnuHVmXDwZcZcE1eb/UhQ=
|
||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||
|
@ -0,0 +1,46 @@
|
||||
load("@io_bazel_rules_go//go:def.bzl", "go_library")
|
||||
load("//bazel/go:go_test.bzl", "go_test")
|
||||
|
||||
go_library(
|
||||
name = "client",
|
||||
srcs = [
|
||||
"api.go",
|
||||
"autoscaler.go",
|
||||
"client.go",
|
||||
"nodeimage.go",
|
||||
"pendingnode.go",
|
||||
"scalinggroup.go",
|
||||
],
|
||||
importpath = "github.com/edgelesssys/constellation/v2/operators/constellation-node-operator/v2/internal/cloud/aws/client",
|
||||
visibility = ["//operators/constellation-node-operator:__subpackages__"],
|
||||
deps = [
|
||||
"//operators/constellation-node-operator/api/v1alpha1",
|
||||
"@com_github_aws_aws_sdk_go_v2_config//:config",
|
||||
"@com_github_aws_aws_sdk_go_v2_feature_ec2_imds//:imds",
|
||||
"@com_github_aws_aws_sdk_go_v2_service_autoscaling//:autoscaling",
|
||||
"@com_github_aws_aws_sdk_go_v2_service_autoscaling//types",
|
||||
"@com_github_aws_aws_sdk_go_v2_service_ec2//:ec2",
|
||||
"@com_github_aws_aws_sdk_go_v2_service_ec2//types",
|
||||
"@io_k8s_sigs_controller_runtime//pkg/log",
|
||||
],
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "client_test",
|
||||
srcs = [
|
||||
"client_test.go",
|
||||
"nodeimage_test.go",
|
||||
"pendingnode_test.go",
|
||||
"scalinggroup_test.go",
|
||||
],
|
||||
embed = [":client"],
|
||||
deps = [
|
||||
"//operators/constellation-node-operator/api/v1alpha1",
|
||||
"@com_github_aws_aws_sdk_go_v2_service_autoscaling//:autoscaling",
|
||||
"@com_github_aws_aws_sdk_go_v2_service_autoscaling//types",
|
||||
"@com_github_aws_aws_sdk_go_v2_service_ec2//:ec2",
|
||||
"@com_github_aws_aws_sdk_go_v2_service_ec2//types",
|
||||
"@com_github_stretchr_testify//assert",
|
||||
"@com_github_stretchr_testify//require",
|
||||
],
|
||||
)
|
@ -0,0 +1,28 @@
|
||||
/*
|
||||
Copyright (c) Edgeless Systems GmbH
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package client
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/aws/aws-sdk-go-v2/service/autoscaling"
|
||||
"github.com/aws/aws-sdk-go-v2/service/ec2"
|
||||
)
|
||||
|
||||
type ec2API interface {
|
||||
DescribeInstances(ctx context.Context, params *ec2.DescribeInstancesInput, optFns ...func(*ec2.Options)) (*ec2.DescribeInstancesOutput, error)
|
||||
DescribeInstanceStatus(ctx context.Context, params *ec2.DescribeInstanceStatusInput, optFns ...func(*ec2.Options)) (*ec2.DescribeInstanceStatusOutput, error)
|
||||
CreateLaunchTemplateVersion(ctx context.Context, params *ec2.CreateLaunchTemplateVersionInput, optFns ...func(*ec2.Options)) (*ec2.CreateLaunchTemplateVersionOutput, error)
|
||||
ModifyLaunchTemplate(ctx context.Context, params *ec2.ModifyLaunchTemplateInput, optFns ...func(*ec2.Options)) (*ec2.ModifyLaunchTemplateOutput, error)
|
||||
DescribeLaunchTemplateVersions(ctx context.Context, params *ec2.DescribeLaunchTemplateVersionsInput, optFns ...func(*ec2.Options)) (*ec2.DescribeLaunchTemplateVersionsOutput, error)
|
||||
}
|
||||
|
||||
type scalingAPI interface {
|
||||
DescribeAutoScalingGroups(ctx context.Context, params *autoscaling.DescribeAutoScalingGroupsInput, optFns ...func(*autoscaling.Options)) (*autoscaling.DescribeAutoScalingGroupsOutput, error)
|
||||
SetDesiredCapacity(ctx context.Context, params *autoscaling.SetDesiredCapacityInput, optFns ...func(*autoscaling.Options)) (*autoscaling.SetDesiredCapacityOutput, error)
|
||||
TerminateInstanceInAutoScalingGroup(ctx context.Context, params *autoscaling.TerminateInstanceInAutoScalingGroupInput, optFns ...func(*autoscaling.Options)) (*autoscaling.TerminateInstanceInAutoScalingGroupOutput, error)
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
/*
|
||||
Copyright (c) Edgeless Systems GmbH
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package client
|
||||
|
||||
// AutoscalingCloudProvider returns the cloud-provider name as used by k8s cluster-autoscaler.
|
||||
func (c *Client) AutoscalingCloudProvider() string {
|
||||
return "aws"
|
||||
}
|
@ -0,0 +1,65 @@
|
||||
/*
|
||||
Copyright (c) Edgeless Systems GmbH
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package client
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/aws/aws-sdk-go-v2/config"
|
||||
"github.com/aws/aws-sdk-go-v2/feature/ec2/imds"
|
||||
"github.com/aws/aws-sdk-go-v2/service/autoscaling"
|
||||
"github.com/aws/aws-sdk-go-v2/service/ec2"
|
||||
)
|
||||
|
||||
// Client is a client for the AWS Cloud.
|
||||
type Client struct {
|
||||
ec2Client ec2API
|
||||
scalingClient scalingAPI
|
||||
}
|
||||
|
||||
// New creates a client with initialized clients.
|
||||
func New(ctx context.Context) (*Client, error) {
|
||||
cfg, err := config.LoadDefaultConfig(ctx)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to load aws config: %w", err)
|
||||
}
|
||||
// get region from ec2metadata
|
||||
imdsClient := imds.NewFromConfig(cfg)
|
||||
regionOut, err := imdsClient.GetRegion(ctx, &imds.GetRegionInput{})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get region from ec2metadata: %w", err)
|
||||
}
|
||||
return NewWithRegion(ctx, regionOut.Region)
|
||||
}
|
||||
|
||||
// NewWithRegion creates a client with initialized clients and a given region.
|
||||
func NewWithRegion(ctx context.Context, region string) (*Client, error) {
|
||||
cfg, err := config.LoadDefaultConfig(ctx)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to load aws config: %w", err)
|
||||
}
|
||||
|
||||
cfg.Region = region
|
||||
|
||||
ec2Client := ec2.NewFromConfig(cfg)
|
||||
scalingClient := autoscaling.NewFromConfig(cfg)
|
||||
return &Client{
|
||||
ec2Client: ec2Client,
|
||||
scalingClient: scalingClient,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func getInstanceNameFromProviderID(providerID string) (string, error) {
|
||||
// aws:///us-east-2a/i-06888991e7138ed4e
|
||||
providerIDParts := strings.Split(providerID, "/")
|
||||
if len(providerIDParts) != 5 {
|
||||
return "", fmt.Errorf("invalid providerID: %s", providerID)
|
||||
}
|
||||
return providerIDParts[4], nil
|
||||
}
|
@ -0,0 +1,50 @@
|
||||
/*
|
||||
Copyright (c) Edgeless Systems GmbH
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package client
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestGetInstanceNameFromProviderID(t *testing.T) {
|
||||
testCases := map[string]struct {
|
||||
providerID string
|
||||
want string
|
||||
wantErr bool
|
||||
}{
|
||||
"valid": {
|
||||
providerID: "aws:///us-east-2a/i-06888991e7138ed4e",
|
||||
want: "i-06888991e7138ed4e",
|
||||
},
|
||||
"too many parts": {
|
||||
providerID: "aws:///us-east-2a/i-06888991e7138ed4e/invalid",
|
||||
wantErr: true,
|
||||
},
|
||||
"too few parts": {
|
||||
providerID: "aws:///us-east-2a",
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for name, tc := range testCases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
require := require.New(t)
|
||||
|
||||
got, err := getInstanceNameFromProviderID(tc.providerID)
|
||||
if tc.wantErr {
|
||||
require.Error(err)
|
||||
return
|
||||
}
|
||||
require.NoError(err)
|
||||
assert.Equal(tc.want, got)
|
||||
})
|
||||
}
|
||||
}
|
@ -0,0 +1,219 @@
|
||||
/*
|
||||
Copyright (c) Edgeless Systems GmbH
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package client
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/aws/aws-sdk-go-v2/service/autoscaling"
|
||||
"github.com/aws/aws-sdk-go-v2/service/autoscaling/types"
|
||||
"github.com/aws/aws-sdk-go-v2/service/ec2"
|
||||
)
|
||||
|
||||
// GetNodeImage returns the image name of the node.
|
||||
func (c *Client) GetNodeImage(ctx context.Context, providerID string) (string, error) {
|
||||
instanceName, err := getInstanceNameFromProviderID(providerID)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to get instance name from providerID: %w", err)
|
||||
}
|
||||
|
||||
params := &ec2.DescribeInstancesInput{
|
||||
InstanceIds: []string{
|
||||
instanceName,
|
||||
},
|
||||
}
|
||||
|
||||
resp, err := c.ec2Client.DescribeInstances(ctx, params)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to describe instances: %w", err)
|
||||
}
|
||||
|
||||
if len(resp.Reservations) == 0 {
|
||||
return "", fmt.Errorf("no reservations for instance %q", instanceName)
|
||||
}
|
||||
|
||||
if len(resp.Reservations[0].Instances) == 0 {
|
||||
return "", fmt.Errorf("no instances for instance %q", instanceName)
|
||||
}
|
||||
|
||||
if resp.Reservations[0].Instances[0].ImageId == nil {
|
||||
return "", fmt.Errorf("no image for instance %q", instanceName)
|
||||
}
|
||||
|
||||
return *resp.Reservations[0].Instances[0].ImageId, nil
|
||||
}
|
||||
|
||||
// GetScalingGroupID returns the scaling group ID of the node.
|
||||
func (c *Client) GetScalingGroupID(ctx context.Context, providerID string) (string, error) {
|
||||
instanceName, err := getInstanceNameFromProviderID(providerID)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to get instance name from providerID: %w", err)
|
||||
}
|
||||
params := &ec2.DescribeInstancesInput{
|
||||
InstanceIds: []string{
|
||||
instanceName,
|
||||
},
|
||||
}
|
||||
|
||||
resp, err := c.ec2Client.DescribeInstances(ctx, params)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to describe instances: %w", err)
|
||||
}
|
||||
|
||||
if len(resp.Reservations) == 0 {
|
||||
return "", fmt.Errorf("no reservations for instance %q", instanceName)
|
||||
}
|
||||
|
||||
if len(resp.Reservations[0].Instances) == 0 {
|
||||
return "", fmt.Errorf("no instances for instance %q", instanceName)
|
||||
}
|
||||
|
||||
if resp.Reservations[0].Instances[0].Tags == nil {
|
||||
return "", fmt.Errorf("no tags for instance %q", instanceName)
|
||||
}
|
||||
|
||||
for _, tag := range resp.Reservations[0].Instances[0].Tags {
|
||||
if tag.Key == nil || tag.Value == nil {
|
||||
continue
|
||||
}
|
||||
if *tag.Key == "aws:autoscaling:groupName" {
|
||||
return *tag.Value, nil
|
||||
}
|
||||
}
|
||||
|
||||
return "", fmt.Errorf("node %q does not have valid tags", providerID)
|
||||
}
|
||||
|
||||
// CreateNode creates a node in the specified scaling group.
|
||||
func (c *Client) CreateNode(ctx context.Context, scalingGroupID string) (nodeName, providerID string, err error) {
|
||||
containsInstance := func(instances []types.Instance, target types.Instance) bool {
|
||||
for _, i := range instances {
|
||||
if i.InstanceId == nil || target.InstanceId == nil {
|
||||
continue
|
||||
}
|
||||
if *i.InstanceId == *target.InstanceId {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// get current capacity
|
||||
groups, err := c.scalingClient.DescribeAutoScalingGroups(
|
||||
ctx,
|
||||
&autoscaling.DescribeAutoScalingGroupsInput{
|
||||
AutoScalingGroupNames: []string{scalingGroupID},
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return "", "", fmt.Errorf("failed to describe autoscaling group: %w", err)
|
||||
}
|
||||
|
||||
if len(groups.AutoScalingGroups) != 1 {
|
||||
return "", "", fmt.Errorf("expected exactly one autoscaling group, got %d", len(groups.AutoScalingGroups))
|
||||
}
|
||||
|
||||
if groups.AutoScalingGroups[0].DesiredCapacity == nil {
|
||||
return "", "", fmt.Errorf("desired capacity is nil")
|
||||
}
|
||||
currentCapacity := int(*groups.AutoScalingGroups[0].DesiredCapacity)
|
||||
|
||||
// check for int32 overflow
|
||||
if currentCapacity >= int(^uint32(0)>>1) {
|
||||
return "", "", fmt.Errorf("current capacity is at maximum")
|
||||
}
|
||||
|
||||
// get current list of instances
|
||||
previousInstances := groups.AutoScalingGroups[0].Instances
|
||||
|
||||
// create new instance by increasing capacity by 1
|
||||
_, err = c.scalingClient.SetDesiredCapacity(
|
||||
ctx,
|
||||
&autoscaling.SetDesiredCapacityInput{
|
||||
AutoScalingGroupName: &scalingGroupID,
|
||||
DesiredCapacity: toPtr(int32(currentCapacity + 1)),
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return "", "", fmt.Errorf("failed to set desired capacity: %w", err)
|
||||
}
|
||||
|
||||
// poll until new instance is created with 30 second timeout
|
||||
newInstance := types.Instance{}
|
||||
for i := 0; i < 30; i++ {
|
||||
groups, err := c.scalingClient.DescribeAutoScalingGroups(
|
||||
ctx,
|
||||
&autoscaling.DescribeAutoScalingGroupsInput{
|
||||
AutoScalingGroupNames: []string{scalingGroupID},
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return "", "", fmt.Errorf("failed to describe autoscaling group: %w", err)
|
||||
}
|
||||
|
||||
if len(groups.AutoScalingGroups) != 1 {
|
||||
return "", "", fmt.Errorf("expected exactly one autoscaling group, got %d", len(groups.AutoScalingGroups))
|
||||
}
|
||||
|
||||
for _, instance := range groups.AutoScalingGroups[0].Instances {
|
||||
if !containsInstance(previousInstances, instance) {
|
||||
newInstance = instance
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// break if new instance is found
|
||||
if newInstance.InstanceId != nil {
|
||||
break
|
||||
}
|
||||
|
||||
// wait 1 second
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return "", "", fmt.Errorf("context cancelled")
|
||||
case <-time.After(1 * time.Second):
|
||||
}
|
||||
}
|
||||
|
||||
if newInstance.InstanceId == nil {
|
||||
return "", "", fmt.Errorf("timed out waiting for new instance")
|
||||
}
|
||||
|
||||
if newInstance.AvailabilityZone == nil {
|
||||
return "", "", fmt.Errorf("new instance %s does not have availability zone", *newInstance.InstanceId)
|
||||
}
|
||||
|
||||
// return new instance
|
||||
return *newInstance.InstanceId, fmt.Sprintf("aws:///%s/%s", *newInstance.AvailabilityZone, *newInstance.InstanceId), nil
|
||||
}
|
||||
|
||||
// DeleteNode deletes a node from the specified scaling group.
|
||||
func (c *Client) DeleteNode(ctx context.Context, providerID string) error {
|
||||
instanceID, err := getInstanceNameFromProviderID(providerID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get instance name from providerID: %w", err)
|
||||
}
|
||||
|
||||
_, err = c.scalingClient.TerminateInstanceInAutoScalingGroup(
|
||||
ctx,
|
||||
&autoscaling.TerminateInstanceInAutoScalingGroupInput{
|
||||
InstanceId: &instanceID,
|
||||
ShouldDecrementDesiredCapacity: toPtr(true),
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to terminate instance: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func toPtr[T any](v T) *T {
|
||||
return &v
|
||||
}
|
@ -0,0 +1,459 @@
|
||||
/*
|
||||
Copyright (c) Edgeless Systems GmbH
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package client
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/aws/aws-sdk-go-v2/service/autoscaling"
|
||||
autoscalingtypes "github.com/aws/aws-sdk-go-v2/service/autoscaling/types"
|
||||
"github.com/aws/aws-sdk-go-v2/service/ec2"
|
||||
ec2types "github.com/aws/aws-sdk-go-v2/service/ec2/types"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestGetNodeImage(t *testing.T) {
|
||||
ami := "ami-00000000000000000"
|
||||
testCases := map[string]struct {
|
||||
providerID string
|
||||
describeInstancesErr error
|
||||
describeInstancesOut *ec2.DescribeInstancesOutput
|
||||
wantImage string
|
||||
wantErr bool
|
||||
}{
|
||||
"getting node image works": {
|
||||
providerID: "aws:///us-east-2a/i-00000000000000000",
|
||||
describeInstancesOut: &ec2.DescribeInstancesOutput{
|
||||
Reservations: []ec2types.Reservation{
|
||||
{
|
||||
Instances: []ec2types.Instance{
|
||||
{
|
||||
ImageId: &ami,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
wantImage: ami,
|
||||
},
|
||||
"no reservations": {
|
||||
providerID: "aws:///us-east-2a/i-00000000000000000",
|
||||
describeInstancesOut: &ec2.DescribeInstancesOutput{
|
||||
Reservations: []ec2types.Reservation{},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
"no instances": {
|
||||
providerID: "aws:///us-east-2a/i-00000000000000000",
|
||||
describeInstancesOut: &ec2.DescribeInstancesOutput{
|
||||
Reservations: []ec2types.Reservation{
|
||||
{
|
||||
Instances: []ec2types.Instance{},
|
||||
},
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
"no image": {
|
||||
providerID: "aws:///us-east-2a/i-00000000000000000",
|
||||
describeInstancesOut: &ec2.DescribeInstancesOutput{
|
||||
Reservations: []ec2types.Reservation{
|
||||
{
|
||||
Instances: []ec2types.Instance{
|
||||
{},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
"error describing instances": {
|
||||
providerID: "aws:///us-east-2a/i-00000000000000000",
|
||||
describeInstancesErr: assert.AnError,
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
for name, tc := range testCases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
require := require.New(t)
|
||||
|
||||
client := Client{
|
||||
ec2Client: &stubEC2API{
|
||||
describeInstancesOut: tc.describeInstancesOut,
|
||||
describeInstancesErr: tc.describeInstancesErr,
|
||||
},
|
||||
}
|
||||
gotImage, err := client.GetNodeImage(context.Background(), tc.providerID)
|
||||
if tc.wantErr {
|
||||
assert.Error(err)
|
||||
return
|
||||
}
|
||||
require.NoError(err)
|
||||
assert.Equal(tc.wantImage, gotImage)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetScalingGroupID(t *testing.T) {
|
||||
asgName := "my-asg"
|
||||
testCases := map[string]struct {
|
||||
providerID string
|
||||
describeInstancesErr error
|
||||
describeInstancesOut *ec2.DescribeInstancesOutput
|
||||
wantASGID string
|
||||
wantErr bool
|
||||
}{
|
||||
"getting node's tag works": {
|
||||
providerID: "aws:///us-east-2a/i-00000000000000000",
|
||||
describeInstancesOut: &ec2.DescribeInstancesOutput{
|
||||
Reservations: []ec2types.Reservation{
|
||||
{
|
||||
Instances: []ec2types.Instance{
|
||||
{
|
||||
Tags: []ec2types.Tag{
|
||||
{
|
||||
Key: toPtr("aws:autoscaling:groupName"),
|
||||
Value: &asgName,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
wantASGID: asgName,
|
||||
},
|
||||
"no valid tags": {
|
||||
providerID: "aws:///us-east-2a/i-00000000000000000",
|
||||
describeInstancesOut: &ec2.DescribeInstancesOutput{
|
||||
Reservations: []ec2types.Reservation{
|
||||
{
|
||||
Instances: []ec2types.Instance{
|
||||
{
|
||||
Tags: []ec2types.Tag{
|
||||
{
|
||||
Key: toPtr("foo"),
|
||||
Value: toPtr("bar"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
"no reservations": {
|
||||
providerID: "aws:///us-east-2a/i-00000000000000000",
|
||||
describeInstancesOut: &ec2.DescribeInstancesOutput{
|
||||
Reservations: []ec2types.Reservation{},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
"no instances": {
|
||||
providerID: "aws:///us-east-2a/i-00000000000000000",
|
||||
describeInstancesOut: &ec2.DescribeInstancesOutput{
|
||||
Reservations: []ec2types.Reservation{
|
||||
{
|
||||
Instances: []ec2types.Instance{},
|
||||
},
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
"no image": {
|
||||
providerID: "aws:///us-east-2a/i-00000000000000000",
|
||||
describeInstancesOut: &ec2.DescribeInstancesOutput{
|
||||
Reservations: []ec2types.Reservation{
|
||||
{
|
||||
Instances: []ec2types.Instance{
|
||||
{},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
"error describing instances": {
|
||||
providerID: "aws:///us-east-2a/i-00000000000000000",
|
||||
describeInstancesErr: assert.AnError,
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
for name, tc := range testCases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
require := require.New(t)
|
||||
|
||||
client := Client{
|
||||
ec2Client: &stubEC2API{
|
||||
describeInstancesOut: tc.describeInstancesOut,
|
||||
describeInstancesErr: tc.describeInstancesErr,
|
||||
},
|
||||
}
|
||||
gotScalingID, err := client.GetScalingGroupID(context.Background(), tc.providerID)
|
||||
if tc.wantErr {
|
||||
assert.Error(err)
|
||||
return
|
||||
}
|
||||
require.NoError(err)
|
||||
assert.Equal(tc.wantASGID, gotScalingID)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateNode(t *testing.T) {
|
||||
testCases := map[string]struct {
|
||||
providerID string
|
||||
describeAutoscalingOutFirst *autoscaling.DescribeAutoScalingGroupsOutput
|
||||
describeAutoscalingFirstErr error
|
||||
describeAutoscalingOutSecond *autoscaling.DescribeAutoScalingGroupsOutput
|
||||
describeAutoscalingSecondErr error
|
||||
setDesiredCapacityErr error
|
||||
wantNodeName string
|
||||
wantProviderID string
|
||||
wantErr bool
|
||||
}{
|
||||
"creating a new node works": {
|
||||
providerID: "aws:///us-east-2a/i-00000000000000000",
|
||||
describeAutoscalingOutFirst: &autoscaling.DescribeAutoScalingGroupsOutput{
|
||||
AutoScalingGroups: []autoscalingtypes.AutoScalingGroup{
|
||||
{
|
||||
AutoScalingGroupName: toPtr("my-asg"),
|
||||
Instances: []autoscalingtypes.Instance{
|
||||
{
|
||||
InstanceId: toPtr("i-00000000000000000"),
|
||||
},
|
||||
},
|
||||
DesiredCapacity: toPtr(int32(1)),
|
||||
},
|
||||
},
|
||||
},
|
||||
describeAutoscalingOutSecond: &autoscaling.DescribeAutoScalingGroupsOutput{
|
||||
AutoScalingGroups: []autoscalingtypes.AutoScalingGroup{
|
||||
{
|
||||
AutoScalingGroupName: toPtr("my-asg"),
|
||||
Instances: []autoscalingtypes.Instance{
|
||||
{
|
||||
InstanceId: toPtr("i-00000000000000000"),
|
||||
},
|
||||
{
|
||||
InstanceId: toPtr("i-00000000000000001"),
|
||||
AvailabilityZone: toPtr("us-east-2a"),
|
||||
},
|
||||
},
|
||||
DesiredCapacity: toPtr(int32(2)),
|
||||
},
|
||||
},
|
||||
},
|
||||
wantNodeName: "i-00000000000000001",
|
||||
wantProviderID: "aws:///us-east-2a/i-00000000000000001",
|
||||
},
|
||||
"creating a new node fails when describing the auto scaling group the first time": {
|
||||
providerID: "aws:///us-east-2a/i-00000000000000000",
|
||||
describeAutoscalingFirstErr: assert.AnError,
|
||||
wantErr: true,
|
||||
},
|
||||
"creating a new node fails when describing the auto scaling group the second time": {
|
||||
providerID: "aws:///us-east-2a/i-00000000000000000",
|
||||
describeAutoscalingOutFirst: &autoscaling.DescribeAutoScalingGroupsOutput{
|
||||
AutoScalingGroups: []autoscalingtypes.AutoScalingGroup{
|
||||
{
|
||||
AutoScalingGroupName: toPtr("my-asg"),
|
||||
Instances: []autoscalingtypes.Instance{
|
||||
{
|
||||
InstanceId: toPtr("i-00000000000000000"),
|
||||
},
|
||||
},
|
||||
DesiredCapacity: toPtr(int32(1)),
|
||||
},
|
||||
},
|
||||
},
|
||||
describeAutoscalingSecondErr: assert.AnError,
|
||||
wantErr: true,
|
||||
},
|
||||
"creating a new node fails when the auto scaling group is not found": {
|
||||
providerID: "aws:///us-east-2a/i-00000000000000000",
|
||||
describeAutoscalingOutFirst: &autoscaling.DescribeAutoScalingGroupsOutput{
|
||||
AutoScalingGroups: []autoscalingtypes.AutoScalingGroup{},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
"creating a new node fails when set desired capacity fails": {
|
||||
providerID: "aws:///us-east-2a/i-00000000000000000",
|
||||
describeAutoscalingOutFirst: &autoscaling.DescribeAutoScalingGroupsOutput{
|
||||
AutoScalingGroups: []autoscalingtypes.AutoScalingGroup{
|
||||
{
|
||||
AutoScalingGroupName: toPtr("my-asg"),
|
||||
Instances: []autoscalingtypes.Instance{
|
||||
{
|
||||
InstanceId: toPtr("i-00000000000000000"),
|
||||
},
|
||||
},
|
||||
DesiredCapacity: toPtr(int32(1)),
|
||||
},
|
||||
},
|
||||
},
|
||||
setDesiredCapacityErr: assert.AnError,
|
||||
wantErr: true,
|
||||
},
|
||||
"creating a new node fails when the found vm does not contain an availability zone": {
|
||||
providerID: "aws:///us-east-2a/i-00000000000000000",
|
||||
describeAutoscalingOutFirst: &autoscaling.DescribeAutoScalingGroupsOutput{
|
||||
AutoScalingGroups: []autoscalingtypes.AutoScalingGroup{
|
||||
{
|
||||
AutoScalingGroupName: toPtr("my-asg"),
|
||||
Instances: []autoscalingtypes.Instance{
|
||||
{
|
||||
InstanceId: toPtr("i-00000000000000000"),
|
||||
},
|
||||
},
|
||||
DesiredCapacity: toPtr(int32(1)),
|
||||
},
|
||||
},
|
||||
},
|
||||
describeAutoscalingOutSecond: &autoscaling.DescribeAutoScalingGroupsOutput{
|
||||
AutoScalingGroups: []autoscalingtypes.AutoScalingGroup{
|
||||
{
|
||||
AutoScalingGroupName: toPtr("my-asg"),
|
||||
Instances: []autoscalingtypes.Instance{
|
||||
{
|
||||
InstanceId: toPtr("i-00000000000000000"),
|
||||
},
|
||||
{
|
||||
InstanceId: toPtr("i-00000000000000001"),
|
||||
},
|
||||
},
|
||||
DesiredCapacity: toPtr(int32(2)),
|
||||
},
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
for name, tc := range testCases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
require := require.New(t)
|
||||
|
||||
client := Client{
|
||||
scalingClient: &stubAutoscalingAPI{
|
||||
describeAutoScalingGroupsOut: []*autoscaling.DescribeAutoScalingGroupsOutput{
|
||||
tc.describeAutoscalingOutFirst,
|
||||
tc.describeAutoscalingOutSecond,
|
||||
},
|
||||
describeAutoScalingGroupsErr: []error{
|
||||
tc.describeAutoscalingFirstErr,
|
||||
tc.describeAutoscalingSecondErr,
|
||||
},
|
||||
setDesiredCapacityErr: tc.setDesiredCapacityErr,
|
||||
},
|
||||
}
|
||||
nodeName, providerID, err := client.CreateNode(context.Background(), tc.providerID)
|
||||
if tc.wantErr {
|
||||
assert.Error(err)
|
||||
return
|
||||
}
|
||||
require.NoError(err)
|
||||
assert.Equal(tc.wantNodeName, nodeName)
|
||||
assert.Equal(tc.wantProviderID, providerID)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeleteNode(t *testing.T) {
|
||||
testCases := map[string]struct {
|
||||
providerID string
|
||||
terminateInstanceErr error
|
||||
wantErr bool
|
||||
}{
|
||||
"deleting node works": {
|
||||
providerID: "aws:///us-east-2a/i-00000000000000000",
|
||||
},
|
||||
"deleting node fails when terminating the instance fails": {
|
||||
providerID: "aws:///us-east-2a/i-00000000000000000",
|
||||
terminateInstanceErr: assert.AnError,
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
for name, tc := range testCases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
require := require.New(t)
|
||||
|
||||
client := Client{
|
||||
scalingClient: &stubAutoscalingAPI{
|
||||
terminateInstanceErr: tc.terminateInstanceErr,
|
||||
},
|
||||
}
|
||||
err := client.DeleteNode(context.Background(), tc.providerID)
|
||||
if tc.wantErr {
|
||||
assert.Error(err)
|
||||
return
|
||||
}
|
||||
require.NoError(err)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
type stubEC2API struct {
|
||||
describeInstancesOut *ec2.DescribeInstancesOutput
|
||||
describeInstancesErr error
|
||||
describeInstanceStatusOut *ec2.DescribeInstanceStatusOutput
|
||||
describeInstanceStatusErr error
|
||||
describeLaunchTemplateVersionsOut *ec2.DescribeLaunchTemplateVersionsOutput
|
||||
describeLaunchTemplateVersionsErr error
|
||||
createLaunchTemplateVersionOut *ec2.CreateLaunchTemplateVersionOutput
|
||||
createLaunchTemplateVersionErr error
|
||||
modifyLaunchTemplateErr error
|
||||
}
|
||||
|
||||
func (a *stubEC2API) DescribeInstances(_ context.Context, _ *ec2.DescribeInstancesInput, _ ...func(*ec2.Options)) (*ec2.DescribeInstancesOutput, error) {
|
||||
return a.describeInstancesOut, a.describeInstancesErr
|
||||
}
|
||||
|
||||
func (a *stubEC2API) DescribeInstanceStatus(_ context.Context, _ *ec2.DescribeInstanceStatusInput, _ ...func(*ec2.Options)) (*ec2.DescribeInstanceStatusOutput, error) {
|
||||
return a.describeInstanceStatusOut, a.describeInstanceStatusErr
|
||||
}
|
||||
|
||||
func (a *stubEC2API) CreateLaunchTemplateVersion(_ context.Context, _ *ec2.CreateLaunchTemplateVersionInput, _ ...func(*ec2.Options)) (*ec2.CreateLaunchTemplateVersionOutput, error) {
|
||||
return a.createLaunchTemplateVersionOut, a.createLaunchTemplateVersionErr
|
||||
}
|
||||
|
||||
func (a *stubEC2API) ModifyLaunchTemplate(_ context.Context, _ *ec2.ModifyLaunchTemplateInput, _ ...func(*ec2.Options)) (*ec2.ModifyLaunchTemplateOutput, error) {
|
||||
return nil, a.modifyLaunchTemplateErr
|
||||
}
|
||||
|
||||
func (a *stubEC2API) DescribeLaunchTemplateVersions(_ context.Context, _ *ec2.DescribeLaunchTemplateVersionsInput, _ ...func(*ec2.Options)) (*ec2.DescribeLaunchTemplateVersionsOutput, error) {
|
||||
return a.describeLaunchTemplateVersionsOut, a.describeLaunchTemplateVersionsErr
|
||||
}
|
||||
|
||||
type stubAutoscalingAPI struct {
|
||||
describeAutoScalingGroupsOut []*autoscaling.DescribeAutoScalingGroupsOutput
|
||||
describeAutoScalingGroupsErr []error
|
||||
describeCounter int
|
||||
setDesiredCapacityErr error
|
||||
terminateInstanceErr error
|
||||
}
|
||||
|
||||
func (a *stubAutoscalingAPI) DescribeAutoScalingGroups(_ context.Context, _ *autoscaling.DescribeAutoScalingGroupsInput, _ ...func(*autoscaling.Options)) (*autoscaling.DescribeAutoScalingGroupsOutput, error) {
|
||||
out := a.describeAutoScalingGroupsOut[a.describeCounter]
|
||||
err := a.describeAutoScalingGroupsErr[a.describeCounter]
|
||||
a.describeCounter++
|
||||
return out, err
|
||||
}
|
||||
|
||||
func (a *stubAutoscalingAPI) SetDesiredCapacity(_ context.Context, _ *autoscaling.SetDesiredCapacityInput, _ ...func(*autoscaling.Options)) (*autoscaling.SetDesiredCapacityOutput, error) {
|
||||
return nil, a.setDesiredCapacityErr
|
||||
}
|
||||
|
||||
func (a *stubAutoscalingAPI) TerminateInstanceInAutoScalingGroup(_ context.Context, _ *autoscaling.TerminateInstanceInAutoScalingGroupInput, _ ...func(*autoscaling.Options)) (*autoscaling.TerminateInstanceInAutoScalingGroupOutput, error) {
|
||||
return nil, a.terminateInstanceErr
|
||||
}
|
@ -0,0 +1,68 @@
|
||||
/*
|
||||
Copyright (c) Edgeless Systems GmbH
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package client
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/aws/aws-sdk-go-v2/service/ec2"
|
||||
ec2types "github.com/aws/aws-sdk-go-v2/service/ec2/types"
|
||||
updatev1alpha1 "github.com/edgelesssys/constellation/v2/operators/constellation-node-operator/v2/api/v1alpha1"
|
||||
"sigs.k8s.io/controller-runtime/pkg/log"
|
||||
)
|
||||
|
||||
// GetNodeState returns the state of the node.
|
||||
func (c *Client) GetNodeState(ctx context.Context, providerID string) (updatev1alpha1.CSPNodeState, error) {
|
||||
logr := log.FromContext(ctx)
|
||||
logr.Info("GetNodeState", "providerID", providerID)
|
||||
instanceName, err := getInstanceNameFromProviderID(providerID)
|
||||
if err != nil {
|
||||
return updatev1alpha1.NodeStateUnknown, fmt.Errorf("failed to get instance name from providerID: %w", err)
|
||||
}
|
||||
|
||||
statusOut, err := c.ec2Client.DescribeInstanceStatus(ctx, &ec2.DescribeInstanceStatusInput{
|
||||
InstanceIds: []string{instanceName},
|
||||
IncludeAllInstances: toPtr(true),
|
||||
})
|
||||
if err != nil {
|
||||
if strings.Contains(err.Error(), "InvalidInstanceID.NotFound") {
|
||||
return updatev1alpha1.NodeStateTerminated, nil
|
||||
}
|
||||
return updatev1alpha1.NodeStateUnknown, err
|
||||
}
|
||||
|
||||
if len(statusOut.InstanceStatuses) != 1 {
|
||||
return updatev1alpha1.NodeStateUnknown, fmt.Errorf("expected 1 instance status, got %d", len(statusOut.InstanceStatuses))
|
||||
}
|
||||
|
||||
if statusOut.InstanceStatuses[0].InstanceState == nil {
|
||||
return updatev1alpha1.NodeStateUnknown, errors.New("instance state is nil")
|
||||
}
|
||||
|
||||
// Translate AWS instance state to node state.
|
||||
switch statusOut.InstanceStatuses[0].InstanceState.Name {
|
||||
case ec2types.InstanceStateNameRunning:
|
||||
return updatev1alpha1.NodeStateReady, nil
|
||||
case ec2types.InstanceStateNameTerminated:
|
||||
return updatev1alpha1.NodeStateTerminated, nil
|
||||
case ec2types.InstanceStateNameShuttingDown:
|
||||
return updatev1alpha1.NodeStateTerminating, nil
|
||||
case ec2types.InstanceStateNameStopped:
|
||||
return updatev1alpha1.NodeStateStopped, nil
|
||||
// For "Stopping" we can only know the next state in the state machine
|
||||
// so we preemptively set it to "Stopped".
|
||||
case ec2types.InstanceStateNameStopping:
|
||||
return updatev1alpha1.NodeStateStopped, nil
|
||||
case ec2types.InstanceStateNamePending:
|
||||
return updatev1alpha1.NodeStateCreating, nil
|
||||
default:
|
||||
return updatev1alpha1.NodeStateUnknown, fmt.Errorf("unknown instance state %q", statusOut.InstanceStatuses[0].InstanceState.Name)
|
||||
}
|
||||
}
|
@ -0,0 +1,173 @@
|
||||
/*
|
||||
Copyright (c) Edgeless Systems GmbH
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package client
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"github.com/aws/aws-sdk-go-v2/service/ec2"
|
||||
ec2types "github.com/aws/aws-sdk-go-v2/service/ec2/types"
|
||||
updatev1alpha1 "github.com/edgelesssys/constellation/v2/operators/constellation-node-operator/v2/api/v1alpha1"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestGetNodeState(t *testing.T) {
|
||||
testCases := map[string]struct {
|
||||
providerID string
|
||||
describeInstanceStatusOut *ec2.DescribeInstanceStatusOutput
|
||||
describeInstanceStatusErr error
|
||||
wantState updatev1alpha1.CSPNodeState
|
||||
wantErr bool
|
||||
}{
|
||||
"getting node state works for running VM": {
|
||||
providerID: "aws:///us-east-2a/i-00000000000000000",
|
||||
describeInstanceStatusOut: &ec2.DescribeInstanceStatusOutput{
|
||||
InstanceStatuses: []ec2types.InstanceStatus{
|
||||
{
|
||||
InstanceState: &ec2types.InstanceState{
|
||||
Name: ec2types.InstanceStateNameRunning,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
wantState: updatev1alpha1.NodeStateReady,
|
||||
},
|
||||
"getting node state works for terminated VM": {
|
||||
providerID: "aws:///us-east-2a/i-00000000000000000",
|
||||
describeInstanceStatusOut: &ec2.DescribeInstanceStatusOutput{
|
||||
InstanceStatuses: []ec2types.InstanceStatus{
|
||||
{
|
||||
InstanceState: &ec2types.InstanceState{
|
||||
Name: ec2types.InstanceStateNameTerminated,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
wantState: updatev1alpha1.NodeStateTerminated,
|
||||
},
|
||||
"getting node state works for stopping VM": {
|
||||
providerID: "aws:///us-east-2a/i-00000000000000000",
|
||||
describeInstanceStatusOut: &ec2.DescribeInstanceStatusOutput{
|
||||
InstanceStatuses: []ec2types.InstanceStatus{
|
||||
{
|
||||
InstanceState: &ec2types.InstanceState{
|
||||
Name: ec2types.InstanceStateNameStopping,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
wantState: updatev1alpha1.NodeStateStopped,
|
||||
},
|
||||
"getting node state works for stopped VM": {
|
||||
providerID: "aws:///us-east-2a/i-00000000000000000",
|
||||
describeInstanceStatusOut: &ec2.DescribeInstanceStatusOutput{
|
||||
InstanceStatuses: []ec2types.InstanceStatus{
|
||||
{
|
||||
InstanceState: &ec2types.InstanceState{
|
||||
Name: ec2types.InstanceStateNameStopped,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
wantState: updatev1alpha1.NodeStateStopped,
|
||||
},
|
||||
"getting node state works for pending VM": {
|
||||
providerID: "aws:///us-east-2a/i-00000000000000000",
|
||||
describeInstanceStatusOut: &ec2.DescribeInstanceStatusOutput{
|
||||
InstanceStatuses: []ec2types.InstanceStatus{
|
||||
{
|
||||
InstanceState: &ec2types.InstanceState{
|
||||
Name: ec2types.InstanceStateNamePending,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
wantState: updatev1alpha1.NodeStateCreating,
|
||||
},
|
||||
"getting node state works for shutting-down VM": {
|
||||
providerID: "aws:///us-east-2a/i-00000000000000000",
|
||||
describeInstanceStatusOut: &ec2.DescribeInstanceStatusOutput{
|
||||
InstanceStatuses: []ec2types.InstanceStatus{
|
||||
{
|
||||
InstanceState: &ec2types.InstanceState{
|
||||
Name: ec2types.InstanceStateNameShuttingDown,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
wantState: updatev1alpha1.NodeStateTerminating,
|
||||
},
|
||||
"getting node state fails when the state is unknown": {
|
||||
providerID: "aws:///us-east-2a/i-00000000000000000",
|
||||
describeInstanceStatusOut: &ec2.DescribeInstanceStatusOutput{
|
||||
InstanceStatuses: []ec2types.InstanceStatus{
|
||||
{
|
||||
InstanceState: &ec2types.InstanceState{
|
||||
Name: "unknown",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
wantState: updatev1alpha1.NodeStateUnknown,
|
||||
wantErr: true,
|
||||
},
|
||||
"cannot find instance": {
|
||||
providerID: "aws:///us-east-2a/i-00000000000000000",
|
||||
describeInstanceStatusErr: errors.New("InvalidInstanceID.NotFound"),
|
||||
wantState: updatev1alpha1.NodeStateTerminated,
|
||||
},
|
||||
"unknown error when describing the instance error": {
|
||||
providerID: "aws:///us-east-2a/i-00000000000000000",
|
||||
describeInstanceStatusErr: assert.AnError,
|
||||
wantState: updatev1alpha1.NodeStateUnknown,
|
||||
wantErr: true,
|
||||
},
|
||||
"fails when getting no instances": {
|
||||
providerID: "aws:///us-east-2a/i-00000000000000000",
|
||||
describeInstanceStatusOut: &ec2.DescribeInstanceStatusOutput{
|
||||
InstanceStatuses: []ec2types.InstanceStatus{},
|
||||
},
|
||||
wantState: updatev1alpha1.NodeStateUnknown,
|
||||
wantErr: true,
|
||||
},
|
||||
"fails when the instance state is nil": {
|
||||
providerID: "aws:///us-east-2a/i-00000000000000000",
|
||||
describeInstanceStatusOut: &ec2.DescribeInstanceStatusOutput{
|
||||
InstanceStatuses: []ec2types.InstanceStatus{
|
||||
{
|
||||
InstanceState: nil,
|
||||
},
|
||||
},
|
||||
},
|
||||
wantState: updatev1alpha1.NodeStateUnknown,
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
for name, tc := range testCases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
require := require.New(t)
|
||||
|
||||
client := Client{
|
||||
ec2Client: &stubEC2API{
|
||||
describeInstanceStatusOut: tc.describeInstanceStatusOut,
|
||||
describeInstanceStatusErr: tc.describeInstanceStatusErr,
|
||||
},
|
||||
}
|
||||
nodeState, err := client.GetNodeState(context.Background(), tc.providerID)
|
||||
assert.Equal(tc.wantState, nodeState)
|
||||
if tc.wantErr {
|
||||
assert.Error(err)
|
||||
return
|
||||
}
|
||||
require.NoError(err)
|
||||
})
|
||||
}
|
||||
}
|
@ -0,0 +1,174 @@
|
||||
/*
|
||||
Copyright (c) Edgeless Systems GmbH
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package client
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/aws/aws-sdk-go-v2/service/autoscaling"
|
||||
scalingtypes "github.com/aws/aws-sdk-go-v2/service/autoscaling/types"
|
||||
"github.com/aws/aws-sdk-go-v2/service/ec2"
|
||||
ec2types "github.com/aws/aws-sdk-go-v2/service/ec2/types"
|
||||
)
|
||||
|
||||
// GetScalingGroupImage returns the image URI of the scaling group.
|
||||
func (c *Client) GetScalingGroupImage(ctx context.Context, scalingGroupID string) (string, error) {
|
||||
launchTemplate, err := c.getScalingGroupTemplate(ctx, scalingGroupID)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if launchTemplate.LaunchTemplateData == nil {
|
||||
return "", fmt.Errorf("launch template data is nil for scaling group %q", scalingGroupID)
|
||||
}
|
||||
|
||||
if launchTemplate.LaunchTemplateData.ImageId == nil {
|
||||
return "", fmt.Errorf("image ID is nil for scaling group %q", scalingGroupID)
|
||||
}
|
||||
|
||||
return *launchTemplate.LaunchTemplateData.ImageId, nil
|
||||
}
|
||||
|
||||
// SetScalingGroupImage sets the image URI of the scaling group.
|
||||
func (c *Client) SetScalingGroupImage(ctx context.Context, scalingGroupID, imageURI string) error {
|
||||
launchTemplate, err := c.getScalingGroupTemplate(ctx, scalingGroupID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get launch template for scaling group %q: %w", scalingGroupID, err)
|
||||
}
|
||||
|
||||
if launchTemplate.VersionNumber == nil {
|
||||
return fmt.Errorf("version number is nil for scaling group %q", scalingGroupID)
|
||||
}
|
||||
|
||||
createLaunchTemplateOut, err := c.ec2Client.CreateLaunchTemplateVersion(
|
||||
ctx,
|
||||
&ec2.CreateLaunchTemplateVersionInput{
|
||||
LaunchTemplateData: &ec2types.RequestLaunchTemplateData{
|
||||
ImageId: &imageURI,
|
||||
},
|
||||
LaunchTemplateId: launchTemplate.LaunchTemplateId,
|
||||
SourceVersion: toPtr(fmt.Sprintf("%d", *launchTemplate.VersionNumber)),
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create launch template version: %w", err)
|
||||
}
|
||||
|
||||
if createLaunchTemplateOut == nil {
|
||||
return fmt.Errorf("create launch template version output is nil")
|
||||
}
|
||||
if createLaunchTemplateOut.LaunchTemplateVersion == nil {
|
||||
return fmt.Errorf("created launch template version is nil")
|
||||
}
|
||||
if createLaunchTemplateOut.LaunchTemplateVersion.VersionNumber == nil {
|
||||
return fmt.Errorf("created launch template version number is nil")
|
||||
}
|
||||
|
||||
// set created version as default
|
||||
_, err = c.ec2Client.ModifyLaunchTemplate(
|
||||
ctx,
|
||||
&ec2.ModifyLaunchTemplateInput{
|
||||
LaunchTemplateId: launchTemplate.LaunchTemplateId,
|
||||
DefaultVersion: toPtr(fmt.Sprintf("%d", createLaunchTemplateOut.LaunchTemplateVersion.VersionNumber)),
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to modify launch template: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Client) getScalingGroupTemplate(ctx context.Context, scalingGroupID string) (ec2types.LaunchTemplateVersion, error) {
|
||||
groupOutput, err := c.scalingClient.DescribeAutoScalingGroups(
|
||||
ctx,
|
||||
&autoscaling.DescribeAutoScalingGroupsInput{
|
||||
AutoScalingGroupNames: []string{scalingGroupID},
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return ec2types.LaunchTemplateVersion{}, fmt.Errorf("failed to describe scaling group %q: %w", scalingGroupID, err)
|
||||
}
|
||||
|
||||
if len(groupOutput.AutoScalingGroups) != 1 {
|
||||
return ec2types.LaunchTemplateVersion{}, fmt.Errorf("expected exactly one scaling group, got %d", len(groupOutput.AutoScalingGroups))
|
||||
}
|
||||
|
||||
if groupOutput.AutoScalingGroups[0].LaunchTemplate == nil {
|
||||
return ec2types.LaunchTemplateVersion{}, fmt.Errorf("launch template is nil for scaling group %q", scalingGroupID)
|
||||
}
|
||||
|
||||
if groupOutput.AutoScalingGroups[0].LaunchTemplate.LaunchTemplateId == nil {
|
||||
return ec2types.LaunchTemplateVersion{}, fmt.Errorf("launch template ID is nil for scaling group %q", scalingGroupID)
|
||||
}
|
||||
|
||||
launchTemplateID := groupOutput.AutoScalingGroups[0].LaunchTemplate.LaunchTemplateId
|
||||
|
||||
launchTemplateOutput, err := c.ec2Client.DescribeLaunchTemplateVersions(
|
||||
ctx,
|
||||
&ec2.DescribeLaunchTemplateVersionsInput{
|
||||
LaunchTemplateId: launchTemplateID,
|
||||
Versions: []string{"$Latest"},
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return ec2types.LaunchTemplateVersion{}, fmt.Errorf("failed to describe launch template %q: %w", *launchTemplateID, err)
|
||||
}
|
||||
|
||||
if len(launchTemplateOutput.LaunchTemplateVersions) != 1 {
|
||||
return ec2types.LaunchTemplateVersion{}, fmt.Errorf("expected exactly one launch template, got %d", len(launchTemplateOutput.LaunchTemplateVersions))
|
||||
}
|
||||
return launchTemplateOutput.LaunchTemplateVersions[0], nil
|
||||
}
|
||||
|
||||
// GetScalingGroupName retrieves the name of a scaling group.
|
||||
// This keeps the casing of the original name, but Kubernetes requires the name to be lowercase,
|
||||
// so use strings.ToLower() on the result if using the name in a Kubernetes context.
|
||||
func (c *Client) GetScalingGroupName(scalingGroupID string) (string, error) {
|
||||
return strings.ToLower(scalingGroupID), nil
|
||||
}
|
||||
|
||||
// GetAutoscalingGroupName retrieves the name of a scaling group as needed by the cluster-autoscaler.
|
||||
func (c *Client) GetAutoscalingGroupName(scalingGroupID string) (string, error) {
|
||||
return scalingGroupID, nil
|
||||
}
|
||||
|
||||
// ListScalingGroups retrieves a list of scaling groups for the cluster.
|
||||
func (c *Client) ListScalingGroups(ctx context.Context, uid string) (controlPlaneGroupIDs []string, workerGroupIDs []string, err error) {
|
||||
output, err := c.scalingClient.DescribeAutoScalingGroups(
|
||||
ctx,
|
||||
&autoscaling.DescribeAutoScalingGroupsInput{
|
||||
Filters: []scalingtypes.Filter{
|
||||
{
|
||||
Name: toPtr("tag:constellation-uid"),
|
||||
Values: []string{uid},
|
||||
},
|
||||
},
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to describe scaling groups: %w", err)
|
||||
}
|
||||
|
||||
for _, group := range output.AutoScalingGroups {
|
||||
if group.Tags == nil {
|
||||
continue
|
||||
}
|
||||
for _, tag := range group.Tags {
|
||||
if *tag.Key == "constellation-role" {
|
||||
if *tag.Value == "control-plane" {
|
||||
controlPlaneGroupIDs = append(controlPlaneGroupIDs, *group.AutoScalingGroupName)
|
||||
} else if *tag.Value == "worker" {
|
||||
workerGroupIDs = append(workerGroupIDs, *group.AutoScalingGroupName)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return controlPlaneGroupIDs, workerGroupIDs, nil
|
||||
}
|
@ -0,0 +1,306 @@
|
||||
/*
|
||||
Copyright (c) Edgeless Systems GmbH
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package client
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/aws/aws-sdk-go-v2/service/autoscaling"
|
||||
scalingtypes "github.com/aws/aws-sdk-go-v2/service/autoscaling/types"
|
||||
"github.com/aws/aws-sdk-go-v2/service/ec2"
|
||||
ec2types "github.com/aws/aws-sdk-go-v2/service/ec2/types"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestGetScalingGroupImage(t *testing.T) {
|
||||
testCases := map[string]struct {
|
||||
providerID string
|
||||
describeAutoScalingGroupsOut *autoscaling.DescribeAutoScalingGroupsOutput
|
||||
describeAutoScalingGroupsErr error
|
||||
describeLaunchTemplateVersionsOut *ec2.DescribeLaunchTemplateVersionsOutput
|
||||
describeLaunchTemplateVersionsErr error
|
||||
wantImage string
|
||||
wantErr bool
|
||||
}{
|
||||
"getting scaling group image works": {
|
||||
providerID: "aws:///us-east-2a/i-00000000000000000",
|
||||
describeAutoScalingGroupsOut: &autoscaling.DescribeAutoScalingGroupsOutput{
|
||||
AutoScalingGroups: []scalingtypes.AutoScalingGroup{
|
||||
{
|
||||
LaunchTemplate: &scalingtypes.LaunchTemplateSpecification{
|
||||
LaunchTemplateId: toPtr("lt-00000000000000000"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
describeLaunchTemplateVersionsOut: &ec2.DescribeLaunchTemplateVersionsOutput{
|
||||
LaunchTemplateVersions: []ec2types.LaunchTemplateVersion{
|
||||
{
|
||||
LaunchTemplateData: &ec2types.ResponseLaunchTemplateData{
|
||||
ImageId: toPtr("ami-00000000000000000"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
wantImage: "ami-00000000000000000",
|
||||
},
|
||||
"fails when describing autoscaling group fails": {
|
||||
providerID: "aws:///us-east-2a/i-00000000000000000",
|
||||
describeAutoScalingGroupsErr: assert.AnError,
|
||||
wantErr: true,
|
||||
},
|
||||
"fails when describing launch template versions fails": {
|
||||
providerID: "aws:///us-east-2a/i-00000000000000000",
|
||||
describeAutoScalingGroupsOut: &autoscaling.DescribeAutoScalingGroupsOutput{
|
||||
AutoScalingGroups: []scalingtypes.AutoScalingGroup{
|
||||
{
|
||||
LaunchTemplate: &scalingtypes.LaunchTemplateSpecification{
|
||||
LaunchTemplateId: toPtr("lt-00000000000000000"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
describeLaunchTemplateVersionsErr: assert.AnError,
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
for name, tc := range testCases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
require := require.New(t)
|
||||
|
||||
client := Client{
|
||||
ec2Client: &stubEC2API{
|
||||
describeLaunchTemplateVersionsOut: tc.describeLaunchTemplateVersionsOut,
|
||||
describeLaunchTemplateVersionsErr: tc.describeLaunchTemplateVersionsErr,
|
||||
},
|
||||
scalingClient: &stubAutoscalingAPI{
|
||||
describeAutoScalingGroupsOut: []*autoscaling.DescribeAutoScalingGroupsOutput{
|
||||
tc.describeAutoScalingGroupsOut,
|
||||
},
|
||||
describeAutoScalingGroupsErr: []error{
|
||||
tc.describeAutoScalingGroupsErr,
|
||||
},
|
||||
},
|
||||
}
|
||||
scalingGroupImage, err := client.GetScalingGroupImage(context.Background(), tc.providerID)
|
||||
if tc.wantErr {
|
||||
assert.Error(err)
|
||||
return
|
||||
}
|
||||
require.NoError(err)
|
||||
assert.Equal(tc.wantImage, scalingGroupImage)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetScalingGroupImage(t *testing.T) {
|
||||
testCases := map[string]struct {
|
||||
providerID string
|
||||
describeAutoScalingGroupsOut *autoscaling.DescribeAutoScalingGroupsOutput
|
||||
describeAutoScalingGroupsErr error
|
||||
describeLaunchTemplateVersionsOut *ec2.DescribeLaunchTemplateVersionsOutput
|
||||
describeLaunchTemplateVersionsErr error
|
||||
createLaunchTemplateVersionOut *ec2.CreateLaunchTemplateVersionOutput
|
||||
createLaunchTemplateVersionErr error
|
||||
modifyLaunchTemplateErr error
|
||||
imageURI string
|
||||
wantErr bool
|
||||
}{
|
||||
"getting scaling group image works": {
|
||||
providerID: "aws:///us-east-2a/i-00000000000000000",
|
||||
describeAutoScalingGroupsOut: &autoscaling.DescribeAutoScalingGroupsOutput{
|
||||
AutoScalingGroups: []scalingtypes.AutoScalingGroup{
|
||||
{
|
||||
LaunchTemplate: &scalingtypes.LaunchTemplateSpecification{
|
||||
LaunchTemplateId: toPtr("lt-00000000000000000"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
describeLaunchTemplateVersionsOut: &ec2.DescribeLaunchTemplateVersionsOutput{
|
||||
LaunchTemplateVersions: []ec2types.LaunchTemplateVersion{
|
||||
{
|
||||
LaunchTemplateData: &ec2types.ResponseLaunchTemplateData{
|
||||
ImageId: toPtr("ami-00000000000000000"),
|
||||
},
|
||||
VersionNumber: toPtr(int64(1)),
|
||||
},
|
||||
},
|
||||
},
|
||||
createLaunchTemplateVersionOut: &ec2.CreateLaunchTemplateVersionOutput{
|
||||
LaunchTemplateVersion: &ec2types.LaunchTemplateVersion{
|
||||
VersionNumber: toPtr(int64(2)),
|
||||
},
|
||||
},
|
||||
imageURI: "ami-00000000000000000",
|
||||
},
|
||||
"fails when creating launch template version fails": {
|
||||
providerID: "aws:///us-east-2a/i-00000000000000000",
|
||||
describeAutoScalingGroupsOut: &autoscaling.DescribeAutoScalingGroupsOutput{
|
||||
AutoScalingGroups: []scalingtypes.AutoScalingGroup{
|
||||
{
|
||||
LaunchTemplate: &scalingtypes.LaunchTemplateSpecification{
|
||||
LaunchTemplateId: toPtr("lt-00000000000000000"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
describeLaunchTemplateVersionsOut: &ec2.DescribeLaunchTemplateVersionsOutput{
|
||||
LaunchTemplateVersions: []ec2types.LaunchTemplateVersion{
|
||||
{
|
||||
LaunchTemplateData: &ec2types.ResponseLaunchTemplateData{
|
||||
ImageId: toPtr("ami-00000000000000000"),
|
||||
},
|
||||
VersionNumber: toPtr(int64(1)),
|
||||
},
|
||||
},
|
||||
},
|
||||
imageURI: "ami-00000000000000000",
|
||||
createLaunchTemplateVersionErr: assert.AnError,
|
||||
wantErr: true,
|
||||
},
|
||||
"fails when modifying launch template fails": {
|
||||
providerID: "aws:///us-east-2a/i-00000000000000000",
|
||||
describeAutoScalingGroupsOut: &autoscaling.DescribeAutoScalingGroupsOutput{
|
||||
AutoScalingGroups: []scalingtypes.AutoScalingGroup{
|
||||
{
|
||||
LaunchTemplate: &scalingtypes.LaunchTemplateSpecification{
|
||||
LaunchTemplateId: toPtr("lt-00000000000000000"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
describeLaunchTemplateVersionsOut: &ec2.DescribeLaunchTemplateVersionsOutput{
|
||||
LaunchTemplateVersions: []ec2types.LaunchTemplateVersion{
|
||||
{
|
||||
LaunchTemplateData: &ec2types.ResponseLaunchTemplateData{
|
||||
ImageId: toPtr("ami-00000000000000000"),
|
||||
},
|
||||
VersionNumber: toPtr(int64(1)),
|
||||
},
|
||||
},
|
||||
},
|
||||
imageURI: "ami-00000000000000000",
|
||||
modifyLaunchTemplateErr: assert.AnError,
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
for name, tc := range testCases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
require := require.New(t)
|
||||
|
||||
client := Client{
|
||||
ec2Client: &stubEC2API{
|
||||
describeLaunchTemplateVersionsOut: tc.describeLaunchTemplateVersionsOut,
|
||||
describeLaunchTemplateVersionsErr: tc.describeLaunchTemplateVersionsErr,
|
||||
createLaunchTemplateVersionOut: tc.createLaunchTemplateVersionOut,
|
||||
createLaunchTemplateVersionErr: tc.createLaunchTemplateVersionErr,
|
||||
modifyLaunchTemplateErr: tc.modifyLaunchTemplateErr,
|
||||
},
|
||||
scalingClient: &stubAutoscalingAPI{
|
||||
describeAutoScalingGroupsOut: []*autoscaling.DescribeAutoScalingGroupsOutput{
|
||||
tc.describeAutoScalingGroupsOut,
|
||||
},
|
||||
describeAutoScalingGroupsErr: []error{
|
||||
tc.describeAutoScalingGroupsErr,
|
||||
},
|
||||
},
|
||||
}
|
||||
err := client.SetScalingGroupImage(context.Background(), tc.providerID, tc.imageURI)
|
||||
if tc.wantErr {
|
||||
assert.Error(err)
|
||||
return
|
||||
}
|
||||
require.NoError(err)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestListScalingGroups(t *testing.T) {
|
||||
testCases := map[string]struct {
|
||||
providerID string
|
||||
describeAutoScalingGroupsOut []*autoscaling.DescribeAutoScalingGroupsOutput
|
||||
describeAutoScalingGroupsErr []error
|
||||
wantControlPlaneGroupIDs []string
|
||||
wantWorkerGroupIDs []string
|
||||
wantErr bool
|
||||
}{
|
||||
"listing scaling groups work": {
|
||||
providerID: "aws:///us-east-2a/i-00000000000000000",
|
||||
describeAutoScalingGroupsOut: []*autoscaling.DescribeAutoScalingGroupsOutput{
|
||||
{
|
||||
AutoScalingGroups: []scalingtypes.AutoScalingGroup{
|
||||
{
|
||||
AutoScalingGroupName: toPtr("control-plane-asg"),
|
||||
Tags: []scalingtypes.TagDescription{
|
||||
{
|
||||
Key: toPtr("constellation-role"),
|
||||
Value: toPtr("control-plane"),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
AutoScalingGroupName: toPtr("worker-asg"),
|
||||
Tags: []scalingtypes.TagDescription{
|
||||
{
|
||||
Key: toPtr("constellation-role"),
|
||||
Value: toPtr("worker"),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
AutoScalingGroupName: toPtr("worker-asg-2"),
|
||||
Tags: []scalingtypes.TagDescription{
|
||||
{
|
||||
Key: toPtr("constellation-role"),
|
||||
Value: toPtr("worker"),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
AutoScalingGroupName: toPtr("other-asg"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
describeAutoScalingGroupsErr: []error{nil},
|
||||
wantControlPlaneGroupIDs: []string{"control-plane-asg"},
|
||||
wantWorkerGroupIDs: []string{"worker-asg", "worker-asg-2"},
|
||||
},
|
||||
"fails when describing scaling groups fails": {
|
||||
providerID: "aws:///us-east-2a/i-00000000000000000",
|
||||
describeAutoScalingGroupsOut: []*autoscaling.DescribeAutoScalingGroupsOutput{nil},
|
||||
describeAutoScalingGroupsErr: []error{assert.AnError},
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
for name, tc := range testCases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
require := require.New(t)
|
||||
|
||||
client := Client{
|
||||
scalingClient: &stubAutoscalingAPI{
|
||||
describeAutoScalingGroupsOut: tc.describeAutoScalingGroupsOut,
|
||||
describeAutoScalingGroupsErr: tc.describeAutoScalingGroupsErr,
|
||||
},
|
||||
}
|
||||
controlPlaneGroupIDs, workerGroupIDs, err := client.ListScalingGroups(context.Background(), tc.providerID)
|
||||
if tc.wantErr {
|
||||
assert.Error(err)
|
||||
return
|
||||
}
|
||||
require.NoError(err)
|
||||
assert.Equal(tc.wantControlPlaneGroupIDs, controlPlaneGroupIDs)
|
||||
assert.Equal(tc.wantWorkerGroupIDs, workerGroupIDs)
|
||||
})
|
||||
}
|
||||
}
|
@ -25,6 +25,7 @@ import (
|
||||
"sigs.k8s.io/controller-runtime/pkg/healthz"
|
||||
"sigs.k8s.io/controller-runtime/pkg/log/zap"
|
||||
|
||||
awsclient "github.com/edgelesssys/constellation/v2/operators/constellation-node-operator/v2/internal/cloud/aws/client"
|
||||
azureclient "github.com/edgelesssys/constellation/v2/operators/constellation-node-operator/v2/internal/cloud/azure/client"
|
||||
cloudfake "github.com/edgelesssys/constellation/v2/operators/constellation-node-operator/v2/internal/cloud/fake/client"
|
||||
gcpclient "github.com/edgelesssys/constellation/v2/operators/constellation-node-operator/v2/internal/cloud/gcp/client"
|
||||
@ -101,6 +102,12 @@ func main() {
|
||||
setupLog.Error(clientErr, "unable to create GCP client")
|
||||
os.Exit(1)
|
||||
}
|
||||
case "aws":
|
||||
cspClient, clientErr = awsclient.New(context.Background())
|
||||
if clientErr != nil {
|
||||
setupLog.Error(clientErr, "unable to create AWS client")
|
||||
os.Exit(1)
|
||||
}
|
||||
default:
|
||||
setupLog.Info("CSP does not support upgrades", "csp", csp)
|
||||
cspClient = &cloudfake.Client{}
|
||||
@ -142,7 +149,7 @@ func main() {
|
||||
os.Exit(1)
|
||||
}
|
||||
// Create Controllers
|
||||
if csp == "azure" || csp == "gcp" {
|
||||
if csp == "azure" || csp == "gcp" || csp == "aws" {
|
||||
if err = controllers.NewNodeVersionReconciler(
|
||||
cspClient, etcdClient, upgrade.NewClient(), discoveryClient, mgr.GetClient(), mgr.GetScheme(),
|
||||
).SetupWithManager(mgr); err != nil {
|
||||
|
Loading…
Reference in New Issue
Block a user