Repository navigation
Add a "preview gate" concept, use it for external volumes - #1994
Conversation
e8c6697 to
4953bd4
Compare
|
rebased, only conflict was on imports. |
There was a problem hiding this comment.
Thoughts on how we will gate fields on our CRDs using gates?
There was a problem hiding this comment.
Nope. DV doesn't apply and as far as I know there is no general-purpose field-gating function in CRDs. We have to solve it on our own.
Benjamin Elder (BenTheElder)
left a comment
There was a problem hiding this comment.
How do e2e tests for these work? what happens when a test needs N gates?
I would expect at least one e2e change + possibly a github workflow changes related to external volumes to illustrate this
|
Ben: wrt e2e, yeah I didn't go there yet. I wasn't sure what the right answer was and I wasn't sure this approach would stand up to review. If we can hold our nose on the overall approach, we can look into e2e. |
|
Fair. IMHO that's a critical aspect of the featuregate story though, how it gets communicated to tests, both unit (where globals may be an issue) and e2e (where it gets confusing and as much as possible you want to dedupe enabling between prod and test) |
4953bd4 to
e4c8521
Compare
|
Rebased, e2e comments not yet addressed |
e4c8521 to
11d6fc8
Compare
Global is simple but it means unit tests must be serial. I don't know if that is really a problem or not. We COULD go to a non-global WRT e2e - I am unsure what is best. We could re-run the entire e2e suite with all gates turned on, or we could try to be more surgical. Will stare at it some more. |
a509d33 to
08f5fa8
Compare
| // nested mount support in general. | ||
| if err := appendExternalVolumes(workloadSpec, actorTemplate, actor); err != nil { | ||
| return nil, err | ||
| if preview.IsEnabled(preview.GatePreview) { |
There was a problem hiding this comment.
🤖 blocking 🔴 – Gating this at runtime breaks actors that were created while the gate was on. With it off, the external Volume is dropped from the spec, but the container's mount of it stays. atelet then rejects the whole spec (cmd/atelet/main.go:1619, "mounts volume … which is not defined"). So Terminate fails and DeleteActor stays in DELETING on every retry. Suspend, pause and resume fail the same way.
I'd gate admission only: the ifDisabled(Preview)=forbidden validation on create already stops new uses. Paths that act on objects already using the feature (spec building, terminate, detach, delete, atelet's mount and unmount) shouldn't depend on the flag.
There was a problem hiding this comment.
So, there's simply no good answer to the problem of what we should do when a feature was enabled, used, then disabled. Different features will have different "ideal" behaviors.
First, let's clarify -- if it is in the API and then you disable the gate, it is still in the API. We don't wipe the field. You can do a RMW as long as you do not change the value of the gated fields. You cannot change the field or add new uses of it while the gate is disabled.
So it should not be REJECTED.
The reason to gate logic is this: Why would someone disable a gate that used to be enabled? In short, the most likely reason is that the feature is buggy. If we do as you suggest, then we are leaving the buggy feature active. The user THINKS that they have neutralized the impact, but they haven't. We wrestled with this in Kubernetes and decided that while we are not 100% enforcing it, most gates are safest if they are neutralized totally.
Note that this means that any such actor will likely be broken. That's an unfortunate effect, but I think it is safer than leaving potentially buggy code in, in general. OTOH, just gating the API means maybe we don't need to run e2e twice? :)
Now for THIS SPECIFIC feature, we could argue that it has been in for so long that the risk is low of simply gating the API. I can buy that, but only for this one. In general I think the current approach is better.
We could split the difference and leave the unmount logic ungated, but that itself is a risk and suggests more testing.
Do you want to leave logic ungated for this feature? I can buy that argument.
There was a problem hiding this comment.
I think maybe we should reject the actor as invalid throughout instead of just wiping the field.
There was a problem hiding this comment.
I'm not sure what you mean - we don't wipe the field. What am I missing?
There was a problem hiding this comment.
ACK, following up from mobile in both cases, I thought I remembered we basically just dropped the field (versus refusing to boot the actor / throwing an error)
There was a problem hiding this comment.
Oh, I see. We don't "wipe it" as in the data disappears, but we do silently ignore the volumes. We could instead just refuse to resume it. That leaves an issue of what to do when it is already running at the time the gate is removed.
There are no good answers here, but I could buy that we should allow it to unmount but not mount?
| // be called whether or not Init has been. Tests that use it must not run in | ||
| // parallel. |
There was a problem hiding this comment.
Is there any way to express this to go?
If not, I think we can in a follow-up leverage re-exec for those and avoid mutating the shared global directly anyhow.
There was a problem hiding this comment.
Yeah, I think we can afford to follow up on this.
One hack others have used it t make the helper take a shared (global) lock and register a Cleanup(). Still a potential for deadlock. Or we could TryLock and throw an error, giving us probabilistic odds of finding it.
FWIW, some tests already call Setenv() which has the same problem.
There was a problem hiding this comment.
SetEnv is a HUGE smell we should eliminate and not mimic. I'll take that as an AI :-)
|
Open questions:
Anything else? |
bb9d6d6 to
7d94afd
Compare
92a076c to
d58a8bc
Compare
|
rebased |
d58a8bc to
410331f
Compare
To keep it simple:
* All gates are disabled unless enabled
* Only "preview" and "ga" - no "alpha" or "beta"
* We only have one gate for now, called "Preview" (later we can break
this down)
* Package internal/preview has an Init() function which gets passed a
list of gates to enable, which is stored as a package-global
* Code can call preview.IsEnabled(gatename)
* Each cmd/* takes `--preview=<value>` where value can be:
- "" = no gates
- "*" = all gates
- "Foo,Bar" = specific gates only
* The --preview flag can be repeated
- e.g. `--preview=Foo --preview=Bar`
This does not yet mark anything as being preview.
Given the consequences of turning the gate off while in-use (leaving undeletable Actors, orphaned attached disks, etc) and that it has been in the code for a while, Ben and Julian's arguments won me over (FOR THIS FEATURE only :)
410331f to
2edfe40
Compare
|
Rebased |
```
$ k ate create atespace th
NAME AGE
th 0s
$ grep -i -B4 -A4 extern at.json
"name": "data"
},
{
"name": "ext",
"externalVolumeTemplate": {
"capacity": "1Gi",
"storageClassName": "local-path"
}
}
$ k ate create actor-template -f at.json
Error: failed to create actor template: rpc error: code = InvalidArgument desc = actor_template.volumes[1].external_volume_template: Forbidden
$ k ate --preview create actor-template -f at.json
ATESPACE NAME SANDBOX CLASS GOLDEN TAG ERROR AGE
th extvol SANDBOX_CLASS_GVISOR 0s
```
This prevents un-deletable actors.
2edfe40 to
c431652
Compare
|
rebased |
I went through a number of models and this seems to be the simplest and lowest overall impact.
To keep it simple:
* All gates are disabled unless enabled
* Only "preview" and "ga" - no "alpha" or "beta"
* We only have one gate for now, called "Preview" (later we can break this down, maybe)
* All gates can be activated by passing "*"
* Package internal/preview has an Init() function which gets passed a list of gates to enable, which is stored as a package-global
* Code can call preview.IsEnabled(gatename)
Fixes #1690
Julian Gutierrez Oschmann (@juli4n) Benjamin Elder (@BenTheElder) Michelle Au (@msau42)