Skip to content

feat(ws): add workspace pause actions backend API #340

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

Merged

Conversation

andyatmiami
Copy link
Contributor

@andyatmiami andyatmiami commented May 14, 2025

related: #298

  • Added PauseActionWorkspaceHandler to handle pausing or unpausing a given workspace
  • Introduced single new route for starting and pausing workspaces in the API.
    • api/v1/workspaces/{namespace}/{name}/actions/pause
      - pausing or unpausing operation is specified in the request payload
  • Created a new WorkspaceActionPauseEnvelope type for successful responses.
  • Leveraging JSONPatch / client.RawPatch to ensure Workspace in "valid state" before attempting action
    • for start: spec.paused must be true, and status.state must be Paused
    • for pause: spec.paused must be false
      • note: I would love to have a status.state check here of status.state != Paused, but that type of comparison is not supported in JSONPatch
  • Added tests for the new API, including success and error cases.
  • Updated README/OpenAPI documentation to include the new endpoints.

As an interesting "edge case" worth calling out, the following payload is currently honored by the API:

{
  "data": {}
}

Given the WorkspaceActionPause struct is simply {"paused": true|false}, the "empty" Envelope presented above deserializes the JSON using the zero value of bool (which is false).

Our validation today is always performed against the deserialized object, and as such impossible to distinguish the following cases w.r.t actual JSON request payload:

{
  "data": {}
}

vs

{
  "data": {
    "paused": false
  }
}

The effort and (relative) complexity to prevent this and return a 422 in this scenario was not deemed "worth it" for the time being. As a result, a test case has been added for this specific scenario to at minimum document this "strange" behavior.

  • Clients, however, should NOT rely on this behavior and always provide a fully defined WorkspaceActionPause JSON object to ensure future compatibility.

Rendered Swagger doc:

image

@ederign
Copy link
Member

ederign commented May 14, 2025

/ok-to-test

@andyatmiami andyatmiami force-pushed the feat/workspace-start-api branch 3 times, most recently from fb2e2fb to f22b3d3 Compare May 15, 2025 20:54
Copy link

@harshad16 harshad16 left a comment

Choose a reason for hiding this comment

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

Great work 💯
Tested with cli:
Screenshot from 2025-05-20 16-50-35

@andyatmiami andyatmiami force-pushed the feat/workspace-start-api branch 2 times, most recently from 8d5e467 to 3237336 Compare May 21, 2025 18:05
@andyatmiami andyatmiami marked this pull request as ready for review May 21, 2025 18:08
@andyatmiami andyatmiami changed the title feat(ws): Add start workspace functionality to backend API feat(ws): Implement workspace start + pause as backend APIs May 21, 2025
@andyatmiami andyatmiami requested a review from harshad16 May 21, 2025 18:10
Copy link
Member

@ederign ederign left a comment

Choose a reason for hiding this comment

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

Some questions inline.

@andyatmiami andyatmiami force-pushed the feat/workspace-start-api branch from 3237336 to ba9e1bc Compare June 3, 2025 20:36
@google-oss-prow google-oss-prow bot added size/XXL and removed size/XL labels Jun 3, 2025
@andyatmiami andyatmiami force-pushed the feat/workspace-start-api branch from ba9e1bc to 575e1a8 Compare June 3, 2025 21:46
@andyatmiami andyatmiami requested a review from ederign June 3, 2025 22:12
Copy link

@harshad16 harshad16 left a comment

Choose a reason for hiding this comment

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

The new changes looks great, with return payload
Tested it with running system:
Screenshot from 2025-06-18 06-14-10
Works as intended.

great work 💯

/lgtm

Copy link

@harshad16: changing LGTM is restricted to collaborators

In response to this:

The new changes looks great, with return payload
Tested it with running system:
Screenshot from 2025-06-18 06-14-10
Works as intended.

great work 💯

/lgtm

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes/test-infra repository.

Copy link
Member

@thesuperzapper thesuperzapper left a comment

Choose a reason for hiding this comment

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

I left a few comments (we were on a call during the review so @andyatmiami should be aware of what to do and we were in agreement on the changes).

@andyatmiami andyatmiami force-pushed the feat/workspace-start-api branch from 575e1a8 to 69ff24a Compare July 8, 2025 19:39
@google-oss-prow google-oss-prow bot added area/backend area - related to backend components area/v2 area - version - kubeflow notebooks v2 size/XL and removed size/XXL labels Jul 8, 2025
@andyatmiami andyatmiami force-pushed the feat/workspace-start-api branch from 69ff24a to 41f8118 Compare July 16, 2025 13:54
related: kubeflow#298

- Added PauseActionWorkspaceHandler to handle pausing or unpausing a given workspace
- Introduced single new route for starting and pausing workspaces in the API.
    - `api/v1/workspaces/{namespace}/{name}/actions/pause`
			- pausing or unpausing operation is specified in the request payload
- Created a new WorkspaceActionPauseEnvelope type for successful responses.
- Leveraging JSONPatch / client.RawPatch to ensure Workspace in "valid state" before attempting action
    - for `start`: `spec.paused` must be `true`, and `status.state` must be `Paused`
    - for `pause`: `spec.paused` must be `false`
        - note: I would love to have a `status.state` check here of `status.state != Paused`, but that type of comparison is not supported in [JSONPatch](https://datatracker.ietf.org/doc/html/rfc6902#section-4.6)
- Added tests for the new API, including success and error cases.
- Updated README/OpenAPI documentation to include the new endpoints.

---

As an interesting "edge case" worth calling out, the following payload is currently honored by the API:
```
{
  "data": {}
}
```

Given the `WorkspaceActionPause` struct is simply `{"paused": true|false}`, the "empty" Envelope presented above deserializes the JSON using the zero value of `bool` (which is `false`).

Our validation today is always performed against the **deserialized** object, and as such impossible to distinguish the following cases:
```
{
  "data": {}
}
```

vs

```
{
  "data": {
    "paused": false
  }
}
```

The effort and (relative) complexity to prevent this and return a `422` in this scenario was not deemed "worth it" for the time being.  As a result, a test case has been added for this specific scenario to at minimum document this "strange" behavior.
- Clients, however, should **NOT** rely on this behavior and always provide a fully defined `WorkspaceActionPause` JSON object to ensure future compatibility.

Signed-off-by: Andy Stoneberg <[email protected]>
@andyatmiami andyatmiami force-pushed the feat/workspace-start-api branch from 41f8118 to e0592d4 Compare July 16, 2025 15:39
@andyatmiami
Copy link
Contributor Author

Verifying behavior

Check existing state

➜ backend/ git:(feat/workspace-start-api) $ kubectl get workspaces -A
NAMESPACE   NAME                         STATE
default     jupyterlab-scipy-workspace   Running
default     jupyterlab-workspace         Running

Pause running jupyterlab-workspace workspace

➜ backend/ git:(feat/workspace-start-api) $ curl -X 'POST' \
  'http://localhost:4000/api/v1/workspaces/default/jupyterlab-workspace/actions/pause' \
  -H 'accept: application/json' \
  -H 'Content-Type: application/json' \
  -d '{
  "data": {
    "paused": true
  }
}'

{
	"data": {
		"paused": true
	}
}

Verify jupyterlab-workspace now paused

➜ backend/ git:(feat/workspace-start-api) $ kubectl get workspaces -A
NAMESPACE   NAME                         STATE
default     jupyterlab-scipy-workspace   Running
default     jupyterlab-workspace         Paused

Start (unpause) paused jupyterlab-workspace workspace

➜ backend/ git:(feat/workspace-start-api) $ curl -X 'POST' \
  'http://localhost:4000/api/v1/workspaces/default/jupyterlab-workspace/actions/pause' \
  -H 'accept: application/json' \
  -H 'Content-Type: application/json' \
  -d '{
  "data": {
    "paused": false
  }
}'

{
	"data": {
		"paused": false
	}
}

Verify jupyterlab-workspace now running (unpaused)

➜ backend/ git:(feat/workspace-start-api) $ kubectl get workspaces -A
NAMESPACE   NAME                         STATE
default     jupyterlab-scipy-workspace   Running
default     jupyterlab-workspace         Running

Attempt to start (unpause) running jupyterlab-workspace workspace

➜ backend/ git:(feat/workspace-start-api) $ curl -X 'POST' \
  'http://localhost:4000/api/v1/workspaces/default/jupyterlab-workspace/actions/pause' \
  -H 'accept: application/json' \
  -H 'Content-Type: application/json' \
  -d '{
  "data": {
    "paused": false
  }
}'
{
	"error": {
		"code": "422",
		"message": "workspace is in an invalid state for this operation",
		"cause": {}
	}
}

Attempt to start (unpause) running jupyterlab-workspace workspace exercising "empty" Envelope edge case

➜ backend/ git:(feat/workspace-start-api) $ curl -X 'POST' \
  'http://localhost:4000/api/v1/workspaces/default/jupyterlab-workspace/actions/pause' \
  -H 'accept: application/json' \
  -H 'Content-Type: application/json' \
  -d '{
  "data": {}
}'               
{
	"error": {
		"code": "422",
		"message": "workspace is in an invalid state for this operation",
		"cause": {}
	}
}

Verify state

➜ backend/ git:(feat/workspace-start-api) $ kubectl get workspaces -A
NAMESPACE   NAME                         STATE
default     jupyterlab-scipy-workspace   Running
default     jupyterlab-workspace         Running

@thesuperzapper thesuperzapper changed the title feat(ws): Implement workspace start + pause as backend APIs feat(ws): add workspace pause actions backend API Jul 24, 2025
@thesuperzapper
Copy link
Member

@andyatmiami thanks, it took a while but we got there.

/approve

Copy link

[APPROVALNOTIFIER] This PR is APPROVED

This pull-request has been approved by: thesuperzapper

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 /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@thesuperzapper
Copy link
Member

/lgtm

@google-oss-prow google-oss-prow bot added the lgtm label Jul 24, 2025
@google-oss-prow google-oss-prow bot merged commit 843e5c3 into kubeflow:notebooks-v2 Jul 24, 2025
12 checks passed
@github-project-automation github-project-automation bot moved this from Needs Triage to Done in Kubeflow Notebooks Jul 24, 2025
@andyatmiami andyatmiami deleted the feat/workspace-start-api branch August 6, 2025 14:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
approved area/backend area - related to backend components area/v2 area - version - kubeflow notebooks v2 lgtm ok-to-test size/XXL
Projects
Status: Done
Development

Successfully merging this pull request may close these issues.

5 participants