feat(cmd): Add '--rollout' flag when creating - #309
Conversation
There was a problem hiding this comment.
Pull request overview
Adds support for a --rollout mode to instance create, intended to replace existing instances in the same service group that use the same image “base name”, while also adjusting how service-group and volume identifiers are parsed during instance creation.
Changes:
- Add
--rolloutflag toinstance createand route execution through a newrunRolloutworkflow. - Modify parsing behavior for
--serviceand--volume-style inputs (notablyInstanceService.UnmarshalTextandInstanceVolume.UnmarshalText). - Remove the client-side “volume metro mismatch” validation during instance creation.
Comments suppressed due to low confidence (1)
internal/cmd/instances.go:783
- The volume creation path no longer validates
vol.Metroagainst the instance metro (unlike the service-group check below). If a volume is specified via structured input (--set volumes.0.metro=..., JSON/YAML, or if text parsing supportsmetro/...), the CLI will now proceed and fail later with a less actionable API error; consider restoring the metro-mismatch validation for volumes whenvol.Metrois set.
case "volumes":
for _, vol := range field.Create.Set.([]*InstanceVolume) {
reqVol := platform.CreateInstanceRequestVolume{
At: vol.At,
}
if vol.UUID != "" {
reqVol.Uuid = &vol.UUID
}
if vol.Name != "" {
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
926d9db to
ce661f7
Compare
8ccf7d3 to
c3adcf6
Compare
|
I reworked the implementation as you asked @jedevc Right now only instance rollout is implemented, but we can do other also I think |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 4 out of 4 changed files in this pull request and generated 5 comments.
Comments suppressed due to low confidence (1)
internal/cmd/instances.go:904
- The volume link type (Link[Volume]) includes a Metro field, but the create path no longer validates that an explicitly metro-qualified volume matches the instance metro. Without this check, a user can specify e.g. "sfo/myvol" while creating in another metro and the metro will be silently ignored in the API request (only Name/UUID are sent). Consider restoring the metro-mismatch validation (similar to the service group check) or explicitly rejecting metro-qualified volume references here.
case "volumes":
for _, vol := range field.Create.Set.([]*InstanceVolume) {
reqVol := platform.CreateInstanceRequestVolume{
At: vol.At,
}
if vol.UUID != "" {
reqVol.Uuid = &vol.UUID
}
if vol.Name != "" {
reqVol.Name = &vol.Name
}
if vol.Size > 0 {
reqVol.SizeMb = new(uint64(vol.Size))
}
if vol.Readonly {
reqVol.Readonly = &vol.Readonly
}
req.Volumes = append(req.Volumes, reqVol)
}
143ed09 to
89cd18a
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 5 out of 5 changed files in this pull request and generated 8 comments.
Comments suppressed due to low confidence (1)
internal/cmd/instances.go:1229
- Calling
Instance{}.Deletedirectly bypasses the sandbox wrappers used by resource commands, so--rolloutcan delete matching service instances even when they are not tracked by the active sandbox. This breaks sandbox isolation and can remove resources that normal delete commands would reject.
delErr := (Instance{}).Delete(ctx, []resource.Resource{old})
c68b276 to
144843c
Compare
|
something funky going on with tests, probably server is misbehaving. Will need to wait till I can make sure tests pass |
144843c to
a2e506a
Compare
|
One thing left to discuss here is if we're going for the loop-wait-retry logic If yes, then feel free to merge, if not, then we got to wait for the platform changes to go in I would say we leave as is because a rollout can take significantly more than a simple start or restart, si it's fine to have a more complex logic in place (at least conceptually) |
This adds sequential rolling over services Signed-off-by: Cezar Craciunoiu <cezar@unikraft.io>
a2e506a to
d0260b3
Compare
Signed-off-by: Cezar Craciunoiu <cezar@unikraft.io>
|
Before merging the timeouts stuff here, could we get #223 in first? |
| if rollout && req.Image == nil && req.Template == nil { | ||
| return nil, fmt.Errorf("--rollout requires an image or template (use --image or --template)") | ||
| } |
There was a problem hiding this comment.
Unnecessary, duplicate check below.
| return results, nil | ||
| } | ||
|
|
||
| func (Instance) Rollout(ctx context.Context, created []resource.Resource, fields []resource.Field) error { |
There was a problem hiding this comment.
Which of this logic is actually specific to just instances. I was imagining that we would just be able to use the existing delete methods.
If it's this specialized, there's no point in it being a special case like this, your choice to go back, or to make it more generic.
There was a problem hiding this comment.
At some point we wanted to do rollout for all resources, not just instances
But each wait and creation is unique so you got to implement this in every resource right?
| if inst.Image.Reference == nil { | ||
| continue | ||
| } | ||
| if inst.Image.Reference.Name() == newImageName { |
There was a problem hiding this comment.
I thought we didn't want to do rollouts based on image name - we moved over from doing that in kraftlet.
I think we should just do it based on the service.
There was a problem hiding this comment.
Hmm, I didn't know about this?
So just rollout everything in a service?
| // Create an additional replacement instance with a modified name. | ||
| rolloutFields := slices.Clone(fields) | ||
| for j := range rolloutFields { |
There was a problem hiding this comment.
Why are we doing more creation after our existing creation? We should do all the creation in one step, then do all our deletions.
Also, surely we could do bulk creation using replicas.
There was a problem hiding this comment.
Well this is the exact opposite of what we want 😓
If you have a service of max size 16 (default), and you have 9 instances in it, it means you can't rollout
If you do it sequentially, it means it will actually fit even if you are at 15/16 used
Alternatively we can add a --sequential flag to rollout
| if waitErr := group.DoMetro(ctx, g, metro, func(ctx context.Context, mc multimetro.MetroClient) error { | ||
| _, err := mc.WaitInstanceByUUID(ctx, newInst.UUID, platform.WaitInstanceByUUIDRequestBody{ | ||
| State: platform.InstanceStateRunning, | ||
| }) | ||
| return err | ||
| }); waitErr != nil { | ||
| return fmt.Errorf("waiting for new instance to be running: %w", waitErr) | ||
| } |
There was a problem hiding this comment.
I think this should be configurable - we should be able to configure some timeout, in which we check that after the instance has started running that it is still running after t time.
We don't really have a good way to check the health of an instance, so we need some way to make sure that we don't start up an instance, it immediately crashes, and then we delete the old one.
There was a problem hiding this comment.
How would that flag look like like --recheck 5s
Aka, wait 5s and check if the instance is still running? I can add that, but one could argue that it's on them if they deploy a service that crashes after X seconds
If it were to crash at boot it would do starting -> stopped without going into running I think
| if extra := len(toRemove) - len(created); extra > 0 { | ||
| var baseName string | ||
| for key, field := range resource.IterFields(fields) { | ||
| if key.String() == "name" && field.Create != nil && field.Create.Set != nil { |
There was a problem hiding this comment.
resource.GetFieldByPath instead of iteration.
| Features []string `group:"flag-create" shortcut:"features" help:"Instance features." placeholder:"feature"` | ||
| Template string `group:"flag-create" shortcut:"template" help:"Create from instance template." placeholder:"name"` | ||
| DeleteOnStop bool `group:"flag-create" name:"rm" help:"Automatically delete the instance when it stops."` | ||
| Rollout bool `group:"flag-create" shortcut:"rollout" help:"Replace old instances with new ones matching image name"` |
There was a problem hiding this comment.
I wonder if we should rename this to be more specific --service-rollout. We rollout on a service.
There was a problem hiding this comment.
The original intent/discussion was to move this to be generic and implementable for all resources
Not sure if we still want that?
| if rollout && req.Replicas != nil && *req.Replicas > 0 { | ||
| return nil, fmt.Errorf("--replicas cannot be used with --rollout") | ||
| } |
There was a problem hiding this comment.
It would make the math complicated and confusing? What would replicas mean in this context?
If I'm rolling ~6 instances in a service, and I specify 2 replicas, does it mean I want 2 instances in the end or 6*2==12?
We would need to be very clear in this case
| Delete(ctx context.Context, keys []string) error | ||
| } | ||
|
|
||
| type RolloutableResource interface { |
There was a problem hiding this comment.
In hindsight, maybe this abstraction isn't right. Apologies.
|
A couple thoughts. There are two modes of rollout I think:
Does that make sense? I don't really know what to call these "modes", but I think we should have both of them. Also, as far as I can tell, the current implementation doesn't have the failure mode, where we restore back to the "known" good state. It assumes that every rollout is perfectly successful, which it may not be. |
|
for for I think we can do both, with With the small note that running a database in a service group is conceptually wrong so if we want to truly support database rollout we need to support non-servicegroup based rollout |
Hm, so how would you imagine database rollout working? I think service group still makes sense here - it's about the database service that you care about, but I'm curious why this is wrong.
Indeed, that's why I was suggesting creating them all, and only deleting once you're convinced of the health of the others. Similar to how rolling deployments work in k8s.
I think the safety of them is the same no? I don't actually know actually if the names |
|
Note, the platform doesn't really have a concept of instance health… this is sad. IMO, we should currently think of healthy as:
However, as we build the rollout flag, etc, we should consider that at some point, we would add a "real" healthcheck system (or add the ability to monitor health in new ways, like by checking logs, etc) |
Open to UI/UX suggestions, right now it prints every new instance that was rolled
Closes: TOOL-794