@opensoha/contracts 0.1.23 → 0.1.25
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/COMPATIBILITY.md +95 -0
- package/README.md +24 -6
- package/agent/environment/README.md +55 -0
- package/agent/environment/export.go +139 -0
- package/agent/environment/export_test.go +62 -0
- package/agent/environment/preflight.go +62 -0
- package/agent/environment/preflight_test.go +41 -0
- package/agent/environment/protocol.go +221 -0
- package/agent/environment/protocol_test.go +81 -0
- package/agent/environment-lease.schema.json +2 -2
- package/agent/environment-template.schema.json +1 -1
- package/agent/guest/browser_network.go +77 -0
- package/agent/guest/data.go +62 -0
- package/agent/guest/protocol.go +235 -0
- package/agent/guest/protocol_test.go +142 -0
- package/agent/guest/renewal.go +56 -0
- package/agent/guest/renewal_test.go +53 -0
- package/agent/guest/runtime.go +315 -0
- package/agent/guest/runtime_json.go +75 -0
- package/agent/guest/runtime_test.go +110 -0
- package/agent/personal-browser-network-request.schema.json +40 -0
- package/agent/personal-browser-network-response.schema.json +44 -0
- package/agent/personal-environment-export.schema.json +55 -0
- package/agent/personal-environment-preflight-request.schema.json +52 -0
- package/agent/personal-environment-preflight-result.schema.json +69 -0
- package/agent/personal-environment-request.schema.json +137 -0
- package/agent/personal-environment-result.schema.json +101 -0
- package/agent/personal-guest-binding.schema.json +92 -0
- package/agent/personal-guest-data-plan.schema.json +32 -0
- package/agent/personal-guest-enrollment.schema.json +21 -0
- package/agent/personal-guest-heartbeat.schema.json +44 -0
- package/agent/personal-guest-provisioning.schema.json +71 -0
- package/agent/personal-guest-renewal.schema.json +33 -0
- package/agent/personal-guest-runtime-result.schema.json +164 -0
- package/agent/personal-guest-runtime.schema.json +304 -0
- package/agent/personal-guest-tools.schema.json +75 -0
- package/auth/catalog_generated.go +2 -2
- package/auth/gateway_permissions_test.go +48 -0
- package/auth/permission-catalog.json +331 -1
- package/auth/token-claims.schema.json +5 -0
- package/compat/consumer-revisions.json +3 -2
- package/compat/json-schema-ecosystem-0.1.0.json +1 -1
- package/compat/openapi-loose-boundaries-0.1.0.json +1 -1
- package/connectors/connector-action.schema.json +138 -0
- package/fixtures/json-schema/connector-action/invalid/empty-values.json +11 -0
- package/fixtures/json-schema/connector-action/invalid/invalid-expiry.json +16 -0
- package/fixtures/json-schema/connector-action/invalid/lowercase-alias.json +16 -0
- package/fixtures/json-schema/connector-action/invalid/unknown-entry-field.json +17 -0
- package/fixtures/json-schema/connector-action/invalid/unknown-frame-field.json +17 -0
- package/fixtures/json-schema/connector-action/invalid/unknown-version.json +16 -0
- package/fixtures/json-schema/connector-action/invalid/unversioned-reference.json +16 -0
- package/fixtures/json-schema/connector-action/invalid/zero-version.json +16 -0
- package/fixtures/json-schema/connector-action/valid/pinned.json +16 -0
- package/fixtures/json-schema/connector-definition/invalid/credential-userinfo.json +8 -0
- package/fixtures/json-schema/connector-definition/invalid/reference-traversal.json +8 -0
- package/fixtures/json-schema/connector-definition/invalid/zero-version.json +8 -0
- package/fixtures/json-schema/connector-definition/valid/canonical.json +8 -0
- package/fixtures/json-schema/connector-definition/valid/pinned.json +8 -0
- package/fixtures/json-schema/personal-browser-network-request/invalid/bounded.json +4 -0
- package/fixtures/json-schema/personal-browser-network-request/valid/bounded.json +4 -0
- package/fixtures/json-schema/personal-browser-network-response/invalid/bounded.json +5 -0
- package/fixtures/json-schema/personal-browser-network-response/valid/bounded.json +5 -0
- package/fixtures/json-schema/personal-environment-export/invalid/extra.json +11 -0
- package/fixtures/json-schema/personal-environment-export/invalid/ownerId.json +10 -0
- package/fixtures/json-schema/personal-environment-export/invalid/sha256.json +10 -0
- package/fixtures/json-schema/personal-environment-export/invalid/sizeBytes.json +10 -0
- package/fixtures/json-schema/personal-environment-export/valid/complete.json +10 -0
- package/fixtures/json-schema/personal-environment-preflight-request/invalid/disk.json +11 -0
- package/fixtures/json-schema/personal-environment-preflight-request/invalid/network.json +11 -0
- package/fixtures/json-schema/personal-environment-preflight-request/invalid/node.json +12 -0
- package/fixtures/json-schema/personal-environment-preflight-request/invalid/owner.json +12 -0
- package/fixtures/json-schema/personal-environment-preflight-request/valid/bounded.json +11 -0
- package/fixtures/json-schema/personal-environment-preflight-result/invalid/disk.json +15 -0
- package/fixtures/json-schema/personal-environment-preflight-result/invalid/network.json +15 -0
- package/fixtures/json-schema/personal-environment-preflight-result/invalid/no-hard-quota.json +15 -0
- package/fixtures/json-schema/personal-environment-preflight-result/invalid/node.json +16 -0
- package/fixtures/json-schema/personal-environment-preflight-result/invalid/not-ready.json +15 -0
- package/fixtures/json-schema/personal-environment-preflight-result/invalid/owner.json +16 -0
- package/fixtures/json-schema/personal-environment-preflight-result/valid/bounded.json +15 -0
- package/fixtures/json-schema/personal-environment-request/invalid/delete-data-argv.json +24 -0
- package/fixtures/json-schema/personal-environment-request/invalid/delete-data-createData.json +22 -0
- package/fixtures/json-schema/personal-environment-request/invalid/delete-data-taskId.json +22 -0
- package/fixtures/json-schema/personal-environment-request/invalid/export-argv.json +22 -0
- package/fixtures/json-schema/personal-environment-request/invalid/export-createData.json +22 -0
- package/fixtures/json-schema/personal-environment-request/invalid/export-taskId.json +22 -0
- package/fixtures/json-schema/personal-environment-request/invalid/extra-command.json +24 -0
- package/fixtures/json-schema/personal-environment-request/invalid/host-workspace.json +22 -0
- package/fixtures/json-schema/personal-environment-request/invalid/image-injection.json +22 -0
- package/fixtures/json-schema/personal-environment-request/invalid/network-egress.json +21 -0
- package/fixtures/json-schema/personal-environment-request/invalid/no-command.json +21 -0
- package/fixtures/json-schema/personal-environment-request/invalid/recreate-on-release.json +22 -0
- package/fixtures/json-schema/personal-environment-request/invalid/relative-command.json +24 -0
- package/fixtures/json-schema/personal-environment-request/invalid/released-identity.json +22 -0
- package/fixtures/json-schema/personal-environment-request/invalid/task-empty.json +27 -0
- package/fixtures/json-schema/personal-environment-request/invalid/task-host-path.json +27 -0
- package/fixtures/json-schema/personal-environment-request/invalid/task-noncanonical.json +27 -0
- package/fixtures/json-schema/personal-environment-request/invalid/task-null.json +27 -0
- package/fixtures/json-schema/personal-environment-request/invalid/task-on-inspection.json +22 -0
- package/fixtures/json-schema/personal-environment-request/invalid/unbounded-quota.json +21 -0
- package/fixtures/json-schema/personal-environment-request/valid/acquire.json +21 -0
- package/fixtures/json-schema/personal-environment-request/valid/delete-data.json +21 -0
- package/fixtures/json-schema/personal-environment-request/valid/execute.json +26 -0
- package/fixtures/json-schema/personal-environment-request/valid/export-data.json +21 -0
- package/fixtures/json-schema/personal-environment-request/valid/inspect.json +21 -0
- package/fixtures/json-schema/personal-environment-request/valid/provision.json +22 -0
- package/fixtures/json-schema/personal-environment-request/valid/release.json +21 -0
- package/fixtures/json-schema/personal-environment-request/valid/task-workspace.json +27 -0
- package/fixtures/json-schema/personal-environment-result/invalid/active-without-observation.json +7 -0
- package/fixtures/json-schema/personal-environment-result/invalid/deleted-output.json +8 -0
- package/fixtures/json-schema/personal-environment-result/invalid/deleted-snapshot.json +8 -0
- package/fixtures/json-schema/personal-environment-result/invalid/deleted-taskId.json +8 -0
- package/fixtures/json-schema/personal-environment-result/invalid/private-handle.json +8 -0
- package/fixtures/json-schema/personal-environment-result/invalid/stopped-fake-observation.json +20 -0
- package/fixtures/json-schema/personal-environment-result/invalid/task-empty.json +22 -0
- package/fixtures/json-schema/personal-environment-result/invalid/task-host-path.json +22 -0
- package/fixtures/json-schema/personal-environment-result/invalid/task-null.json +22 -0
- package/fixtures/json-schema/personal-environment-result/invalid/task-on-stopped.json +8 -0
- package/fixtures/json-schema/personal-environment-result/invalid/task-without-command-output.json +21 -0
- package/fixtures/json-schema/personal-environment-result/valid/active.json +21 -0
- package/fixtures/json-schema/personal-environment-result/valid/deleted.json +7 -0
- package/fixtures/json-schema/personal-environment-result/valid/expired.json +7 -0
- package/fixtures/json-schema/personal-environment-result/valid/missing.json +7 -0
- package/fixtures/json-schema/personal-environment-result/valid/released.json +7 -0
- package/fixtures/json-schema/personal-environment-result/valid/task-workspace.json +22 -0
- package/fixtures/json-schema/personal-guest-binding/invalid/host-path.json +17 -0
- package/fixtures/json-schema/personal-guest-binding/invalid/invalid-policy.json +17 -0
- package/fixtures/json-schema/personal-guest-binding/invalid/invalid-serial.json +17 -0
- package/fixtures/json-schema/personal-guest-binding/invalid/legacy-version.json +17 -0
- package/fixtures/json-schema/personal-guest-binding/invalid/missing-generation.json +17 -0
- package/fixtures/json-schema/personal-guest-binding/invalid/missing-identity.json +16 -0
- package/fixtures/json-schema/personal-guest-binding/invalid/missing-provider-identity.json +16 -0
- package/fixtures/json-schema/personal-guest-binding/invalid/raw-provider-identity.json +17 -0
- package/fixtures/json-schema/personal-guest-binding/valid/minimal.json +17 -0
- package/fixtures/json-schema/personal-guest-data-plan/invalid/quota.json +1 -0
- package/fixtures/json-schema/personal-guest-data-plan/valid/basic.json +1 -0
- package/fixtures/json-schema/personal-guest-enrollment/invalid/capabilities.json +7 -0
- package/fixtures/json-schema/personal-guest-enrollment/invalid/legacy-version.json +4 -0
- package/fixtures/json-schema/personal-guest-enrollment/invalid/missing-identity.json +3 -0
- package/fixtures/json-schema/personal-guest-enrollment/invalid/ownerId.json +5 -0
- package/fixtures/json-schema/personal-guest-enrollment/invalid/token.json +5 -0
- package/fixtures/json-schema/personal-guest-enrollment/valid/minimal.json +4 -0
- package/fixtures/json-schema/personal-guest-heartbeat/invalid/argv.json +11 -0
- package/fixtures/json-schema/personal-guest-heartbeat/invalid/legacy-version.json +8 -0
- package/fixtures/json-schema/personal-guest-heartbeat/invalid/missing-identity.json +7 -0
- package/fixtures/json-schema/personal-guest-heartbeat/invalid/owner.json +9 -0
- package/fixtures/json-schema/personal-guest-heartbeat/invalid/self-ready.json +9 -0
- package/fixtures/json-schema/personal-guest-heartbeat/invalid/zero-sequence.json +8 -0
- package/fixtures/json-schema/personal-guest-heartbeat/valid/minimal.json +8 -0
- package/fixtures/json-schema/personal-guest-provisioning/invalid/network.json +1 -0
- package/fixtures/json-schema/personal-guest-provisioning/valid/basic.json +1 -0
- package/fixtures/json-schema/personal-guest-renewal/invalid/unknown.json +1 -0
- package/fixtures/json-schema/personal-guest-renewal/valid/basic.json +1 -0
- package/fixtures/json-schema/personal-guest-runtime/invalid/minimal.json +45 -0
- package/fixtures/json-schema/personal-guest-runtime/valid/minimal.json +45 -0
- package/fixtures/json-schema/personal-guest-runtime-result/invalid/minimal.json +28 -0
- package/fixtures/json-schema/personal-guest-runtime-result/valid/minimal.json +27 -0
- package/fixtures/json-schema/personal-guest-tools/invalid/minimal.json +31 -0
- package/fixtures/json-schema/personal-guest-tools/valid/minimal.json +30 -0
- package/fixtures/json-schema/source-import-policy/invalid/case-variant-scope.json +4 -0
- package/fixtures/json-schema/source-import-policy/invalid/case-variant-version.json +4 -0
- package/fixtures/json-schema/source-import-policy/invalid/duplicate-grant.json +4 -0
- package/fixtures/json-schema/source-import-policy/invalid/empty-id.json +4 -0
- package/fixtures/json-schema/source-import-policy/invalid/future-version.json +4 -0
- package/fixtures/json-schema/source-import-policy/invalid/unknown-field.json +4 -0
- package/fixtures/json-schema/source-import-policy/valid/deny-all.json +4 -0
- package/fixtures/json-schema/source-import-policy/valid/shared.json +4 -0
- package/fixtures/json-schema/token-claims/invalid/authz-negative.json +23 -0
- package/fixtures/json-schema/token-claims/invalid/authz-string.json +23 -0
- package/fixtures/json-schema/token-claims/invalid/authz-zero.json +23 -0
- package/fixtures/json-schema/token-claims/valid/versioned-session.json +23 -0
- package/fixtures/openapi/AgentRunEventListEnvelope/invalid/basic.json +8 -0
- package/fixtures/openapi/AgentRunEventListEnvelope/valid/basic.json +8 -0
- package/fixtures/openapi/AgentRunEventListEnvelope/valid/native.json +1 -0
- package/fixtures/openapi/AgentRunResumeRequest/invalid/basic.json +3 -0
- package/fixtures/openapi/AgentRunResumeRequest/valid/basic.json +3 -0
- package/fixtures/openapi/AgentSSHCommandRequest/invalid/task51.json +24 -0
- package/fixtures/openapi/AgentSSHCommandRequest/valid/task51.json +23 -0
- package/fixtures/openapi/AgentSSHCommandResult/invalid/task51.json +8 -0
- package/fixtures/openapi/AgentSSHCommandResult/valid/task51.json +7 -0
- package/fixtures/openapi/AgentSSHSessionRequest/invalid/task51.json +20 -0
- package/fixtures/openapi/AgentSSHSessionRequest/valid/task51.json +19 -0
- package/fixtures/openapi/CapabilityTaskInput/invalid/duplicate-scope.json +36 -0
- package/fixtures/openapi/CapabilityTaskInput/valid/scoped.json +32 -0
- package/fixtures/openapi/DockerQuickCreateHostInput/invalid/guest-access.json +7 -0
- package/fixtures/openapi/DockerQuickCreateHostInput/valid/guest-access.json +9 -0
- package/fixtures/openapi/KnowledgeSourceDocument/invalid/tombstone-with-content.json +1 -0
- package/fixtures/openapi/KnowledgeSourceDocument/valid/tombstone.json +1 -0
- package/fixtures/openapi/KubernetesAgentActionCheck/invalid/unproven-state.json +6 -0
- package/fixtures/openapi/KubernetesAgentActionCheck/valid/configured-denied.json +9 -0
- package/fixtures/openapi/KubernetesAgentActionProfile/invalid/wildcard.json +1 -0
- package/fixtures/openapi/KubernetesAgentActionProfile/valid/workbench.json +1 -0
- package/fixtures/openapi/KubernetesClusterRegistrationRequest/invalid/action-profile.json +3 -0
- package/fixtures/openapi/KubernetesClusterRegistrationRequest/valid/action-profile.json +5 -0
- package/fixtures/openapi/KubernetesNodeListEnvelope/invalid/condition-status-boolean.json +17 -0
- package/fixtures/openapi/KubernetesNodeListEnvelope/invalid/taint-effect-missing.json +16 -0
- package/fixtures/openapi/KubernetesNodeListEnvelope/valid/legacy-fields-absent.json +11 -0
- package/fixtures/openapi/KubernetesNodeListEnvelope/valid/no-taints.json +13 -0
- package/fixtures/openapi/KubernetesNodeListEnvelope/valid/ready-cordoned-tainted.json +28 -0
- package/fixtures/openapi/KubernetesNodeListEnvelope/valid/unknown-fields-null.json +13 -0
- package/fixtures/openapi/KubernetesResourceUpdatePlanRequest/invalid/missing-required.json +1 -0
- package/fixtures/openapi/KubernetesResourceYaml/invalid/missing-required.json +1 -0
- package/fixtures/openapi/KubernetesTopologyCoverage/invalid/empty-as-success.json +5 -0
- package/fixtures/openapi/KubernetesTopologyCoverage/valid/not-installed.json +6 -0
- package/fixtures/openapi/KubernetesTopologyGraphEdge/invalid/negative-weight.json +8 -0
- package/fixtures/openapi/KubernetesTopologyGraphEdge/valid/zero-weight.json +20 -0
- package/fixtures/openapi/KubernetesTopologyGraphNode/invalid/fake-flow-kind.json +42 -0
- package/fixtures/openapi/KubernetesTopologyGraphNode/valid/rule.json +42 -0
- package/fixtures/openapi/KubernetesTopologySnapshot/invalid/raw-source-field.json +33 -0
- package/fixtures/openapi/KubernetesTopologySnapshot/valid/projected-source.json +32 -0
- package/fixtures/openapi/KubernetesTopologySnapshotRequest/invalid/arbitrary-gvr.json +8 -0
- package/fixtures/openapi/KubernetesTopologySnapshotRequest/valid/bounded-sources.json +11 -0
- package/fixtures/openapi/KubernetesTopologySourceQuery/invalid/cluster-namespace.json +4 -0
- package/fixtures/openapi/KubernetesTopologySourceQuery/valid/all-namespaces.json +4 -0
- package/fixtures/openapi/ObservabilityDataSource/invalid/missing-required.json +1 -0
- package/fixtures/openapi/PersonalAgentAccess/invalid/invalid.json +1 -0
- package/fixtures/openapi/PersonalAgentAccess/valid/valid.json +1 -0
- package/fixtures/openapi/PersonalAgentAccessDecision/invalid/invalid.json +1 -0
- package/fixtures/openapi/PersonalAgentAccessDecision/valid/valid.json +1 -0
- package/fixtures/openapi/PersonalAssistantArtifactListEnvelope/invalid/owner.json +8 -0
- package/fixtures/openapi/PersonalAssistantArtifactListEnvelope/valid/side-chat.json +9 -0
- package/fixtures/openapi/PersonalAssistantAvatarRequest/invalid/motion.json +1 -0
- package/fixtures/openapi/PersonalAssistantAvatarRequest/invalid/owner.json +1 -0
- package/fixtures/openapi/PersonalAssistantAvatarRequest/invalid/remote.json +1 -0
- package/fixtures/openapi/PersonalAssistantAvatarRequest/invalid/svg.json +1 -0
- package/fixtures/openapi/PersonalAssistantAvatarRequest/valid/raster.json +1 -0
- package/fixtures/openapi/PersonalAssistantAvatarRequest/valid/reset.json +1 -0
- package/fixtures/openapi/PersonalAssistantUpdateRequest/invalid/empty.json +1 -0
- package/fixtures/openapi/PersonalAssistantUpdateRequest/invalid/owner.json +1 -0
- package/fixtures/openapi/PersonalAssistantUpdateRequest/valid/name.json +1 -0
- package/fixtures/openapi/PersonalDockerEnvironmentPolicy/invalid/host-url.json +12 -0
- package/fixtures/openapi/PersonalDockerEnvironmentPolicy/invalid/lifetime.json +11 -0
- package/fixtures/openapi/PersonalDockerEnvironmentPolicy/invalid/unknown-agent.json +11 -0
- package/fixtures/openapi/PersonalDockerEnvironmentPolicy/valid/basic.json +11 -0
- package/fixtures/openapi/PersonalEnvironment/invalid/invented-state.json +6 -0
- package/fixtures/openapi/PersonalEnvironment/invalid/missing-state.json +5 -0
- package/fixtures/openapi/PersonalEnvironment/invalid/negative-generation.json +7 -0
- package/fixtures/openapi/PersonalEnvironment/invalid/network.json +23 -0
- package/fixtures/openapi/PersonalEnvironment/invalid/private-node.json +24 -0
- package/fixtures/openapi/PersonalEnvironment/invalid/unsupported-backend.json +7 -0
- package/fixtures/openapi/PersonalEnvironment/valid/deleted.json +23 -0
- package/fixtures/openapi/PersonalEnvironment/valid/deleting.json +23 -0
- package/fixtures/openapi/PersonalEnvironment/valid/deletion_unknown.json +23 -0
- package/fixtures/openapi/PersonalEnvironment/valid/frozen-binding.json +13 -0
- package/fixtures/openapi/PersonalEnvironment/valid/ready.json +23 -0
- package/fixtures/openapi/PersonalEnvironment/valid/unknown.json +23 -0
- package/fixtures/openapi/PersonalEnvironment/valid/unprovisioned.json +6 -0
- package/fixtures/openapi/PersonalEnvironmentActionRequest/invalid/command.json +3 -0
- package/fixtures/openapi/PersonalEnvironmentActionRequest/invalid/node.json +4 -0
- package/fixtures/openapi/PersonalEnvironmentActionRequest/invalid/owner.json +4 -0
- package/fixtures/openapi/PersonalEnvironmentActionRequest/invalid/quota.json +10 -0
- package/fixtures/openapi/PersonalEnvironmentActionRequest/valid/reconcile.json +3 -0
- package/fixtures/openapi/PersonalEnvironmentActionRequest/valid/sleep.json +3 -0
- package/fixtures/openapi/PersonalEnvironmentActionRequest/valid/wake.json +4 -0
- package/fixtures/openapi/PersonalEnvironmentDataDeletionRequest/invalid/empty-name.json +5 -0
- package/fixtures/openapi/PersonalEnvironmentDataDeletionRequest/invalid/long-name.json +5 -0
- package/fixtures/openapi/PersonalEnvironmentDataDeletionRequest/invalid/missing-name.json +4 -0
- package/fixtures/openapi/PersonalEnvironmentDataDeletionRequest/invalid/owner.json +6 -0
- package/fixtures/openapi/PersonalEnvironmentDataDeletionRequest/invalid/stale-version.json +5 -0
- package/fixtures/openapi/PersonalEnvironmentDataDeletionRequest/valid/confirmed.json +5 -0
- package/fixtures/openapi/PersonalEnvironmentDataExportRequest/invalid/assistantId.json +4 -0
- package/fixtures/openapi/PersonalEnvironmentDataExportRequest/invalid/environmentVersion.json +4 -0
- package/fixtures/openapi/PersonalEnvironmentDataExportRequest/invalid/ownerId.json +5 -0
- package/fixtures/openapi/PersonalEnvironmentDataExportRequest/valid/current.json +4 -0
- package/fixtures/openapi/PersonalEnvironmentEnvelope/invalid/missing-data.json +1 -0
- package/fixtures/openapi/PersonalEnvironmentEnvelope/valid/state.json +8 -0
- package/fixtures/openapi/PersonalEnvironmentQuota/invalid/cpu.json +7 -0
- package/fixtures/openapi/PersonalEnvironmentQuota/invalid/disk.json +7 -0
- package/fixtures/openapi/PersonalEnvironmentQuota/invalid/network.json +7 -0
- package/fixtures/openapi/PersonalEnvironmentQuota/invalid/processes.json +7 -0
- package/fixtures/openapi/PersonalEnvironmentQuota/valid/basic.json +7 -0
- package/fixtures/openapi/PersonalEnvironmentTemplate/invalid/mode.json +13 -0
- package/fixtures/openapi/PersonalEnvironmentTemplate/invalid/network.json +13 -0
- package/fixtures/openapi/PersonalEnvironmentTemplate/invalid/node.json +14 -0
- package/fixtures/openapi/PersonalEnvironmentTemplate/valid/template.json +13 -0
- package/fixtures/openapi/PersonalEnvironmentTemplateListEnvelope/invalid/missing.json +1 -0
- package/fixtures/openapi/PersonalEnvironmentTemplateListEnvelope/invalid/unbounded.json +1317 -0
- package/fixtures/openapi/PersonalEnvironmentTemplateListEnvelope/valid/choices.json +17 -0
- package/fixtures/openapi/PersonalEnvironmentTemplateListEnvelope/valid/empty.json +3 -0
- package/fixtures/openapi/PersonalEnvironmentUsage/invalid/negative.json +6 -0
- package/fixtures/openapi/PersonalEnvironmentUsage/valid/usage.json +6 -0
- package/fixtures/openapi/PersonalFeedFeedback/invalid/owner.json +4 -0
- package/fixtures/openapi/PersonalFeedFeedback/valid/liked.json +3 -0
- package/fixtures/openapi/PersonalFeedItem/invalid/no-source.json +9 -0
- package/fixtures/openapi/PersonalFeedItem/valid/post.json +15 -0
- package/fixtures/openapi/PersonalFeedSettings/invalid/owner.json +6 -0
- package/fixtures/openapi/PersonalFeedSettings/valid/default.json +5 -0
- package/fixtures/openapi/PersonalGuestPreparationInput/invalid/foreign-owner.json +5 -0
- package/fixtures/openapi/PersonalGuestPreparationInput/invalid/unsafe-counter.json +4 -0
- package/fixtures/openapi/PersonalGuestPreparationInput/valid/ed25519-csr.json +4 -0
- package/fixtures/openapi/PersonalGuestRuntimeEnvelope/invalid/minimal.json +10 -0
- package/fixtures/openapi/PersonalGuestRuntimeEnvelope/valid/minimal.json +9 -0
- package/fixtures/openapi/PersonalGuestRuntimeInput/invalid/minimal.json +6 -0
- package/fixtures/openapi/PersonalGuestRuntimeInput/valid/minimal.json +8 -0
- package/fixtures/openapi/PersonalGuestToolsInput/invalid/minimal.json +5 -0
- package/fixtures/openapi/PersonalGuestToolsInput/valid/minimal.json +4 -0
- package/fixtures/openapi/PersonalVMEnvironmentPolicy/invalid/device.json +15 -0
- package/fixtures/openapi/PersonalVMEnvironmentPolicy/invalid/fractional-size.json +15 -0
- package/fixtures/openapi/PersonalVMEnvironmentPolicy/invalid/network.json +15 -0
- package/fixtures/openapi/PersonalVMEnvironmentPolicy/invalid/owner.json +15 -0
- package/fixtures/openapi/PersonalVMEnvironmentPolicy/invalid/provider.json +15 -0
- package/fixtures/openapi/PersonalVMEnvironmentPolicy/invalid/unknown.json +16 -0
- package/fixtures/openapi/PersonalVMEnvironmentPolicy/valid/pve.json +15 -0
- package/fixtures/openapi/PersonalWorkspaceRuntime/invalid/shared.json +3 -0
- package/fixtures/openapi/PersonalWorkspaceRuntime/valid/shared.json +4 -0
- package/fixtures/openapi/PersonalWorkspaceRuntimeEnvelope/invalid/shared.json +5 -0
- package/fixtures/openapi/PersonalWorkspaceRuntimeEnvelope/valid/shared.json +6 -0
- package/fixtures/openapi/ResourceMonitoringPanel/invalid/unknown-quality.json +7 -0
- package/fixtures/openapi/ResourceMonitoringPanel/valid/not-collected.json +7 -0
- package/fixtures/openapi/ResourceMonitoringPoint/invalid/string-zero.json +4 -0
- package/fixtures/openapi/ResourceMonitoringPoint/valid/missing.json +4 -0
- package/fixtures/openapi/ResourceMonitoringPoint/valid/zero.json +4 -0
- package/fixtures/openapi/ResourceMonitoringView/invalid/raw-query.json +12 -0
- package/fixtures/openapi/ResourceMonitoringView/valid/unconfigured.json +12 -0
- package/fixtures/openapi/SecretGenerateSSHKeyRequest/invalid/task51.json +6 -0
- package/fixtures/openapi/SecretGenerateSSHKeyRequest/valid/task51.json +12 -0
- package/fixtures/openapi/SecretReferenceUsage/invalid/version.json +6 -0
- package/fixtures/openapi/SecretReferenceUsage/valid/version.json +8 -0
- package/fixtures/openapi/VMSSHCredentialRedemptionInput/invalid/task51.json +7 -0
- package/fixtures/openapi/VMSSHCredentialRedemptionInput/valid/task51.json +6 -0
- package/fixtures/openapi/VirtualMachineAccessBindingInput/invalid/task51.json +10 -0
- package/fixtures/openapi/VirtualMachineAccessBindingInput/valid/task51.json +10 -0
- package/fixtures/openapi/VirtualMachineActionInput/invalid/capacity-not-boolean.json +1 -0
- package/fixtures/openapi/VirtualMachineActionInput/valid/new-data-with-admission.json +1 -0
- package/fixtures/openapi/VirtualMachineCreateInput/valid/cloud-image.json +12 -0
- package/fixtures/openapi/VirtualMachineDiskChange/invalid/serial-config-injection.json +1 -0
- package/fixtures/openapi/VirtualMachineDiskChange/invalid/serial-existing-disk.json +1 -0
- package/fixtures/openapi/VirtualMachineDiskChange/invalid/serial-too-long.json +1 -0
- package/fixtures/openapi/VirtualMachineDiskChange/valid/personal-data.json +1 -0
- package/fixtures/openapi/VirtualMachineGuestAccessInput/invalid/task51.json +4 -0
- package/fixtures/openapi/VirtualMachineGuestAccessInput/valid/task51.json +6 -0
- package/fixtures/openapi/VirtualMachineSSHCommand/invalid/task51.json +9 -0
- package/fixtures/openapi/VirtualMachineSSHCommand/valid/task51.json +8 -0
- package/fixtures/openapi/VirtualMachineSSHCommandInput/invalid/task51.json +5 -0
- package/fixtures/openapi/VirtualMachineSSHCommandInput/valid/task51.json +5 -0
- package/fixtures/openapi/VirtualMachineSSHFrame/invalid/encoding.json +5 -0
- package/fixtures/openapi/VirtualMachineSSHFrame/invalid/task51.json +4 -0
- package/fixtures/openapi/VirtualMachineSSHFrame/valid/binary.json +5 -0
- package/fixtures/openapi/VirtualMachineSSHFrame/valid/task51.json +5 -0
- package/fixtures/openapi/VirtualMachineSSHSessionInput/invalid/task51.json +4 -0
- package/fixtures/openapi/VirtualMachineSSHSessionInput/valid/task51.json +5 -0
- package/fixtures/openapi/VirtualizationCapacityPolicyInput/invalid/task51.json +12 -0
- package/fixtures/openapi/VirtualizationCapacityPolicyInput/valid/task51.json +13 -0
- package/fixtures/openapi/VirtualizationImageInput/invalid/cloud-image-config-type.json +6 -0
- package/fixtures/openapi/VirtualizationImageInput/valid/cloud-image.json +11 -0
- package/fixtures/openapi/WorkbenchAssistantTask/invalid/kind.json +1 -0
- package/fixtures/openapi/WorkbenchAssistantTask/invalid/owner.json +1 -0
- package/fixtures/openapi/WorkbenchAssistantTask/valid/generate.json +1 -0
- package/fixtures/openapi/WorkbenchAssistantTask/valid/undo.json +1 -0
- package/fixtures/openapi/WorkbenchContextSelection/invalid/remote-image.json +1 -0
- package/fixtures/openapi/WorkbenchContextSelection/invalid/too-many-images.json +1 -0
- package/fixtures/openapi/WorkbenchContextSelection/valid/chat-image.json +9 -0
- package/fixtures/openapi/WorkbenchContextUsage/invalid/fractional-capacity.json +1 -0
- package/fixtures/openapi/WorkbenchContextUsage/invalid/negative.json +1 -0
- package/fixtures/openapi/WorkbenchContextUsage/valid/reported.json +1 -0
- package/fixtures/openapi/WorkbenchContextUsage/valid/unknown.json +1 -0
- package/fixtures/openapi/WorkbenchMainSessionRequest/invalid/owner.json +1 -0
- package/fixtures/openapi/WorkbenchMainSessionRequest/invalid/provider.json +1 -0
- package/fixtures/openapi/WorkbenchMainSessionRequest/valid/empty.json +1 -0
- package/fixtures/openapi/WorkbenchMainSessionRequest/valid/provider.json +1 -0
- package/fixtures/openapi/WorkbenchMessageSearchPage/invalid/role.json +12 -0
- package/fixtures/openapi/WorkbenchMessageSearchPage/valid/page.json +12 -0
- package/fixtures/openapi/WorkbenchMessageWindow/invalid/missing.json +1 -0
- package/fixtures/openapi/WorkbenchMessageWindow/valid/empty.json +3 -0
- package/fixtures/openapi/WorkbenchModelOption/invalid/zero-capacity.json +1 -0
- package/fixtures/openapi/WorkbenchModelOption/valid/capacity.json +1 -0
- package/fixtures/openapi/WorkbenchModelOption/valid/unknown.json +1 -0
- package/fixtures/openapi/WorkbenchProfessionalCapabilityInput/invalid/basic.json +7 -0
- package/fixtures/openapi/WorkbenchProfessionalCapabilityInput/valid/basic.json +11 -0
- package/fixtures/openapi/WorkbenchProfessionalCapabilityInput/valid/selected-profile.json +23 -0
- package/fixtures/openapi/WorkbenchSendMessageStreamRequest/invalid/missing-required.json +1 -0
- package/fixtures/openapi/WorkbenchSendMessageStreamRequest/valid/mixed-professional-task.json +22 -0
- package/fixtures/openapi/WorkbenchTaskIntent/invalid/basic.json +5 -0
- package/fixtures/openapi/WorkbenchTaskIntent/invalid/oversized-capability-source.json +3 -0
- package/fixtures/openapi/WorkbenchTaskIntent/valid/basic.json +9 -0
- package/fixtures/openapi/WorkbenchTaskIntent/valid/independent-sources.json +17 -0
- package/gen/go/sohaapi/client.go +1 -1
- package/gen/go/sohaapi/kubernetes_monitoring_test.go +67 -0
- package/gen/go/sohaapi/personal_environment_test.go +25 -0
- package/gen/go/sohaapi/types.go +3460 -433
- package/gen/go/sohaapi/vm_disk_test.go +21 -0
- package/gen/ts/sohaapi/index.ts +5454 -1397
- package/go.mod +61 -49
- package/go.sum +202 -176
- package/knowledge/connector-definition.schema.json +6 -1
- package/knowledge/source-import-policy.schema.json +32 -0
- package/memory/memory-record.schema.json +1 -1
- package/openapi/soha-api.json +24199 -16810
- package/openapi/soha-api.yaml +2537 -14
- package/openapi/spec_generated.go +3 -3
- package/openapi/spec_test.go +34 -0
- package/package.json +19 -3
- package/resource/agent_access.go +12 -0
- package/resource/agent_profiles.go +62 -0
- package/resource/agent_profiles_test.go +36 -0
- package/resource/kubernetes_kinds.go +73 -0
- package/resource/kubernetes_kinds_test.go +38 -0
- package/resource/models.go +23 -14
- package/resource/network_topology.go +166 -0
- package/resource/network_topology_projection.go +208 -0
- package/resource/network_topology_projection_test.go +53 -0
- package/resource/network_topology_test.go +51 -0
- package/resource/network_topology_validation.go +29 -0
- package/resource/node_drain.go +34 -0
- package/resource/resource_monitoring.go +99 -0
- package/resource/runtime/network_topology.go +232 -0
- package/resource/runtime/network_topology_test.go +62 -0
- package/resource/runtime/node_drain.go +124 -0
- package/resource/runtime/node_drain_test.go +95 -0
- package/resource/runtime/resource_edit.go +128 -0
- package/resource/runtime/resource_edit_test.go +183 -0
- package/resource/runtime/resource_monitoring.go +243 -0
- package/resource/runtime/resource_monitoring_test.go +88 -0
- /package/fixtures/openapi/DeliveryExecutionHistoryPage/{invalid-kind.json → invalid/invalid-kind.json} +0 -0
- /package/fixtures/openapi/DeliveryExecutionHistoryPage/{valid-build.json → valid/valid-build.json} +0 -0
- /package/fixtures/openapi/DeliveryExecutionHistoryPage/{valid-empty.json → valid/valid-empty.json} +0 -0
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
package runtime
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"context"
|
|
5
|
+
"encoding/json"
|
|
6
|
+
"errors"
|
|
7
|
+
"fmt"
|
|
8
|
+
"testing"
|
|
9
|
+
|
|
10
|
+
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
|
11
|
+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
12
|
+
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
|
13
|
+
k8sruntime "k8s.io/apimachinery/pkg/runtime"
|
|
14
|
+
"k8s.io/apimachinery/pkg/runtime/schema"
|
|
15
|
+
"k8s.io/apimachinery/pkg/types"
|
|
16
|
+
"k8s.io/apimachinery/pkg/util/validation/field"
|
|
17
|
+
dynamicfake "k8s.io/client-go/dynamic/fake"
|
|
18
|
+
clienttesting "k8s.io/client-go/testing"
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
const testEditManager = "opensoha-resource-edit/v1"
|
|
22
|
+
|
|
23
|
+
func editFixture() *unstructured.Unstructured {
|
|
24
|
+
item := &unstructured.Unstructured{Object: map[string]any{"apiVersion": "v1", "kind": "ConfigMap", "metadata": map[string]any{"name": "sample", "namespace": "demo", "uid": "original", "resourceVersion": "1"}, "data": map[string]any{"phase": "created"}}}
|
|
25
|
+
item.SetManagedFields([]metav1.ManagedFieldsEntry{{Manager: testEditManager, Operation: metav1.ManagedFieldsOperationUpdate, APIVersion: "v1", FieldsType: "FieldsV1", FieldsV1: metav1.NewFieldsV1(`{"f:data":{"f:phase":{}}}`)}})
|
|
26
|
+
return item
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
func editConflict(manager string) error {
|
|
30
|
+
return apierrors.NewApplyConflict([]metav1.StatusCause{{Type: metav1.CauseTypeFieldManagerConflict, Field: ".data.phase", Message: fmt.Sprintf("conflict with %q using v1", manager)}}, "field conflict")
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
func TestApplyResourceEditMigratesOnlyItsOwnCreationFields(t *testing.T) {
|
|
34
|
+
for _, test := range []struct {
|
|
35
|
+
name string
|
|
36
|
+
dryRun, foreign, race, retryFail, probePass, portInvalid bool
|
|
37
|
+
}{
|
|
38
|
+
{name: "write"}, {name: "dry-run", dryRun: true}, {name: "successful-ssa-preview", dryRun: true, probePass: true}, {name: "port-list-preview", dryRun: true, portInvalid: true},
|
|
39
|
+
{name: "external", foreign: true}, {name: "external-dry-run", foreign: true, dryRun: true},
|
|
40
|
+
{name: "concurrent-migration", race: true}, {name: "concurrent-retry", retryFail: true},
|
|
41
|
+
} {
|
|
42
|
+
t.Run(test.name, func(t *testing.T) {
|
|
43
|
+
live := editFixture()
|
|
44
|
+
if test.portInvalid {
|
|
45
|
+
live.SetKind("Service")
|
|
46
|
+
}
|
|
47
|
+
if test.foreign {
|
|
48
|
+
owners := live.GetManagedFields()
|
|
49
|
+
owners = append(owners, metav1.ManagedFieldsEntry{Manager: "helm", Operation: metav1.ManagedFieldsOperationApply, APIVersion: "v1", FieldsType: "FieldsV1", FieldsV1: metav1.NewFieldsV1(`{"f:data":{"f:external":{}}}`)})
|
|
50
|
+
live.SetManagedFields(owners)
|
|
51
|
+
}
|
|
52
|
+
desired := live.DeepCopy()
|
|
53
|
+
desired.SetManagedFields(nil)
|
|
54
|
+
desired.Object["data"] = map[string]any{"phase": "updated"}
|
|
55
|
+
client := dynamicfake.NewSimpleDynamicClient(k8sruntime.NewScheme(), live)
|
|
56
|
+
applyCalls, migrations, updates := 0, 0, 0
|
|
57
|
+
client.PrependReactor("patch", "configmaps", func(action clienttesting.Action) (bool, k8sruntime.Object, error) {
|
|
58
|
+
patch, ok := action.(clienttesting.PatchActionImpl)
|
|
59
|
+
if !ok {
|
|
60
|
+
t.Fatalf("unexpected action %T", action)
|
|
61
|
+
}
|
|
62
|
+
if patch.GetPatchType() == types.JSONPatchType {
|
|
63
|
+
migrations++
|
|
64
|
+
assertEditMigration(t, patch.GetPatch(), test.foreign)
|
|
65
|
+
if test.race {
|
|
66
|
+
return true, nil, apierrors.NewConflict(schema.GroupResource{Resource: "configmaps"}, "sample", errors.New("concurrent update"))
|
|
67
|
+
}
|
|
68
|
+
moved := live.DeepCopy()
|
|
69
|
+
moved.SetResourceVersion("2")
|
|
70
|
+
return true, moved, nil
|
|
71
|
+
}
|
|
72
|
+
applyCalls++
|
|
73
|
+
options := patch.GetPatchOptions()
|
|
74
|
+
if options.Force == nil || *options.Force || options.FieldManager != testEditManager || (len(options.DryRun) > 0) != test.dryRun {
|
|
75
|
+
t.Fatalf("unsafe apply options: %+v", options)
|
|
76
|
+
}
|
|
77
|
+
if test.probePass {
|
|
78
|
+
return true, desired, nil
|
|
79
|
+
}
|
|
80
|
+
if test.portInvalid {
|
|
81
|
+
return true, nil, apierrors.NewInvalid(schema.GroupKind{Kind: "Service"}, "sample", field.ErrorList{field.Required(field.NewPath("spec", "ports").Index(0).Child("name"), "")})
|
|
82
|
+
}
|
|
83
|
+
if test.dryRun || test.foreign {
|
|
84
|
+
manager := testEditManager
|
|
85
|
+
if test.foreign {
|
|
86
|
+
manager = "helm"
|
|
87
|
+
}
|
|
88
|
+
return true, nil, editConflict(manager)
|
|
89
|
+
}
|
|
90
|
+
if test.retryFail {
|
|
91
|
+
return true, nil, editConflict("external-controller")
|
|
92
|
+
}
|
|
93
|
+
var body unstructured.Unstructured
|
|
94
|
+
if err := json.Unmarshal(patch.GetPatch(), &body); err != nil || body.GetResourceVersion() != "2" || body.GetUID() != live.GetUID() {
|
|
95
|
+
t.Fatalf("retry lost observed identity: %s %v", patch.GetPatch(), err)
|
|
96
|
+
}
|
|
97
|
+
return true, &body, nil
|
|
98
|
+
})
|
|
99
|
+
client.PrependReactor("update", "configmaps", func(action clienttesting.Action) (bool, k8sruntime.Object, error) {
|
|
100
|
+
update, ok := action.(clienttesting.UpdateActionImpl)
|
|
101
|
+
if !ok {
|
|
102
|
+
t.Fatalf("unexpected update %T", action)
|
|
103
|
+
}
|
|
104
|
+
options := update.GetUpdateOptions()
|
|
105
|
+
if !test.dryRun || len(options.DryRun) != 1 || options.DryRun[0] != metav1.DryRunAll || options.FieldManager != testEditManager {
|
|
106
|
+
t.Fatalf("migration validation wrote live data: %+v", options)
|
|
107
|
+
}
|
|
108
|
+
updates++
|
|
109
|
+
return true, update.GetObject(), nil
|
|
110
|
+
})
|
|
111
|
+
resource := client.Resource(schema.GroupVersionResource{Version: "v1", Resource: "configmaps"}).Namespace("demo")
|
|
112
|
+
_, err := ApplyResourceEdit(context.Background(), resource, live, desired, testEditManager, test.dryRun)
|
|
113
|
+
if (err != nil) != (test.foreign || test.race || test.retryFail) {
|
|
114
|
+
t.Fatalf("result: %v", err)
|
|
115
|
+
}
|
|
116
|
+
wantMigrations, wantUpdates, wantApply := 1, 0, 1
|
|
117
|
+
if test.foreign {
|
|
118
|
+
wantMigrations, wantApply = 1, 1
|
|
119
|
+
}
|
|
120
|
+
if test.dryRun {
|
|
121
|
+
wantMigrations, wantApply = 0, 1
|
|
122
|
+
if !test.foreign && !test.probePass {
|
|
123
|
+
wantUpdates = 1
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
if test.race {
|
|
127
|
+
wantApply = 0
|
|
128
|
+
}
|
|
129
|
+
if migrations != wantMigrations || updates != wantUpdates || applyCalls != wantApply || desired.GetResourceVersion() != "1" {
|
|
130
|
+
t.Fatalf("apply=%d migrate=%d validate=%d; caller RV=%s", applyCalls, migrations, updates, desired.GetResourceVersion())
|
|
131
|
+
}
|
|
132
|
+
})
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
func assertEditMigration(t *testing.T, payload []byte, foreign bool) {
|
|
137
|
+
t.Helper()
|
|
138
|
+
var ops []struct {
|
|
139
|
+
Op, Path string
|
|
140
|
+
Value json.RawMessage
|
|
141
|
+
}
|
|
142
|
+
if err := json.Unmarshal(payload, &ops); err != nil || len(ops) != 3 {
|
|
143
|
+
t.Fatalf("invalid migration: %s %v", payload, err)
|
|
144
|
+
}
|
|
145
|
+
if ops[0].Op != "test" || ops[0].Path != "/metadata/uid" || string(ops[0].Value) != `"original"` || ops[2].Path != "/metadata/resourceVersion" || string(ops[2].Value) != `"1"` {
|
|
146
|
+
t.Fatalf("migration lacks UID/RV protection: %s", payload)
|
|
147
|
+
}
|
|
148
|
+
var owners []metav1.ManagedFieldsEntry
|
|
149
|
+
if err := json.Unmarshal(ops[1].Value, &owners); err != nil || len(owners) < 1 || owners[0].Manager != testEditManager || owners[0].Operation != metav1.ManagedFieldsOperationApply {
|
|
150
|
+
t.Fatalf("unexpected ownership migration: %s %v", payload, err)
|
|
151
|
+
}
|
|
152
|
+
if foreign && (len(owners) != 2 || owners[1].Manager != "helm" || owners[1].Operation != metav1.ManagedFieldsOperationApply || owners[1].FieldsV1.GetRawString() != `{"f:data":{"f:external":{}}}`) {
|
|
153
|
+
t.Fatal("migration changed external ownership")
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
func TestResourceEditMigrationFailsClosed(t *testing.T) {
|
|
158
|
+
for _, missing := range []string{"uid", "resourceVersion"} {
|
|
159
|
+
live := editFixture()
|
|
160
|
+
unstructured.RemoveNestedField(live.Object, "metadata", missing)
|
|
161
|
+
if patch, err := resourceEditMigrationPatch(live, testEditManager); err != nil || patch != nil {
|
|
162
|
+
t.Fatalf("migrated without %s: %s %v", missing, patch, err)
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
for _, manager := range []string{"helm", testEditManager + "-other", testEditManager + `" using v1`} {
|
|
166
|
+
if onlyResourceEditConflicts(editFixture(), editConflict(manager), testEditManager) {
|
|
167
|
+
t.Fatalf("misidentified foreign manager %q", manager)
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
for _, entry := range []metav1.ManagedFieldsEntry{
|
|
171
|
+
{Manager: testEditManager, Operation: metav1.ManagedFieldsOperationUpdate, APIVersion: "v1beta1"},
|
|
172
|
+
{Manager: testEditManager, Operation: metav1.ManagedFieldsOperationApply, APIVersion: "v1beta1"},
|
|
173
|
+
} {
|
|
174
|
+
live := editFixture()
|
|
175
|
+
live.SetManagedFields(append(live.GetManagedFields(), entry))
|
|
176
|
+
if patch, err := resourceEditMigrationPatch(live, testEditManager); err != nil || patch != nil {
|
|
177
|
+
t.Fatalf("discarded another version's ownership: %s %v", patch, err)
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
if onlyResourceEditConflicts(editFixture(), apierrors.NewApplyConflict(nil, "unknown conflict"), testEditManager) {
|
|
181
|
+
t.Fatal("accepted incomplete conflict evidence")
|
|
182
|
+
}
|
|
183
|
+
}
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
package runtime
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"context"
|
|
5
|
+
"encoding/json"
|
|
6
|
+
"fmt"
|
|
7
|
+
"io"
|
|
8
|
+
"math"
|
|
9
|
+
"sort"
|
|
10
|
+
"time"
|
|
11
|
+
|
|
12
|
+
cr "github.com/opensoha/soha-contracts/resource"
|
|
13
|
+
corev1 "k8s.io/api/core/v1"
|
|
14
|
+
"k8s.io/client-go/rest"
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
// ReadResourceMonitoringInventory returns bounded current aggregate facts, not Pod manifests.
|
|
18
|
+
func ReadResourceMonitoringInventory(ctx context.Context, client rest.Interface, clusterID string) (cr.ResourceMonitoringInventory, error) {
|
|
19
|
+
ctx, cancel := context.WithTimeout(ctx, 10*time.Second)
|
|
20
|
+
defer cancel()
|
|
21
|
+
now := time.Now().UTC().Format(time.RFC3339)
|
|
22
|
+
output := cr.ResourceMonitoringInventory{Nodes: []cr.ResourceMonitoringNode{}, Quality: "valid", ObservedAt: now}
|
|
23
|
+
var nodes corev1.NodeList
|
|
24
|
+
if err := readMonitoringList(ctx, client, "nodes", cr.ResourceMonitoringMaxNodes, 2<<20, &nodes); err != nil {
|
|
25
|
+
return output, err
|
|
26
|
+
}
|
|
27
|
+
var pods corev1.PodList
|
|
28
|
+
podErr := readMonitoringList(ctx, client, "pods", 5000, 16<<20, &pods)
|
|
29
|
+
complete := podErr == nil && pods.Continue == "" && len(pods.Items) <= 5000
|
|
30
|
+
if !complete {
|
|
31
|
+
output.Quality = "partial"
|
|
32
|
+
output.ReasonCode = "pod_snapshot_incomplete"
|
|
33
|
+
output.Truncated = pods.Continue != "" || len(pods.Items) > 5000
|
|
34
|
+
}
|
|
35
|
+
if nodes.Continue != "" || len(nodes.Items) > cr.ResourceMonitoringMaxNodes {
|
|
36
|
+
output.Quality = "partial"
|
|
37
|
+
output.ReasonCode = "node_budget"
|
|
38
|
+
output.Truncated = true
|
|
39
|
+
}
|
|
40
|
+
aggregates := monitoringPodAggregates(pods.Items)
|
|
41
|
+
if complete {
|
|
42
|
+
output.UnscheduledPendingPods = number(aggregates[""].pending)
|
|
43
|
+
}
|
|
44
|
+
for index, node := range nodes.Items {
|
|
45
|
+
if index >= cr.ResourceMonitoringMaxNodes {
|
|
46
|
+
break
|
|
47
|
+
}
|
|
48
|
+
fact := monitoringNodeFact(clusterID, node, aggregates[node.Name], complete, now)
|
|
49
|
+
output.Nodes = append(output.Nodes, fact)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
sort.Slice(output.Nodes, func(i, j int) bool { return output.Nodes[i].NodeRef.Name < output.Nodes[j].NodeRef.Name })
|
|
53
|
+
return output, nil
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
func readMonitoringList(ctx context.Context, client rest.Interface, resource string, limit, maxBytes int, target any) error {
|
|
57
|
+
if client == nil {
|
|
58
|
+
return fmt.Errorf("kubernetes REST client unavailable")
|
|
59
|
+
}
|
|
60
|
+
stream, err := client.Get().AbsPath("/api/v1/"+resource).Param("limit", fmt.Sprint(limit)).SetHeader("Accept", "application/json").Stream(ctx)
|
|
61
|
+
if err != nil {
|
|
62
|
+
return err
|
|
63
|
+
}
|
|
64
|
+
defer func() { _ = stream.Close() }()
|
|
65
|
+
raw, err := io.ReadAll(io.LimitReader(stream, int64(maxBytes)+1))
|
|
66
|
+
if err != nil {
|
|
67
|
+
return err
|
|
68
|
+
}
|
|
69
|
+
if len(raw) > maxBytes {
|
|
70
|
+
return fmt.Errorf("monitoring inventory byte budget")
|
|
71
|
+
}
|
|
72
|
+
return json.Unmarshal(raw, target)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
type monitoringPodAggregate struct {
|
|
76
|
+
cpuRequest, memoryRequest, cpuLimit, memoryLimit, count, pending float64
|
|
77
|
+
cpuComplete, memoryComplete bool
|
|
78
|
+
initialized bool
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
func monitoringPodAggregates(pods []corev1.Pod) map[string]monitoringPodAggregate {
|
|
82
|
+
result := map[string]monitoringPodAggregate{}
|
|
83
|
+
for _, pod := range pods {
|
|
84
|
+
if monitoringTerminalPod(pod) {
|
|
85
|
+
continue
|
|
86
|
+
}
|
|
87
|
+
aggregate := result[pod.Spec.NodeName]
|
|
88
|
+
if !aggregate.initialized {
|
|
89
|
+
aggregate.cpuComplete = true
|
|
90
|
+
aggregate.memoryComplete = true
|
|
91
|
+
aggregate.initialized = true
|
|
92
|
+
}
|
|
93
|
+
aggregate.count++
|
|
94
|
+
if pod.Status.Phase == corev1.PodPending {
|
|
95
|
+
aggregate.pending++
|
|
96
|
+
}
|
|
97
|
+
requests, limits, cpuComplete, memoryComplete := effectiveMonitoringPodResources(pod)
|
|
98
|
+
aggregate.cpuRequest += requests.cpu
|
|
99
|
+
aggregate.memoryRequest += requests.memory
|
|
100
|
+
aggregate.cpuLimit += limits.cpu
|
|
101
|
+
aggregate.memoryLimit += limits.memory
|
|
102
|
+
aggregate.cpuComplete = aggregate.cpuComplete && cpuComplete
|
|
103
|
+
aggregate.memoryComplete = aggregate.memoryComplete && memoryComplete
|
|
104
|
+
result[pod.Spec.NodeName] = aggregate
|
|
105
|
+
}
|
|
106
|
+
return result
|
|
107
|
+
}
|
|
108
|
+
func monitoringTerminalPod(pod corev1.Pod) bool {
|
|
109
|
+
return pod.Status.Phase == corev1.PodSucceeded || pod.Status.Phase == corev1.PodFailed
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
type monitoringResources struct{ cpu, memory float64 }
|
|
113
|
+
|
|
114
|
+
func monitoringResourcesFromList(values corev1.ResourceList) monitoringResources {
|
|
115
|
+
cpu := values[corev1.ResourceCPU]
|
|
116
|
+
memory := values[corev1.ResourceMemory]
|
|
117
|
+
return monitoringResources{cpu: float64(cpu.MilliValue()) / 1000, memory: float64(memory.Value())}
|
|
118
|
+
}
|
|
119
|
+
func (r monitoringResources) add(other monitoringResources) monitoringResources {
|
|
120
|
+
return monitoringResources{cpu: r.cpu + other.cpu, memory: r.memory + other.memory}
|
|
121
|
+
}
|
|
122
|
+
func (r monitoringResources) max(other monitoringResources) monitoringResources {
|
|
123
|
+
return monitoringResources{cpu: math.Max(r.cpu, other.cpu), memory: math.Max(r.memory, other.memory)}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
func effectiveMonitoringPodResources(pod corev1.Pod) (monitoringResources, monitoringResources, bool, bool) {
|
|
127
|
+
var regularRequest, regularLimit, sidecarRequest, sidecarLimit, initRequest, initLimit monitoringResources
|
|
128
|
+
cpuComplete, memoryComplete := true, true
|
|
129
|
+
addCoverage := func(container corev1.Container) {
|
|
130
|
+
cpuValue, cpu := container.Resources.Limits[corev1.ResourceCPU]
|
|
131
|
+
memoryValue, memory := container.Resources.Limits[corev1.ResourceMemory]
|
|
132
|
+
cpu = cpu && !cpuValue.IsZero()
|
|
133
|
+
memory = memory && !memoryValue.IsZero()
|
|
134
|
+
cpuComplete = cpuComplete && cpu
|
|
135
|
+
memoryComplete = memoryComplete && memory
|
|
136
|
+
}
|
|
137
|
+
for _, container := range pod.Spec.Containers {
|
|
138
|
+
regularRequest = regularRequest.add(monitoringResourcesFromList(container.Resources.Requests))
|
|
139
|
+
regularLimit = regularLimit.add(monitoringResourcesFromList(container.Resources.Limits))
|
|
140
|
+
addCoverage(container)
|
|
141
|
+
}
|
|
142
|
+
for _, container := range pod.Spec.InitContainers {
|
|
143
|
+
requests := monitoringResourcesFromList(container.Resources.Requests)
|
|
144
|
+
limits := monitoringResourcesFromList(container.Resources.Limits)
|
|
145
|
+
addCoverage(container)
|
|
146
|
+
if container.RestartPolicy != nil && *container.RestartPolicy == corev1.ContainerRestartPolicyAlways {
|
|
147
|
+
sidecarRequest = sidecarRequest.add(requests)
|
|
148
|
+
sidecarLimit = sidecarLimit.add(limits)
|
|
149
|
+
requests = sidecarRequest
|
|
150
|
+
limits = sidecarLimit
|
|
151
|
+
} else {
|
|
152
|
+
requests = requests.add(sidecarRequest)
|
|
153
|
+
limits = limits.add(sidecarLimit)
|
|
154
|
+
}
|
|
155
|
+
initRequest = initRequest.max(requests)
|
|
156
|
+
initLimit = initLimit.max(limits)
|
|
157
|
+
}
|
|
158
|
+
requests := regularRequest.add(sidecarRequest).max(initRequest)
|
|
159
|
+
limits := regularLimit.add(sidecarLimit).max(initLimit)
|
|
160
|
+
if pod.Spec.Resources != nil {
|
|
161
|
+
requests = monitoringPodLevelOverride(requests, pod.Spec.Resources.Requests)
|
|
162
|
+
limits = monitoringPodLevelOverride(limits, pod.Spec.Resources.Limits)
|
|
163
|
+
if value, ok := pod.Spec.Resources.Limits[corev1.ResourceCPU]; ok {
|
|
164
|
+
cpuComplete = !value.IsZero()
|
|
165
|
+
}
|
|
166
|
+
if value, ok := pod.Spec.Resources.Limits[corev1.ResourceMemory]; ok {
|
|
167
|
+
memoryComplete = !value.IsZero()
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
overhead := monitoringResourcesFromList(pod.Spec.Overhead)
|
|
171
|
+
requests = requests.add(overhead)
|
|
172
|
+
if limits.cpu > 0 {
|
|
173
|
+
limits.cpu += overhead.cpu
|
|
174
|
+
}
|
|
175
|
+
if limits.memory > 0 {
|
|
176
|
+
limits.memory += overhead.memory
|
|
177
|
+
}
|
|
178
|
+
return requests, limits, cpuComplete, memoryComplete
|
|
179
|
+
}
|
|
180
|
+
func monitoringPodLevelOverride(current monitoringResources, values corev1.ResourceList) monitoringResources {
|
|
181
|
+
if value, ok := values[corev1.ResourceCPU]; ok {
|
|
182
|
+
current.cpu = float64(value.MilliValue()) / 1000
|
|
183
|
+
}
|
|
184
|
+
if value, ok := values[corev1.ResourceMemory]; ok {
|
|
185
|
+
current.memory = float64(value.Value())
|
|
186
|
+
}
|
|
187
|
+
return current
|
|
188
|
+
}
|
|
189
|
+
func monitoringQuantity(values corev1.ResourceList) cr.ResourceMonitoringQuantity {
|
|
190
|
+
result := cr.ResourceMonitoringQuantity{}
|
|
191
|
+
if cpu, ok := values[corev1.ResourceCPU]; ok {
|
|
192
|
+
result.CPUCores = number(float64(cpu.MilliValue()) / 1000)
|
|
193
|
+
}
|
|
194
|
+
if memory, ok := values[corev1.ResourceMemory]; ok {
|
|
195
|
+
result.MemoryBytes = number(float64(memory.Value()))
|
|
196
|
+
}
|
|
197
|
+
if pods, ok := values[corev1.ResourcePods]; ok {
|
|
198
|
+
result.PodSlots = number(float64(pods.Value()))
|
|
199
|
+
}
|
|
200
|
+
return result
|
|
201
|
+
}
|
|
202
|
+
func monitoringNodeFact(clusterID string, node corev1.Node, aggregate monitoringPodAggregate, complete bool, now string) cr.ResourceMonitoringNode {
|
|
203
|
+
fact := cr.ResourceMonitoringNode{NodeRef: cr.ResourceRef{ClusterID: clusterID, APIVersion: "v1", Kind: "Node", Name: node.Name, ScopeMode: cr.ResourceScopeModeCluster, UID: string(node.UID), ResourceVersion: node.ResourceVersion}, OS: node.Status.NodeInfo.OperatingSystem, CreatedAt: node.CreationTimestamp.UTC().Format(time.RFC3339), Ready: "Unknown", MemoryPressure: "Unknown", DiskPressure: "Unknown", PIDPressure: "Unknown", Cordoned: node.Spec.Unschedulable, Capacity: monitoringQuantity(node.Status.Capacity), Allocatable: monitoringQuantity(node.Status.Allocatable), Quality: "valid", ObservedAt: now}
|
|
204
|
+
for _, address := range node.Status.Addresses {
|
|
205
|
+
if address.Type == corev1.NodeInternalIP {
|
|
206
|
+
fact.InternalIP = address.Address
|
|
207
|
+
break
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
for _, condition := range node.Status.Conditions {
|
|
211
|
+
switch condition.Type {
|
|
212
|
+
case corev1.NodeReady:
|
|
213
|
+
fact.Ready = string(condition.Status)
|
|
214
|
+
case corev1.NodeMemoryPressure:
|
|
215
|
+
fact.MemoryPressure = string(condition.Status)
|
|
216
|
+
case corev1.NodeDiskPressure:
|
|
217
|
+
fact.DiskPressure = string(condition.Status)
|
|
218
|
+
case corev1.NodePIDPressure:
|
|
219
|
+
fact.PIDPressure = string(condition.Status)
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
if !complete {
|
|
223
|
+
fact.Quality = "partial"
|
|
224
|
+
return fact
|
|
225
|
+
}
|
|
226
|
+
fact.Requests = cr.ResourceMonitoringQuantity{CPUCores: number(aggregate.cpuRequest), MemoryBytes: number(aggregate.memoryRequest)}
|
|
227
|
+
fact.CPULimitsComplete = !aggregate.initialized || aggregate.cpuComplete
|
|
228
|
+
fact.MemoryLimitsComplete = !aggregate.initialized || aggregate.memoryComplete
|
|
229
|
+
if fact.CPULimitsComplete {
|
|
230
|
+
fact.Limits.CPUCores = number(aggregate.cpuLimit)
|
|
231
|
+
}
|
|
232
|
+
if fact.MemoryLimitsComplete {
|
|
233
|
+
fact.Limits.MemoryBytes = number(aggregate.memoryLimit)
|
|
234
|
+
}
|
|
235
|
+
fact.BoundPods = number(aggregate.count)
|
|
236
|
+
fact.PendingPods = number(aggregate.pending)
|
|
237
|
+
if fact.Allocatable.PodSlots != nil {
|
|
238
|
+
fact.RemainingPodSlots = number(math.Max(0, *fact.Allocatable.PodSlots-aggregate.count))
|
|
239
|
+
fact.OverflowPodSlots = number(math.Max(0, aggregate.count-*fact.Allocatable.PodSlots))
|
|
240
|
+
}
|
|
241
|
+
return fact
|
|
242
|
+
}
|
|
243
|
+
func number(value float64) *float64 { return &value }
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
package runtime
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"context"
|
|
5
|
+
"encoding/json"
|
|
6
|
+
"net/http"
|
|
7
|
+
"net/http/httptest"
|
|
8
|
+
"testing"
|
|
9
|
+
|
|
10
|
+
corev1 "k8s.io/api/core/v1"
|
|
11
|
+
"k8s.io/apimachinery/pkg/api/resource"
|
|
12
|
+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
13
|
+
"k8s.io/apimachinery/pkg/types"
|
|
14
|
+
"k8s.io/client-go/kubernetes"
|
|
15
|
+
"k8s.io/client-go/rest"
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
func monitoringResourceList(cpu, memory string) corev1.ResourceList {
|
|
19
|
+
return corev1.ResourceList{corev1.ResourceCPU: resource.MustParse(cpu), corev1.ResourceMemory: resource.MustParse(memory)}
|
|
20
|
+
}
|
|
21
|
+
func TestMonitoringFootprintHandlesSidecarsInitAndOverhead(t *testing.T) {
|
|
22
|
+
always := corev1.ContainerRestartPolicyAlways
|
|
23
|
+
container := func(name, cpu string) corev1.Container {
|
|
24
|
+
return corev1.Container{Name: name, Resources: corev1.ResourceRequirements{Requests: monitoringResourceList(cpu, "1Mi"), Limits: monitoringResourceList(cpu, "1Mi")}}
|
|
25
|
+
}
|
|
26
|
+
sidecar := container("sidecar", "1")
|
|
27
|
+
sidecar.RestartPolicy = &always
|
|
28
|
+
pod := corev1.Pod{Spec: corev1.PodSpec{Containers: []corev1.Container{container("app", "2")}, InitContainers: []corev1.Container{sidecar, container("init", "4")}, Overhead: monitoringResourceList("100m", "1Mi")}}
|
|
29
|
+
requests, limits, cpuComplete, memoryComplete := effectiveMonitoringPodResources(pod)
|
|
30
|
+
if requests.cpu != 5.1 || limits.cpu != 5.1 || !cpuComplete || !memoryComplete {
|
|
31
|
+
t.Fatalf("effective footprint requests=%+v limits=%+v complete=%v/%v", requests, limits, cpuComplete, memoryComplete)
|
|
32
|
+
}
|
|
33
|
+
pod.Spec.Containers[0].Resources.Limits = monitoringResourceList("0", "0")
|
|
34
|
+
_, _, cpuComplete, memoryComplete = effectiveMonitoringPodResources(pod)
|
|
35
|
+
if cpuComplete || memoryComplete {
|
|
36
|
+
t.Fatal("zero limits treated as an explicit finite limit")
|
|
37
|
+
}
|
|
38
|
+
pod.Spec.Resources = &corev1.ResourceRequirements{Limits: monitoringResourceList("0", "0")}
|
|
39
|
+
_, _, cpuComplete, memoryComplete = effectiveMonitoringPodResources(pod)
|
|
40
|
+
if cpuComplete || memoryComplete {
|
|
41
|
+
t.Fatal("Pod-level zero limits treated as complete finite limits")
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
func TestMonitoringInventoryCountsOnlyBoundNonterminalPods(t *testing.T) {
|
|
45
|
+
nodes := corev1.NodeList{Items: []corev1.Node{{ObjectMeta: metav1.ObjectMeta{Name: "node", UID: types.UID("node-uid")}, Status: corev1.NodeStatus{Capacity: monitoringResourceList("4", "8Gi"), Allocatable: corev1.ResourceList{corev1.ResourcePods: resource.MustParse("1")}}}}}
|
|
46
|
+
deleting := metav1.Now()
|
|
47
|
+
pods := corev1.PodList{Items: []corev1.Pod{
|
|
48
|
+
{ObjectMeta: metav1.ObjectMeta{DeletionTimestamp: &deleting}, Spec: corev1.PodSpec{NodeName: "node"}, Status: corev1.PodStatus{Phase: corev1.PodRunning}},
|
|
49
|
+
{Spec: corev1.PodSpec{NodeName: "node"}, Status: corev1.PodStatus{Phase: corev1.PodPending}},
|
|
50
|
+
{Spec: corev1.PodSpec{NodeName: "node"}, Status: corev1.PodStatus{Phase: corev1.PodSucceeded}},
|
|
51
|
+
{Spec: corev1.PodSpec{NodeName: "node"}, Status: corev1.PodStatus{Phase: corev1.PodFailed}},
|
|
52
|
+
{Status: corev1.PodStatus{Phase: corev1.PodPending}},
|
|
53
|
+
}}
|
|
54
|
+
forbidden := false
|
|
55
|
+
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
56
|
+
switch r.URL.Path {
|
|
57
|
+
case "/api/v1/nodes":
|
|
58
|
+
_ = json.NewEncoder(w).Encode(nodes)
|
|
59
|
+
case "/api/v1/pods":
|
|
60
|
+
if forbidden {
|
|
61
|
+
http.Error(w, "Forbidden", http.StatusForbidden)
|
|
62
|
+
return
|
|
63
|
+
}
|
|
64
|
+
_ = json.NewEncoder(w).Encode(pods)
|
|
65
|
+
default:
|
|
66
|
+
t.Errorf("unexpected path %s", r.URL.Path)
|
|
67
|
+
http.NotFound(w, r)
|
|
68
|
+
}
|
|
69
|
+
}))
|
|
70
|
+
defer server.Close()
|
|
71
|
+
client, _ := kubernetes.NewForConfig(&rest.Config{Host: server.URL})
|
|
72
|
+
inventory, err := ReadResourceMonitoringInventory(context.Background(), client.CoreV1().RESTClient(), "c")
|
|
73
|
+
if err != nil {
|
|
74
|
+
t.Fatal(err)
|
|
75
|
+
}
|
|
76
|
+
fact := inventory.Nodes[0]
|
|
77
|
+
if fact.BoundPods == nil || *fact.BoundPods != 2 || *fact.PendingPods != 1 || *fact.RemainingPodSlots != 0 || *fact.OverflowPodSlots != 1 || *inventory.UnscheduledPendingPods != 1 {
|
|
78
|
+
t.Fatalf("wrong capacity: %+v", fact)
|
|
79
|
+
}
|
|
80
|
+
forbidden = true
|
|
81
|
+
inventory, err = ReadResourceMonitoringInventory(context.Background(), client.CoreV1().RESTClient(), "c")
|
|
82
|
+
if err != nil {
|
|
83
|
+
t.Fatal(err)
|
|
84
|
+
}
|
|
85
|
+
if inventory.Quality != "partial" || inventory.Nodes[0].BoundPods != nil || inventory.Nodes[0].Requests.CPUCores != nil {
|
|
86
|
+
t.Fatalf("partial Pod read invented zero capacity facts: %+v", inventory)
|
|
87
|
+
}
|
|
88
|
+
}
|
|
File without changes
|
/package/fixtures/openapi/DeliveryExecutionHistoryPage/{valid-build.json → valid/valid-build.json}
RENAMED
|
File without changes
|
/package/fixtures/openapi/DeliveryExecutionHistoryPage/{valid-empty.json → valid/valid-empty.json}
RENAMED
|
File without changes
|