Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion hyperfleet/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ Kubernetes resources (Jobs, Secrets, ConfigMaps, Services) created by adapters t
- Fetches cluster details from API
- Evaluates preconditions
- Creates Kubernetes resources if conditions met
- Reports status → POST /clusters/{id}/statuses
- Reports status → PUT /clusters/{id}/statuses
Comment thread
coderabbitai[bot] marked this conversation as resolved.
6. API aggregates adapter statuses → Updates cluster status
7. Cycle repeats until cluster reaches Ready phase
```
Expand Down
2 changes: 1 addition & 1 deletion hyperfleet/components/adapter/adapter-versioning.md
Original file line number Diff line number Diff line change
Expand Up @@ -233,7 +233,7 @@ When Sentinel introduces a breaking event schema change, follow the expand-contr
- Report adapter status and provisioning results (WRITE)

**Adapter status report payloads:**
- When adapters report status or provisioning results, they POST/PUT to HyperFleet API endpoints
- When adapters report status or provisioning results, they PUT to HyperFleet API endpoints
- Status payload structure follows the API schema version from the imported API client library
Comment thread
coderabbitai[bot] marked this conversation as resolved.

**API version targeting:**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -585,7 +585,7 @@ post:
post_actions:
- name: updateStatus
api_call:
method: POST
method: PUT
url: /clusters/{{ .clusterId }}/statuses
body: '{{ .clusterStatusPayload }}'
```
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ This document captures the key design decisions, trade-offs, and rationale behin
2. [Kubernetes Resource Management](#2-kubernetes-resource-management-vs-in-process-execution)
3. [Anemic Events Pattern](#3-anemic-events-pattern)
4. [Condition-Based Status Reporting](#4-condition-based-status-reporting)
5. [Adapters POST Status Updates](#5-adapters-post-status-updates)
5. [Adapters PUT Status Updates](#5-adapters-put-status-updates)
6. [Helm-Based Deployment](#6-helm-based-deployment)
7. [Layered Configuration Architecture](#7-layered-configuration-architecture)
8. [CEL Expression Language](#8-cel-expression-language)
Expand Down Expand Up @@ -178,20 +178,20 @@ observed_time: "..." # Timestamp when status was reported

---

## 5. Adapters POST Status Updates
## 5. Adapters PUT Status Updates
Comment thread
ma-hill marked this conversation as resolved.

**Decision:** Adapters POST status updates directly to HyperFleet API without checking if status exists first.
**Decision:** Adapters PUT status updates directly to HyperFleet API without checking if status exists first.

**Why:**
- **Simple flow** - single API call per status update
- **API handles create-or-update** - server-side logic determines if creating or updating
- **Idempotent** - same POST multiple times produces same result
- **Less latency** - no GET call before POST
- **Idempotent** - same PUT multiple times produces same result
- **Less latency** - no GET call before PUT
- **Stateless adapter** - adapter doesn't need to track if status exists

**Implementation:**
```
POST /api/hyperfleet/v1/clusters/{clusterId}/statuses
PUT /api/hyperfleet/v1/clusters/{clusterId}/statuses
{
"adapter": "validation",
"observed_generation": 1,
Expand Down
38 changes: 10 additions & 28 deletions hyperfleet/components/adapter/framework/adapter-flow-diagrams.md
Original file line number Diff line number Diff line change
Expand Up @@ -123,13 +123,7 @@ sequenceDiagram
A->>A: Log skip reason (debug)

Note over A: Report status - not applied
A->>API: GET /api/hyperfleet/v1/clusters/cls-123/statuses

alt ClusterStatus exists (200 OK)
A->>API: PATCH /statuses/{statusId}<br/>Applied=False, Available=False, Health=True
else ClusterStatus not found (404)
A->>API: POST /statuses<br/>Applied=False, Available=False, Health=True
end
A->>API: PUT /statuses<br/>Applied=False, Available=False, Health=True

API-->>A: Status updated
A->>B: Acknowledge message
Expand All @@ -143,13 +137,7 @@ sequenceDiagram
K-->>A: Resources created

Note over A: Report status - resources created
A->>API: GET /api/hyperfleet/v1/clusters/cls-123/statuses

alt ClusterStatus exists (200 OK)
A->>API: PATCH /statuses/{statusId}<br/>Applied=True, Available=False, Health=True
else ClusterStatus not found (404)
A->>API: POST /statuses<br/>Applied=True, Available=False, Health=True
end
A->>API: PUT /statuses<br/>Applied=True, Available=False, Health=True

API-->>A: Status updated
A->>B: Acknowledge message
Expand All @@ -160,13 +148,7 @@ sequenceDiagram

alt Postconditions NOT met (workload in progress)
Note over A: Workload still running
A->>API: GET /api/hyperfleet/v1/clusters/cls-123/statuses

alt ClusterStatus exists (200 OK)
A->>API: PATCH /statuses/{statusId}<br/>Applied=True, Available=False, Health=True
else ClusterStatus not found (404)
A->>API: POST /statuses<br/>Applied=True, Available=False, Health=True
end
A->>API: PUT /statuses<br/>Applied=True, Available=False, Health=True

API-->>A: Status updated
A->>B: Acknowledge message
Expand All @@ -175,7 +157,7 @@ sequenceDiagram
alt Workload Succeeded
Note over A: Aggregate conditions
A->>A: Available=True (all conditions True)
A->>API: PATCH /statuses/{statusId}<br/>Available=True, Applied=True, Health=True
A->>API: PUT /statuses/{statusId}<br/>Available=True, Applied=True, Health=True
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.

Hmm, this should be PUT /statuses, right? 🤔

Suggested change
A->>API: PUT /statuses/{statusId}<br/>Available=True, Applied=True, Health=True
A->>API: PUT /statuses<br/>Available=True, Applied=True, Health=True

Same for https://github.com/openshift-hyperfleet/architecture/pull/137/changes#diff-f0e96841e8f0442581d7cfabce483bf133ea32e6656d0de18bb4d348d11b1338R175

API-->>A: Status updated

Note over A: Check resource management
Expand All @@ -190,7 +172,7 @@ sequenceDiagram
else Workload Failed
Note over A: Aggregate conditions
A->>A: Available=False (workload failed)
A->>API: PATCH /statuses/{statusId}<br/>Available=False, Applied=True, Health=True
A->>API: PUT /statuses/{statusId}<br/>Available=False, Applied=True, Health=True
API-->>A: Status updated

Note over A: Check resource management
Expand Down Expand Up @@ -266,9 +248,9 @@ flowchart LR
- `clusterId` enables parent-child relationships (e.g., nodepools → cluster)

### Status Upsert Pattern
- Adapters POST status updates to HyperFleet API
- Adapters PUT status updates to HyperFleet API
- API handles create-or-update logic server-side
- Idempotent: same POST multiple times = same result
- Idempotent: same PUT multiple times = same result
- Prevents race conditions between adapters

### Status Reporting Pattern
Expand Down Expand Up @@ -396,7 +378,7 @@ sequenceDiagram
rect rgb(240, 255, 240)
Note over Adapter: Post-Processing (always runs)
Adapter->>Adapter: Evaluate conditions (CEL)
Adapter->>API: POST /resources/{id}/statuses (Applied=False)
Adapter->>API: PUT /resources/{id}/statuses (Applied=False)
end

Note over User, K8s: Phase 4 - API Aggregates & Deletes (Hierarchical)
Expand Down Expand Up @@ -524,12 +506,12 @@ sequenceDiagram
Sentinel->>Sub_Adapter: CloudEvent (subresource)
Sub_Adapter->>Sub_Adapter: Capture deleted_time, evaluate lifecycle.delete
Sub_Adapter->>Sub_Adapter: Clean up subresource resources (per-resource ordering)
Sub_Adapter->>API: POST status (Applied=False, Health=True, Finalized=True)
Sub_Adapter->>API: PUT status (Applied=False, Health=True, Finalized=True)
and Resource cleanup (in parallel)
Sentinel->>Res_Adapter: CloudEvent (resource)
Res_Adapter->>Res_Adapter: Capture deleted_time, evaluate lifecycle.delete
Res_Adapter->>Res_Adapter: Clean up resource resources (per-resource ordering)
Res_Adapter->>API: POST status (Applied=False, Health=True, Finalized=True)
Res_Adapter->>API: PUT status (Applied=False, Health=True, Finalized=True)
Comment thread
coderabbitai[bot] marked this conversation as resolved.
end

API->>API: Subresource Reconciled=True?
Expand Down
10 changes: 5 additions & 5 deletions hyperfleet/components/adapter/framework/adapter-frame-design.md
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ graph TB
BrokerLib -->|Connects| MessageBroker
K8sClient -->|CRUD Operations| K8sAPI
APIClient -->|REST Calls| HyperFleetAPI
Reporter -->|POST Status| HyperFleetAPI
Reporter -->|PUT Status| HyperFleetAPI

Logger -.->|Used by| Components
Errors -.->|Used by| Components
Expand Down Expand Up @@ -634,7 +634,7 @@ subscriber.Subscribe(ctx, func(ctx context.Context, msg []byte) error {
#### Purpose
- Evaluate tracked Kubernetes resources using CEL expressions
- Build status payload with conditions (applied, available, health) and custom data
- Report status to HyperFleet API via POST requests
- Report status to HyperFleet API via PUT requests
- Support conditional reporting based on expression evaluation
- **Always executes**, even when preconditions fail or resources weren't created

Expand Down Expand Up @@ -772,7 +772,7 @@ subscriber.Subscribe(ctx, func(ctx context.Context, msg []byte) error {
- Build status payload with evaluated conditions and data
- Execute postActions (from `post.postActions`):
- Evaluate `when` condition (if specified)
- POST status payload to HyperFleet API endpoint
- PUT status payload to HyperFleet API endpoint
Comment thread
coderabbitai[bot] marked this conversation as resolved.
- Execute additional actions (e.g., logging)
- Acknowledge message to broker

Expand Down Expand Up @@ -972,9 +972,9 @@ These patterns align with the workflow described in [Adapter Flow Diagrams](./ad
- Kubernetes standard fields remain unchanged: `metadata.name`, `status.phase`

### Status Upsert Pattern
- Adapters POST status updates to HyperFleet API: `POST /api/hyperfleet/v1/clusters/{resourceId}/statuses`
- Adapters PUT status updates to HyperFleet API: `PUT /api/hyperfleet/v1/clusters/{resourceId}/statuses`
- API handles create-or-update logic server-side (idempotent)
- Same POST multiple times = same result
- Same PUT multiple times = same result
- Prevents race conditions between adapters

### Idempotency Pattern
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ pubsubContainer, err := testcontainers.GenericContainer(ctx, testcontainers.Gene
apiServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Handle GET /clusters/{id}
// Handle GET /clusters/{id}/statuses
// Handle POST /clusters/{id}/statuses
// Handle PUT /clusters/{id}/statuses
// Handle PATCH /clusters/{id}/statuses/{statusId}
}))
defer apiServer.Close()
Expand All @@ -104,7 +104,7 @@ defer apiServer.Close()
**API Endpoints to Mock**:
- `GET /api/hyperfleet/v1/clusters/{clusterId}` - Return cluster object
- `GET /api/hyperfleet/v1/clusters/{clusterId}/statuses` - Return existing status or 404
- `POST /api/hyperfleet/v1/clusters/{clusterId}/statuses` - Create/upsert status
- `PUT /api/hyperfleet/v1/clusters/{clusterId}/statuses` - Create/upsert status
- `PATCH /api/hyperfleet/v1/clusters/{clusterId}/statuses/{statusId}` - Update status

**Request/Response Tracking**:
Expand Down Expand Up @@ -391,7 +391,7 @@ spec:
postActions:
- name: "reportStatus"
apiCall:
method: "POST"
method: "PUT"
url: "{{ .hyperfleetApiBaseUrl }}/api/hyperfleet/{{ .hyperfleetApiVersion }}/clusters/{{ .clusterId }}/statuses"
body: "{{ .statusPayload }}"
timeout: 30s
Expand Down Expand Up @@ -513,7 +513,7 @@ spec:
2. Wait for adapter to process event (max 5 seconds)
3. Verify API was called: `GET /clusters/cls-test-001`
4. Verify Job was created in Kubernetes
5. Verify status was reported: `POST /clusters/cls-test-001/statuses`
5. Verify status was reported: `PUT /clusters/cls-test-001/statuses`

**Expected Outcomes**:
- ✅ Event consumed from broker
Expand Down
4 changes: 2 additions & 2 deletions hyperfleet/components/adapter/framework/adapter-metrics.md
Original file line number Diff line number Diff line change
Expand Up @@ -215,14 +215,14 @@ hyperfleet_adapter_resources_deleted_total{component="adapter-validation",versio
**Labels**:
- `adapter_name` - Name of the adapter
- `api` - API being called: `hyperfleet`, `kubernetes`, `external`
- `method` - HTTP method: `GET`, `POST`, `PATCH`, `DELETE`
- `method` - HTTP method: `GET`, `POST`, `PATCH`, `DELETE`, `PUT`
- `endpoint` - API endpoint (sanitized, no IDs): e.g., `/clusters/{id}`, `/statuses`
- `status_code` - HTTP status code: `200`, `404`, `500`, etc.

**Example**:
```prometheus
hyperfleet_adapter_api_requests_total{component="adapter-validation",version="v1.0.0",adapter_name="validation",api="hyperfleet",method="GET",endpoint="/clusters/{id}",status_code="200"} 1523
hyperfleet_adapter_api_requests_total{component="adapter-validation",version="v1.0.0",adapter_name="validation",api="hyperfleet",method="POST",endpoint="/statuses",status_code="200"} 1487
hyperfleet_adapter_api_requests_total{component="adapter-validation",version="v1.0.0",adapter_name="validation",api="hyperfleet",method="PUT",endpoint="/statuses",status_code="200"} 1487
Comment thread
coderabbitai[bot] marked this conversation as resolved.
hyperfleet_adapter_api_requests_total{component="adapter-validation",version="v1.0.0",adapter_name="validation",api="kubernetes",method="POST",endpoint="/namespaces/{ns}/jobs",status_code="201"} 1432
hyperfleet_adapter_api_requests_total{component="adapter-validation",version="v1.0.0",adapter_name="validation",api="kubernetes",method="GET",endpoint="/namespaces/{ns}/jobs/{name}",status_code="200"} 2145
```
Expand Down
50 changes: 35 additions & 15 deletions hyperfleet/components/adapter/framework/adapter-status-contract.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ Last Updated: 2025-12-09
- [Status Reporting Endpoint](#status-reporting-endpoint)
- [Upsert Pattern](#upsert-pattern)
- [Status Payload Structure](#status-payload-structure)
- [POST Request (Upsert ClusterStatus)](#post-request-upsert-clusterstatus)
- [PUT Request (Upsert ClusterStatus)](#put-request-upsert-clusterstatus)
- [Required Fields](#required-fields)
- [Adapter Status Request Fields](#adapter-status-request-fields)
- [Condition Request Fields](#condition-request-fields)
Expand Down Expand Up @@ -48,7 +48,7 @@ Last Updated: 2025-12-09
- [5. Always Report observed_generation and observed_time](#5-always-report-observed_generation-and-observed_time)
- [6. Use Data Field for Structured Information](#6-use-data-field-for-structured-information)
- [7. Handle Errors Gracefully](#7-handle-errors-gracefully)
- [8. Always Use POST](#8-always-use-post)
- [8. Always Use PUT](#8-always-use-put)
- [9. Conditions: reason and message Are Optional](#9-conditions-reason-and-message-are-optional)
- [Versioning](#versioning)
- [References](#references)
Expand All @@ -71,31 +71,31 @@ This document defines the contract between HyperFleet adapters and the HyperFlee
**Base URL**: `{hyperfleetApiBaseUrl}/api/hyperfleet/{hyperfleetApiVersion}/clusters/{clusterId}/statuses`

**Method**:
- `POST` - Upsert ClusterStatus (create or update)
- `PUT` - Upsert ClusterStatus (create or update)

### Upsert Pattern

Adapters **always use POST** for status reporting:
Adapters **always use PUT** for status reporting:

**API Behavior**:
- The HyperFleet API handles the upsert logic server-side
- If ClusterStatus doesn't exist: API creates it
- If ClusterStatus exists: API updates the adapter's status within it
- Idempotent: Same POST multiple times = same result
- Idempotent: Same PUT multiple times = same result

**Adapter Implementation**:
- No need to GET first to check if status exists
- Always POST to the same endpoint
- Always PUT to the same endpoint
- API handles create-or-update logic automatically
- Simpler adapter code, fewer HTTP requests

---

## Status Payload Structure

### POST Request (Upsert ClusterStatus)
### PUT Request (Upsert ClusterStatus)

Always POST the adapter status in this structure:
Always PUT the adapter status in this structure:
Comment thread
ma-hill marked this conversation as resolved.

```json
{
Expand Down Expand Up @@ -136,7 +136,7 @@ Always POST the adapter status in this structure:

**Notes**:
- The API will upsert this adapter status (create or update based on adapter name)
- Other adapter statuses for this cluster are preserved (not affected by this POST)
- Other adapter statuses for this cluster are preserved (not affected by this PUT)
- API will set `created_time` and `last_report_time` automatically
- API will add `last_transition_time` to each condition automatically

Expand Down Expand Up @@ -637,7 +637,7 @@ post:
description: "Example resource must exist"
postActions:
- type: "api_call"
method: "POST"
method: "PUT"
endpoint: "{{ .hyperfleetApiBaseUrl }}/api/{{ .hyperfleetApiVersion }}/clusters/{{ .clusterId }}/statuses"
headers:
Comment thread
ma-hill marked this conversation as resolved.
- name: "Authorization"
Expand All @@ -654,7 +654,7 @@ post:
3. **Evaluate Conditions**: Evaluate CEL expressions for applied, available, health
4. **Evaluate Data**: Evaluate CEL expressions for custom data fields
5. **Build Payload**: Construct status payload with conditions and data
6. **Execute PostActions**: POST to HyperFleet API endpoint
6. **Execute PostActions**: PUT to HyperFleet API endpoint

---

Expand All @@ -675,7 +675,7 @@ Content-Type: application/json
### Example Request

```http
POST /api/v1/clusters/cls-123/statuses HTTP/1.1
PUT /api/v1/clusters/cls-123/statuses HTTP/1.1
Comment thread
ma-hill marked this conversation as resolved.
Host: api.hyperfleet.example.com
Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...
Content-Type: application/json
Expand Down Expand Up @@ -769,9 +769,29 @@ Use the `data` field for adapter-specific structured data that other components

Report adapter errors with `Health=False` and appropriate error messages.

### 8. Always Use POST

Always POST to the same endpoint - the API handles upsert logic server-side for idempotency.
### 8. Always Use PUT

Report status with **PUT** only. The API applies [upsert semantics](#upsert-pattern) on the status resource: the first write creates your adapter’s entry if needed; later writes replace that entry (other adapters’ statuses on the same cluster are unchanged). Repeating the same PUT is safe, so **retries after timeouts or `5xx` responses should replay the same request** rather than using a new verb or URL.

- **One URL per cluster**: `PUT …/api/hyperfleet/{hyperfleetApiVersion}/clusters/{clusterId}/statuses` (same path every reconcile; no “create vs update” branch in the adapter).
- **No prerequisite GET**: you do not need to read existing status before reporting.
- **Headers**: `Authorization: Bearer <token>` and `Content-Type: application/json` (see [HTTP Headers](#http-headers)).

```text
Adapter HyperFleet API
| |
| PUT /api/hyperfleet/{hyperfleetApiVersion}/ |
| clusters/{clusterId}/statuses |
| |
| Authorization: Bearer <token> |
| Content-Type: application/json |
| -----------------------------------------------> |
| adapter, observed_generation, observed_time, |
| conditions[], optional data, optional meta |
| |
| 200 OK (created or updated) |
| <----------------------------------------------- |
```

### 9. Conditions: reason and message Are Optional

Expand Down
Loading