metal: more functions to enable `kops update cluster`

We still have to add VMs, but this is another step closer.
This commit is contained in:
justinsb 2024-08-30 06:50:10 -04:00
parent d5c56dcf98
commit 08481f512c
7 changed files with 152 additions and 20 deletions

View File

@ -20,7 +20,12 @@ set -o pipefail
set -o xtrace
REPO_ROOT=$(git rev-parse --show-toplevel)
cd ${REPO_ROOT}/tests/e2e/scenarios/bare-metal
cd ${REPO_ROOT}
BINDIR=${REPO_ROOT}/.build/bin
go build -o ${BINDIR}/kops ./cmd/kops
KOPS=${BINDIR}/kops
function cleanup() {
echo "running dump-artifacts"
@ -32,7 +37,7 @@ function cleanup() {
trap cleanup EXIT
./start-vms
${REPO_ROOT}/tests/e2e/scenarios/bare-metal/start-vms
echo "Waiting 30 seconds for VMs to start"
sleep 30
@ -64,14 +69,25 @@ aws --version
aws --endpoint-url=${S3_ENDPOINT} s3 mb s3://kops-state-store
# List clusters (there should not be any yet)
go run ./cmd/kops get cluster || true
${KOPS} get cluster || true
# Create a cluster
go run ./cmd/kops create cluster --cloud=metal metal.k8s.local --zones main
${KOPS} create cluster --cloud=metal metal.k8s.local --zones main
# Set the IP ingress, required for metal cloud
# TODO: is this the best option?
${KOPS} edit cluster metal.k8s.local --set spec.api.publicName=10.123.45.10
# List clusters
go run ./cmd/kops get cluster
${KOPS} get cluster
${KOPS} get cluster -oyaml
# List instance groups
go run ./cmd/kops get ig --name metal.k8s.local
go run ./cmd/kops get ig --name metal.k8s.local -oyaml
${KOPS} get ig --name metal.k8s.local
${KOPS} get ig --name metal.k8s.local -oyaml
# Apply basic configuration
${KOPS} update cluster metal.k8s.local
${KOPS} update cluster metal.k8s.local --yes --admin
echo "Test successful"

View File

@ -146,6 +146,12 @@ func (s *S3Server) ServeRequest(ctx context.Context, w http.ResponseWriter, r *h
key := strings.TrimPrefix(r.URL.Path, "/"+bucket+"/")
switch r.Method {
case http.MethodGet:
if values.Has("acl") {
return s.GetObjectACL(ctx, req, &GetObjectACLInput{
Bucket: bucket,
Key: key,
})
}
return s.GetObject(ctx, req, &GetObjectInput{
Bucket: bucket,
Key: key,
@ -174,12 +180,15 @@ type ListObjectsV2Input struct {
const s3TimeFormat = "2006-01-02T15:04:05.000Z"
func (s *S3Server) ListObjectsV2(ctx context.Context, req *s3Request, input *ListObjectsV2Input) error {
bucket, err := s.store.GetBucket(ctx, input.Bucket)
bucket, _, err := s.store.GetBucket(ctx, input.Bucket)
if err != nil {
return fmt.Errorf("failed to get bucket %q: %w", input.Bucket, err)
}
if bucket == nil {
return fmt.Errorf("bucket %q not found", input.Bucket)
return req.writeError(ctx, http.StatusNotFound, &s3model.Error{
Code: "NoSuchBucket",
Message: "The specified bucket does not exist",
})
}
objects, err := bucket.ListObjects(ctx)
@ -238,12 +247,15 @@ type GetObjectInput struct {
}
func (s *S3Server) GetObject(ctx context.Context, req *s3Request, input *GetObjectInput) error {
bucket, err := s.store.GetBucket(ctx, input.Bucket)
bucket, _, err := s.store.GetBucket(ctx, input.Bucket)
if err != nil {
return fmt.Errorf("failed to get bucket %q: %w", input.Bucket, err)
}
if bucket == nil {
return req.writeError(ctx, http.StatusNotFound, nil)
return req.writeError(ctx, http.StatusNotFound, &s3model.Error{
Code: "NoSuchBucket",
Message: "The specified bucket does not exist",
})
}
object, err := bucket.GetObject(ctx, input.Key)
@ -261,6 +273,55 @@ func (s *S3Server) GetObject(ctx context.Context, req *s3Request, input *GetObje
return object.WriteTo(req.w)
}
type GetObjectACLInput struct {
Bucket string
Key string
}
func (s *S3Server) GetObjectACL(ctx context.Context, req *s3Request, input *GetObjectACLInput) error {
bucket, bucketInfo, err := s.store.GetBucket(ctx, input.Bucket)
if err != nil {
return fmt.Errorf("failed to get bucket %q: %w", input.Bucket, err)
}
if bucket == nil {
return req.writeError(ctx, http.StatusNotFound, &s3model.Error{
Code: "NoSuchBucket",
Message: "The specified bucket does not exist",
})
}
object, err := bucket.GetObject(ctx, input.Key)
if err != nil {
return fmt.Errorf("failed to get object %q in bucket %q: %w", input.Key, input.Bucket, err)
}
if object == nil {
return req.writeError(ctx, http.StatusNotFound, &s3model.Error{
Code: "NoSuchKey",
Message: "The specified key does not exist.",
})
}
owner := bucketInfo.Owner
output := &s3model.ObjectACLResult{
Owner: &s3model.Owner{
ID: owner,
},
Grants: []*s3model.Grant{
{
Grantee: &s3model.Grantee{
ID: owner,
Type: "CanonicalUser",
},
Permission: "FULL_CONTROL",
},
},
}
return req.writeXML(ctx, output)
}
type PutObjectInput struct {
Bucket string
Key string
@ -269,12 +330,15 @@ type PutObjectInput struct {
func (s *S3Server) PutObject(ctx context.Context, req *s3Request, input *PutObjectInput) error {
log := klog.FromContext(ctx)
bucket, err := s.store.GetBucket(ctx, input.Bucket)
bucket, _, err := s.store.GetBucket(ctx, input.Bucket)
if err != nil {
return fmt.Errorf("failed to get bucket %q: %w", input.Bucket, err)
}
if bucket == nil {
return req.writeError(ctx, http.StatusNotFound, nil)
return req.writeError(ctx, http.StatusNotFound, &s3model.Error{
Code: "NoSuchBucket",
Message: "The specified bucket does not exist",
})
}
objectInfo, err := bucket.PutObject(ctx, input.Key, req.r.Body)

View File

@ -28,7 +28,7 @@ type ObjectStore interface {
// GetBucket returns the bucket with the given name.
// If the bucket does not exist, it returns (nil, nil).
GetBucket(ctx context.Context, name string) (Bucket, error)
GetBucket(ctx context.Context, name string) (Bucket, *BucketInfo, error)
// CreateBucket creates the bucket with the given name.
// If the bucket already exist, it returns codes.AlreadyExists.
@ -38,6 +38,9 @@ type ObjectStore interface {
type BucketInfo struct {
Name string
CreationDate time.Time
// Owner is the AWS ID of the bucket owner.
Owner string
}
type Bucket interface {

View File

@ -54,15 +54,21 @@ func (m *TestObjectStore) ListBuckets(ctx context.Context) []objectstore.BucketI
return buckets
}
func (m *TestObjectStore) GetBucket(ctx context.Context, bucketName string) (objectstore.Bucket, error) {
func (m *TestObjectStore) GetBucket(ctx context.Context, bucketName string) (objectstore.Bucket, *objectstore.BucketInfo, error) {
m.mutex.Lock()
defer m.mutex.Unlock()
bucket := m.buckets[bucketName]
if bucket == nil {
return nil, nil
return nil, nil, nil
}
return bucket, nil
return bucket, &bucket.info, nil
}
// getOwnerID returns the owner ID for the given context.
// This is a fake implementation for testing purposes.
func getOwnerID(ctx context.Context) string {
return "fake-owner"
}
func (m *TestObjectStore) CreateBucket(ctx context.Context, bucketName string) (*objectstore.BucketInfo, error) {
@ -83,7 +89,8 @@ func (m *TestObjectStore) CreateBucket(ctx context.Context, bucketName string) (
bucket = &TestBucket{
info: objectstore.BucketInfo{
Name: bucketName,
CreationDate: time.Now(),
CreationDate: time.Now().UTC(),
Owner: getOwnerID(ctx),
},
objects: make(map[string]*TestObject),
}

View File

@ -67,3 +67,18 @@ type RestoreStatus struct {
IsRestoreInProgress bool `xml:"IsRestoreInProgress"`
RestoreExpiryDate *string `xml:"RestoreExpiryDate"`
}
type ObjectACLResult struct {
Owner *Owner `xml:"Owner"`
Grants []*Grant `xml:"Grant"`
}
type Grant struct {
Grantee *Grantee `xml:"Grantee"`
Permission string `xml:"Permission"`
}
type Grantee struct {
ID string `xml:"ID"`
Type string `xml:"Type"`
}

View File

@ -59,6 +59,7 @@ import (
"k8s.io/kops/upup/pkg/fi/cloudup/do"
"k8s.io/kops/upup/pkg/fi/cloudup/gce"
"k8s.io/kops/upup/pkg/fi/cloudup/hetzner"
"k8s.io/kops/upup/pkg/fi/cloudup/metal"
"k8s.io/kops/upup/pkg/fi/cloudup/openstack"
"k8s.io/kops/upup/pkg/fi/cloudup/scaleway"
"k8s.io/kops/upup/pkg/fi/cloudup/terraform"
@ -722,6 +723,8 @@ func (c *ApplyClusterCmd) Run(ctx context.Context) (*ApplyResults, error) {
target = azure.NewAzureAPITarget(cloud.(azure.AzureCloud))
case kops.CloudProviderScaleway:
target = scaleway.NewScwAPITarget(cloud.(scaleway.ScwCloud))
case kops.CloudProviderMetal:
target = metal.NewAPITarget(cloud.(*metal.Cloud), nil)
default:
return nil, fmt.Errorf("direct configuration not supported with CloudProvider:%q", cluster.GetCloudProvider())
}

View File

@ -18,8 +18,10 @@ package metal
import (
"fmt"
"net"
v1 "k8s.io/api/core/v1"
"k8s.io/klog/v2"
"k8s.io/kops/dnsprovider/pkg/dnsprovider"
"k8s.io/kops/pkg/apis/kops"
"k8s.io/kops/pkg/cloudinstances"
@ -84,9 +86,31 @@ func (c *Cloud) Region() string {
// FindClusterStatus discovers the status of the cluster, by inspecting the cloud objects
func (c *Cloud) FindClusterStatus(cluster *kops.Cluster) (*kops.ClusterStatus, error) {
return nil, fmt.Errorf("method metal.Cloud::FindClusterStatus not implemented")
// etcdStatus, err := findEtcdStatus(c, cluster)
// if err != nil {
// return nil, err
// }
klog.Warningf("method metal.Cloud::FindClusterStatus stub-implemented")
return &kops.ClusterStatus{
// EtcdClusters: etcdStatus,
}, nil
}
func (c *Cloud) GetApiIngressStatus(cluster *kops.Cluster) ([]fi.ApiIngressStatus, error) {
return nil, fmt.Errorf("method metal.Cloud::GetApiIngressStatus not implemented")
var ret []fi.ApiIngressStatus
publicName := cluster.Spec.API.PublicName
if publicName == "" {
return ret, fmt.Errorf("spec.api.publicName must be set for bare metal")
}
ip := net.ParseIP(publicName)
if ip == nil {
ret = append(ret, fi.ApiIngressStatus{
Hostname: publicName,
})
} else {
ret = append(ret, fi.ApiIngressStatus{
IP: publicName,
})
}
return ret, nil
}