-
Notifications
You must be signed in to change notification settings - Fork 248
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Create PVC if possible even if the StorageClass is missing #2683
Conversation
86241ca
to
d88d20f
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
storageClass is optional on a PV
user may ask for storageClass "" on a pvc and they will get a PV with no storageclass (assuming other param match of course)
causes = validateNameLength(spec.ManagedDataSource, validation.DNS1035LabelMaxLength) | ||
if len(causes) > 0 { | ||
if cause := validateNameLength(spec.ManagedDataSource, validation.DNS1035LabelMaxLength); cause != nil { | ||
causes = append(causes, *cause) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there any functional difference in the diff of this file or just style?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since we moved from returning a slice of causes to a single cause in validateNameLength. This just changes the way that is handled, but functionally does look the same.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right, the previous one was a bit clumsy.
if *sc == "" { | ||
return &metav1.StatusCause{ | ||
Type: metav1.CauseTypeFieldValueInvalid, | ||
Message: fmt.Sprintf("storageClassName is empty"), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"" storage class name is valid
apiVersion: v1
kind: PersistentVolume
metadata:
name: host-volume
spec:
capacity:
storage: 10Gi
accessModes:
- ReadWriteOnce
hostPath:
path: "/mnt/data"
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: my-pvc
spec:
volumeMode: Filesystem
storageClassName: ""
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 5Gi
✗ k get pvc
selecting docker as container runtime
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
my-pvc Bound host-volume 10Gi RWO 3s
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So had this discussion with Arnon, if you read this, it does say "" is equivalent to missing, due to historical reasons, but having a blank string is sort of gray at this point. This is fixing a reported issue where someone is passing a blank storage class by accident. I think we should disallow blank, and if someone wants to use the default, they can omit the field.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I dunno, this pr provides no way to explicitly bind to "" storageclass . That combined with the fact that in 1.26 "" on PVC will bind to default storage class if exists, makes me believe that we should leave things as-is OR allow updating the storageclass on the DV as you can do with PVCs in 1.26
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Finally tried this out on a 1.26 cluster. Apparently, I misread the doc. "" will not bind to the default storageclass. And pvc with "" storageClassName can't be updated.
Nevertheless because "" is a legit value I don't think we should disallow it
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree that "" is legit value. Fixing accordingly. Keeping the style changes, as the previous code (my lines) is quit ugly.
d88d20f
to
e2203a3
Compare
e2203a3
to
2e23d09
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is all a little awkward and racy. Remind me why we can't go straight to "create the pvc even if the storage class doesn't exist"?
pkg/controller/common/util.go
Outdated
@@ -338,7 +338,7 @@ func GetStorageClassByName(client client.Client, name *string) (*storagev1.Stora | |||
storageClass := &storagev1.StorageClass{} | |||
if err := client.Get(context.TODO(), types.NamespacedName{Name: *name}, storageClass); err != nil { | |||
klog.V(3).Info("Unable to retrieve storage class", "storage class name", *name) | |||
return nil, errors.New("unable to retrieve storage class") | |||
return nil, errors.New(fmt.Sprintf("unable to retrieve storage class %s", *name)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
not a big deal, no need to change, but errors.Errorf
can be used when string formatting errors
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixed
@@ -122,7 +122,7 @@ func UpdateReadyCondition(conditions []cdiv1.DataVolumeCondition, status corev1. | |||
return updateCondition(conditions, cdiv1.DataVolumeReady, status, message, reason) | |||
} | |||
|
|||
func updateBoundCondition(conditions []cdiv1.DataVolumeCondition, pvc *corev1.PersistentVolumeClaim, reason string) []cdiv1.DataVolumeCondition { | |||
func updateBoundCondition(conditions []cdiv1.DataVolumeCondition, pvc *corev1.PersistentVolumeClaim, message, reason string) []cdiv1.DataVolumeCondition { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is there a unit test for this change where message != ""
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
added
@@ -76,6 +76,8 @@ const ( | |||
dvPhaseField = "status.phase" | |||
|
|||
claimRefField = "spec.claimRef" | |||
|
|||
claimStorageClassField = "spec.claimStorageClass" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
spec.storageClassName
is the json property and the standard naming scheme we use
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixed
return | ||
} | ||
for _, dv := range dvList.Items { | ||
storage := dv.Spec.Storage |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
what about dv.spec.pvc
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's not needed as we create the pvc even if the storage class doesn't exist
if AccessModes
is set (otherwise k8s PVC
validation fails). PVC/DV
phase will be Pending
until a satisfying SC/PV
is found.
We will |
2e23d09
to
c4b3453
Compare
0967366
to
58c8536
Compare
Okay, that makes sense, so what about this in the description, what is the follow up?
|
pkg/controller/datavolume/util.go
Outdated
// Gets either the bound PV or an available satisfying PV | ||
func getPV(c client.Client, pvcSpec *v1.PersistentVolumeClaimSpec, pvcName, pvcNamespace string) (*corev1.PersistentVolume, error) { | ||
if pvcSpec.StorageClassName == nil { | ||
return nil, fmt.Errorf("PVC spec is missing StorageClassName") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this really an error? Can't storageclass name be nil for either spec.pvc
or spec.storage
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is that tested?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's not an error. Fixed. Covered in [test_id:8383]Import fails when no default storage class, and recovers when default is set
.
just forgot to remove it :) |
58c8536
to
343f977
Compare
tests/datavolume_test.go
Outdated
}, | ||
table.Entry("[test_id:XXXX]the storage class is created", testScName, createStorageClass), | ||
table.Entry("[test_id:XXXX]PV with the SC name is created", testScName, createPV), | ||
table.Entry("[test_id:XXXX]PV with the SC name (\"\" blank) is created", "", createPV), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
test_ids: CNV-9922
test_ids: CNV-9924
test_ids: CNV-9925
343f977
to
047dd6f
Compare
/test pull-containerized-data-importer-fossa |
pkg/controller/datavolume/util.go
Outdated
if pv.Status.Phase == corev1.VolumeBound && | ||
pv.Spec.ClaimRef != nil && | ||
pv.Spec.ClaimRef.Name == pvcName && | ||
pv.Spec.ClaimRef.Namespace == pvcNamespace { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure about this loop. If a PV exists that meets this criteria, I'm pretty sure it would mean that the target PVC already exists.
I think just the following loop is sufficient
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixed
047dd6f
to
a202278
Compare
64d1109
to
57ef88a
Compare
/approve |
[APPROVALNOTIFIER] This PR is APPROVED This pull-request has been approved by: mhenriks The full list of commands accepted by this bot can be found here. The pull request process is described here
Needs approval from an approver in each of these files:
Approvers can indicate their approval by writing |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
looks good, just a few minor nitpicky things.
causes = validateNameLength(spec.ManagedDataSource, validation.DNS1035LabelMaxLength) | ||
if len(causes) > 0 { | ||
if cause := validateNameLength(spec.ManagedDataSource, validation.DNS1035LabelMaxLength); cause != nil { | ||
causes = append(causes, *cause) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since we moved from returning a slice of causes to a single cause in validateNameLength. This just changes the way that is handled, but functionally does look the same.
@@ -1702,7 +1705,7 @@ var _ = Describe("[vendor:cnv-qe@redhat.com][level:component]DataVolume tests", | |||
|
|||
createDataVolumeForImport := func(f *framework.Framework, storageSpec cdiv1.StorageSpec) *cdiv1.DataVolume { | |||
dataVolume := utils.NewDataVolumeWithHTTPImportAndStorageSpec( | |||
dataVolumeName, "1Gi", fmt.Sprintf(utils.TinyCoreQcow2URLRateLimit, f.CdiInstallNs)) | |||
dataVolumeName, "1Gi", fmt.Sprintf(utils.TinyCoreQcow2URL, f.CdiInstallNs)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good catch, there seems to be no reason this was the rate limited url.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice job! I have some comments and questions about spec rendering, which I think is getting confusing. Other than that looks good to me.
pkg/controller/datavolume/util.go
Outdated
@@ -151,6 +152,41 @@ func copyStorageAsPvc(log logr.Logger, storage *cdiv1.StorageSpec) *v1.Persisten | |||
return pvcSpec | |||
} | |||
|
|||
// Update the PVC spec from either the created PVC or an available satisfying PV | |||
func updatePvcSpecFromPvcOrPv(c client.Client, pvcSpec *v1.PersistentVolumeClaimSpec, pvc *v1.PersistentVolumeClaim) error { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see we update the pvcSpec here but I'm struggling to see the cases where this is relevant if the PVC already exists. Why do we need to set VolumeMode and AccessMode if the PVC already has them?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good catch! fixed.
pkg/controller/datavolume/util.go
Outdated
if storageClass == nil { | ||
if err := updatePvcSpecFromPvcOrPv(client, pvcSpec, pvc); err != nil { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this flow is getting confusing, it'd be good to have some comments explaining why are we doing this. I'm also not a fan of the updatePvcSpecFromPvcOrPv
name, it's not a must but I think this whole storageClass handling would benefit from being in a separate function.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Simplified and renamed.
37f9791
to
a549247
Compare
When PVC is created with storageClassName and the SC is not found, k8s looks for PV with the storageClassName for satisfying this claim. In this case k8s supports also a blank (“”, not the nil default one) storageClassName. To support this behavior we added: -DV controller support for this flow (for both “” and non-existing SC) -Condition update and event when StorageSpec PVC rendering errors and PVC is not created (e.g. missing both AccessModes and SC/PV) -PVC is created even if a satisfying SC/PV doesn't exist if pvc/storage AccessModes is set (otherwise k8s PVC validation fails). PVC/DV phase will be Pending until a satisfying SC/PV is found -PV watch to reconcile DVs waiting for the PV storageClassName -PV storageClassName indexer, so we can list the relevant PVs Signed-off-by: Arnon Gilboa <agilboa@redhat.com>
a549247
to
3829441
Compare
3829441
to
137ec17
Compare
/test pull-cdi-linter |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good! just a couple of nitpicks that I think could improve the spec rendering code
@@ -44,85 +44,105 @@ const ( | |||
) | |||
|
|||
// renderPvcSpec creates a new PVC Spec based on either the dv.spec.pvc or dv.spec.storage section | |||
func renderPvcSpec(client client.Client, recorder record.EventRecorder, log logr.Logger, dv *cdiv1.DataVolume) (*v1.PersistentVolumeClaimSpec, error) { | |||
func renderPvcSpec(client client.Client, recorder record.EventRecorder, log logr.Logger, dv *cdiv1.DataVolume, pvc *v1.PersistentVolumeClaim) (*v1.PersistentVolumeClaimSpec, error) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this would look cleaner if we do something like:
// If the PVC already exists, just return its spec
if pvc != nil {
return pvc.Spec.DeepCopy(), nil
}
if dv.Spec.PVC != nil {
return dv.Spec.PVC.DeepCopy(), nil
}
if dv.Spec.Storage != nil {
return pvcFromStorage(client, recorder, log, dv)
}
pvcFromStorage
would require fewer arguments too, wdyt?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It won't work, see the following comment about renderPvcSpecVolumeSize.
pvcSpec = pvc.Spec.DeepCopy() | ||
} | ||
|
||
if err := renderPvcSpecVolumeSize(client, dv.Spec, pvcSpec); err != nil { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Related to my previous comment, we are still rendering the volume size for already existing PVCs, which I believe is not necessary. I think this function would be better without any PVC handling.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Or do we really want to update the volume size? In that case, are we updating the PVC later to reflect the new pvcSpec?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is necessary to render the volume size for smart clones, as the DV may require volume size greater than the source PVC, so the traget PVC should be expanded.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh nice, wasn't sure about that. Thanks for explaining!
|
||
func renderPvcSpecVolumeModeAndAccessModes(client client.Client, recorder record.EventRecorder, log logr.Logger, dv *cdiv1.DataVolume, pvcSpec *v1.PersistentVolumeClaimSpec) error { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe renderVolumeAndAccessModes
? I think we could remove the pvcSpec from the name, but renderPvcSpecVolumeAndAccessModes
would also work
/lgtm |
What this PR does / why we need it:
When
PVC
is created withstorageClassName
and theSC
is not found, k8s looks forPV
with thestorageClassName
for satisfying this claim. In this case k8s supports also a blank (“”, not the nil default one)storageClassName
. To support this behavior we added:DV
controller support for this flow (for both “” and non-existingSC
)StorageSpec
PVC
rendering errors andPVC
is not created (e.g. missing bothAccessModes
andSC
/PV
)PVC
is created even if a satisfyingSC
/PV
doesn't exist ifspec.pvc/storage
AccessModes
is set (otherwise k8s PVC validation fails). PVC/DV phase will bePending
until a satisfyingSC
/PV
is foundPV
watch to reconcileDVs
waiting for thePV
storageClassName
PV
storageClassName
indexer, so we can list the relevantPVs
Which issue(s) this PR fixes (optional, in
fixes #<issue number>(, fixes #<issue_number>, ...)
format, will close the issue(s) when PR gets merged):Fixes #2186
Special notes for your reviewer:
Release note: