Skip to content

Adds declarative-friendly state transition guidance - #1079

Draft
mbleigh wants to merge 1 commit into
masterfrom
decl-state-transitions
Draft

Adds declarative-friendly state transition guidance#1079
mbleigh wants to merge 1 commit into
masterfrom
decl-state-transitions

Conversation

@mbleigh

@mbleigh mbleigh commented Apr 27, 2023

Copy link
Copy Markdown
Contributor

This PR introduces a new desired_state field to accommodate declarative tooling that needs to be able to modify state.

Problem: Without this guidance, state guidance says "make custom methods" and declarative-friendly guidance says "don't make custom methods".

Solution: Allow a desired_state field that declarative tooling can use to perform state transitions. The state field can transition through temporary active states before arriving at the requested resting state.

This PR introduces a new `desired_state` field to accommodate declarative-friendly tooling that needs to be able to modify state.
@mbleigh
mbleigh requested a review from a team as a code owner April 27, 2023 17:18
Comment thread aip/general/0216.md

@bgrant0607 bgrant0607 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for taking a stab at this. It's been on the backlog for a long time.

Do you have a specific, urgent need for this?

In general, AIP 128's prohibition on custom methods isn't viable, even if we exclude data-plane methods. We need more pragmatic, nuanced guidance regarding when to use custom methods and when to use alternatives.

Custom methods can be appropriate for making some operational changes or to perform other control actions. RestartVM is the canonical example. In those cases, we want to ensure that the resource desired state can be configured using the surface of choice (declarative, CLI, UI, chatbot, voice assistant, terminal console, IDE, etc.) and that the operational changes can be made independently with the surface or automation of choice (e.g., runtime controller, rollout process, operational console). This can also simplify more restricted permissions and narrower blast radius for such common administrative or operational actions.

Comment thread aip/general/0216.md
}

State state = 1 [(google.api.field_behaviopr = OUTPUT_ONLY)];
State desired_state = 2;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Issues with this:

  1. Many State values (e.g., transitional ones) wouldn't be valid for the desired state.
  2. Enums can't be extended without potentially breaking clients.
  3. Per-service state enums prevent cross-service lifecycle management. This is a major gap in AIP 152 (Jobs), as one example. Also in 164 (soft delete).

Separate attributes are often better. For instance, Kubernetes Deployment has a "paused" field:
https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.27/#deploymentspec-v1-apps

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. As mentioned below, transitional states would be INVALID_ARGUMENT if supplied to desired_state.
  2. I feel like I've known what scenario this breaks before but can't recall it - can you give an example?
  3. It sounds like you're saying that this entire AIP is a mistake and should be deprecated -- if that's the case we really need guidance to replace it.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel like I've known what scenario this breaks before but can't recall it - can you give an example?

@bgrant0607 I feel like we should just update google.aip.dev/180 to explain this one. One reason is that clients unaware of the new enum value will receive the unspecified value, and send that over the wire potentially erasing the currently set parameter.

It sounds like you're saying that this entire AIP is a mistake and should be deprecated -- if that's the case we really need guidance to replace it.

I'm not sure if it's that strong of a case, but we do need updated guidance that enables known gaps like cross-service lifecycle management.

I definitely agree guidance needs be updated, but it's on a longer backlog. At minimum I think anything that removes guidance from AIP-128 and reduces the gap between declarative-friendly and non-declarative-friendly APIs is an improvement. Thanks for authoring this!

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To clarify I think Brian is arguing for more granular switches on fields (paused with a boolean vs desired_state that has the same set of possible enum values). I do agree that, at minimum, desired_state shouldn't have the ability to be set to transient states (RUNNING)

@loudej loudej Apr 29, 2023

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd suggest this actually isn't a recommendation we should make. It may even be an anti-pattern, although it's understandable why it comes up as an idea every so often.

  • It's true that custom methods are essentially invisible to desired-state systems

  • It's also true that some custom methods have the effect of moving the output-only state field (eventually) to a similarly-named value.

So it seems like there's a connection between the two. But having an assignable field of the output-only enum is very rarely the best way to design the new hand-free resource control feature.

A different question to ask is "What config-feature on this resource would I add that, when optionally configured by the customer, would tell the backend when these custom methods should be called automatically?"

Looking at VM instance, some custom methods that can't be reached are start/stop/reset/suspend/resume... A quick hypothetical of those...

message Instance {
  ...
  // Assign PowerControl details to enable automated control of running state.
  // Default behavior when nil is to leave all running-state to imperative method calls.
  PowerControl power_control;

  message PowerControl {
    // When specified, indicates that the appropriate custom method should be called as needed.
    // Calls to the custom methods "stop" "start" "suspend" and "resume" will fail if they contradict
    // the configured goal.
    enum goal = UNSPECIFIED | STARTED | SUSPENDED | STOPPED;

    // If the instance has been running since before this time, and it is not after this time,
    // then the instance will restart. Setting it to "now" is an effective way to cause a single reset.
    // Setting to a future time will schedule a single reset for when that time arrives.
    Timestamp restart_when;

    // other control features added here as they become necessary
  }
}

... just a gist, but you get the idea.

  • if unassigned the current behavior (non-automated, custom method driven only) is fully in effect
  • if there is an enum goal, the values can be only what's needed and meaningful. they don't even necessarily have to align with actual output_only state enum.
  • any other resource-type-specific subtlety about rules and conditions of when those automatic effects can/should take place can be taken into account with additional fields in the new structure (and more can be added in the future to make the hands-free control more sophisticated)

Comment thread aip/general/0216.md
PUBLISHED = 3;
}

State state = 1 [(google.api.field_behaviopr = OUTPUT_ONLY)];

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typo

Comment thread aip/general/0216.md
reached the `desired_state` (or **must** error if it cannot).
- If a resource supports both declarative `desired_state` and imperative
[state transition methods](#state-transition-methods), executing a state
transition method **should** modify the desired state appropriately.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will create drift in declarative clients. It would be better to not have the desired state field in that case.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, it will create drift - but I'm trying to consider:

  1. Existing APIs with custom methods that might migrate to this pattern and have both for that reason.
  2. Flexibility for clients to use a more imperative state transition method if they prefer (presumably the client is not generally declarative in this scenario).

Declarative tooling expects to be the only manager of a given resource, so out of band updates of any kind create drift problems. This is something that API consumers have to manage for themselves.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe this is why it's best to not have desired_state, but rather individual flags that limit state transitions?

e.g. suspended would set the resource to be suspended, and not allow other state transitions.

Or maybe have a way to clear desired state (unspecified) that allows custom methods to mutate the state? effectively a lock.

Comment thread aip/general/0216.md
- If a resource supports both declarative `desired_state` and imperative
[state transition methods](#state-transition-methods), executing a state
transition method **should** modify the desired state appropriately.
- If a resource is created without `desired_state`, the field **must** default

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We're working on guidance around how to handle default values. When services change values set by clients, it causes problems for declarative and other state-driven clients. It would be better to leave the value unspecified.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can buy an argument for leaving it unspecified.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1 to unspecified. unspecified IMO means "I'm managing this state outside of the resource". If i saw a resting value I'd read that to be "keep this resource in the resting state".

@mbleigh mbleigh left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nothing urgent blocked by this, but this pattern occurs very regularly in APIs I am reviewing and it's really become a problem that there's no declarative-friendly way to solve it.

I think this proposal is entirely compatible with a :restart type custom method - the desired_state would remain unchanged, state would move to a temporary RESTARTING value and then back to ACTIVE or similar. I specifically called out that out-of-band changes to state that will transition back to the desired state don't need to mutate desired state.

Communication of resource state is an important and very common pattern. If the current AIP needs to be entirely reworked, I'm happy to pitch in and help write replacement guidance but it's been frustrating to feel like I have no good advice to give when I'm asked about how to do this "properly" for declarative-friendly APIs.

Comment thread aip/general/0216.md
}

State state = 1 [(google.api.field_behaviopr = OUTPUT_ONLY)];
State desired_state = 2;

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. As mentioned below, transitional states would be INVALID_ARGUMENT if supplied to desired_state.
  2. I feel like I've known what scenario this breaks before but can't recall it - can you give an example?
  3. It sounds like you're saying that this entire AIP is a mistake and should be deprecated -- if that's the case we really need guidance to replace it.

Comment thread aip/general/0216.md
reached the `desired_state` (or **must** error if it cannot).
- If a resource supports both declarative `desired_state` and imperative
[state transition methods](#state-transition-methods), executing a state
transition method **should** modify the desired state appropriately.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, it will create drift - but I'm trying to consider:

  1. Existing APIs with custom methods that might migrate to this pattern and have both for that reason.
  2. Flexibility for clients to use a more imperative state transition method if they prefer (presumably the client is not generally declarative in this scenario).

Declarative tooling expects to be the only manager of a given resource, so out of band updates of any kind create drift problems. This is something that API consumers have to manage for themselves.

Comment thread aip/general/0216.md
- If a resource supports both declarative `desired_state` and imperative
[state transition methods](#state-transition-methods), executing a state
transition method **should** modify the desired state appropriately.
- If a resource is created without `desired_state`, the field **must** default

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can buy an argument for leaving it unspecified.

@toumorokoshi toumorokoshi left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the changes! some high-level feedback so we can align on design.

Comment thread aip/general/0216.md
- The comment for the field **should** document the resource pattern.
- Other fields **may** be included.

### Declarative-friendly state transitions

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we shouldn't call this "declarative-friendly": there really shouldn't be a different between declarative-friendly APIs and non-declarative friendly APIs.

Is there a way to make the guidance more generic?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd say this guidance is more "managing state with resource fields" than declarative.

Comment thread aip/general/0216.md
}

State state = 1 [(google.api.field_behaviopr = OUTPUT_ONLY)];
State desired_state = 2;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel like I've known what scenario this breaks before but can't recall it - can you give an example?

@bgrant0607 I feel like we should just update google.aip.dev/180 to explain this one. One reason is that clients unaware of the new enum value will receive the unspecified value, and send that over the wire potentially erasing the currently set parameter.

It sounds like you're saying that this entire AIP is a mistake and should be deprecated -- if that's the case we really need guidance to replace it.

I'm not sure if it's that strong of a case, but we do need updated guidance that enables known gaps like cross-service lifecycle management.

I definitely agree guidance needs be updated, but it's on a longer backlog. At minimum I think anything that removes guidance from AIP-128 and reduces the gap between declarative-friendly and non-declarative-friendly APIs is an improvement. Thanks for authoring this!

Comment thread aip/general/0216.md
}

State state = 1 [(google.api.field_behaviopr = OUTPUT_ONLY)];
State desired_state = 2;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To clarify I think Brian is arguing for more granular switches on fields (paused with a boolean vs desired_state that has the same set of possible enum values). I do agree that, at minimum, desired_state shouldn't have the ability to be set to transient states (RUNNING)

Comment thread aip/general/0216.md

- The `state` field's output **may** change to more granular
[active states](#common-states) to indicate progress toward the `desired_state`.
- The `desired_state` field **must** only allow [resting state](#common-states)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As discussed above, the best way to enforce this is to use a separate enum entirely.

Doing so eliminates the need to require the runtime behavior outlined below (preconditions, invalid_argument)

Comment thread aip/general/0216.md
error with `INVALID_ARGUMENT` (HTTP 400).
- Attempting to set `desired_state` to a state that cannot be transitioned to
from the current state **must** error with `FAILED_PRECONDITION` (HTTP 400).
- A [long-running][] operation **must not** complete until the `state` has

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

would this apply for custom methods, or for updates?

In either case I kind of feel like this shouldn't be the case - the client can poll the resource state if desired, and if the operation takes hours, have a several-hour-long LRO just to see if your parameter was accepted feels like it'll limit it's utility for local clients like gcloud and Terraform.

Comment thread aip/general/0216.md
reached the `desired_state` (or **must** error if it cannot).
- If a resource supports both declarative `desired_state` and imperative
[state transition methods](#state-transition-methods), executing a state
transition method **should** modify the desired state appropriately.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe this is why it's best to not have desired_state, but rather individual flags that limit state transitions?

e.g. suspended would set the resource to be suspended, and not allow other state transitions.

Or maybe have a way to clear desired state (unspecified) that allows custom methods to mutate the state? effectively a lock.

Comment thread aip/general/0216.md
- If a resource supports both declarative `desired_state` and imperative
[state transition methods](#state-transition-methods), executing a state
transition method **should** modify the desired state appropriately.
- If a resource is created without `desired_state`, the field **must** default

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1 to unspecified. unspecified IMO means "I'm managing this state outside of the resource". If i saw a resting value I'd read that to be "keep this resource in the resting state".

@kaibolay

kaibolay commented Apr 9, 2026

Copy link
Copy Markdown

@mbleigh Are you still planning to get this merged in the short to medium term? Or would it make sense to change the status to "draft"?

@mbleigh
mbleigh marked this pull request as draft April 9, 2026 18:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants