Skip to content

Commit 240f94e

Browse files
committed
WIP: better versioned checkpoints
1 parent 19d8be4 commit 240f94e

18 files changed

Lines changed: 1449 additions & 97 deletions

File tree

Makefile

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -102,15 +102,24 @@ coverage: test
102102
cat $(COVERAGE_FILE) | grep -v "_mock.go" > $(COVERAGE_FILE).no-mocks
103103
go tool cover -func=$(COVERAGE_FILE).no-mocks
104104

105-
generate: generate-deepcopy
105+
generate: generate-deepcopy generate-conversion
106106

107107
generate-deepcopy:
108108
for api in $(APIS); do \
109-
rm -f $(CURDIR)/api/$(VENDOR)/resource/$${api}/zz_generated.deepcopy.go; \
109+
rm -f $${api}/zz_generated.deepcopy.go; \
110110
controller-gen \
111111
object:headerFile=$(CURDIR)/hack/boilerplate.generatego.txt \
112-
paths=$(CURDIR)/api/$(VENDOR)/resource/$${api}/ \
113-
output:object:dir=$(CURDIR)/api/$(VENDOR)/resource/$${api}; \
112+
paths=$${api}/ \
113+
output:object:dir=$${api}; \
114+
done
115+
116+
generate-conversion:
117+
for api in $(APIS); do \
118+
rm -f $${api}/zz_generated.conversion.go; \
119+
conversion-gen \
120+
--go-header-file=$(CURDIR)/hack/boilerplate.generatego.txt \
121+
--output-file=zz_generated.conversion.go \
122+
$${api}/; \
114123
done
115124

116125
setup-e2e:
@@ -128,7 +137,6 @@ teardown-e2e:
128137
.build-image: docker/Dockerfile.devel
129138
if [ x"$(SKIP_IMAGE_BUILD)" = x"" ]; then \
130139
$(CONTAINER_TOOL) build \
131-
--progress=plain \
132140
--build-arg GOLANG_VERSION="$(GOLANG_VERSION)" \
133141
--tag $(BUILDIMAGE) \
134142
-f $(^) \

cmd/dra-example-kubeletplugin/cdi.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ import (
2626
cdiparser "tags.cncf.io/container-device-interface/pkg/parser"
2727
cdispec "tags.cncf.io/container-device-interface/specs-go"
2828

29-
"sigs.k8s.io/dra-example-driver/internal/profiles"
29+
checkpointapi "sigs.k8s.io/dra-example-driver/internal/api/checkpoint"
3030
)
3131

3232
const cdiCommonDeviceName = "common"
@@ -85,7 +85,7 @@ func (cdi *CDIHandler) CreateCommonSpecFile() error {
8585
return cdi.cache.WriteSpec(spec, specName)
8686
}
8787

88-
func (cdi *CDIHandler) CreateClaimSpecFile(claimUID string, devices profiles.PreparedDevices) error {
88+
func (cdi *CDIHandler) CreateClaimSpecFile(claimUID string, devices []checkpointapi.PreparedDevice) error {
8989
specName := cdiapi.GenerateTransientSpecName(cdi.vendor(), cdi.class, claimUID)
9090

9191
spec := &cdispec.Spec{

cmd/dra-example-kubeletplugin/checkpoint.go

Lines changed: 22 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -17,49 +17,31 @@
1717
package main
1818

1919
import (
20-
"encoding/json"
2120
"fmt"
2221
"os"
2322
"path/filepath"
2423

25-
"sigs.k8s.io/dra-example-driver/internal/profiles"
26-
)
27-
28-
type PreparedClaims map[string]profiles.PreparedDevices
29-
30-
type Checkpoint struct {
31-
V1 *CheckpointV1 `json:"v1,omitempty"`
32-
}
33-
34-
type CheckpointV1 struct {
35-
PreparedClaims PreparedClaims `json:"preparedClaims,omitempty"`
36-
}
24+
"k8s.io/apimachinery/pkg/runtime/serializer/json"
25+
drapbv1 "k8s.io/kubelet/pkg/apis/dra/v1beta1"
26+
"k8s.io/utils/ptr"
3727

38-
func newCheckpoint() *Checkpoint {
39-
pc := &Checkpoint{
40-
V1: &CheckpointV1{},
41-
}
42-
return pc
43-
}
28+
checkpointapi "sigs.k8s.io/dra-example-driver/internal/api/checkpoint"
29+
)
4430

45-
func readCheckpoint(path string) (*Checkpoint, error) {
31+
func readCheckpoint(serializer *json.Serializer, path string) (*checkpointapi.Checkpoint, error) {
4632
data, err := os.ReadFile(path)
4733
if err != nil {
4834
return nil, err
4935
}
50-
checkpoint := new(Checkpoint)
51-
err = json.Unmarshal(data, checkpoint)
36+
checkpoint := new(checkpointapi.Checkpoint)
37+
_, _, err = serializer.Decode(data, ptr.To(checkpointapi.SchemeGroupVersion.WithKind("Checkpoint")), checkpoint)
5238
if err != nil {
5339
return nil, fmt.Errorf("unmarshal json from %s: %w", path, err)
5440
}
5541
return checkpoint, nil
5642
}
5743

58-
func writeCheckpoint(path string, checkpoint *Checkpoint) (err error) {
59-
data, err := json.Marshal(checkpoint)
60-
if err != nil {
61-
return fmt.Errorf("marshal json: %w", err)
62-
}
44+
func writeCheckpoint(serializer *json.Serializer, path string, checkpoint *checkpointapi.Checkpoint) (err error) {
6345
dir := filepath.Dir(path)
6446
tmp, err := os.CreateTemp(dir, "tmp-checkpoint-*")
6547
if err != nil {
@@ -70,8 +52,9 @@ func writeCheckpoint(path string, checkpoint *Checkpoint) (err error) {
7052
err = fmt.Errorf("close temp file: %w", err1)
7153
}
7254
}()
73-
if _, err := tmp.Write(data); err != nil {
74-
return fmt.Errorf("write to temp file %s: %w", tmp.Name(), err)
55+
err = serializer.Encode(checkpoint, tmp)
56+
if err != nil {
57+
return fmt.Errorf("serialize json: %w", err)
7558
}
7659
if err := tmp.Sync(); err != nil {
7760
return fmt.Errorf("sync temp file: %w", err)
@@ -82,32 +65,15 @@ func writeCheckpoint(path string, checkpoint *Checkpoint) (err error) {
8265
return nil
8366
}
8467

85-
func (cp *Checkpoint) GetPreparedDevices(claimUID string) profiles.PreparedDevices {
86-
if cp.V1 == nil {
87-
return nil
68+
func getPreparedDevices(preparedClaim checkpointapi.PreparedClaim) []*drapbv1.Device {
69+
var devices []*drapbv1.Device
70+
for _, preparedDevice := range preparedClaim.PreparedDevices {
71+
devices = append(devices, &drapbv1.Device{
72+
RequestNames: preparedDevice.Device.RequestNames,
73+
PoolName: preparedDevice.Device.PoolName,
74+
DeviceName: preparedDevice.Device.DeviceName,
75+
CdiDeviceIds: preparedDevice.Device.CdiDeviceIds,
76+
})
8877
}
89-
if devices, ok := cp.V1.PreparedClaims[claimUID]; ok {
90-
return devices
91-
}
92-
return nil
93-
}
94-
95-
func (cp *Checkpoint) AddPreparedDevices(claimUID string, pds profiles.PreparedDevices) {
96-
if cp.V1 == nil {
97-
return
98-
}
99-
100-
if cp.V1.PreparedClaims == nil {
101-
cp.V1.PreparedClaims = make(PreparedClaims)
102-
}
103-
104-
cp.V1.PreparedClaims[claimUID] = pds
105-
}
106-
107-
func (cp *Checkpoint) RemovePreparedDevices(claimUID string) {
108-
if cp.V1 == nil {
109-
return
110-
}
111-
112-
delete(cp.V1.PreparedClaims, claimUID)
78+
return devices
11379
}

cmd/dra-example-kubeletplugin/state.go

Lines changed: 54 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ import (
3030
"k8s.io/dynamic-resource-allocation/resourceslice"
3131
drapbv1 "k8s.io/kubelet/pkg/apis/dra/v1beta1"
3232

33+
checkpointapi "sigs.k8s.io/dra-example-driver/internal/api/checkpoint"
34+
checkpointv1alpha1 "sigs.k8s.io/dra-example-driver/internal/api/checkpoint/v1alpha1"
3335
"sigs.k8s.io/dra-example-driver/internal/profiles"
3436
)
3537

@@ -42,13 +44,14 @@ type OpaqueDeviceConfig struct {
4244

4345
type DeviceState struct {
4446
sync.Mutex
45-
driverName string
46-
cdi *CDIHandler
47-
driverResources resourceslice.DriverResources
48-
allocatable AllocatableDevices
49-
configDecoder runtime.Decoder
50-
configHandler profiles.ConfigHandler
51-
checkpointPath string
47+
driverName string
48+
cdi *CDIHandler
49+
driverResources resourceslice.DriverResources
50+
allocatable AllocatableDevices
51+
configDecoder runtime.Decoder
52+
configHandler profiles.ConfigHandler
53+
checkpointPath string
54+
checkpointSerializer *json.Serializer
5255
}
5356

5457
func NewDeviceState(config *Config) (*DeviceState, error) {
@@ -84,6 +87,23 @@ func NewDeviceState(config *Config) (*DeviceState, error) {
8487
},
8588
)
8689

90+
checkpointScheme := runtime.NewScheme()
91+
sb = runtime.NewSchemeBuilder(
92+
checkpointapi.AddToScheme,
93+
checkpointv1alpha1.AddToScheme,
94+
)
95+
if err := sb.AddToScheme(checkpointScheme); err != nil {
96+
return nil, fmt.Errorf("create config scheme: %w", err)
97+
}
98+
checkpointSerializer := json.NewSerializerWithOptions(
99+
json.DefaultMetaFactory,
100+
checkpointScheme,
101+
checkpointScheme,
102+
json.SerializerOptions{
103+
Pretty: true, Strict: true,
104+
},
105+
)
106+
87107
allocatable := make(AllocatableDevices)
88108
for _, slice := range driverResources.Pools[config.flags.nodeName].Slices {
89109
for _, device := range slice.Devices {
@@ -92,24 +112,25 @@ func NewDeviceState(config *Config) (*DeviceState, error) {
92112
}
93113

94114
state := &DeviceState{
95-
driverName: config.flags.driverName,
96-
cdi: cdi,
97-
driverResources: driverResources,
98-
allocatable: allocatable,
99-
configDecoder: decoder,
100-
configHandler: configHandler,
101-
checkpointPath: filepath.Join(config.DriverPluginPath(), DriverPluginCheckpointFile),
115+
driverName: config.flags.driverName,
116+
cdi: cdi,
117+
driverResources: driverResources,
118+
allocatable: allocatable,
119+
configDecoder: decoder,
120+
configHandler: configHandler,
121+
checkpointPath: filepath.Join(config.DriverPluginPath(), DriverPluginCheckpointFile),
122+
checkpointSerializer: checkpointSerializer,
102123
}
103124

104-
_, err = readCheckpoint(state.checkpointPath)
125+
_, err = readCheckpoint(state.checkpointSerializer, state.checkpointPath)
105126
if err != nil && !errors.Is(err, fs.ErrNotExist) {
106127
return nil, fmt.Errorf("failed to read checkpoint: %w", err)
107128
}
108129
if err == nil {
109130
return state, nil
110131
}
111132

112-
if err := writeCheckpoint(state.checkpointPath, newCheckpoint()); err != nil {
133+
if err := writeCheckpoint(state.checkpointSerializer, state.checkpointPath, &checkpointapi.Checkpoint{}); err != nil {
113134
return nil, fmt.Errorf("unable to sync to checkpoint: %v", err)
114135
}
115136

@@ -122,30 +143,30 @@ func (s *DeviceState) Prepare(claim *resourceapi.ResourceClaim) ([]*drapbv1.Devi
122143

123144
claimUID := string(claim.UID)
124145

125-
checkpoint, err := readCheckpoint(s.checkpointPath)
146+
checkpoint, err := readCheckpoint(s.checkpointSerializer, s.checkpointPath)
126147
if err != nil {
127148
return nil, fmt.Errorf("unable to sync from checkpoint: %v", err)
128149
}
129150

130-
preparedDevices := checkpoint.GetPreparedDevices(claimUID)
131-
if preparedDevices != nil {
132-
return preparedDevices.GetDevices(), nil
151+
preparedClaim, ok := checkpoint.PreparedClaims[claim.UID]
152+
if ok {
153+
return getPreparedDevices(preparedClaim), nil
133154
}
134-
preparedDevices, err = s.prepareDevices(claim)
155+
preparedClaim, err = s.prepareDevices(claim)
135156
if err != nil {
136157
return nil, fmt.Errorf("prepare failed: %v", err)
137158
}
138159

139-
if err = s.cdi.CreateClaimSpecFile(claimUID, preparedDevices); err != nil {
160+
if err = s.cdi.CreateClaimSpecFile(claimUID, preparedClaim); err != nil {
140161
return nil, fmt.Errorf("unable to create CDI spec file for claim: %v", err)
141162
}
142163

143-
checkpoint.AddPreparedDevices(claimUID, preparedDevices)
164+
checkpoint.AddPreparedDevices(claimUID, preparedClaim)
144165
if err := writeCheckpoint(s.checkpointPath, checkpoint); err != nil {
145166
return nil, fmt.Errorf("unable to sync to checkpoint: %v", err)
146167
}
147168

148-
return preparedDevices.GetDevices(), nil
169+
return preparedClaim.GetDevices(), nil
149170
}
150171

151172
func (s *DeviceState) Unprepare(claimUID string) error {
@@ -182,9 +203,9 @@ func (s *DeviceState) Unprepare(claimUID string) error {
182203
return nil
183204
}
184205

185-
func (s *DeviceState) prepareDevices(claim *resourceapi.ResourceClaim) (profiles.PreparedDevices, error) {
206+
func (s *DeviceState) prepareDevices(claim *resourceapi.ResourceClaim) (checkpointapi.PreparedClaim, error) {
186207
if claim.Status.Allocation == nil {
187-
return nil, fmt.Errorf("claim not yet allocated")
208+
return checkpointapi.PreparedClaim{}, fmt.Errorf("claim not yet allocated")
188209
}
189210
// Check if any device request has admin access
190211
hasAdminAccess := s.checkAdminAccess(claim)
@@ -196,7 +217,7 @@ func (s *DeviceState) prepareDevices(claim *resourceapi.ResourceClaim) (profiles
196217
claim.Status.Allocation.Devices.Config,
197218
)
198219
if err != nil {
199-
return nil, fmt.Errorf("error getting opaque device configs: %v", err)
220+
return checkpointapi.PreparedClaim{}, fmt.Errorf("error getting opaque device configs: %v", err)
200221
}
201222

202223
// Add the default GPU Config to the front of the config list with the
@@ -213,7 +234,7 @@ func (s *DeviceState) prepareDevices(claim *resourceapi.ResourceClaim) (profiles
213234
continue
214235
}
215236
if _, exists := s.allocatable[result.Device]; !exists {
216-
return nil, fmt.Errorf("requested device is not allocatable: %v", result.Device)
237+
return checkpointapi.PreparedClaim{}, fmt.Errorf("requested device is not allocatable: %v", result.Device)
217238
}
218239
for _, c := range slices.Backward(configs) {
219240
if len(c.Requests) == 0 || slices.Contains(c.Requests, result.Request) {
@@ -231,7 +252,7 @@ func (s *DeviceState) prepareDevices(claim *resourceapi.ResourceClaim) (profiles
231252
// Apply the config to the list of results associated with it.
232253
containerEdits, err := s.configHandler.ApplyConfig(config, results)
233254
if err != nil {
234-
return nil, fmt.Errorf("error applying config: %w", err)
255+
return checkpointapi.PreparedClaim{}, fmt.Errorf("error applying config: %w", err)
235256
}
236257

237258
// Merge any new container edits with the overall per device map.
@@ -242,11 +263,11 @@ func (s *DeviceState) prepareDevices(claim *resourceapi.ResourceClaim) (profiles
242263

243264
// Walk through each config and its associated device allocation results
244265
// and construct the list of prepared devices to return.
245-
var preparedDevices profiles.PreparedDevices
266+
var preparedDevices []checkpointapi.PreparedDevice
246267
for _, results := range configResultsMap {
247268
for _, result := range results {
248-
device := &profiles.PreparedDevice{
249-
Device: drapbv1.Device{
269+
device := checkpointapi.PreparedDevice{
270+
Device: checkpointapi.Device{
250271
RequestNames: []string{result.Request},
251272
PoolName: result.Pool,
252273
DeviceName: result.Device,
@@ -259,7 +280,7 @@ func (s *DeviceState) prepareDevices(claim *resourceapi.ResourceClaim) (profiles
259280
}
260281
}
261282

262-
return preparedDevices, nil
283+
return checkpointapi.PreparedClaim{PreparedDevices: preparedDevices}, nil
263284
}
264285

265286
func (s *DeviceState) unprepareDevices(claimUID string, devices profiles.PreparedDevices) error {

common.mk

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ VERSION ?=
2121
vVERSION := v$(VERSION:v%=%)
2222

2323
VENDOR := example.com
24-
APIS := gpu/v1alpha1
24+
APIS := $(CURDIR)/api/$(VENDOR)/resource/gpu/v1alpha1 $(CURDIR)/internal/api/checkpoint $(CURDIR)/internal/api/checkpoint/v1alpha1
2525

2626
PLURAL_EXCEPTIONS = DeviceClassParameters:DeviceClassParameters
2727
PLURAL_EXCEPTIONS += GpuClaimParameters:GpuClaimParameters

docker/Dockerfile.devel

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,3 +20,4 @@ RUN go install github.com/gordonklaus/ineffassign@latest && \
2020
RUN go install github.com/golangci/golangci-lint/cmd/golangci-lint@v1.52.0
2121
RUN go install sigs.k8s.io/controller-tools/cmd/controller-gen@v0.20.0
2222
RUN go install k8s.io/code-generator/cmd/client-gen@v0.35.0
23+
RUN go install k8s.io/code-generator/cmd/conversion-gen@v0.35.0

internal/api/checkpoint/doc.go

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
/*
2+
Copyright 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+
// +k8s:deepcopy-gen=package
18+
19+
// Package checkpoint contains internal (unversioned) types for checkpoints.
20+
// These types are the canonical in-memory representation that the driver
21+
// programs against. Versioned types (e.g. v1alpha1) are converted to/from these
22+
// internal types via the scheme.
23+
package checkpoint

0 commit comments

Comments
 (0)