Skip to content

Commit

Permalink
metal: more functions to enable kops update cluster
Browse files Browse the repository at this point in the history
We still have to add VMs, but this is another step closer.
  • Loading branch information
justinsb committed Aug 30, 2024
1 parent d5c56dc commit 08481f5
Show file tree
Hide file tree
Showing 7 changed files with 152 additions and 20 deletions.
30 changes: 23 additions & 7 deletions tests/e2e/scenarios/bare-metal/run-test
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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
Expand Down Expand Up @@ -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"
76 changes: 70 additions & 6 deletions tools/metal/storage/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand All @@ -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
Expand All @@ -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)
Expand Down
5 changes: 4 additions & 1 deletion tools/metal/storage/pkg/objectstore/interfaces.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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),
}
Expand Down
15 changes: 15 additions & 0 deletions tools/metal/storage/pkg/s3model/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"`
}
3 changes: 3 additions & 0 deletions upup/pkg/fi/cloudup/apply_cluster.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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())
}
Expand Down
28 changes: 26 additions & 2 deletions upup/pkg/fi/cloudup/metal/cloud.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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
}

0 comments on commit 08481f5

Please sign in to comment.