Skip to content
This repository was archived by the owner on Jun 23, 2020. It is now read-only.

Commit d7875c4

Browse files
authored
Round up volumes smaller than 50GB (#121)
* Implement support for rounding up small volume requests
1 parent 05197cc commit d7875c4

File tree

7 files changed

+112
-73
lines changed

7 files changed

+112
-73
lines changed

Makefile

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ version:
117117
@echo ${VERSION}
118118

119119
.PHONY: run-dev
120-
run-dev: build
121-
NODE_NAME=localhost OCI_VOLUME_PROVISIONER_REGION=$(OCI_SHORT_REGION) ./dist/oci-volume-provisioner \
120+
run-dev:
121+
NODE_NAME=$(shell hostname) OCI_VOLUME_PROVISIONER_REGION=$(OCI_SHORT_REGION) go run cmd/main.go \
122122
--kubeconfig=${KUBECONFIG} \
123123
-v=4

cmd/main.go

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ import (
2929
"k8s.io/client-go/informers"
3030
"k8s.io/client-go/kubernetes"
3131
"k8s.io/client-go/tools/clientcmd"
32+
33+
"k8s.io/apimachinery/pkg/api/resource"
3234
)
3335

3436
const (
@@ -56,6 +58,8 @@ func main() {
5658
syscall.Umask(0)
5759

5860
kubeconfig := flag.String("kubeconfig", "", "Path to Kubeconfig file with authorization and master location information.")
61+
volumeRoundingEnabled := flag.Bool("rounding-enabled", true, "When enabled volumes will be rounded up if less than 'minVolumeSizeMB'")
62+
minVolumeSize := flag.String("min-volume-size", "50Gi", "The minimum size for a block volume. By default OCI only supports block volumes > 50GB")
5963
master := flag.String("master", "", "The address of the Kubernetes API server (overrides any value in kubeconfig).")
6064
flag.Parse()
6165
flag.Set("logtostderr", "true")
@@ -90,9 +94,14 @@ func main() {
9094

9195
sharedInformerFactory := informers.NewSharedInformerFactory(clientset, informerResyncPeriod(minResyncPeriod)())
9296

97+
volumeSizeLowerBound, err := resource.ParseQuantity(*minVolumeSize)
98+
if err != nil {
99+
glog.Fatalf("Cannot parse volume size %s", *minVolumeSize)
100+
}
101+
93102
// Create the provisioner: it implements the Provisioner interface expected by
94103
// the controller
95-
ociProvisioner := core.NewOCIProvisioner(clientset, sharedInformerFactory.Core().V1().Nodes(), nodeName)
104+
ociProvisioner := core.NewOCIProvisioner(clientset, sharedInformerFactory.Core().V1().Nodes(), nodeName, *volumeRoundingEnabled, volumeSizeLowerBound)
96105

97106
// Start the provision controller which will dynamically provision oci
98107
// PVs

pkg/oci/client/client.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@ func FromConfig(cfg *Config) (ProvisionerClient, error) {
129129

130130
metadata, err := instancemeta.New().Get()
131131
if err != nil {
132-
glog.Warning("Unable to retrieve instance metadata: %v", err)
132+
glog.Warningf("Unable to retrieve instance metadata: %s", err)
133133
}
134134

135135
return &provisionerClient{

pkg/oci/client/config_test.go

Lines changed: 0 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -34,31 +34,6 @@ auth:
3434
user: ocid1.user.oc1..aaaaaaaai77mql2xerv7cn6wu3nhxang3y4jk56vo5bn5l5lysl34avnui3q
3535
key: |
3636
-----BEGIN RSA PRIVATE KEY-----
37-
MIIEowIBAAKCAQEA4KpLGy/BLbph55HMjWLxCO657DLQTk4o+WWPi1+5oeAUVgyh
38-
kdvPR22jn9HiAL9jKv7PR3/OdHSp/6E3d05htksI7Tct4M/eWVMGRIzoMJvpJ99e
39-
ZP7MtQT9yknbJDSJoibSwLmPoInnPE/WbcgrTKSAfNURK0bKw1tnLd85qt7zdLI3
40-
g6O/14Bsmf+ovGiQHP6oiTuC4l3D8eTLlKdSrRVqZXhdvslpZU8MtNB8pPHMB4GZ
41-
R6HccBi7TJY7kkNg+5flRBTdYL8bvaji3zxSlvawvet+bJmEtApkUoLnovLCviVp
42-
NVTJZb5iQxMJLZlDJJT/ruq+HMJ3PiiYFOjFVwIDAQABAoIBAQDNkiT9MFoj/Hpf
43-
SOKRsKn60W3gObKvJAeMBKkvD50tCHuzLQWeEDJ/GkxxDbwtkPItwlBqDQEdQC7Z
44-
UGwPR/JSuh/l5uqc3beHpleC3CgNamwSZunZoegv7uxGcAQMAeK6M6n+XQyWCflD
45-
D46Wj2VHUPKcxt1Z6wHXdchYifwbYwUNA3hOlRJK3ODgk/X6UjTGb3+gpY3qU4kX
46-
Iz5L1ekCSgVIPBFVwdZQUyUC7+iIySaK+qcmEEx/UwOZ6uxhcmRzca31cjeaRS4H
47-
pUjrl/aqLIW57E2MQ/vSzfQn7kEGBOrS0RjHZgq9u4Qdq6EkjHj3fenKpwWB7S1z
48-
4t0PpinJAoGBAPRmxAcCd88EhWh5HhN+RWjmXdDCOmZ0yXbxxVBTQtK5pPnP8I9A
49-
3Jd2ughHk7dFBvgKbHkVsyWgAk8zRZdD2hkQBOXvoeJF2scmvgFUBs1otf6xiFsf
50-
IC0I8A/wXn3IHmyrG7xmPAtHWKvTTAFg7IjIIofcX7cuzMeLXEUMvLQdAoGBAOtT
51-
wJCtPTNs4c3vhO4gba98c30U3tHmbLVKJXGEeZkSv3/ez5eIiYBJTzwLB2+ppy8j
52-
2lYsdkLvsoyKF3LUwyt0gsX+AU9DJ2dmSJZ3E67UHsY6+qog5QlYfWWD8mKWeE9L
53-
2r0rhG6l0WHR15LdvVc9MJ8e3YVUvNJJJJhQ2v0DAoGAAosXOyNxb7wST1YDVBya
54-
SE8tZsC+rtZESnKVpRJYvayk5NyfGj6IjSL1KKTmCqAzRF2HZ3MsXBXgMEbOUJaq
55-
LFyYUHQ/8QTdE/l5PLZNI9IVIsNiMeCPCyjuppvPv+tXNbZKIZnGwi9J4u/d+J2z
56-
mHDMuzE15cgc5W6z1Rwe0pkCgYBzRwvF05dvYZ8bqoGLxQb2OBi65UZhvGb0R+Yf
57-
va1zduOoWBWJPbFdzoup9h0mbg0f4ohKPm2QTKtCfUMPVXpmByUoqE0r7tGWrVxR
58-
mPNjaTXKFYpFXOfVtCt5VzGdaeh1r8rvcCnnqgLv0EOyBj2CRs9So2QQtHnq6Tms
59-
A6/C0QKBgAw8IsCnkNoZujCEOR/6ZHbK3eeyAs2yuJumsjYYosIGZ/bzsXTpfzAw
60-
bs45GZxrW67zB/0HA7bVWS9ZkCVflHI2uBCFofm+y55IAzg9/c1xYU19PA3KRxHZ
61-
D/yEDdXVK/lIzNt7kIMFhtoYGrwv1JQGfK5Wh2bi+AwbBDZ45/17
6237
-----END RSA PRIVATE KEY-----
6338
fingerprint: 97:84:f7:26:a3:7b:74:d0:bd:4e:08:a7:79:c9:d0:1d
6439
`
@@ -68,31 +43,6 @@ auth:
6843
user: ocid1.user.oc1..aaaaaaaai77mql2xerv7cn6wu3nhxang3y4jk56vo5bn5l5lysl34avnui3q
6944
key: |
7045
-----BEGIN RSA PRIVATE KEY-----
71-
MIIEowIBAAKCAQEA4KpLGy/BLbph55HMjWLxCO657DLQTk4o+WWPi1+5oeAUVgyh
72-
kdvPR22jn9HiAL9jKv7PR3/OdHSp/6E3d05htksI7Tct4M/eWVMGRIzoMJvpJ99e
73-
ZP7MtQT9yknbJDSJoibSwLmPoInnPE/WbcgrTKSAfNURK0bKw1tnLd85qt7zdLI3
74-
g6O/14Bsmf+ovGiQHP6oiTuC4l3D8eTLlKdSrRVqZXhdvslpZU8MtNB8pPHMB4GZ
75-
R6HccBi7TJY7kkNg+5flRBTdYL8bvaji3zxSlvawvet+bJmEtApkUoLnovLCviVp
76-
NVTJZb5iQxMJLZlDJJT/ruq+HMJ3PiiYFOjFVwIDAQABAoIBAQDNkiT9MFoj/Hpf
77-
SOKRsKn60W3gObKvJAeMBKkvD50tCHuzLQWeEDJ/GkxxDbwtkPItwlBqDQEdQC7Z
78-
UGwPR/JSuh/l5uqc3beHpleC3CgNamwSZunZoegv7uxGcAQMAeK6M6n+XQyWCflD
79-
D46Wj2VHUPKcxt1Z6wHXdchYifwbYwUNA3hOlRJK3ODgk/X6UjTGb3+gpY3qU4kX
80-
Iz5L1ekCSgVIPBFVwdZQUyUC7+iIySaK+qcmEEx/UwOZ6uxhcmRzca31cjeaRS4H
81-
pUjrl/aqLIW57E2MQ/vSzfQn7kEGBOrS0RjHZgq9u4Qdq6EkjHj3fenKpwWB7S1z
82-
4t0PpinJAoGBAPRmxAcCd88EhWh5HhN+RWjmXdDCOmZ0yXbxxVBTQtK5pPnP8I9A
83-
3Jd2ughHk7dFBvgKbHkVsyWgAk8zRZdD2hkQBOXvoeJF2scmvgFUBs1otf6xiFsf
84-
IC0I8A/wXn3IHmyrG7xmPAtHWKvTTAFg7IjIIofcX7cuzMeLXEUMvLQdAoGBAOtT
85-
wJCtPTNs4c3vhO4gba98c30U3tHmbLVKJXGEeZkSv3/ez5eIiYBJTzwLB2+ppy8j
86-
2lYsdkLvsoyKF3LUwyt0gsX+AU9DJ2dmSJZ3E67UHsY6+qog5QlYfWWD8mKWeE9L
87-
2r0rhG6l0WHR15LdvVc9MJ8e3YVUvNJJJJhQ2v0DAoGAAosXOyNxb7wST1YDVBya
88-
SE8tZsC+rtZESnKVpRJYvayk5NyfGj6IjSL1KKTmCqAzRF2HZ3MsXBXgMEbOUJaq
89-
LFyYUHQ/8QTdE/l5PLZNI9IVIsNiMeCPCyjuppvPv+tXNbZKIZnGwi9J4u/d+J2z
90-
mHDMuzE15cgc5W6z1Rwe0pkCgYBzRwvF05dvYZ8bqoGLxQb2OBi65UZhvGb0R+Yf
91-
va1zduOoWBWJPbFdzoup9h0mbg0f4ohKPm2QTKtCfUMPVXpmByUoqE0r7tGWrVxR
92-
mPNjaTXKFYpFXOfVtCt5VzGdaeh1r8rvcCnnqgLv0EOyBj2CRs9So2QQtHnq6Tms
93-
A6/C0QKBgAw8IsCnkNoZujCEOR/6ZHbK3eeyAs2yuJumsjYYosIGZ/bzsXTpfzAw
94-
bs45GZxrW67zB/0HA7bVWS9ZkCVflHI2uBCFofm+y55IAzg9/c1xYU19PA3KRxHZ
95-
D/yEDdXVK/lIzNt7kIMFhtoYGrwv1JQGfK5Wh2bi+AwbBDZ45/17
9646
-----END RSA PRIVATE KEY-----
9747
fingerprint: 97:84:f7:26:a3:7b:74:d0:bd:4e:08:a7:79:c9:d0:1d
9848
`

pkg/provisioner/block/block.go

Lines changed: 28 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import (
2424

2525
"github.com/golang/glog"
2626
"github.com/kubernetes-incubator/external-storage/lib/controller"
27+
"k8s.io/apimachinery/pkg/api/resource"
2728
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2829
"k8s.io/client-go/pkg/api/v1"
2930

@@ -45,17 +46,21 @@ const (
4546

4647
// blockProvisioner is the internal provisioner for OCI block volumes
4748
type blockProvisioner struct {
48-
client client.ProvisionerClient
49-
metadata instancemeta.Interface
49+
client client.ProvisionerClient
50+
metadata instancemeta.Interface
51+
volumeRoundingEnabled bool
52+
minVolumeSize resource.Quantity
5053
}
5154

5255
var _ plugin.ProvisionerPlugin = &blockProvisioner{}
5356

5457
// NewBlockProvisioner creates a new instance of the block storage provisioner
55-
func NewBlockProvisioner(client client.ProvisionerClient, metadata instancemeta.Interface) plugin.ProvisionerPlugin {
58+
func NewBlockProvisioner(client client.ProvisionerClient, metadata instancemeta.Interface, volumeRoundingEnabled bool, minVolumeSize resource.Quantity) plugin.ProvisionerPlugin {
5659
return &blockProvisioner{
57-
client: client,
58-
metadata: metadata,
60+
client: client,
61+
metadata: metadata,
62+
volumeRoundingEnabled: volumeRoundingEnabled,
63+
minVolumeSize: minVolumeSize,
5964
}
6065
}
6166

@@ -75,19 +80,29 @@ func roundUpSize(volumeSizeBytes int64, allocationUnitBytes int64) int64 {
7580
return (volumeSizeBytes + allocationUnitBytes - 1) / allocationUnitBytes
7681
}
7782

78-
// Provision creates an OCI block volume acording to the spec
83+
// Provision creates an OCI block volume
7984
func (block *blockProvisioner) Provision(options controller.VolumeOptions, ad *identity.AvailabilityDomain) (*v1.PersistentVolume, error) {
8085
for _, accessMode := range options.PVC.Spec.AccessModes {
8186
if accessMode != v1.ReadWriteOnce {
82-
return nil, fmt.Errorf("invalid access mode %q specified (only %q is supported)", accessMode, v1.ReadWriteOnce)
87+
return nil, fmt.Errorf("invalid access mode %v specified. Only %v is supported", accessMode, v1.ReadWriteOnce)
8388
}
8489
}
8590

86-
// Calculate the size
87-
volSize := options.PVC.Spec.Resources.Requests[v1.ResourceName(v1.ResourceStorage)]
88-
volSizeBytes := volSize.Value()
89-
glog.Infof("Volume size (bytes): %v", volSizeBytes)
90-
volSizeMB := int(roundUpSize(volSizeBytes, 1024*1024))
91+
// Calculate the volume size
92+
capacity, ok := options.PVC.Spec.Resources.Requests[v1.ResourceName(v1.ResourceStorage)]
93+
if !ok {
94+
return nil, fmt.Errorf("could not determine volume size for PVC")
95+
}
96+
97+
volSizeMB := int(roundUpSize(capacity.Value(), 1024*1024))
98+
glog.Infof("Volume size: %dMB", volSizeMB)
99+
100+
if block.volumeRoundingEnabled && block.minVolumeSize.Cmp(capacity) == 1 {
101+
glog.Warningf("PVC requested storage less than %s. Rounding up to ensure volume creation", block.minVolumeSize.String())
102+
103+
volSizeMB = int(roundUpSize(block.minVolumeSize.Value(), 1024*1024))
104+
capacity = block.minVolumeSize
105+
}
91106

92107
glog.Infof("Creating volume size=%v AD=%s compartmentOCID=%q", volSizeMB, *ad.Name, block.client.CompartmentOCID())
93108

@@ -117,7 +132,6 @@ func (block *blockProvisioner) Provision(options controller.VolumeOptions, ad *i
117132
return nil, err
118133
}
119134

120-
//volumeName := mapVolumeIDToName(*newVolume.Id)
121135
filesystemType := resolveFSType(options)
122136

123137
region, ok := os.LookupEnv("OCI_SHORT_REGION")
@@ -144,7 +158,7 @@ func (block *blockProvisioner) Provision(options controller.VolumeOptions, ad *i
144158
PersistentVolumeReclaimPolicy: options.PersistentVolumeReclaimPolicy,
145159
AccessModes: options.PVC.Spec.AccessModes,
146160
Capacity: v1.ResourceList{
147-
v1.ResourceName(v1.ResourceStorage): options.PVC.Spec.Resources.Requests[v1.ResourceName(v1.ResourceStorage)],
161+
v1.ResourceName(v1.ResourceStorage): capacity,
148162
},
149163
PersistentVolumeSource: v1.PersistentVolumeSource{
150164
FlexVolume: &v1.FlexVolumeSource{

pkg/provisioner/block/block_test.go

Lines changed: 68 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ package block
1616

1717
import (
1818
"context"
19+
"fmt"
1920
"testing"
2021
"time"
2122

@@ -26,12 +27,14 @@ import (
2627
"github.com/oracle/oci-go-sdk/common"
2728
"github.com/oracle/oci-go-sdk/core"
2829
"github.com/oracle/oci-go-sdk/identity"
30+
"k8s.io/apimachinery/pkg/api/resource"
2931
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
3032
"k8s.io/client-go/pkg/api/v1"
3133
)
3234

3335
var (
3436
volumeBackupID = "dummyVolumeBackupId"
37+
defaultAD = identity.AvailabilityDomain{Name: common.String("PHX-AD-1"), CompartmentId: common.String("ocid1.compartment.oc1")}
3538
)
3639

3740
func TestResolveFSTypeWhenNotConfigured(t *testing.T) {
@@ -105,22 +108,31 @@ func NewClientProvisioner(pcData client.ProvisionerClient) client.ProvisionerCli
105108

106109
func TestCreateVolumeFromBackup(t *testing.T) {
107110
// test creating a volume from an existing backup
108-
options := controller.VolumeOptions{PVName: "dummyVolumeOptions",
111+
options := controller.VolumeOptions{
112+
PVName: "dummyVolumeOptions",
109113
PVC: &v1.PersistentVolumeClaim{
110114
ObjectMeta: metav1.ObjectMeta{
111115
Annotations: map[string]string{
112116
ociVolumeBackupID: volumeBackupID,
113117
},
114118
},
119+
Spec: v1.PersistentVolumeClaimSpec{
120+
StorageClassName: common.String("oci"),
121+
Resources: v1.ResourceRequirements{
122+
Requests: v1.ResourceList{
123+
v1.ResourceName(v1.ResourceStorage): resource.MustParse("50Gi"),
124+
},
125+
},
126+
},
115127
}}
116-
ad := identity.AvailabilityDomain{Name: common.String("dummyAdName"), CompartmentId: common.String("dummyCompartmentId")}
128+
117129
block := blockProvisioner{
118130
client: NewClientProvisioner(nil),
119131
metadata: instancemeta.NewMock(&instancemeta.InstanceMetadata{
120132
CompartmentOCID: "",
121133
Region: "phx",
122134
})}
123-
provisionedVolume, err := block.Provision(options, &ad)
135+
provisionedVolume, err := block.Provision(options, &defaultAD)
124136
if err != nil {
125137
t.Fatalf("Failed to provision volume from block storage: %v", err)
126138
}
@@ -129,3 +141,56 @@ func TestCreateVolumeFromBackup(t *testing.T) {
129141
provisionedVolume.Annotations[ociVolumeID])
130142
}
131143
}
144+
145+
func TestVolumeRoundingLogic(t *testing.T) {
146+
var volumeRoundingTests = []struct {
147+
requestedStorage string
148+
enabled bool
149+
minVolumeSize resource.Quantity
150+
expected string
151+
}{
152+
{"20Gi", true, resource.MustParse("50Gi"), "50Gi"},
153+
{"30Gi", true, resource.MustParse("25Gi"), "30Gi"},
154+
{"50Gi", true, resource.MustParse("50Gi"), "50Gi"},
155+
{"20Gi", false, resource.MustParse("50Gi"), "20Gi"},
156+
}
157+
for i, tt := range volumeRoundingTests {
158+
t.Run(fmt.Sprintf("test-%d", i), func(t *testing.T) {
159+
volumeOptions := controller.VolumeOptions{
160+
PVC: createPVC(tt.requestedStorage),
161+
}
162+
metadata := instancemeta.NewMock(&instancemeta.InstanceMetadata{
163+
CompartmentOCID: "",
164+
Region: "phx",
165+
})
166+
block := NewBlockProvisioner(NewClientProvisioner(nil), metadata, tt.enabled, tt.minVolumeSize)
167+
provisionedVolume, err := block.Provision(volumeOptions, &defaultAD)
168+
if err != nil {
169+
t.Fatalf("Expected no error but got %s", err)
170+
}
171+
172+
expectedCapacity := resource.MustParse(tt.expected)
173+
actualCapacity := provisionedVolume.Spec.Capacity[v1.ResourceName(v1.ResourceStorage)]
174+
175+
actual := actualCapacity.String()
176+
expected := expectedCapacity.String()
177+
if actual != expected {
178+
t.Fatalf("Expected volume to be %s but got %s", expected, actual)
179+
}
180+
})
181+
}
182+
}
183+
184+
func createPVC(size string) *v1.PersistentVolumeClaim {
185+
return &v1.PersistentVolumeClaim{
186+
ObjectMeta: metav1.ObjectMeta{},
187+
Spec: v1.PersistentVolumeClaimSpec{
188+
StorageClassName: common.String("oci"),
189+
Resources: v1.ResourceRequirements{
190+
Requests: v1.ResourceList{
191+
v1.ResourceName(v1.ResourceStorage): resource.MustParse(size),
192+
},
193+
},
194+
},
195+
}
196+
}

pkg/provisioner/core/provisioner.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import (
2323
"github.com/golang/glog"
2424

2525
"github.com/kubernetes-incubator/external-storage/lib/controller"
26+
"k8s.io/apimachinery/pkg/api/resource"
2627
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2728
informersv1 "k8s.io/client-go/informers/core/v1"
2829
"k8s.io/client-go/kubernetes"
@@ -56,7 +57,7 @@ type OCIProvisioner struct {
5657
}
5758

5859
// NewOCIProvisioner creates a new OCI provisioner.
59-
func NewOCIProvisioner(kubeClient kubernetes.Interface, nodeInformer informersv1.NodeInformer, nodeName string) *OCIProvisioner {
60+
func NewOCIProvisioner(kubeClient kubernetes.Interface, nodeInformer informersv1.NodeInformer, nodeName string, volumeRoundingEnabled bool, minVolumeSize resource.Quantity) *OCIProvisioner {
6061
configPath, ok := os.LookupEnv("CONFIG_YAML_FILENAME")
6162
if !ok {
6263
configPath = configFilePath
@@ -78,7 +79,7 @@ func NewOCIProvisioner(kubeClient kubernetes.Interface, nodeInformer informersv1
7879
glog.Fatalf("Unable to create volume provisioner client: %v", err)
7980
}
8081

81-
blockProvisioner := block.NewBlockProvisioner(client, instancemeta.New())
82+
blockProvisioner := block.NewBlockProvisioner(client, instancemeta.New(), volumeRoundingEnabled, minVolumeSize)
8283

8384
return &OCIProvisioner{
8485
client: client,

0 commit comments

Comments
 (0)