Skip to content

Commit d5c56dc

Browse files
authored
Merge pull request #16788 from justinsb/metal_test_2
metal: stub out functions to enable cluster creation
2 parents d269ac2 + 7f58570 commit d5c56dc

File tree

15 files changed

+289
-21
lines changed

15 files changed

+289
-21
lines changed

nodeup/pkg/model/bootstrap_client.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ func (b BootstrapClientBuilder) Build(c *fi.NodeupModelBuilderContext) error {
9494
}
9595
authenticator = a
9696

97-
case "metal":
97+
case kops.CloudProviderMetal:
9898
a, err := pkibootstrap.NewAuthenticatorFromFile("/etc/kubernetes/kops/pki/machine/private.pem")
9999
if err != nil {
100100
return err

pkg/model/components/apiserver.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,8 @@ func (b *KubeAPIServerOptionsBuilder) BuildOptions(cluster *kops.Cluster) error
112112
c.CloudProvider = "azure"
113113
case kops.CloudProviderScaleway:
114114
c.CloudProvider = "external"
115+
case kops.CloudProviderMetal:
116+
c.CloudProvider = "external"
115117
default:
116118
return fmt.Errorf("unknown cloudprovider %q", cluster.GetCloudProvider())
117119
}

pkg/model/components/etcdmanager/model.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -525,6 +525,11 @@ func (b *EtcdManagerBuilder) buildPod(etcdCluster kops.EtcdClusterSpec, instance
525525
fmt.Sprintf("%s=%s", scaleway.TagNameRolePrefix, scaleway.TagRoleControlPlane),
526526
}
527527
config.VolumeNameTag = fmt.Sprintf("%s=%s", scaleway.TagInstanceGroup, instanceGroupName)
528+
529+
case kops.CloudProviderMetal:
530+
config.VolumeProvider = "external"
531+
// TODO: Use static configuration here?
532+
528533
default:
529534
return nil, fmt.Errorf("CloudProvider %q not supported with etcd-manager", b.Cluster.GetCloudProvider())
530535
}

pkg/model/master_volumes.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,10 @@ func (b *MasterVolumeBuilder) Build(c *fi.CloudupModelBuilderContext) error {
122122
}
123123
case kops.CloudProviderScaleway:
124124
b.addScalewayVolume(c, name, volumeSize, zone, etcd, m, allMembers)
125+
126+
case kops.CloudProviderMetal:
127+
// Nothing special to do for Metal (yet)
128+
125129
default:
126130
return fmt.Errorf("unknown cloudprovider %q", b.Cluster.GetCloudProvider())
127131
}

tests/e2e/scenarios/bare-metal/run-test

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,17 @@ export S3_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY}
6161
# Create the state-store bucket in our mock s3 server
6262
export KOPS_STATE_STORE=s3://kops-state-store/
6363
aws --version
64-
aws --endpoint-url=${S3_ENDPOINT} --debug s3 mb s3://kops-state-store
64+
aws --endpoint-url=${S3_ENDPOINT} s3 mb s3://kops-state-store
6565

6666
# List clusters (there should not be any yet)
6767
go run ./cmd/kops get cluster || true
68+
69+
# Create a cluster
70+
go run ./cmd/kops create cluster --cloud=metal metal.k8s.local --zones main
71+
72+
# List clusters
73+
go run ./cmd/kops get cluster
74+
75+
# List instance groups
76+
go run ./cmd/kops get ig --name metal.k8s.local
77+
go run ./cmd/kops get ig --name metal.k8s.local -oyaml

tools/metal/storage/main.go

Lines changed: 62 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,9 @@ import (
2222
"flag"
2323
"fmt"
2424
"net/http"
25+
"net/url"
2526
"os"
2627
"strings"
27-
"time"
2828

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

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

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

110+
values, err := url.ParseQuery(r.URL.RawQuery)
111+
if err != nil {
112+
return fmt.Errorf("failed to parse query: %w", err)
113+
}
114+
110115
req := &s3Request{
111116
w: w,
112117
r: r,
@@ -121,7 +126,9 @@ func (s *S3Server) ServeRequest(ctx context.Context, w http.ResponseWriter, r *h
121126
switch r.Method {
122127
case http.MethodGet:
123128
return s.ListObjectsV2(ctx, req, &ListObjectsV2Input{
124-
Bucket: bucket,
129+
Bucket: bucket,
130+
Delimiter: values.Get("delimiter"),
131+
Prefix: values.Get("prefix"),
125132
})
126133
case http.MethodPut:
127134
return s.CreateBucket(ctx, req, &CreateBucketInput{
@@ -136,19 +143,36 @@ func (s *S3Server) ServeRequest(ctx context.Context, w http.ResponseWriter, r *h
136143

137144
if len(tokens) > 1 {
138145
bucket := tokens[0]
139-
return s.GetObject(ctx, req, &GetObjectInput{
140-
Bucket: bucket,
141-
Key: strings.TrimPrefix(r.URL.Path, "/"+bucket+"/"),
142-
})
146+
key := strings.TrimPrefix(r.URL.Path, "/"+bucket+"/")
147+
switch r.Method {
148+
case http.MethodGet:
149+
return s.GetObject(ctx, req, &GetObjectInput{
150+
Bucket: bucket,
151+
Key: key,
152+
})
153+
case http.MethodPut:
154+
return s.PutObject(ctx, req, &PutObjectInput{
155+
Bucket: bucket,
156+
Key: key,
157+
})
158+
default:
159+
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
160+
return nil
161+
}
143162
}
144163

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

148167
type ListObjectsV2Input struct {
149168
Bucket string
169+
170+
Delimiter string
171+
Prefix string
150172
}
151173

174+
const s3TimeFormat = "2006-01-02T15:04:05.000Z"
175+
152176
func (s *S3Server) ListObjectsV2(ctx context.Context, req *s3Request, input *ListObjectsV2Input) error {
153177
bucket, err := s.store.GetBucket(ctx, input.Bucket)
154178
if err != nil {
@@ -168,12 +192,17 @@ func (s *S3Server) ListObjectsV2(ctx context.Context, req *s3Request, input *Lis
168192
}
169193

170194
for _, object := range objects {
195+
if input.Prefix != "" && !strings.HasPrefix(object.Key, input.Prefix) {
196+
continue
197+
}
198+
// TODO: support delimiter
171199
output.Contents = append(output.Contents, s3model.Object{
172200
Key: object.Key,
173-
LastModified: object.LastModified.Format(time.RFC3339),
201+
LastModified: object.LastModified.Format(s3TimeFormat),
174202
Size: object.Size,
175203
})
176204
}
205+
output.KeyCount = len(output.Contents)
177206

178207
return req.writeXML(ctx, output)
179208
}
@@ -232,6 +261,31 @@ func (s *S3Server) GetObject(ctx context.Context, req *s3Request, input *GetObje
232261
return object.WriteTo(req.w)
233262
}
234263

264+
type PutObjectInput struct {
265+
Bucket string
266+
Key string
267+
}
268+
269+
func (s *S3Server) PutObject(ctx context.Context, req *s3Request, input *PutObjectInput) error {
270+
log := klog.FromContext(ctx)
271+
272+
bucket, err := s.store.GetBucket(ctx, input.Bucket)
273+
if err != nil {
274+
return fmt.Errorf("failed to get bucket %q: %w", input.Bucket, err)
275+
}
276+
if bucket == nil {
277+
return req.writeError(ctx, http.StatusNotFound, nil)
278+
}
279+
280+
objectInfo, err := bucket.PutObject(ctx, input.Key, req.r.Body)
281+
if err != nil {
282+
return fmt.Errorf("failed to create object %q in bucket %q: %w", input.Key, input.Bucket, err)
283+
}
284+
log.Info("object created", "object", objectInfo)
285+
286+
return nil
287+
}
288+
235289
type s3Request struct {
236290
Action string
237291
Version string

tools/metal/storage/pkg/objectstore/interfaces.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ package objectstore
1818

1919
import (
2020
"context"
21+
"io"
2122
"net/http"
2223
"time"
2324
)
@@ -44,6 +45,9 @@ type Bucket interface {
4445
// If the object does not exist, it returns (nil, nil).
4546
GetObject(ctx context.Context, key string) (Object, error)
4647

48+
// PutObject creates the object with the given key.
49+
PutObject(ctx context.Context, key string, r io.Reader) (*ObjectInfo, error)
50+
4751
// ListObjects returns the list of objects in the bucket.
4852
ListObjects(ctx context.Context) ([]ObjectInfo, error)
4953
}

tools/metal/storage/pkg/objectstore/testobjectstore/testobjectstore.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ package testobjectstore
1818

1919
import (
2020
"context"
21+
"fmt"
22+
"io"
2123
"net/http"
2224
"sync"
2325
"time"
@@ -119,6 +121,28 @@ func (m *TestBucket) GetObject(ctx context.Context, key string) (objectstore.Obj
119121
return obj, nil
120122
}
121123

124+
func (m *TestBucket) PutObject(ctx context.Context, key string, r io.Reader) (*objectstore.ObjectInfo, error) {
125+
m.mutex.Lock()
126+
defer m.mutex.Unlock()
127+
128+
b, err := io.ReadAll(r)
129+
if err != nil {
130+
return nil, fmt.Errorf("reading data: %w", err)
131+
}
132+
133+
info := objectstore.ObjectInfo{
134+
Key: key,
135+
LastModified: time.Now().UTC(),
136+
Size: int64(len(b)),
137+
}
138+
139+
m.objects[key] = &TestObject{
140+
data: b,
141+
info: info,
142+
}
143+
return &info, nil
144+
}
145+
122146
type TestObject struct {
123147
data []byte
124148
info objectstore.ObjectInfo

tools/metal/storage/pkg/s3model/api.go

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -49,21 +49,21 @@ type ListBucketResult struct {
4949
}
5050

5151
type Object struct {
52-
ChecksumAlgorithm string `xml:"ChecksumAlgorithm"`
53-
ETag string `xml:"ETag"`
54-
Key string `xml:"Key"`
55-
LastModified string `xml:"LastModified"`
56-
Owner Owner `xml:"Owner"`
57-
RestoreStatus RestoreStatus `xml:"RestoreStatus"`
58-
Size int64 `xml:"Size"`
59-
StorageClass string `xml:"StorageClass"`
52+
ChecksumAlgorithm string `xml:"ChecksumAlgorithm"`
53+
ETag string `xml:"ETag"`
54+
Key string `xml:"Key"`
55+
LastModified string `xml:"LastModified"`
56+
Owner *Owner `xml:"Owner"`
57+
RestoreStatus *RestoreStatus `xml:"RestoreStatus"`
58+
Size int64 `xml:"Size"`
59+
StorageClass string `xml:"StorageClass"`
6060
}
6161
type Owner struct {
6262
DisplayName string `xml:"DisplayName"`
6363
ID string `xml:"ID"`
6464
}
6565

6666
type RestoreStatus struct {
67-
IsRestoreInProgress bool `xml:"IsRestoreInProgress"`
68-
RestoreExpiryDate string `xml:"RestoreExpiryDate"`
67+
IsRestoreInProgress bool `xml:"IsRestoreInProgress"`
68+
RestoreExpiryDate *string `xml:"RestoreExpiryDate"`
6969
}

upup/pkg/fi/cloudup/apply_cluster.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -482,6 +482,9 @@ func (c *ApplyClusterCmd) Run(ctx context.Context) (*ApplyResults, error) {
482482
scwZone = scwCloud.Zone()
483483
}
484484

485+
case kops.CloudProviderMetal:
486+
// Metal is a special case, we don't need to do anything here (yet)
487+
485488
default:
486489
return nil, fmt.Errorf("unknown CloudProvider %q", cluster.GetCloudProvider())
487490
}
@@ -686,6 +689,9 @@ func (c *ApplyClusterCmd) Run(ctx context.Context) (*ApplyResults, error) {
686689
&scalewaymodel.SSHKeyModelBuilder{ScwModelContext: scwModelContext, Lifecycle: securityLifecycle},
687690
)
688691

692+
case kops.CloudProviderMetal:
693+
// No special builders for bare metal (yet)
694+
689695
default:
690696
return nil, fmt.Errorf("unknown cloudprovider %q", cluster.GetCloudProvider())
691697
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
/*
2+
Copyright 2024 The Kubernetes Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package metal
18+
19+
import (
20+
"k8s.io/klog/v2"
21+
"k8s.io/kops/upup/pkg/fi"
22+
"k8s.io/kops/upup/pkg/fi/cloudup/awsup"
23+
)
24+
25+
type APITarget struct {
26+
Cloud *Cloud
27+
OtherClouds []fi.Cloud
28+
}
29+
30+
var _ fi.CloudupTarget = &APITarget{}
31+
32+
func NewAPITarget(cloud *Cloud, otherClouds []fi.Cloud) *APITarget {
33+
return &APITarget{
34+
Cloud: cloud,
35+
OtherClouds: otherClouds,
36+
}
37+
}
38+
39+
func (t *APITarget) GetAWSCloud() awsup.AWSCloud {
40+
klog.Fatalf("cannot find instance of AWSCloud in context")
41+
return nil
42+
}
43+
44+
func (t *APITarget) Finish(taskMap map[string]fi.CloudupTask) error {
45+
return nil
46+
}
47+
48+
func (t *APITarget) DefaultCheckExisting() bool {
49+
return true
50+
}

0 commit comments

Comments
 (0)