Skip to content
Closed
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
Original file line number Diff line number Diff line change
Expand Up @@ -134,5 +134,95 @@
"source": "openshift:payload:cluster-version-operator",
"lifecycle": "blocking",
"environmentSelector": {}
},
{
"name": "[Jira:\"Cluster Version Operator\"] cluster-version-operator readiness checks should run all checks without errors",
"labels": {},
"resources": {
"isolation": {}
},
"source": "openshift:payload:cluster-version-operator",
"lifecycle": "blocking",
"environmentSelector": {}
},
{
"name": "[Jira:\"Cluster Version Operator\"] cluster-version-operator readiness checks should produce valid JSON that round-trips",
"labels": {},
"resources": {
"isolation": {}
},
"source": "openshift:payload:cluster-version-operator",
"lifecycle": "blocking",
"environmentSelector": {}
},
{
"name": "[Jira:\"Cluster Version Operator\"] cluster-version-operator readiness checks should report node count matching the actual cluster",
"labels": {},
"resources": {
"isolation": {}
},
"source": "openshift:payload:cluster-version-operator",
"lifecycle": "blocking",
"environmentSelector": {}
},
{
"name": "[Jira:\"Cluster Version Operator\"] cluster-version-operator readiness checks should report operator count matching actual ClusterOperators",
"labels": {},
"resources": {
"isolation": {}
},
"source": "openshift:payload:cluster-version-operator",
"lifecycle": "blocking",
"environmentSelector": {}
},
{
"name": "[Jira:\"Cluster Version Operator\"] cluster-version-operator readiness checks should report etcd member count matching actual etcd pods",
"labels": {},
"resources": {
"isolation": {}
},
"source": "openshift:payload:cluster-version-operator",
"lifecycle": "blocking",
"environmentSelector": {}
},
{
"name": "[Jira:\"Cluster Version Operator\"] cluster-version-operator readiness checks should report network type matching actual Network config",
"labels": {},
"resources": {
"isolation": {}
},
"source": "openshift:payload:cluster-version-operator",
"lifecycle": "blocking",
"environmentSelector": {}
},
{
"name": "[Jira:\"Cluster Version Operator\"] cluster-version-operator readiness checks should report PDB count matching actual PodDisruptionBudgets",
"labels": {},
"resources": {
"isolation": {}
},
"source": "openshift:payload:cluster-version-operator",
"lifecycle": "blocking",
"environmentSelector": {}
},
{
"name": "[Jira:\"Cluster Version Operator\"] cluster-version-operator readiness checks should report cluster conditions matching ClusterVersion status",
"labels": {},
"resources": {
"isolation": {}
},
"source": "openshift:payload:cluster-version-operator",
"lifecycle": "blocking",
"environmentSelector": {}
},
{
"name": "[Jira:\"Cluster Version Operator\"] cluster-version-operator readiness checks should complete all checks within 60 seconds",
"labels": {},
"resources": {
"isolation": {}
},
"source": "openshift:payload:cluster-version-operator",
"lifecycle": "blocking",
"environmentSelector": {}
}
]
44 changes: 43 additions & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -188,4 +188,46 @@ Subsystems include: `pkg/cvo`, `pkg/payload`, `lib/resourceapply`, `hack`, etc.

### Development and Testing
- Never test against production clusters - always use disposable test environments
- CVO has significant control over cluster state and can disrupt operations during development
- CVO has significant control over cluster state and can disrupt operations during development

### Deploying CVO with Lightspeed Proposals (Dev)

The proposal controller is gated behind `TechPreviewNoUpgrade`. To test on a Default feature set cluster:

1. **Bypass the feature gate** (local only, do not commit):
```go
// In pkg/cvo/cvo.go, shouldEnableProposalController()
return true
```

2. **Build and push the skills image** to the cluster's internal registry:
```bash
oc new-project cvo-dev
# Build from the agentic-skills repo
podman build -f Dockerfile -t <registry-route>/cvo-dev/agentic-skills:latest .
podman push --tls-verify=false <registry-route>/cvo-dev/agentic-skills:latest
```

3. **Set `LIGHTSPEED_SKILLS_IMAGE`** in `install/0000_00_cluster-version-operator_30_deployment.yaml` to the internal registry image (local only, do not commit):
```yaml
- name: LIGHTSPEED_SKILLS_IMAGE
value: "image-registry.openshift-image-registry.svc:5000/cvo-dev/agentic-skills@sha256:..."
```

4. **Build and deploy** following the standard dev workflow in `docs/dev/README.md`:
```bash
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o _output/linux/amd64/cluster-version-operator ./cmd/cluster-version-operator/
# Build image on cluster, then: oc adm release new ...
```

5. **Grant image pull access** for the sandbox namespace:
```bash
oc policy add-role-to-group system:image-puller system:serviceaccounts:openshift-lightspeed -n cvo-dev
```

6. **Apply the prompt ConfigMap**:
```bash
oc apply -f install/0000_00_cluster-version-operator_50_lightspeed-prompts.yaml
```

7. **Revert local changes** before committing (steps 1 and 3)
2 changes: 2 additions & 0 deletions install/0000_00_cluster-version-operator_30_deployment.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,8 @@ spec:
fieldPath: spec.nodeName
- name: CLUSTER_PROFILE
value: '{{ .ClusterProfile }}'
- name: LIGHTSPEED_SKILLS_IMAGE
value: "quay.io/openshift/ci:ocp_5.0_agentic-skills"
# this pod is hostNetwork and uses the internal LB DNS name when possible, which the kubelet also uses.
# this dnsPolicy allows us to use the same dnsConfig as the kubelet, without access to read it ourselves.
dnsPolicy: Default
Expand Down
2 changes: 1 addition & 1 deletion pkg/cvo/availableupdates_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -209,7 +209,7 @@ func newOperator(url string, cluster release, promqlMock clusterconditions.Condi
func() ([]configv1.Release, []configv1.ConditionalUpdate, error) {
return nil, nil, nil
},
fake.NewClientBuilder().Build(), func(_ string) (*configv1.ClusterVersion, error) {
fake.NewClientBuilder().Build(), nil, func(_ string) (*configv1.ClusterVersion, error) {
return &configv1.ClusterVersion{}, nil
},
func(_ context.Context, namespace, name string, _ metav1.GetOptions) (*corev1.ConfigMap, error) {
Expand Down
5 changes: 5 additions & 0 deletions pkg/cvo/cvo.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/apimachinery/pkg/util/wait"
"k8s.io/client-go/dynamic"
informerscorev1 "k8s.io/client-go/informers/core/v1"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/kubernetes/scheme"
Expand Down Expand Up @@ -111,6 +112,7 @@ type Operator struct {

client clientset.Interface
kubeClient kubernetes.Interface
dynamicClient dynamic.Interface
operatorClient operatorclientset.Interface
eventRecorder record.EventRecorder

Expand Down Expand Up @@ -244,6 +246,7 @@ func New(
overrides *cvotls.Settings,
client clientset.Interface,
kubeClient kubernetes.Interface,
dynamicClient dynamic.Interface,
operatorClient operatorclientset.Interface,
exclude string,
clusterProfile string,
Expand Down Expand Up @@ -276,6 +279,7 @@ func New(

client: client,
kubeClient: kubeClient,
dynamicClient: dynamicClient,
operatorClient: operatorClient,
eventRecorder: eventBroadcaster.NewRecorder(scheme.Scheme, corev1.EventSource{Component: namespace}),
queue: workqueue.NewTypedRateLimitingQueueWithConfig(workqueue.DefaultTypedControllerRateLimiter[any](), workqueue.TypedRateLimitingQueueConfig[any]{Name: "clusterversion"}),
Expand Down Expand Up @@ -370,6 +374,7 @@ func New(
return availableUpdates.Updates, availableUpdates.ConditionalUpdates, nil
},
rtClient,
dynamicClient,
cvInformer.Lister().Get,
func(ctx context.Context, namespace, name string, opts metav1.GetOptions) (*corev1.ConfigMap, error) {
return kubeClient.CoreV1().ConfigMaps(namespace).Get(ctx, name, opts)
Expand Down
2 changes: 1 addition & 1 deletion pkg/cvo/cvo_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2756,7 +2756,7 @@ func TestOperator_availableUpdatesSync(t *testing.T) {
ctx := context.Background()
optr.proposalController = proposal.NewController(func() ([]configv1.Release, []configv1.ConditionalUpdate, error) {
return nil, nil, nil
}, ctrlruntimefake.NewClientBuilder().Build(), func(_ string) (*configv1.ClusterVersion, error) {
}, ctrlruntimefake.NewClientBuilder().Build(), nil, func(_ string) (*configv1.ClusterVersion, error) {
return &configv1.ClusterVersion{}, nil
}, func(_ context.Context, namespace, name string, _ metav1.GetOptions) (*corev1.ConfigMap, error) {
return &corev1.ConfigMap{}, nil
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,8 @@ spec:
fieldPath: spec.nodeName
- name: CLUSTER_PROFILE
value: 'some-profile'
- name: LIGHTSPEED_SKILLS_IMAGE
value: "quay.io/openshift/ci:ocp_5.0_agentic-skills"
# this pod is hostNetwork and uses the internal LB DNS name when possible, which the kubelet also uses.
# this dnsPolicy allows us to use the same dnsConfig as the kubelet, without access to read it ourselves.
dnsPolicy: Default
Expand Down
105 changes: 105 additions & 0 deletions pkg/proposal/analysis_schema.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
{
"type": "object",
"required": ["analysisData"],
"properties": {
"analysisData": {
"type": "array",
"description": "Typed components describing upgrade readiness. Must include exactly one ota_readiness_summary. Include one ota_finding per blocker or warning. Include one ota_olm_operator_status if OLM operators are present.",
"minItems": 1,
"items": {
"oneOf": [
{
"type": "object",
"description": "Overall upgrade readiness summary with per-check results.",
"properties": {
"type": { "type": "string", "const": "ota_readiness_summary" },
"decision": {
"type": "string",
"enum": ["recommend", "caution", "block", "escalate"],
"description": "recommend=all clear, caution=warnings only, block=blockers found, escalate=insufficient data"
},
"checks": {
"type": "array",
"description": "One entry per readiness check from the input JSON.",
"items": {
"type": "object",
"properties": {
"name": { "type": "string", "description": "Check name, e.g. Cluster Conditions, Operator Health" },
"status": { "type": "string", "enum": ["pass", "warn", "fail", "error"] },
"detail": { "type": "string", "description": "One-line summary" }
},
"required": ["name", "status"]
}
}
},
"required": ["type", "decision", "checks"]
},
{
"type": "object",
"description": "A specific blocker, warning, or informational finding.",
"properties": {
"type": { "type": "string", "const": "ota_finding" },
"severity": { "type": "string", "enum": ["blocker", "warning", "info"] },
"check": { "type": "string", "description": "Which readiness check surfaced this" },
"detail": { "type": "string", "description": "Description for a cluster administrator" },
"affectedResources": { "type": "array", "items": { "type": "string" } },
"prerequisite": { "type": "string", "description": "Action to resolve before upgrading" },
"verifyCommand": { "type": "string", "description": "Command to verify the finding is resolved" }
},
"required": ["type", "severity", "check", "detail"]
},
{
"type": "object",
"description": "Per-operator OLM lifecycle status.",
"properties": {
"type": { "type": "string", "const": "ota_olm_operator_status" },
"operators": {
"type": "array",
"items": {
"type": "object",
"properties": {
"name": { "type": "string" },
"namespace": { "type": "string" },
"displayName": { "type": "string" },
"installedVersion": { "type": "string" },
"channel": { "type": "string" },
"source": { "type": "string" },
"installPlanApproval": { "type": "string", "enum": ["Automatic", "Manual"] },
"pendingUpgrade": { "type": "boolean" },
"pendingVersion": { "type": "string" },
"compatibleWithTarget": { "type": "boolean" },
"availableChannels": { "type": "array", "items": { "type": "string" } },
"ocpCompat": {
"type": "object",
"properties": { "min": { "type": "string" }, "max": { "type": "string" } }
},
"lifecycle": {
"type": "object",
"properties": {
"productName": { "type": "string" },
"supportPhase": { "type": "string", "enum": ["Full Support", "Maintenance Support", "End of life"] },
"ocpVersions": { "type": "string" },
"maintenanceEnds": { "type": "string" }
}
}
},
"required": ["name", "namespace"]
}
},
"summary": {
"type": "object",
"properties": {
"totalOperators": { "type": "integer" },
"pendingUpgrades": { "type": "integer" },
"manualApproval": { "type": "integer" },
"incompatibleWithTarget": { "type": "integer" }
}
}
},
"required": ["type", "operators", "summary"]
}
]
}
}
}
}
Loading