Skip to content

Commit 110d3d0

Browse files
committed
capacity: read-only Capacity struct
The previous approach of adapting remaining capacity while adding or removing volumes was unnecessarily complex. When we accept that we need to sum up allocated space repeatedly, the remaining code becomes simpler.
1 parent 07bb5e7 commit 110d3d0

File tree

3 files changed

+37
-83
lines changed

3 files changed

+37
-83
lines changed

pkg/hostpath/capacity.go

Lines changed: 2 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -22,15 +22,12 @@ import (
2222
"fmt"
2323
"strings"
2424

25-
"google.golang.org/grpc/codes"
26-
"google.golang.org/grpc/status"
2725
"k8s.io/apimachinery/pkg/api/resource"
2826
)
2927

3028
// Capacity simulates linear storage of certain types ("fast",
31-
// "slow"). When volumes of those types get created, they must
32-
// allocate storage (which can fail!) and that storage must
33-
// be freed again when volumes get destroyed.
29+
// "slow"). To calculate the amount of allocated space, the size of
30+
// all currently existing volumes of the same kind is summed up.
3431
//
3532
// Available capacity is configurable with a command line flag
3633
// -capacity <type>=<size> where <type> is a string and <size>
@@ -63,63 +60,6 @@ func (c *Capacity) String() string {
6360

6461
var _ flag.Value = &Capacity{}
6562

66-
// Alloc reserves a certain amount of bytes. Errors are
67-
// usable as result of gRPC calls. Empty kind means
68-
// that any large enough one is fine.
69-
func (c *Capacity) Alloc(kind string, size int64) (actualKind string, err error) {
70-
requested := *resource.NewQuantity(size, resource.BinarySI)
71-
72-
if kind == "" {
73-
for k, quantity := range *c {
74-
if quantity.Value() >= size {
75-
kind = k
76-
break
77-
}
78-
}
79-
// Still nothing?
80-
if kind == "" {
81-
available := c.Check("")
82-
return "", status.Error(codes.ResourceExhausted,
83-
fmt.Sprintf("not enough capacity: have %s, need %s", available.String(), requested.String()))
84-
}
85-
}
86-
87-
available, ok := (*c)[kind]
88-
if !ok {
89-
return "", status.Error(codes.InvalidArgument, fmt.Sprintf("unknown capacity kind: %q", kind))
90-
}
91-
if available.Cmp(requested) < 0 {
92-
return "", status.Error(codes.ResourceExhausted,
93-
fmt.Sprintf("not enough capacity of kind %q: have %s, need %s", kind, available.String(), requested.String()))
94-
}
95-
available.Sub(requested)
96-
(*c)[kind] = available
97-
return kind, nil
98-
}
99-
100-
// Free returns capacity reserved earlier with Alloc.
101-
func (c *Capacity) Free(kind string, size int64) {
102-
available := (*c)[kind]
103-
available.Add(*resource.NewQuantity(size, resource.BinarySI))
104-
(*c)[kind] = available
105-
}
106-
107-
// Check reports available capacity for a certain kind.
108-
// If empty, it reports the maximum capacity.
109-
func (c *Capacity) Check(kind string) resource.Quantity {
110-
if kind != "" {
111-
quantity := (*c)[kind]
112-
return quantity
113-
}
114-
available := resource.Quantity{}
115-
for _, q := range *c {
116-
if q.Cmp(available) >= 0 {
117-
available = q
118-
}
119-
}
120-
return available
121-
}
122-
12363
// Enabled returns true if capacities are configured.
12464
func (c *Capacity) Enabled() bool {
12565
return len(*c) > 0

pkg/hostpath/controllerserver.go

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -363,9 +363,13 @@ func (hp *hostPath) GetCapacity(ctx context.Context, req *csi.GetCapacityRequest
363363
// Without configured capacity, we just have the maximum size.
364364
available := hp.config.MaxVolumeSize
365365
if hp.config.Capacity.Enabled() {
366+
// Empty "kind" will return "zero capacity". There is no fallback
367+
// to some arbitrary kind here because in practice it always should
368+
// be set.
366369
kind := req.GetParameters()[storageKind]
367-
quantity := hp.config.Capacity.Check(kind)
368-
available = quantity.Value()
370+
quantity := hp.config.Capacity[kind]
371+
allocated := hp.sumVolumeSizes(kind)
372+
available = quantity.Value() - allocated
369373
}
370374
maxVolumeSize := hp.config.MaxVolumeSize
371375
if maxVolumeSize < available {

pkg/hostpath/hostpath.go

Lines changed: 29 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ import (
3333
timestamp "github.com/golang/protobuf/ptypes/timestamp"
3434
"google.golang.org/grpc/codes"
3535
"google.golang.org/grpc/status"
36+
"k8s.io/apimachinery/pkg/api/resource"
3637
fs "k8s.io/kubernetes/pkg/volume/util/fs"
3738
"k8s.io/kubernetes/pkg/volume/util/volumepathhandler"
3839
utilexec "k8s.io/utils/exec"
@@ -219,12 +220,6 @@ func (hp *hostPath) discoveryExistingVolumes() error {
219220
if err != nil {
220221
return err
221222
}
222-
223-
if hpv.Kind != "" && hp.config.Capacity.Enabled() {
224-
if _, err := hp.config.Capacity.Alloc(hpv.Kind, hpv.VolSize); err != nil {
225-
return fmt.Errorf("existing volume(s) do not match new capacity configuration: %v", err)
226-
}
227-
}
228223
hp.volumes[hpv.VolID] = *hpv
229224
}
230225

@@ -281,17 +276,26 @@ func (hp *hostPath) createVolume(volID, name string, cap int64, volAccessType ac
281276
return nil, status.Errorf(codes.OutOfRange, "Requested capacity %d exceeds maximum allowed %d", cap, hp.config.MaxVolumeSize)
282277
}
283278
if hp.config.Capacity.Enabled() {
284-
actualKind, err := hp.config.Capacity.Alloc(kind, cap)
285-
if err != nil {
286-
return nil, err
287-
}
288-
// Free the capacity in case of any error - either a volume gets created or it doesn't.
289-
defer func() {
290-
if finalErr != nil {
291-
hp.config.Capacity.Free(actualKind, cap)
279+
if kind == "" {
280+
// Pick some kind with sufficient remaining capacity.
281+
for k, c := range hp.config.Capacity {
282+
if hp.sumVolumeSizes(k)+cap <= c.Value() {
283+
kind = k
284+
break
285+
}
292286
}
293-
}()
294-
kind = actualKind
287+
}
288+
if kind == "" {
289+
// Still nothing?!
290+
return nil, status.Errorf(codes.OutOfRange, "requested capacity %d of arbitrary storage exceeds all remaining capacity", cap)
291+
}
292+
used := hp.sumVolumeSizes(kind)
293+
available := hp.config.Capacity[kind]
294+
if used+cap > available.Value() {
295+
296+
return nil, status.Errorf(codes.OutOfRange, "requested capacity %d exceeds remaining capacity for %q, %s out of %s already used",
297+
cap, kind, resource.NewQuantity(used, resource.BinarySI).String(), available.String())
298+
}
295299
} else if kind != "" {
296300
return nil, status.Error(codes.InvalidArgument, fmt.Sprintf("capacity tracking disabled, specifying kind %q is invalid", kind))
297301
}
@@ -383,14 +387,20 @@ func (hp *hostPath) deleteVolume(volID string) error {
383387
if err := os.RemoveAll(path); err != nil && !os.IsNotExist(err) {
384388
return err
385389
}
386-
if hp.config.Capacity.Enabled() {
387-
hp.config.Capacity.Free(vol.Kind, vol.VolSize)
388-
}
389390
delete(hp.volumes, volID)
390391
glog.V(4).Infof("deleted hostpath volume: %s = %+v", volID, vol)
391392
return nil
392393
}
393394

395+
func (hp *hostPath) sumVolumeSizes(kind string) (sum int64) {
396+
for _, volume := range hp.volumes {
397+
if volume.Kind == kind {
398+
sum += volume.VolSize
399+
}
400+
}
401+
return
402+
}
403+
394404
// hostPathIsEmpty is a simple check to determine if the specified hostpath directory
395405
// is empty or not.
396406
func hostPathIsEmpty(p string) (bool, error) {

0 commit comments

Comments
 (0)