Skip to content

Commit

Permalink
Merge pull request kubernetes#16788 from justinsb/metal_test_2
Browse files Browse the repository at this point in the history
metal: stub out functions to enable cluster creation
  • Loading branch information
k8s-ci-robot authored Aug 30, 2024
2 parents d269ac2 + 7f58570 commit d5c56dc
Show file tree
Hide file tree
Showing 15 changed files with 289 additions and 21 deletions.
2 changes: 1 addition & 1 deletion nodeup/pkg/model/bootstrap_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ func (b BootstrapClientBuilder) Build(c *fi.NodeupModelBuilderContext) error {
}
authenticator = a

case "metal":
case kops.CloudProviderMetal:
a, err := pkibootstrap.NewAuthenticatorFromFile("/etc/kubernetes/kops/pki/machine/private.pem")
if err != nil {
return err
Expand Down
2 changes: 2 additions & 0 deletions pkg/model/components/apiserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,8 @@ func (b *KubeAPIServerOptionsBuilder) BuildOptions(cluster *kops.Cluster) error
c.CloudProvider = "azure"
case kops.CloudProviderScaleway:
c.CloudProvider = "external"
case kops.CloudProviderMetal:
c.CloudProvider = "external"
default:
return fmt.Errorf("unknown cloudprovider %q", cluster.GetCloudProvider())
}
Expand Down
5 changes: 5 additions & 0 deletions pkg/model/components/etcdmanager/model.go
Original file line number Diff line number Diff line change
Expand Up @@ -525,6 +525,11 @@ func (b *EtcdManagerBuilder) buildPod(etcdCluster kops.EtcdClusterSpec, instance
fmt.Sprintf("%s=%s", scaleway.TagNameRolePrefix, scaleway.TagRoleControlPlane),
}
config.VolumeNameTag = fmt.Sprintf("%s=%s", scaleway.TagInstanceGroup, instanceGroupName)

case kops.CloudProviderMetal:
config.VolumeProvider = "external"
// TODO: Use static configuration here?

default:
return nil, fmt.Errorf("CloudProvider %q not supported with etcd-manager", b.Cluster.GetCloudProvider())
}
Expand Down
4 changes: 4 additions & 0 deletions pkg/model/master_volumes.go
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,10 @@ func (b *MasterVolumeBuilder) Build(c *fi.CloudupModelBuilderContext) error {
}
case kops.CloudProviderScaleway:
b.addScalewayVolume(c, name, volumeSize, zone, etcd, m, allMembers)

case kops.CloudProviderMetal:
// Nothing special to do for Metal (yet)

default:
return fmt.Errorf("unknown cloudprovider %q", b.Cluster.GetCloudProvider())
}
Expand Down
12 changes: 11 additions & 1 deletion tests/e2e/scenarios/bare-metal/run-test
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,17 @@ export S3_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY}
# Create the state-store bucket in our mock s3 server
export KOPS_STATE_STORE=s3://kops-state-store/
aws --version
aws --endpoint-url=${S3_ENDPOINT} --debug s3 mb s3://kops-state-store
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

# Create a cluster
go run ./cmd/kops create cluster --cloud=metal metal.k8s.local --zones main

# List clusters
go run ./cmd/kops get cluster

# List instance groups
go run ./cmd/kops get ig --name metal.k8s.local
go run ./cmd/kops get ig --name metal.k8s.local -oyaml
70 changes: 62 additions & 8 deletions tools/metal/storage/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,9 @@ import (
"flag"
"fmt"
"net/http"
"net/url"
"os"
"strings"
"time"

"github.com/kubernetes/kops/tools/metal/dhcp/pkg/objectstore"
"github.com/kubernetes/kops/tools/metal/dhcp/pkg/objectstore/testobjectstore"
Expand Down Expand Up @@ -90,7 +90,7 @@ func (s *S3Server) ListAllMyBuckets(ctx context.Context, req *s3Request, r *List

for _, bucket := range s.store.ListBuckets(ctx) {
output.Buckets = append(output.Buckets, s3model.Bucket{
CreationDate: bucket.CreationDate.Format(time.RFC3339),
CreationDate: bucket.CreationDate.Format(s3TimeFormat),
Name: bucket.Name,
})
}
Expand All @@ -107,6 +107,11 @@ func (s *S3Server) ServeRequest(ctx context.Context, w http.ResponseWriter, r *h

tokens := strings.Split(strings.TrimPrefix(r.URL.Path, "/"), "/")

values, err := url.ParseQuery(r.URL.RawQuery)
if err != nil {
return fmt.Errorf("failed to parse query: %w", err)
}

req := &s3Request{
w: w,
r: r,
Expand All @@ -121,7 +126,9 @@ func (s *S3Server) ServeRequest(ctx context.Context, w http.ResponseWriter, r *h
switch r.Method {
case http.MethodGet:
return s.ListObjectsV2(ctx, req, &ListObjectsV2Input{
Bucket: bucket,
Bucket: bucket,
Delimiter: values.Get("delimiter"),
Prefix: values.Get("prefix"),
})
case http.MethodPut:
return s.CreateBucket(ctx, req, &CreateBucketInput{
Expand All @@ -136,19 +143,36 @@ func (s *S3Server) ServeRequest(ctx context.Context, w http.ResponseWriter, r *h

if len(tokens) > 1 {
bucket := tokens[0]
return s.GetObject(ctx, req, &GetObjectInput{
Bucket: bucket,
Key: strings.TrimPrefix(r.URL.Path, "/"+bucket+"/"),
})
key := strings.TrimPrefix(r.URL.Path, "/"+bucket+"/")
switch r.Method {
case http.MethodGet:
return s.GetObject(ctx, req, &GetObjectInput{
Bucket: bucket,
Key: key,
})
case http.MethodPut:
return s.PutObject(ctx, req, &PutObjectInput{
Bucket: bucket,
Key: key,
})
default:
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
return nil
}
}

return fmt.Errorf("unhandled path %q", r.URL.Path)
}

type ListObjectsV2Input struct {
Bucket string

Delimiter string
Prefix string
}

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)
if err != nil {
Expand All @@ -168,12 +192,17 @@ func (s *S3Server) ListObjectsV2(ctx context.Context, req *s3Request, input *Lis
}

for _, object := range objects {
if input.Prefix != "" && !strings.HasPrefix(object.Key, input.Prefix) {
continue
}
// TODO: support delimiter
output.Contents = append(output.Contents, s3model.Object{
Key: object.Key,
LastModified: object.LastModified.Format(time.RFC3339),
LastModified: object.LastModified.Format(s3TimeFormat),
Size: object.Size,
})
}
output.KeyCount = len(output.Contents)

return req.writeXML(ctx, output)
}
Expand Down Expand Up @@ -232,6 +261,31 @@ func (s *S3Server) GetObject(ctx context.Context, req *s3Request, input *GetObje
return object.WriteTo(req.w)
}

type PutObjectInput struct {
Bucket string
Key string
}

func (s *S3Server) PutObject(ctx context.Context, req *s3Request, input *PutObjectInput) error {
log := klog.FromContext(ctx)

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)
}

objectInfo, err := bucket.PutObject(ctx, input.Key, req.r.Body)
if err != nil {
return fmt.Errorf("failed to create object %q in bucket %q: %w", input.Key, input.Bucket, err)
}
log.Info("object created", "object", objectInfo)

return nil
}

type s3Request struct {
Action string
Version string
Expand Down
4 changes: 4 additions & 0 deletions tools/metal/storage/pkg/objectstore/interfaces.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ package objectstore

import (
"context"
"io"
"net/http"
"time"
)
Expand All @@ -44,6 +45,9 @@ type Bucket interface {
// If the object does not exist, it returns (nil, nil).
GetObject(ctx context.Context, key string) (Object, error)

// PutObject creates the object with the given key.
PutObject(ctx context.Context, key string, r io.Reader) (*ObjectInfo, error)

// ListObjects returns the list of objects in the bucket.
ListObjects(ctx context.Context) ([]ObjectInfo, error)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ package testobjectstore

import (
"context"
"fmt"
"io"
"net/http"
"sync"
"time"
Expand Down Expand Up @@ -119,6 +121,28 @@ func (m *TestBucket) GetObject(ctx context.Context, key string) (objectstore.Obj
return obj, nil
}

func (m *TestBucket) PutObject(ctx context.Context, key string, r io.Reader) (*objectstore.ObjectInfo, error) {
m.mutex.Lock()
defer m.mutex.Unlock()

b, err := io.ReadAll(r)
if err != nil {
return nil, fmt.Errorf("reading data: %w", err)
}

info := objectstore.ObjectInfo{
Key: key,
LastModified: time.Now().UTC(),
Size: int64(len(b)),
}

m.objects[key] = &TestObject{
data: b,
info: info,
}
return &info, nil
}

type TestObject struct {
data []byte
info objectstore.ObjectInfo
Expand Down
20 changes: 10 additions & 10 deletions tools/metal/storage/pkg/s3model/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,21 +49,21 @@ type ListBucketResult struct {
}

type Object struct {
ChecksumAlgorithm string `xml:"ChecksumAlgorithm"`
ETag string `xml:"ETag"`
Key string `xml:"Key"`
LastModified string `xml:"LastModified"`
Owner Owner `xml:"Owner"`
RestoreStatus RestoreStatus `xml:"RestoreStatus"`
Size int64 `xml:"Size"`
StorageClass string `xml:"StorageClass"`
ChecksumAlgorithm string `xml:"ChecksumAlgorithm"`
ETag string `xml:"ETag"`
Key string `xml:"Key"`
LastModified string `xml:"LastModified"`
Owner *Owner `xml:"Owner"`
RestoreStatus *RestoreStatus `xml:"RestoreStatus"`
Size int64 `xml:"Size"`
StorageClass string `xml:"StorageClass"`
}
type Owner struct {
DisplayName string `xml:"DisplayName"`
ID string `xml:"ID"`
}

type RestoreStatus struct {
IsRestoreInProgress bool `xml:"IsRestoreInProgress"`
RestoreExpiryDate string `xml:"RestoreExpiryDate"`
IsRestoreInProgress bool `xml:"IsRestoreInProgress"`
RestoreExpiryDate *string `xml:"RestoreExpiryDate"`
}
6 changes: 6 additions & 0 deletions upup/pkg/fi/cloudup/apply_cluster.go
Original file line number Diff line number Diff line change
Expand Up @@ -482,6 +482,9 @@ func (c *ApplyClusterCmd) Run(ctx context.Context) (*ApplyResults, error) {
scwZone = scwCloud.Zone()
}

case kops.CloudProviderMetal:
// Metal is a special case, we don't need to do anything here (yet)

default:
return nil, fmt.Errorf("unknown CloudProvider %q", cluster.GetCloudProvider())
}
Expand Down Expand Up @@ -686,6 +689,9 @@ func (c *ApplyClusterCmd) Run(ctx context.Context) (*ApplyResults, error) {
&scalewaymodel.SSHKeyModelBuilder{ScwModelContext: scwModelContext, Lifecycle: securityLifecycle},
)

case kops.CloudProviderMetal:
// No special builders for bare metal (yet)

default:
return nil, fmt.Errorf("unknown cloudprovider %q", cluster.GetCloudProvider())
}
Expand Down
50 changes: 50 additions & 0 deletions upup/pkg/fi/cloudup/metal/api_target.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/*
Copyright 2024 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package metal

import (
"k8s.io/klog/v2"
"k8s.io/kops/upup/pkg/fi"
"k8s.io/kops/upup/pkg/fi/cloudup/awsup"
)

type APITarget struct {
Cloud *Cloud
OtherClouds []fi.Cloud
}

var _ fi.CloudupTarget = &APITarget{}

func NewAPITarget(cloud *Cloud, otherClouds []fi.Cloud) *APITarget {
return &APITarget{
Cloud: cloud,
OtherClouds: otherClouds,
}
}

func (t *APITarget) GetAWSCloud() awsup.AWSCloud {
klog.Fatalf("cannot find instance of AWSCloud in context")
return nil
}

func (t *APITarget) Finish(taskMap map[string]fi.CloudupTask) error {
return nil
}

func (t *APITarget) DefaultCheckExisting() bool {
return true
}
Loading

0 comments on commit d5c56dc

Please sign in to comment.