cmd/e2e-test/node/create.go (223 lines of code) (raw):
package node
import (
"context"
"fmt"
"os"
awsconfig "github.com/aws/aws-sdk-go-v2/config"
"github.com/aws/aws-sdk-go-v2/service/ec2"
s3sdk "github.com/aws/aws-sdk-go-v2/service/s3"
ssmsdk "github.com/aws/aws-sdk-go-v2/service/ssm"
"github.com/go-logr/logr"
"github.com/integrii/flaggy"
"go.uber.org/zap"
"k8s.io/client-go/dynamic"
clientgo "k8s.io/client-go/kubernetes"
"k8s.io/client-go/tools/clientcmd"
"github.com/aws/eks-hybrid/internal/cli"
"github.com/aws/eks-hybrid/test/e2e"
"github.com/aws/eks-hybrid/test/e2e/cluster"
"github.com/aws/eks-hybrid/test/e2e/credentials"
"github.com/aws/eks-hybrid/test/e2e/kubernetes"
osystem "github.com/aws/eks-hybrid/test/e2e/os"
"github.com/aws/eks-hybrid/test/e2e/peered"
"github.com/aws/eks-hybrid/test/e2e/s3"
)
type create struct {
flaggy *flaggy.Subcommand
configFile string
instanceName string
instanceSize string
credsProvider string
os string
arch string
}
func NewCreateCommand() cli.Command {
cmd := create{
os: "al23",
arch: "amd64",
instanceSize: "Large",
}
createCmd := flaggy.NewSubcommand("create")
createCmd.Description = "Create a Hybrid Node"
createCmd.AddPositionalValue(&cmd.instanceName, "INSTANCE_NAME", 1, true, "Name of the instance to create.")
createCmd.String(&cmd.configFile, "f", "config-file", "Path tests config file.")
createCmd.String(&cmd.credsProvider, "c", "creds-provider", "Credentials provider to use (iam-ra, ssm).")
createCmd.String(&cmd.os, "o", "os", "OS to use (al23, ubuntu2004, ubuntu2204, ubuntu2404, rhel8, rhel9).")
createCmd.String(&cmd.arch, "a", "arch", "Architecture to use (amd64, arm64).")
createCmd.String(&cmd.instanceSize, "s", "instance-size", "Instance size to use (Large, XLarge).")
cmd.flaggy = createCmd
return &cmd
}
func (c *create) Flaggy() *flaggy.Subcommand {
return c.flaggy
}
func (c *create) Run(log *zap.Logger, opts *cli.GlobalOptions) error {
ctx := context.Background()
config, err := e2e.ReadConfig(c.configFile)
if err != nil {
return err
}
logger := e2e.NewLogger()
aws, err := e2e.NewAWSConfig(ctx, awsconfig.WithRegion(config.ClusterRegion))
if err != nil {
return fmt.Errorf("reading AWS configuration: %w", err)
}
infra, err := peered.Setup(ctx, logger, aws, config.ClusterName, config.Endpoint)
if err != nil {
return fmt.Errorf("setting up test infrastructure: %w", err)
}
eksClient := e2e.NewEKSClient(aws, config.Endpoint)
ec2Client := ec2.NewFromConfig(aws)
ssmClient := ssmsdk.NewFromConfig(aws)
s3Client := s3sdk.NewFromConfig(aws)
clientConfig, err := clientcmd.BuildConfigFromFlags("", cluster.KubeconfigPath(config.ClusterName))
if err != nil {
return err
}
k8s, err := clientgo.NewForConfig(clientConfig)
if err != nil {
return err
}
k8sDynamic, err := dynamic.NewForConfig(clientConfig)
if err != nil {
return err
}
cluster, err := peered.GetHybridCluster(ctx, eksClient, ec2Client, config.ClusterName)
if err != nil {
return err
}
urls, err := s3.BuildNodeamURLs(ctx, s3Client, config.NodeadmUrlAMD, config.NodeadmUrlARM)
if err != nil {
return err
}
node := peered.NodeCreate{
AWS: aws,
Cluster: cluster,
EC2: ec2Client,
SSM: ssmClient,
Logger: logger,
NodeadmURLs: *urls,
PublicKey: infra.NodesPublicSSHKey,
}
nodeOS, err := buildOS(c.os, c.arch)
if err != nil {
return err
}
var credsProvider e2e.NodeadmCredentialsProvider
switch c.credsProvider {
case "iam-ra":
credsProvider = &credentials.IamRolesAnywhereProvider{
RoleARN: infra.Credentials.IRANodeRoleARN,
ProfileARN: infra.Credentials.IRAProfileARN,
TrustAnchorARN: infra.Credentials.IRATrustAnchorARN,
CA: infra.Credentials.RolesAnywhereCA,
}
case "ssm":
credsProvider = &credentials.SsmProvider{
SSM: ssmClient,
Role: infra.Credentials.SSMNodeRoleName,
}
}
instanceSize := e2e.Large
if c.instanceSize == "XLarge" {
instanceSize = e2e.XLarge
}
peerdNode, err := node.Create(ctx, &peered.NodeSpec{
InstanceName: c.instanceName,
InstanceSize: instanceSize,
NodeK8sVersion: cluster.KubernetesVersion,
NodeName: c.instanceName,
OS: nodeOS,
Provider: credsProvider,
})
if err != nil {
return err
}
logger.Info("Node created", "instanceID", peerdNode.Instance.ID)
logger.Info("Connecting to the node serial console...")
serial, err := node.SerialConsole(ctx, peerdNode.Instance.ID)
if err != nil {
return fmt.Errorf("preparing EC2 for serial connection: %w", err)
}
defer serial.Close()
pausableOutput := e2e.NewSwitchWriter(os.Stdout)
pausableOutput.Pause()
if err := serial.Copy(pausableOutput); err != nil {
return fmt.Errorf("connecting to EC2 serial console: %w", err)
}
logger.Info("Waiting for the node to initialize...")
if err := pausableOutput.Resume(); err != nil {
return fmt.Errorf("resuming output: %w", err)
}
verifyNode := kubernetes.VerifyNode{
K8s: k8s,
Logger: logr.Discard(),
NodeName: peerdNode.Name,
NodeIP: peerdNode.Instance.IP,
}
vn, err := verifyNode.WaitForNodeReady(ctx)
if err != nil {
return fmt.Errorf("waiting for node to be ready: %w", err)
}
pausableOutput.Pause()
fmt.Println() // newline after pausing the serial output to ensure a clean log after
logger.Info("Node is ready", "nodeName", vn.Name)
network := peered.Network{
EC2: ec2Client,
Logger: logger,
K8s: peered.K8s{
Interface: k8s,
Dynamic: k8sDynamic,
},
Cluster: cluster,
}
if err := network.CreateRoutesForNode(ctx, &peerdNode); err != nil {
return fmt.Errorf("creating routes for node: %w", err)
}
return nil
}
func buildOS(osName, arch string) (e2e.NodeadmOS, error) {
osBuilders, ok := oses[osName]
if !ok {
return nil, fmt.Errorf("unknown OS %s", osName)
}
build, ok := osBuilders[arch]
if !ok {
return nil, fmt.Errorf("unknown architecture %q for OS %q", arch, osName)
}
return build(), nil
}
var oses = map[string]map[string]func() e2e.NodeadmOS{
"al23": {
"amd64": func() e2e.NodeadmOS { return osystem.NewAmazonLinux2023AMD() },
"arm64": func() e2e.NodeadmOS { return osystem.NewAmazonLinux2023ARM() },
},
"ubuntu2004": {
"amd64": func() e2e.NodeadmOS { return osystem.NewUbuntu2004AMD() },
"arm64": func() e2e.NodeadmOS { return osystem.NewUbuntu2004ARM() },
},
"ubuntu2204": {
"amd64": func() e2e.NodeadmOS { return osystem.NewUbuntu2204AMD() },
"arm64": func() e2e.NodeadmOS { return osystem.NewUbuntu2204ARM() },
},
"ubuntu2404": {
"amd64": func() e2e.NodeadmOS { return osystem.NewUbuntu2404AMD() },
"arm64": func() e2e.NodeadmOS { return osystem.NewUbuntu2404ARM() },
},
"rhel8": {
"amd64": func() e2e.NodeadmOS {
return osystem.NewRedHat8AMD(os.Getenv("RHEL_USERNAME"), os.Getenv("RHEL_PASSWORD"))
},
"arm64": func() e2e.NodeadmOS {
return osystem.NewRedHat8ARM(os.Getenv("RHEL_USERNAME"), os.Getenv("RHEL_PASSWORD"))
},
},
"rhel9": {
"amd64": func() e2e.NodeadmOS {
return osystem.NewRedHat9AMD(os.Getenv("RHEL_USERNAME"), os.Getenv("RHEL_PASSWORD"))
},
"arm64": func() e2e.NodeadmOS {
return osystem.NewRedHat9ARM(os.Getenv("RHEL_USERNAME"), os.Getenv("RHEL_PASSWORD"))
},
},
}