From 6f4284d5e720f377f07c3119a73c7fe8bdc933c9 Mon Sep 17 00:00:00 2001 From: Matteo Bunino <48362942+matbun@users.noreply.github.com> Date: Fri, 30 Oct 2026 12:59:33 +0100 Subject: [PATCH 1/3] feat: /createPodSets, the pods of one group in a single call A caller that holds every pod of a group (a Kueue Workload's members) sends them in one POST /createPodSets, by PodSet, so that a plugin can submit the whole group as one job at once instead of buffering /create calls until the last member arrives. - New types PodSetsCreateRequests and PodSetCreateRequests (caller to interLink) and RetrievedPodSetsData and RetrievedPodSetData (interLink to plugin). Each pod is a whole PodCreateRequests: projected and downward API files differ from pod to pod. The order of the pods, PodSet after PodSet, gives their ranks; rank 0 is the head. - CreatePodSetsHandler prepares every pod as CreateHandler does (getData, then JobScriptBuilderURL or JobScriptTemplate), forwards the group to the plugin's /createPodSets and relays its answer: one CreateStruct per pod in request order, or the plugin's error code. A failing job script answers the caller once for the whole group. - The template step moves to renderJobScript, shared with CreateHandler, whose behaviour is unchanged. - The operation is in both OpenAPI specs and in the plugin guide. It is optional: a plugin that implements it lists "podset-create" in the capabilities of its ping answer; status, logs and delete stay per pod. - The caller's access token goes to the plugin as on the other sidecar calls (ForwardAccessToken), and never to a job script builder. Co-Authored-By: Claude Opus 5.5 Signed-off-by: Matteo Bunino <48362942+matbun@users.noreply.github.com> --- cmd/interlink/main.go | 1 + cmd/openapi-gen/main.go | 14 + docs/docs/guides/02-develop-a-plugin.md | 23 ++ docs/openapi/interlink-openapi.json | 2 +- docs/openapi/plugin-openapi.json | 84 ++++++ pkg/interlink/api/create.go | 29 +- pkg/interlink/api/createpodsets.go | 232 +++++++++++++++ pkg/interlink/api/createpodsets_test.go | 364 ++++++++++++++++++++++++ pkg/interlink/types.go | 39 +++ 9 files changed, 773 insertions(+), 15 deletions(-) create mode 100644 pkg/interlink/api/createpodsets.go create mode 100644 pkg/interlink/api/createpodsets_test.go diff --git a/cmd/interlink/main.go b/cmd/interlink/main.go index a658bf9b..b26c3615 100644 --- a/cmd/interlink/main.go +++ b/cmd/interlink/main.go @@ -190,6 +190,7 @@ func main() { mutex := http.NewServeMux() mutex.HandleFunc("/status", interLinkAPIs.StatusHandler) mutex.HandleFunc("/create", interLinkAPIs.CreateHandler) + mutex.HandleFunc("/createPodSets", interLinkAPIs.CreatePodSetsHandler) mutex.HandleFunc("/delete", interLinkAPIs.DeleteHandler) mutex.HandleFunc("/pinglink", interLinkAPIs.Ping) mutex.HandleFunc("/getLogs", interLinkAPIs.GetLogsHandler) diff --git a/cmd/openapi-gen/main.go b/cmd/openapi-gen/main.go index a5c593cf..0e11aadc 100644 --- a/cmd/openapi-gen/main.go +++ b/cmd/openapi-gen/main.go @@ -38,6 +38,20 @@ func main() { panic(err) } + // CREATE PODSETS + createPodSetsOp, err := reflector.NewOperationContext(http.MethodPost, "/createPodSets") + if err != nil { + panic(err) + } + + createPodSetsOp.AddReqStructure(new(interlink.PodSetsCreateRequests)) + createPodSetsOp.AddRespStructure(new([]interlink.CreateStruct), func(cu *openapi.ContentUnit) { cu.HTTPStatus = http.StatusOK }) + + err = reflector.AddOperation(createPodSetsOp) + if err != nil { + panic(err) + } + // DELETE deleteOp, err := reflector.NewOperationContext(http.MethodPost, "/delete") if err != nil { diff --git a/docs/docs/guides/02-develop-a-plugin.md b/docs/docs/guides/02-develop-a-plugin.md index 0160d957..420da0c7 100644 --- a/docs/docs/guides/02-develop-a-plugin.md +++ b/docs/docs/guides/02-develop-a-plugin.md @@ -151,6 +151,29 @@ Retrieves logs from a specific container. **Query Parameters**: Pod UID, container name, log options **Response**: Container logs (plain text) +### POST /createPodSets (optional) + +Creates the pods of one group as a single job. A plugin that implements it lists +`podset-create` in the `capabilities` of its ping answer, and it is used only +then; status, logs and delete stay per pod. + +**Request Body**: `RetrievedPodSetsData` **Response**: `List[CreateStruct]`, one +per pod in the order of the request, all with the job ID of the group + +```json +{ + "groupUID": "workload-uid", + "podSets": [ + { "name": "launcher", "pods": [...] }, // each pod as sent to /create + { "name": "worker", "pods": [...] } + ] +} +``` + +The order of the pods, PodSet after PodSet, gives their ranks; rank 0 is the +head. Answer 400 with a text body for a bad request or a permanent refusal, and +503 when the group cannot be taken now. + ## Developing with the Python SDK ### Basic Plugin Structure diff --git a/docs/openapi/interlink-openapi.json b/docs/openapi/interlink-openapi.json index 70603242..55743ce7 100644 --- a/docs/openapi/interlink-openapi.json +++ b/docs/openapi/interlink-openapi.json @@ -1 +1 @@ -{"openapi":"3.0.3","info":{"title":"interLink server API","description":"This is the API spec for the Virtual Kubelet to interLink API server communication","version":"0.4.0"},"paths":{"/create":{"post":{"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/InterlinkPodCreateRequests"}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InterlinkRetrievedPodData"}}}}}}},"/delete":{"post":{"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Pod"}}}},"responses":{"200":{"description":"OK"}}}},"/getLogs":{"post":{"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/InterlinkLogStruct"}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"string"}}}}}}},"/pinglink":{"post":{"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InterlinkPingResponse"}}}}}}},"/status":{"post":{"requestBody":{"content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/V1Pod"},"nullable":true}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/InterlinkPodStatus"}}}}}}}}},"components":{"schemas":{"InterlinkAcceleratorResponse":{"type":"object","properties":{"available":{"type":"string"},"resourceType":{"type":"string"}}},"InterlinkApptainerOptions":{"type":"object","properties":{"cleanenv":{"type":"boolean"},"containall":{"type":"boolean"},"executable":{"type":"string"},"fakeroot":{"type":"boolean"},"fuseMode":{"type":"string"},"noHome":{"type":"boolean"},"noInit":{"type":"boolean"},"noPrivs":{"type":"boolean"},"nvidiaSupport":{"type":"boolean"},"unsquash":{"type":"boolean"}}},"InterlinkContainerLogOpts":{"type":"object","properties":{"Bytes":{"type":"integer"},"Follow":{"type":"boolean"},"Previous":{"type":"boolean"},"SinceSeconds":{"type":"integer"},"SinceTime":{"type":"string","format":"date-time"},"Tail":{"type":"integer"},"Timestamps":{"type":"boolean"}}},"InterlinkLogStruct":{"type":"object","properties":{"ContainerName":{"type":"string"},"Namespace":{"type":"string"},"Opts":{"$ref":"#/components/schemas/InterlinkContainerLogOpts"},"PodName":{"type":"string"},"PodUID":{"type":"string"}}},"InterlinkPingResponse":{"type":"object","properties":{"resources":{"$ref":"#/components/schemas/InterlinkResourcesResponse"},"status":{"type":"string"},"taints":{"type":"array","items":{"$ref":"#/components/schemas/InterlinkTaintResponse"},"nullable":true}}},"InterlinkPodCreateRequests":{"type":"object","properties":{"configmaps":{"type":"array","items":{"$ref":"#/components/schemas/V1ConfigMap"},"nullable":true},"jobscriptURL":{"type":"string"},"pod":{"$ref":"#/components/schemas/V1Pod"},"projectedvolumesmaps":{"type":"array","items":{"$ref":"#/components/schemas/V1ConfigMap"},"nullable":true},"secrets":{"type":"array","items":{"$ref":"#/components/schemas/V1Secret"},"nullable":true}}},"InterlinkPodStatus":{"type":"object","properties":{"JID":{"type":"string"},"UID":{"type":"string"},"containers":{"type":"array","items":{"$ref":"#/components/schemas/V1ContainerStatus"},"nullable":true},"initContainers":{"type":"array","items":{"$ref":"#/components/schemas/V1ContainerStatus"},"nullable":true},"name":{"type":"string"},"namespace":{"type":"string"},"nodeName":{"type":"string"},"socketDir":{"type":"string"}}},"InterlinkResourcesResponse":{"type":"object","properties":{"accelerators":{"type":"array","items":{"$ref":"#/components/schemas/InterlinkAcceleratorResponse"}},"cpu":{"type":"string"},"memory":{"type":"string"},"pods":{"type":"string"}}},"InterlinkRetrievedContainer":{"type":"object","properties":{"configMaps":{"type":"array","items":{"$ref":"#/components/schemas/V1ConfigMap"},"nullable":true},"emptyDirs":{"type":"array","items":{"type":"string"},"nullable":true},"name":{"type":"string"},"projectedvolumemaps":{"type":"array","items":{"$ref":"#/components/schemas/V1ConfigMap"},"nullable":true},"secrets":{"type":"array","items":{"$ref":"#/components/schemas/V1Secret"},"nullable":true}}},"InterlinkRetrievedPodData":{"type":"object","properties":{"container":{"type":"array","items":{"$ref":"#/components/schemas/InterlinkRetrievedContainer"},"nullable":true},"jobConfig":{"$ref":"#/components/schemas/InterlinkScriptBuildConfig"},"jobScript":{"type":"string"},"pod":{"$ref":"#/components/schemas/V1Pod"}}},"InterlinkScriptBuildConfig":{"type":"object","properties":{"ApptainerOptions":{"$ref":"#/components/schemas/InterlinkApptainerOptions"},"SingularityHubProxy":{"$ref":"#/components/schemas/InterlinkSingularityHubConfig"},"Volumes":{"$ref":"#/components/schemas/InterlinkVolumesOptions"}}},"InterlinkSingularityHubConfig":{"type":"object","properties":{"cache_validity_seconds":{"type":"integer"},"master_token":{"type":"string"},"server":{"type":"string"}}},"InterlinkTaintResponse":{"type":"object","properties":{"effect":{"type":"string"},"key":{"type":"string"},"value":{"type":"string"}}},"InterlinkVolumesOptions":{"type":"object","properties":{"additional_directories_in_path":{"type":"array","items":{"type":"string"},"nullable":true},"apptainer_cachedir":{"type":"string"},"fuse_sleep_seconds":{"type":"integer"},"image_dir":{"type":"string"},"scratch_area":{"type":"string"}}},"IntstrIntOrString":{"type":"object"},"ResourceQuantity":{"type":"object"},"V1AWSElasticBlockStoreVolumeSource":{"type":"object","properties":{"fsType":{"type":"string"},"partition":{"type":"integer"},"readOnly":{"type":"boolean"},"volumeID":{"type":"string"}}},"V1Affinity":{"type":"object","properties":{"nodeAffinity":{"$ref":"#/components/schemas/V1NodeAffinity"},"podAffinity":{"$ref":"#/components/schemas/V1PodAffinity"},"podAntiAffinity":{"$ref":"#/components/schemas/V1PodAntiAffinity"}}},"V1AppArmorProfile":{"type":"object","properties":{"localhostProfile":{"type":"string","nullable":true},"type":{"type":"string"}}},"V1AzureDiskVolumeSource":{"type":"object","properties":{"cachingMode":{"type":"string","nullable":true},"diskName":{"type":"string"},"diskURI":{"type":"string"},"fsType":{"type":"string","nullable":true},"kind":{"type":"string","nullable":true},"readOnly":{"type":"boolean","nullable":true}}},"V1AzureFileVolumeSource":{"type":"object","properties":{"readOnly":{"type":"boolean"},"secretName":{"type":"string"},"shareName":{"type":"string"}}},"V1CSIVolumeSource":{"type":"object","properties":{"driver":{"type":"string"},"fsType":{"type":"string","nullable":true},"nodePublishSecretRef":{"$ref":"#/components/schemas/V1LocalObjectReference"},"readOnly":{"type":"boolean","nullable":true},"volumeAttributes":{"type":"object","additionalProperties":{"type":"string"}}}},"V1Capabilities":{"type":"object","properties":{"add":{"type":"array","items":{"type":"string"}},"drop":{"type":"array","items":{"type":"string"}}}},"V1CephFSVolumeSource":{"type":"object","properties":{"monitors":{"type":"array","items":{"type":"string"},"nullable":true},"path":{"type":"string"},"readOnly":{"type":"boolean"},"secretFile":{"type":"string"},"secretRef":{"$ref":"#/components/schemas/V1LocalObjectReference"},"user":{"type":"string"}}},"V1CinderVolumeSource":{"type":"object","properties":{"fsType":{"type":"string"},"readOnly":{"type":"boolean"},"secretRef":{"$ref":"#/components/schemas/V1LocalObjectReference"},"volumeID":{"type":"string"}}},"V1ClusterTrustBundleProjection":{"type":"object","properties":{"labelSelector":{"$ref":"#/components/schemas/V1LabelSelector"},"name":{"type":"string","nullable":true},"optional":{"type":"boolean","nullable":true},"path":{"type":"string"},"signerName":{"type":"string","nullable":true}}},"V1ConfigMap":{"type":"object","properties":{"apiVersion":{"type":"string"},"binaryData":{"type":"object","additionalProperties":{"type":"string","format":"base64"}},"data":{"type":"object","additionalProperties":{"type":"string"}},"immutable":{"type":"boolean","nullable":true},"kind":{"type":"string"},"metadata":{"$ref":"#/components/schemas/V1ObjectMeta"}}},"V1ConfigMapEnvSource":{"type":"object","properties":{"name":{"type":"string"},"optional":{"type":"boolean","nullable":true}}},"V1ConfigMapKeySelector":{"type":"object","properties":{"key":{"type":"string"},"name":{"type":"string"},"optional":{"type":"boolean","nullable":true}}},"V1ConfigMapProjection":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/V1KeyToPath"}},"name":{"type":"string"},"optional":{"type":"boolean","nullable":true}}},"V1ConfigMapVolumeSource":{"type":"object","properties":{"defaultMode":{"type":"integer","nullable":true},"items":{"type":"array","items":{"$ref":"#/components/schemas/V1KeyToPath"}},"name":{"type":"string"},"optional":{"type":"boolean","nullable":true}}},"V1Container":{"type":"object","properties":{"args":{"type":"array","items":{"type":"string"}},"command":{"type":"array","items":{"type":"string"}},"env":{"type":"array","items":{"$ref":"#/components/schemas/V1EnvVar"}},"envFrom":{"type":"array","items":{"$ref":"#/components/schemas/V1EnvFromSource"}},"image":{"type":"string"},"imagePullPolicy":{"type":"string"},"lifecycle":{"$ref":"#/components/schemas/V1Lifecycle"},"livenessProbe":{"$ref":"#/components/schemas/V1Probe"},"name":{"type":"string"},"ports":{"type":"array","items":{"$ref":"#/components/schemas/V1ContainerPort"}},"readinessProbe":{"$ref":"#/components/schemas/V1Probe"},"resizePolicy":{"type":"array","items":{"$ref":"#/components/schemas/V1ContainerResizePolicy"}},"resources":{"$ref":"#/components/schemas/V1ResourceRequirements"},"restartPolicy":{"type":"string","nullable":true},"securityContext":{"$ref":"#/components/schemas/V1SecurityContext"},"startupProbe":{"$ref":"#/components/schemas/V1Probe"},"stdin":{"type":"boolean"},"stdinOnce":{"type":"boolean"},"terminationMessagePath":{"type":"string"},"terminationMessagePolicy":{"type":"string"},"tty":{"type":"boolean"},"volumeDevices":{"type":"array","items":{"$ref":"#/components/schemas/V1VolumeDevice"}},"volumeMounts":{"type":"array","items":{"$ref":"#/components/schemas/V1VolumeMount"}},"workingDir":{"type":"string"}}},"V1ContainerPort":{"type":"object","properties":{"containerPort":{"type":"integer"},"hostIP":{"type":"string"},"hostPort":{"type":"integer"},"name":{"type":"string"},"protocol":{"type":"string"}}},"V1ContainerResizePolicy":{"type":"object","properties":{"resourceName":{"type":"string"},"restartPolicy":{"type":"string"}}},"V1ContainerState":{"type":"object","properties":{"running":{"$ref":"#/components/schemas/V1ContainerStateRunning"},"terminated":{"$ref":"#/components/schemas/V1ContainerStateTerminated"},"waiting":{"$ref":"#/components/schemas/V1ContainerStateWaiting"}}},"V1ContainerStateRunning":{"type":"object","properties":{"startedAt":{"type":"string"}}},"V1ContainerStateTerminated":{"type":"object","properties":{"containerID":{"type":"string"},"exitCode":{"type":"integer"},"finishedAt":{"type":"string"},"message":{"type":"string"},"reason":{"type":"string"},"signal":{"type":"integer"},"startedAt":{"type":"string"}}},"V1ContainerStateWaiting":{"type":"object","properties":{"message":{"type":"string"},"reason":{"type":"string"}}},"V1ContainerStatus":{"type":"object","properties":{"allocatedResources":{"$ref":"#/components/schemas/V1ResourceList"},"allocatedResourcesStatus":{"type":"array","items":{"$ref":"#/components/schemas/V1ResourceStatus"}},"containerID":{"type":"string"},"image":{"type":"string"},"imageID":{"type":"string"},"lastState":{"$ref":"#/components/schemas/V1ContainerState"},"name":{"type":"string"},"ready":{"type":"boolean"},"resources":{"$ref":"#/components/schemas/V1ResourceRequirements"},"restartCount":{"type":"integer"},"started":{"type":"boolean","nullable":true},"state":{"$ref":"#/components/schemas/V1ContainerState"},"stopSignal":{"type":"string","nullable":true},"user":{"$ref":"#/components/schemas/V1ContainerUser"},"volumeMounts":{"type":"array","items":{"$ref":"#/components/schemas/V1VolumeMountStatus"}}}},"V1ContainerUser":{"type":"object","properties":{"linux":{"$ref":"#/components/schemas/V1LinuxContainerUser"}}},"V1DownwardAPIProjection":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/V1DownwardAPIVolumeFile"}}}},"V1DownwardAPIVolumeFile":{"type":"object","properties":{"fieldRef":{"$ref":"#/components/schemas/V1ObjectFieldSelector"},"mode":{"type":"integer","nullable":true},"path":{"type":"string"},"resourceFieldRef":{"$ref":"#/components/schemas/V1ResourceFieldSelector"}}},"V1DownwardAPIVolumeSource":{"type":"object","properties":{"defaultMode":{"type":"integer","nullable":true},"items":{"type":"array","items":{"$ref":"#/components/schemas/V1DownwardAPIVolumeFile"}}}},"V1EmptyDirVolumeSource":{"type":"object","properties":{"medium":{"type":"string"},"sizeLimit":{"$ref":"#/components/schemas/ResourceQuantity"}}},"V1EnvFromSource":{"type":"object","properties":{"configMapRef":{"$ref":"#/components/schemas/V1ConfigMapEnvSource"},"prefix":{"type":"string"},"secretRef":{"$ref":"#/components/schemas/V1SecretEnvSource"}}},"V1EnvVar":{"type":"object","properties":{"name":{"type":"string"},"value":{"type":"string"},"valueFrom":{"$ref":"#/components/schemas/V1EnvVarSource"}}},"V1EnvVarSource":{"type":"object","properties":{"configMapKeyRef":{"$ref":"#/components/schemas/V1ConfigMapKeySelector"},"fieldRef":{"$ref":"#/components/schemas/V1ObjectFieldSelector"},"resourceFieldRef":{"$ref":"#/components/schemas/V1ResourceFieldSelector"},"secretKeyRef":{"$ref":"#/components/schemas/V1SecretKeySelector"}}},"V1EphemeralContainer":{"type":"object","properties":{"args":{"type":"array","items":{"type":"string"}},"command":{"type":"array","items":{"type":"string"}},"env":{"type":"array","items":{"$ref":"#/components/schemas/V1EnvVar"}},"envFrom":{"type":"array","items":{"$ref":"#/components/schemas/V1EnvFromSource"}},"image":{"type":"string"},"imagePullPolicy":{"type":"string"},"lifecycle":{"$ref":"#/components/schemas/V1Lifecycle"},"livenessProbe":{"$ref":"#/components/schemas/V1Probe"},"name":{"type":"string"},"ports":{"type":"array","items":{"$ref":"#/components/schemas/V1ContainerPort"}},"readinessProbe":{"$ref":"#/components/schemas/V1Probe"},"resizePolicy":{"type":"array","items":{"$ref":"#/components/schemas/V1ContainerResizePolicy"}},"resources":{"$ref":"#/components/schemas/V1ResourceRequirements"},"restartPolicy":{"type":"string","nullable":true},"securityContext":{"$ref":"#/components/schemas/V1SecurityContext"},"startupProbe":{"$ref":"#/components/schemas/V1Probe"},"stdin":{"type":"boolean"},"stdinOnce":{"type":"boolean"},"targetContainerName":{"type":"string"},"terminationMessagePath":{"type":"string"},"terminationMessagePolicy":{"type":"string"},"tty":{"type":"boolean"},"volumeDevices":{"type":"array","items":{"$ref":"#/components/schemas/V1VolumeDevice"}},"volumeMounts":{"type":"array","items":{"$ref":"#/components/schemas/V1VolumeMount"}},"workingDir":{"type":"string"}}},"V1EphemeralVolumeSource":{"type":"object","properties":{"volumeClaimTemplate":{"$ref":"#/components/schemas/V1PersistentVolumeClaimTemplate"}}},"V1ExecAction":{"type":"object","properties":{"command":{"type":"array","items":{"type":"string"}}}},"V1FCVolumeSource":{"type":"object","properties":{"fsType":{"type":"string"},"lun":{"type":"integer","nullable":true},"readOnly":{"type":"boolean"},"targetWWNs":{"type":"array","items":{"type":"string"}},"wwids":{"type":"array","items":{"type":"string"}}}},"V1FieldsV1":{"type":"object"},"V1FlexVolumeSource":{"type":"object","properties":{"driver":{"type":"string"},"fsType":{"type":"string"},"options":{"type":"object","additionalProperties":{"type":"string"}},"readOnly":{"type":"boolean"},"secretRef":{"$ref":"#/components/schemas/V1LocalObjectReference"}}},"V1FlockerVolumeSource":{"type":"object","properties":{"datasetName":{"type":"string"},"datasetUUID":{"type":"string"}}},"V1GCEPersistentDiskVolumeSource":{"type":"object","properties":{"fsType":{"type":"string"},"partition":{"type":"integer"},"pdName":{"type":"string"},"readOnly":{"type":"boolean"}}},"V1GRPCAction":{"type":"object","properties":{"port":{"type":"integer"},"service":{"type":"string","nullable":true}}},"V1GitRepoVolumeSource":{"type":"object","properties":{"directory":{"type":"string"},"repository":{"type":"string"},"revision":{"type":"string"}}},"V1GlusterfsVolumeSource":{"type":"object","properties":{"endpoints":{"type":"string"},"path":{"type":"string"},"readOnly":{"type":"boolean"}}},"V1HTTPGetAction":{"type":"object","properties":{"host":{"type":"string"},"httpHeaders":{"type":"array","items":{"$ref":"#/components/schemas/V1HTTPHeader"}},"path":{"type":"string"},"port":{"$ref":"#/components/schemas/IntstrIntOrString"},"scheme":{"type":"string"}}},"V1HTTPHeader":{"type":"object","properties":{"name":{"type":"string"},"value":{"type":"string"}}},"V1HostAlias":{"type":"object","properties":{"hostnames":{"type":"array","items":{"type":"string"}},"ip":{"type":"string"}}},"V1HostIP":{"type":"object","properties":{"ip":{"type":"string"}}},"V1HostPathVolumeSource":{"type":"object","properties":{"path":{"type":"string"},"type":{"type":"string","nullable":true}}},"V1ISCSIVolumeSource":{"type":"object","properties":{"chapAuthDiscovery":{"type":"boolean"},"chapAuthSession":{"type":"boolean"},"fsType":{"type":"string"},"initiatorName":{"type":"string","nullable":true},"iqn":{"type":"string"},"iscsiInterface":{"type":"string"},"lun":{"type":"integer"},"portals":{"type":"array","items":{"type":"string"}},"readOnly":{"type":"boolean"},"secretRef":{"$ref":"#/components/schemas/V1LocalObjectReference"},"targetPortal":{"type":"string"}}},"V1ImageVolumeSource":{"type":"object","properties":{"pullPolicy":{"type":"string"},"reference":{"type":"string"}}},"V1KeyToPath":{"type":"object","properties":{"key":{"type":"string"},"mode":{"type":"integer","nullable":true},"path":{"type":"string"}}},"V1LabelSelector":{"type":"object","properties":{"matchExpressions":{"type":"array","items":{"$ref":"#/components/schemas/V1LabelSelectorRequirement"}},"matchLabels":{"type":"object","additionalProperties":{"type":"string"}}}},"V1LabelSelectorRequirement":{"type":"object","properties":{"key":{"type":"string"},"operator":{"type":"string"},"values":{"type":"array","items":{"type":"string"}}}},"V1Lifecycle":{"type":"object","properties":{"postStart":{"$ref":"#/components/schemas/V1LifecycleHandler"},"preStop":{"$ref":"#/components/schemas/V1LifecycleHandler"},"stopSignal":{"type":"string","nullable":true}}},"V1LifecycleHandler":{"type":"object","properties":{"exec":{"$ref":"#/components/schemas/V1ExecAction"},"httpGet":{"$ref":"#/components/schemas/V1HTTPGetAction"},"sleep":{"$ref":"#/components/schemas/V1SleepAction"},"tcpSocket":{"$ref":"#/components/schemas/V1TCPSocketAction"}}},"V1LinuxContainerUser":{"type":"object","properties":{"gid":{"type":"integer"},"supplementalGroups":{"type":"array","items":{"type":"integer"}},"uid":{"type":"integer"}}},"V1LocalObjectReference":{"type":"object","properties":{"name":{"type":"string"}}},"V1ManagedFieldsEntry":{"type":"object","properties":{"apiVersion":{"type":"string"},"fieldsType":{"type":"string"},"fieldsV1":{"$ref":"#/components/schemas/V1FieldsV1"},"manager":{"type":"string"},"operation":{"type":"string"},"subresource":{"type":"string"},"time":{"type":"string"}}},"V1NFSVolumeSource":{"type":"object","properties":{"path":{"type":"string"},"readOnly":{"type":"boolean"},"server":{"type":"string"}}},"V1NodeAffinity":{"type":"object","properties":{"preferredDuringSchedulingIgnoredDuringExecution":{"type":"array","items":{"$ref":"#/components/schemas/V1PreferredSchedulingTerm"}},"requiredDuringSchedulingIgnoredDuringExecution":{"$ref":"#/components/schemas/V1NodeSelector"}}},"V1NodeSelector":{"type":"object","properties":{"nodeSelectorTerms":{"type":"array","items":{"$ref":"#/components/schemas/V1NodeSelectorTerm"},"nullable":true}}},"V1NodeSelectorRequirement":{"type":"object","properties":{"key":{"type":"string"},"operator":{"type":"string"},"values":{"type":"array","items":{"type":"string"}}}},"V1NodeSelectorTerm":{"type":"object","properties":{"matchExpressions":{"type":"array","items":{"$ref":"#/components/schemas/V1NodeSelectorRequirement"}},"matchFields":{"type":"array","items":{"$ref":"#/components/schemas/V1NodeSelectorRequirement"}}}},"V1ObjectFieldSelector":{"type":"object","properties":{"apiVersion":{"type":"string"},"fieldPath":{"type":"string"}}},"V1ObjectMeta":{"type":"object","properties":{"annotations":{"type":"object","additionalProperties":{"type":"string"}},"creationTimestamp":{"type":"string"},"deletionGracePeriodSeconds":{"type":"integer","nullable":true},"deletionTimestamp":{"type":"string"},"finalizers":{"type":"array","items":{"type":"string"}},"generateName":{"type":"string"},"generation":{"type":"integer"},"labels":{"type":"object","additionalProperties":{"type":"string"}},"managedFields":{"type":"array","items":{"$ref":"#/components/schemas/V1ManagedFieldsEntry"}},"name":{"type":"string"},"namespace":{"type":"string"},"ownerReferences":{"type":"array","items":{"$ref":"#/components/schemas/V1OwnerReference"}},"resourceVersion":{"type":"string"},"selfLink":{"type":"string"},"uid":{"type":"string"}}},"V1OwnerReference":{"type":"object","properties":{"apiVersion":{"type":"string"},"blockOwnerDeletion":{"type":"boolean","nullable":true},"controller":{"type":"boolean","nullable":true},"kind":{"type":"string"},"name":{"type":"string"},"uid":{"type":"string"}}},"V1PersistentVolumeClaimSpec":{"type":"object","properties":{"accessModes":{"type":"array","items":{"type":"string"}},"dataSource":{"$ref":"#/components/schemas/V1TypedLocalObjectReference"},"dataSourceRef":{"$ref":"#/components/schemas/V1TypedObjectReference"},"resources":{"$ref":"#/components/schemas/V1VolumeResourceRequirements"},"selector":{"$ref":"#/components/schemas/V1LabelSelector"},"storageClassName":{"type":"string","nullable":true},"volumeAttributesClassName":{"type":"string","nullable":true},"volumeMode":{"type":"string","nullable":true},"volumeName":{"type":"string"}}},"V1PersistentVolumeClaimTemplate":{"type":"object","properties":{"metadata":{"$ref":"#/components/schemas/V1ObjectMeta"},"spec":{"$ref":"#/components/schemas/V1PersistentVolumeClaimSpec"}}},"V1PersistentVolumeClaimVolumeSource":{"type":"object","properties":{"claimName":{"type":"string"},"readOnly":{"type":"boolean"}}},"V1PhotonPersistentDiskVolumeSource":{"type":"object","properties":{"fsType":{"type":"string"},"pdID":{"type":"string"}}},"V1Pod":{"type":"object","properties":{"apiVersion":{"type":"string"},"kind":{"type":"string"},"metadata":{"$ref":"#/components/schemas/V1ObjectMeta"},"spec":{"$ref":"#/components/schemas/V1PodSpec"},"status":{"$ref":"#/components/schemas/V1PodStatus"}}},"V1PodAffinity":{"type":"object","properties":{"preferredDuringSchedulingIgnoredDuringExecution":{"type":"array","items":{"$ref":"#/components/schemas/V1WeightedPodAffinityTerm"}},"requiredDuringSchedulingIgnoredDuringExecution":{"type":"array","items":{"$ref":"#/components/schemas/V1PodAffinityTerm"}}}},"V1PodAffinityTerm":{"type":"object","properties":{"labelSelector":{"$ref":"#/components/schemas/V1LabelSelector"},"matchLabelKeys":{"type":"array","items":{"type":"string"}},"mismatchLabelKeys":{"type":"array","items":{"type":"string"}},"namespaceSelector":{"$ref":"#/components/schemas/V1LabelSelector"},"namespaces":{"type":"array","items":{"type":"string"}},"topologyKey":{"type":"string"}}},"V1PodAntiAffinity":{"type":"object","properties":{"preferredDuringSchedulingIgnoredDuringExecution":{"type":"array","items":{"$ref":"#/components/schemas/V1WeightedPodAffinityTerm"}},"requiredDuringSchedulingIgnoredDuringExecution":{"type":"array","items":{"$ref":"#/components/schemas/V1PodAffinityTerm"}}}},"V1PodCondition":{"type":"object","properties":{"lastProbeTime":{"type":"string"},"lastTransitionTime":{"type":"string"},"message":{"type":"string"},"observedGeneration":{"type":"integer"},"reason":{"type":"string"},"status":{"type":"string"},"type":{"type":"string"}}},"V1PodDNSConfig":{"type":"object","properties":{"nameservers":{"type":"array","items":{"type":"string"}},"options":{"type":"array","items":{"$ref":"#/components/schemas/V1PodDNSConfigOption"}},"searches":{"type":"array","items":{"type":"string"}}}},"V1PodDNSConfigOption":{"type":"object","properties":{"name":{"type":"string"},"value":{"type":"string","nullable":true}}},"V1PodIP":{"type":"object","properties":{"ip":{"type":"string"}}},"V1PodOS":{"type":"object","properties":{"name":{"type":"string"}}},"V1PodReadinessGate":{"type":"object","properties":{"conditionType":{"type":"string"}}},"V1PodResourceClaim":{"type":"object","properties":{"name":{"type":"string"},"resourceClaimName":{"type":"string","nullable":true},"resourceClaimTemplateName":{"type":"string","nullable":true}}},"V1PodResourceClaimStatus":{"type":"object","properties":{"name":{"type":"string"},"resourceClaimName":{"type":"string","nullable":true}}},"V1PodSchedulingGate":{"type":"object","properties":{"name":{"type":"string"}}},"V1PodSecurityContext":{"type":"object","properties":{"appArmorProfile":{"$ref":"#/components/schemas/V1AppArmorProfile"},"fsGroup":{"type":"integer","nullable":true},"fsGroupChangePolicy":{"type":"string","nullable":true},"runAsGroup":{"type":"integer","nullable":true},"runAsNonRoot":{"type":"boolean","nullable":true},"runAsUser":{"type":"integer","nullable":true},"seLinuxChangePolicy":{"type":"string","nullable":true},"seLinuxOptions":{"$ref":"#/components/schemas/V1SELinuxOptions"},"seccompProfile":{"$ref":"#/components/schemas/V1SeccompProfile"},"supplementalGroups":{"type":"array","items":{"type":"integer"}},"supplementalGroupsPolicy":{"type":"string","nullable":true},"sysctls":{"type":"array","items":{"$ref":"#/components/schemas/V1Sysctl"}},"windowsOptions":{"$ref":"#/components/schemas/V1WindowsSecurityContextOptions"}}},"V1PodSpec":{"type":"object","properties":{"activeDeadlineSeconds":{"type":"integer","nullable":true},"affinity":{"$ref":"#/components/schemas/V1Affinity"},"automountServiceAccountToken":{"type":"boolean","nullable":true},"containers":{"type":"array","items":{"$ref":"#/components/schemas/V1Container"},"nullable":true},"dnsConfig":{"$ref":"#/components/schemas/V1PodDNSConfig"},"dnsPolicy":{"type":"string"},"enableServiceLinks":{"type":"boolean","nullable":true},"ephemeralContainers":{"type":"array","items":{"$ref":"#/components/schemas/V1EphemeralContainer"}},"hostAliases":{"type":"array","items":{"$ref":"#/components/schemas/V1HostAlias"}},"hostIPC":{"type":"boolean"},"hostNetwork":{"type":"boolean"},"hostPID":{"type":"boolean"},"hostUsers":{"type":"boolean","nullable":true},"hostname":{"type":"string"},"imagePullSecrets":{"type":"array","items":{"$ref":"#/components/schemas/V1LocalObjectReference"}},"initContainers":{"type":"array","items":{"$ref":"#/components/schemas/V1Container"}},"nodeName":{"type":"string"},"nodeSelector":{"type":"object","additionalProperties":{"type":"string"}},"os":{"$ref":"#/components/schemas/V1PodOS"},"overhead":{"$ref":"#/components/schemas/V1ResourceList"},"preemptionPolicy":{"type":"string","nullable":true},"priority":{"type":"integer","nullable":true},"priorityClassName":{"type":"string"},"readinessGates":{"type":"array","items":{"$ref":"#/components/schemas/V1PodReadinessGate"}},"resourceClaims":{"type":"array","items":{"$ref":"#/components/schemas/V1PodResourceClaim"}},"resources":{"$ref":"#/components/schemas/V1ResourceRequirements"},"restartPolicy":{"type":"string"},"runtimeClassName":{"type":"string","nullable":true},"schedulerName":{"type":"string"},"schedulingGates":{"type":"array","items":{"$ref":"#/components/schemas/V1PodSchedulingGate"}},"securityContext":{"$ref":"#/components/schemas/V1PodSecurityContext"},"serviceAccount":{"type":"string"},"serviceAccountName":{"type":"string"},"setHostnameAsFQDN":{"type":"boolean","nullable":true},"shareProcessNamespace":{"type":"boolean","nullable":true},"subdomain":{"type":"string"},"terminationGracePeriodSeconds":{"type":"integer","nullable":true},"tolerations":{"type":"array","items":{"$ref":"#/components/schemas/V1Toleration"}},"topologySpreadConstraints":{"type":"array","items":{"$ref":"#/components/schemas/V1TopologySpreadConstraint"}},"volumes":{"type":"array","items":{"$ref":"#/components/schemas/V1Volume"}}}},"V1PodStatus":{"type":"object","properties":{"conditions":{"type":"array","items":{"$ref":"#/components/schemas/V1PodCondition"}},"containerStatuses":{"type":"array","items":{"$ref":"#/components/schemas/V1ContainerStatus"}},"ephemeralContainerStatuses":{"type":"array","items":{"$ref":"#/components/schemas/V1ContainerStatus"}},"hostIP":{"type":"string"},"hostIPs":{"type":"array","items":{"$ref":"#/components/schemas/V1HostIP"}},"initContainerStatuses":{"type":"array","items":{"$ref":"#/components/schemas/V1ContainerStatus"}},"message":{"type":"string"},"nominatedNodeName":{"type":"string"},"observedGeneration":{"type":"integer"},"phase":{"type":"string"},"podIP":{"type":"string"},"podIPs":{"type":"array","items":{"$ref":"#/components/schemas/V1PodIP"}},"qosClass":{"type":"string"},"reason":{"type":"string"},"resize":{"type":"string"},"resourceClaimStatuses":{"type":"array","items":{"$ref":"#/components/schemas/V1PodResourceClaimStatus"}},"startTime":{"type":"string"}}},"V1PortworxVolumeSource":{"type":"object","properties":{"fsType":{"type":"string"},"readOnly":{"type":"boolean"},"volumeID":{"type":"string"}}},"V1PreferredSchedulingTerm":{"type":"object","properties":{"preference":{"$ref":"#/components/schemas/V1NodeSelectorTerm"},"weight":{"type":"integer"}}},"V1Probe":{"type":"object","properties":{"exec":{"$ref":"#/components/schemas/V1ExecAction"},"failureThreshold":{"type":"integer"},"grpc":{"$ref":"#/components/schemas/V1GRPCAction"},"httpGet":{"$ref":"#/components/schemas/V1HTTPGetAction"},"initialDelaySeconds":{"type":"integer"},"periodSeconds":{"type":"integer"},"successThreshold":{"type":"integer"},"tcpSocket":{"$ref":"#/components/schemas/V1TCPSocketAction"},"terminationGracePeriodSeconds":{"type":"integer","nullable":true},"timeoutSeconds":{"type":"integer"}}},"V1ProjectedVolumeSource":{"type":"object","properties":{"defaultMode":{"type":"integer","nullable":true},"sources":{"type":"array","items":{"$ref":"#/components/schemas/V1VolumeProjection"},"nullable":true}}},"V1QuobyteVolumeSource":{"type":"object","properties":{"group":{"type":"string"},"readOnly":{"type":"boolean"},"registry":{"type":"string"},"tenant":{"type":"string"},"user":{"type":"string"},"volume":{"type":"string"}}},"V1RBDVolumeSource":{"type":"object","properties":{"fsType":{"type":"string"},"image":{"type":"string"},"keyring":{"type":"string"},"monitors":{"type":"array","items":{"type":"string"},"nullable":true},"pool":{"type":"string"},"readOnly":{"type":"boolean"},"secretRef":{"$ref":"#/components/schemas/V1LocalObjectReference"},"user":{"type":"string"}}},"V1ResourceClaim":{"type":"object","properties":{"name":{"type":"string"},"request":{"type":"string"}}},"V1ResourceFieldSelector":{"type":"object","properties":{"containerName":{"type":"string"},"divisor":{"$ref":"#/components/schemas/ResourceQuantity"},"resource":{"type":"string"}}},"V1ResourceHealth":{"type":"object","properties":{"health":{"type":"string"},"resourceID":{"type":"string"}}},"V1ResourceList":{"type":"object","additionalProperties":{"$ref":"#/components/schemas/ResourceQuantity"}},"V1ResourceRequirements":{"type":"object","properties":{"claims":{"type":"array","items":{"$ref":"#/components/schemas/V1ResourceClaim"}},"limits":{"$ref":"#/components/schemas/V1ResourceList"},"requests":{"$ref":"#/components/schemas/V1ResourceList"}}},"V1ResourceStatus":{"type":"object","properties":{"name":{"type":"string"},"resources":{"type":"array","items":{"$ref":"#/components/schemas/V1ResourceHealth"}}}},"V1SELinuxOptions":{"type":"object","properties":{"level":{"type":"string"},"role":{"type":"string"},"type":{"type":"string"},"user":{"type":"string"}}},"V1ScaleIOVolumeSource":{"type":"object","properties":{"fsType":{"type":"string"},"gateway":{"type":"string"},"protectionDomain":{"type":"string"},"readOnly":{"type":"boolean"},"secretRef":{"$ref":"#/components/schemas/V1LocalObjectReference"},"sslEnabled":{"type":"boolean"},"storageMode":{"type":"string"},"storagePool":{"type":"string"},"system":{"type":"string"},"volumeName":{"type":"string"}}},"V1SeccompProfile":{"type":"object","properties":{"localhostProfile":{"type":"string","nullable":true},"type":{"type":"string"}}},"V1Secret":{"type":"object","properties":{"apiVersion":{"type":"string"},"data":{"type":"object","additionalProperties":{"type":"string","format":"base64"}},"immutable":{"type":"boolean","nullable":true},"kind":{"type":"string"},"metadata":{"$ref":"#/components/schemas/V1ObjectMeta"},"stringData":{"type":"object","additionalProperties":{"type":"string"}},"type":{"type":"string"}}},"V1SecretEnvSource":{"type":"object","properties":{"name":{"type":"string"},"optional":{"type":"boolean","nullable":true}}},"V1SecretKeySelector":{"type":"object","properties":{"key":{"type":"string"},"name":{"type":"string"},"optional":{"type":"boolean","nullable":true}}},"V1SecretProjection":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/V1KeyToPath"}},"name":{"type":"string"},"optional":{"type":"boolean","nullable":true}}},"V1SecretVolumeSource":{"type":"object","properties":{"defaultMode":{"type":"integer","nullable":true},"items":{"type":"array","items":{"$ref":"#/components/schemas/V1KeyToPath"}},"optional":{"type":"boolean","nullable":true},"secretName":{"type":"string"}}},"V1SecurityContext":{"type":"object","properties":{"allowPrivilegeEscalation":{"type":"boolean","nullable":true},"appArmorProfile":{"$ref":"#/components/schemas/V1AppArmorProfile"},"capabilities":{"$ref":"#/components/schemas/V1Capabilities"},"privileged":{"type":"boolean","nullable":true},"procMount":{"type":"string","nullable":true},"readOnlyRootFilesystem":{"type":"boolean","nullable":true},"runAsGroup":{"type":"integer","nullable":true},"runAsNonRoot":{"type":"boolean","nullable":true},"runAsUser":{"type":"integer","nullable":true},"seLinuxOptions":{"$ref":"#/components/schemas/V1SELinuxOptions"},"seccompProfile":{"$ref":"#/components/schemas/V1SeccompProfile"},"windowsOptions":{"$ref":"#/components/schemas/V1WindowsSecurityContextOptions"}}},"V1ServiceAccountTokenProjection":{"type":"object","properties":{"audience":{"type":"string"},"expirationSeconds":{"type":"integer","nullable":true},"path":{"type":"string"}}},"V1SleepAction":{"type":"object","properties":{"seconds":{"type":"integer"}}},"V1StorageOSVolumeSource":{"type":"object","properties":{"fsType":{"type":"string"},"readOnly":{"type":"boolean"},"secretRef":{"$ref":"#/components/schemas/V1LocalObjectReference"},"volumeName":{"type":"string"},"volumeNamespace":{"type":"string"}}},"V1Sysctl":{"type":"object","properties":{"name":{"type":"string"},"value":{"type":"string"}}},"V1TCPSocketAction":{"type":"object","properties":{"host":{"type":"string"},"port":{"$ref":"#/components/schemas/IntstrIntOrString"}}},"V1Toleration":{"type":"object","properties":{"effect":{"type":"string"},"key":{"type":"string"},"operator":{"type":"string"},"tolerationSeconds":{"type":"integer","nullable":true},"value":{"type":"string"}}},"V1TopologySpreadConstraint":{"type":"object","properties":{"labelSelector":{"$ref":"#/components/schemas/V1LabelSelector"},"matchLabelKeys":{"type":"array","items":{"type":"string"}},"maxSkew":{"type":"integer"},"minDomains":{"type":"integer","nullable":true},"nodeAffinityPolicy":{"type":"string","nullable":true},"nodeTaintsPolicy":{"type":"string","nullable":true},"topologyKey":{"type":"string"},"whenUnsatisfiable":{"type":"string"}}},"V1TypedLocalObjectReference":{"type":"object","properties":{"apiGroup":{"type":"string","nullable":true},"kind":{"type":"string"},"name":{"type":"string"}}},"V1TypedObjectReference":{"type":"object","properties":{"apiGroup":{"type":"string","nullable":true},"kind":{"type":"string"},"name":{"type":"string"},"namespace":{"type":"string","nullable":true}}},"V1Volume":{"type":"object","properties":{"awsElasticBlockStore":{"$ref":"#/components/schemas/V1AWSElasticBlockStoreVolumeSource"},"azureDisk":{"$ref":"#/components/schemas/V1AzureDiskVolumeSource"},"azureFile":{"$ref":"#/components/schemas/V1AzureFileVolumeSource"},"cephfs":{"$ref":"#/components/schemas/V1CephFSVolumeSource"},"cinder":{"$ref":"#/components/schemas/V1CinderVolumeSource"},"configMap":{"$ref":"#/components/schemas/V1ConfigMapVolumeSource"},"csi":{"$ref":"#/components/schemas/V1CSIVolumeSource"},"downwardAPI":{"$ref":"#/components/schemas/V1DownwardAPIVolumeSource"},"emptyDir":{"$ref":"#/components/schemas/V1EmptyDirVolumeSource"},"ephemeral":{"$ref":"#/components/schemas/V1EphemeralVolumeSource"},"fc":{"$ref":"#/components/schemas/V1FCVolumeSource"},"flexVolume":{"$ref":"#/components/schemas/V1FlexVolumeSource"},"flocker":{"$ref":"#/components/schemas/V1FlockerVolumeSource"},"gcePersistentDisk":{"$ref":"#/components/schemas/V1GCEPersistentDiskVolumeSource"},"gitRepo":{"$ref":"#/components/schemas/V1GitRepoVolumeSource"},"glusterfs":{"$ref":"#/components/schemas/V1GlusterfsVolumeSource"},"hostPath":{"$ref":"#/components/schemas/V1HostPathVolumeSource"},"image":{"$ref":"#/components/schemas/V1ImageVolumeSource"},"iscsi":{"$ref":"#/components/schemas/V1ISCSIVolumeSource"},"name":{"type":"string"},"nfs":{"$ref":"#/components/schemas/V1NFSVolumeSource"},"persistentVolumeClaim":{"$ref":"#/components/schemas/V1PersistentVolumeClaimVolumeSource"},"photonPersistentDisk":{"$ref":"#/components/schemas/V1PhotonPersistentDiskVolumeSource"},"portworxVolume":{"$ref":"#/components/schemas/V1PortworxVolumeSource"},"projected":{"$ref":"#/components/schemas/V1ProjectedVolumeSource"},"quobyte":{"$ref":"#/components/schemas/V1QuobyteVolumeSource"},"rbd":{"$ref":"#/components/schemas/V1RBDVolumeSource"},"scaleIO":{"$ref":"#/components/schemas/V1ScaleIOVolumeSource"},"secret":{"$ref":"#/components/schemas/V1SecretVolumeSource"},"storageos":{"$ref":"#/components/schemas/V1StorageOSVolumeSource"},"vsphereVolume":{"$ref":"#/components/schemas/V1VsphereVirtualDiskVolumeSource"}}},"V1VolumeDevice":{"type":"object","properties":{"devicePath":{"type":"string"},"name":{"type":"string"}}},"V1VolumeMount":{"type":"object","properties":{"mountPath":{"type":"string"},"mountPropagation":{"type":"string","nullable":true},"name":{"type":"string"},"readOnly":{"type":"boolean"},"recursiveReadOnly":{"type":"string","nullable":true},"subPath":{"type":"string"},"subPathExpr":{"type":"string"}}},"V1VolumeMountStatus":{"type":"object","properties":{"mountPath":{"type":"string"},"name":{"type":"string"},"readOnly":{"type":"boolean"},"recursiveReadOnly":{"type":"string","nullable":true}}},"V1VolumeProjection":{"type":"object","properties":{"clusterTrustBundle":{"$ref":"#/components/schemas/V1ClusterTrustBundleProjection"},"configMap":{"$ref":"#/components/schemas/V1ConfigMapProjection"},"downwardAPI":{"$ref":"#/components/schemas/V1DownwardAPIProjection"},"secret":{"$ref":"#/components/schemas/V1SecretProjection"},"serviceAccountToken":{"$ref":"#/components/schemas/V1ServiceAccountTokenProjection"}}},"V1VolumeResourceRequirements":{"type":"object","properties":{"limits":{"$ref":"#/components/schemas/V1ResourceList"},"requests":{"$ref":"#/components/schemas/V1ResourceList"}}},"V1VsphereVirtualDiskVolumeSource":{"type":"object","properties":{"fsType":{"type":"string"},"storagePolicyID":{"type":"string"},"storagePolicyName":{"type":"string"},"volumePath":{"type":"string"}}},"V1WeightedPodAffinityTerm":{"type":"object","properties":{"podAffinityTerm":{"$ref":"#/components/schemas/V1PodAffinityTerm"},"weight":{"type":"integer"}}},"V1WindowsSecurityContextOptions":{"type":"object","properties":{"gmsaCredentialSpec":{"type":"string","nullable":true},"gmsaCredentialSpecName":{"type":"string","nullable":true},"hostProcess":{"type":"boolean","nullable":true},"runAsUserName":{"type":"string","nullable":true}}}}}} \ No newline at end of file +{"openapi":"3.0.3","info":{"title":"interLink server API","description":"This is the API spec for the Virtual Kubelet to interLink API server communication","version":"0.4.0"},"paths":{"/create":{"post":{"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/InterlinkPodCreateRequests"}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InterlinkRetrievedPodData"}}}}}}},"/createPodSets":{"post":{"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/InterlinkPodSetsCreateRequests"}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/InterlinkCreateStruct"}}}}}}}},"/delete":{"post":{"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Pod"}}}},"responses":{"200":{"description":"OK"}}}},"/getLogs":{"post":{"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/InterlinkLogStruct"}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"string"}}}}}}},"/pinglink":{"post":{"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InterlinkPingResponse"}}}}}}},"/status":{"post":{"requestBody":{"content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/V1Pod"},"nullable":true}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/InterlinkPodStatus"}}}}}}}}},"components":{"schemas":{"InterlinkAcceleratorResponse":{"type":"object","properties":{"available":{"type":"string"},"resourceType":{"type":"string"}}},"InterlinkApptainerOptions":{"type":"object","properties":{"cleanenv":{"type":"boolean"},"containall":{"type":"boolean"},"executable":{"type":"string"},"fakeroot":{"type":"boolean"},"fuseMode":{"type":"string"},"noHome":{"type":"boolean"},"noInit":{"type":"boolean"},"noPrivs":{"type":"boolean"},"nvidiaSupport":{"type":"boolean"},"unsquash":{"type":"boolean"}}},"InterlinkContainerLogOpts":{"type":"object","properties":{"Bytes":{"type":"integer"},"Follow":{"type":"boolean"},"Previous":{"type":"boolean"},"SinceSeconds":{"type":"integer"},"SinceTime":{"type":"string","format":"date-time"},"Tail":{"type":"integer"},"Timestamps":{"type":"boolean"}}},"InterlinkCreateStruct":{"type":"object","properties":{"PodJID":{"type":"string"},"PodUID":{"type":"string"}}},"InterlinkLogStruct":{"type":"object","properties":{"ContainerName":{"type":"string"},"Namespace":{"type":"string"},"Opts":{"$ref":"#/components/schemas/InterlinkContainerLogOpts"},"PodName":{"type":"string"},"PodUID":{"type":"string"}}},"InterlinkPingResponse":{"type":"object","properties":{"resources":{"$ref":"#/components/schemas/InterlinkResourcesResponse"},"status":{"type":"string"},"taints":{"type":"array","items":{"$ref":"#/components/schemas/InterlinkTaintResponse"},"nullable":true}}},"InterlinkPodCreateRequests":{"type":"object","properties":{"configmaps":{"type":"array","items":{"$ref":"#/components/schemas/V1ConfigMap"},"nullable":true},"jobscriptURL":{"type":"string"},"pod":{"$ref":"#/components/schemas/V1Pod"},"projectedvolumesmaps":{"type":"array","items":{"$ref":"#/components/schemas/V1ConfigMap"},"nullable":true},"secrets":{"type":"array","items":{"$ref":"#/components/schemas/V1Secret"},"nullable":true}}},"InterlinkPodSetCreateRequests":{"type":"object","properties":{"name":{"type":"string"},"pods":{"type":"array","items":{"$ref":"#/components/schemas/InterlinkPodCreateRequests"},"nullable":true}}},"InterlinkPodSetsCreateRequests":{"type":"object","properties":{"groupUID":{"type":"string"},"podSets":{"type":"array","items":{"$ref":"#/components/schemas/InterlinkPodSetCreateRequests"},"nullable":true}}},"InterlinkPodStatus":{"type":"object","properties":{"JID":{"type":"string"},"UID":{"type":"string"},"containers":{"type":"array","items":{"$ref":"#/components/schemas/V1ContainerStatus"},"nullable":true},"initContainers":{"type":"array","items":{"$ref":"#/components/schemas/V1ContainerStatus"},"nullable":true},"name":{"type":"string"},"namespace":{"type":"string"},"nodeName":{"type":"string"},"socketDir":{"type":"string"}}},"InterlinkResourcesResponse":{"type":"object","properties":{"accelerators":{"type":"array","items":{"$ref":"#/components/schemas/InterlinkAcceleratorResponse"}},"cpu":{"type":"string"},"memory":{"type":"string"},"pods":{"type":"string"}}},"InterlinkRetrievedContainer":{"type":"object","properties":{"configMaps":{"type":"array","items":{"$ref":"#/components/schemas/V1ConfigMap"},"nullable":true},"emptyDirs":{"type":"array","items":{"type":"string"},"nullable":true},"name":{"type":"string"},"projectedvolumemaps":{"type":"array","items":{"$ref":"#/components/schemas/V1ConfigMap"},"nullable":true},"secrets":{"type":"array","items":{"$ref":"#/components/schemas/V1Secret"},"nullable":true}}},"InterlinkRetrievedPodData":{"type":"object","properties":{"container":{"type":"array","items":{"$ref":"#/components/schemas/InterlinkRetrievedContainer"},"nullable":true},"jobConfig":{"$ref":"#/components/schemas/InterlinkScriptBuildConfig"},"jobScript":{"type":"string"},"pod":{"$ref":"#/components/schemas/V1Pod"}}},"InterlinkScriptBuildConfig":{"type":"object","properties":{"ApptainerOptions":{"$ref":"#/components/schemas/InterlinkApptainerOptions"},"SingularityHubProxy":{"$ref":"#/components/schemas/InterlinkSingularityHubConfig"},"Volumes":{"$ref":"#/components/schemas/InterlinkVolumesOptions"}}},"InterlinkSingularityHubConfig":{"type":"object","properties":{"cache_validity_seconds":{"type":"integer"},"master_token":{"type":"string"},"server":{"type":"string"}}},"InterlinkTaintResponse":{"type":"object","properties":{"effect":{"type":"string"},"key":{"type":"string"},"value":{"type":"string"}}},"InterlinkVolumesOptions":{"type":"object","properties":{"additional_directories_in_path":{"type":"array","items":{"type":"string"},"nullable":true},"apptainer_cachedir":{"type":"string"},"fuse_sleep_seconds":{"type":"integer"},"image_dir":{"type":"string"},"scratch_area":{"type":"string"}}},"IntstrIntOrString":{"type":"object"},"ResourceQuantity":{"type":"object"},"V1AWSElasticBlockStoreVolumeSource":{"type":"object","properties":{"fsType":{"type":"string"},"partition":{"type":"integer"},"readOnly":{"type":"boolean"},"volumeID":{"type":"string"}}},"V1Affinity":{"type":"object","properties":{"nodeAffinity":{"$ref":"#/components/schemas/V1NodeAffinity"},"podAffinity":{"$ref":"#/components/schemas/V1PodAffinity"},"podAntiAffinity":{"$ref":"#/components/schemas/V1PodAntiAffinity"}}},"V1AppArmorProfile":{"type":"object","properties":{"localhostProfile":{"type":"string","nullable":true},"type":{"type":"string"}}},"V1AzureDiskVolumeSource":{"type":"object","properties":{"cachingMode":{"type":"string","nullable":true},"diskName":{"type":"string"},"diskURI":{"type":"string"},"fsType":{"type":"string","nullable":true},"kind":{"type":"string","nullable":true},"readOnly":{"type":"boolean","nullable":true}}},"V1AzureFileVolumeSource":{"type":"object","properties":{"readOnly":{"type":"boolean"},"secretName":{"type":"string"},"shareName":{"type":"string"}}},"V1CSIVolumeSource":{"type":"object","properties":{"driver":{"type":"string"},"fsType":{"type":"string","nullable":true},"nodePublishSecretRef":{"$ref":"#/components/schemas/V1LocalObjectReference"},"readOnly":{"type":"boolean","nullable":true},"volumeAttributes":{"type":"object","additionalProperties":{"type":"string"}}}},"V1Capabilities":{"type":"object","properties":{"add":{"type":"array","items":{"type":"string"}},"drop":{"type":"array","items":{"type":"string"}}}},"V1CephFSVolumeSource":{"type":"object","properties":{"monitors":{"type":"array","items":{"type":"string"},"nullable":true},"path":{"type":"string"},"readOnly":{"type":"boolean"},"secretFile":{"type":"string"},"secretRef":{"$ref":"#/components/schemas/V1LocalObjectReference"},"user":{"type":"string"}}},"V1CinderVolumeSource":{"type":"object","properties":{"fsType":{"type":"string"},"readOnly":{"type":"boolean"},"secretRef":{"$ref":"#/components/schemas/V1LocalObjectReference"},"volumeID":{"type":"string"}}},"V1ClusterTrustBundleProjection":{"type":"object","properties":{"labelSelector":{"$ref":"#/components/schemas/V1LabelSelector"},"name":{"type":"string","nullable":true},"optional":{"type":"boolean","nullable":true},"path":{"type":"string"},"signerName":{"type":"string","nullable":true}}},"V1ConfigMap":{"type":"object","properties":{"apiVersion":{"type":"string"},"binaryData":{"type":"object","additionalProperties":{"type":"string","format":"base64"}},"data":{"type":"object","additionalProperties":{"type":"string"}},"immutable":{"type":"boolean","nullable":true},"kind":{"type":"string"},"metadata":{"$ref":"#/components/schemas/V1ObjectMeta"}}},"V1ConfigMapEnvSource":{"type":"object","properties":{"name":{"type":"string"},"optional":{"type":"boolean","nullable":true}}},"V1ConfigMapKeySelector":{"type":"object","properties":{"key":{"type":"string"},"name":{"type":"string"},"optional":{"type":"boolean","nullable":true}}},"V1ConfigMapProjection":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/V1KeyToPath"}},"name":{"type":"string"},"optional":{"type":"boolean","nullable":true}}},"V1ConfigMapVolumeSource":{"type":"object","properties":{"defaultMode":{"type":"integer","nullable":true},"items":{"type":"array","items":{"$ref":"#/components/schemas/V1KeyToPath"}},"name":{"type":"string"},"optional":{"type":"boolean","nullable":true}}},"V1Container":{"type":"object","properties":{"args":{"type":"array","items":{"type":"string"}},"command":{"type":"array","items":{"type":"string"}},"env":{"type":"array","items":{"$ref":"#/components/schemas/V1EnvVar"}},"envFrom":{"type":"array","items":{"$ref":"#/components/schemas/V1EnvFromSource"}},"image":{"type":"string"},"imagePullPolicy":{"type":"string"},"lifecycle":{"$ref":"#/components/schemas/V1Lifecycle"},"livenessProbe":{"$ref":"#/components/schemas/V1Probe"},"name":{"type":"string"},"ports":{"type":"array","items":{"$ref":"#/components/schemas/V1ContainerPort"}},"readinessProbe":{"$ref":"#/components/schemas/V1Probe"},"resizePolicy":{"type":"array","items":{"$ref":"#/components/schemas/V1ContainerResizePolicy"}},"resources":{"$ref":"#/components/schemas/V1ResourceRequirements"},"restartPolicy":{"type":"string","nullable":true},"securityContext":{"$ref":"#/components/schemas/V1SecurityContext"},"startupProbe":{"$ref":"#/components/schemas/V1Probe"},"stdin":{"type":"boolean"},"stdinOnce":{"type":"boolean"},"terminationMessagePath":{"type":"string"},"terminationMessagePolicy":{"type":"string"},"tty":{"type":"boolean"},"volumeDevices":{"type":"array","items":{"$ref":"#/components/schemas/V1VolumeDevice"}},"volumeMounts":{"type":"array","items":{"$ref":"#/components/schemas/V1VolumeMount"}},"workingDir":{"type":"string"}}},"V1ContainerPort":{"type":"object","properties":{"containerPort":{"type":"integer"},"hostIP":{"type":"string"},"hostPort":{"type":"integer"},"name":{"type":"string"},"protocol":{"type":"string"}}},"V1ContainerResizePolicy":{"type":"object","properties":{"resourceName":{"type":"string"},"restartPolicy":{"type":"string"}}},"V1ContainerState":{"type":"object","properties":{"running":{"$ref":"#/components/schemas/V1ContainerStateRunning"},"terminated":{"$ref":"#/components/schemas/V1ContainerStateTerminated"},"waiting":{"$ref":"#/components/schemas/V1ContainerStateWaiting"}}},"V1ContainerStateRunning":{"type":"object","properties":{"startedAt":{"type":"string"}}},"V1ContainerStateTerminated":{"type":"object","properties":{"containerID":{"type":"string"},"exitCode":{"type":"integer"},"finishedAt":{"type":"string"},"message":{"type":"string"},"reason":{"type":"string"},"signal":{"type":"integer"},"startedAt":{"type":"string"}}},"V1ContainerStateWaiting":{"type":"object","properties":{"message":{"type":"string"},"reason":{"type":"string"}}},"V1ContainerStatus":{"type":"object","properties":{"allocatedResources":{"$ref":"#/components/schemas/V1ResourceList"},"allocatedResourcesStatus":{"type":"array","items":{"$ref":"#/components/schemas/V1ResourceStatus"}},"containerID":{"type":"string"},"image":{"type":"string"},"imageID":{"type":"string"},"lastState":{"$ref":"#/components/schemas/V1ContainerState"},"name":{"type":"string"},"ready":{"type":"boolean"},"resources":{"$ref":"#/components/schemas/V1ResourceRequirements"},"restartCount":{"type":"integer"},"started":{"type":"boolean","nullable":true},"state":{"$ref":"#/components/schemas/V1ContainerState"},"stopSignal":{"type":"string","nullable":true},"user":{"$ref":"#/components/schemas/V1ContainerUser"},"volumeMounts":{"type":"array","items":{"$ref":"#/components/schemas/V1VolumeMountStatus"}}}},"V1ContainerUser":{"type":"object","properties":{"linux":{"$ref":"#/components/schemas/V1LinuxContainerUser"}}},"V1DownwardAPIProjection":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/V1DownwardAPIVolumeFile"}}}},"V1DownwardAPIVolumeFile":{"type":"object","properties":{"fieldRef":{"$ref":"#/components/schemas/V1ObjectFieldSelector"},"mode":{"type":"integer","nullable":true},"path":{"type":"string"},"resourceFieldRef":{"$ref":"#/components/schemas/V1ResourceFieldSelector"}}},"V1DownwardAPIVolumeSource":{"type":"object","properties":{"defaultMode":{"type":"integer","nullable":true},"items":{"type":"array","items":{"$ref":"#/components/schemas/V1DownwardAPIVolumeFile"}}}},"V1EmptyDirVolumeSource":{"type":"object","properties":{"medium":{"type":"string"},"sizeLimit":{"$ref":"#/components/schemas/ResourceQuantity"}}},"V1EnvFromSource":{"type":"object","properties":{"configMapRef":{"$ref":"#/components/schemas/V1ConfigMapEnvSource"},"prefix":{"type":"string"},"secretRef":{"$ref":"#/components/schemas/V1SecretEnvSource"}}},"V1EnvVar":{"type":"object","properties":{"name":{"type":"string"},"value":{"type":"string"},"valueFrom":{"$ref":"#/components/schemas/V1EnvVarSource"}}},"V1EnvVarSource":{"type":"object","properties":{"configMapKeyRef":{"$ref":"#/components/schemas/V1ConfigMapKeySelector"},"fieldRef":{"$ref":"#/components/schemas/V1ObjectFieldSelector"},"resourceFieldRef":{"$ref":"#/components/schemas/V1ResourceFieldSelector"},"secretKeyRef":{"$ref":"#/components/schemas/V1SecretKeySelector"}}},"V1EphemeralContainer":{"type":"object","properties":{"args":{"type":"array","items":{"type":"string"}},"command":{"type":"array","items":{"type":"string"}},"env":{"type":"array","items":{"$ref":"#/components/schemas/V1EnvVar"}},"envFrom":{"type":"array","items":{"$ref":"#/components/schemas/V1EnvFromSource"}},"image":{"type":"string"},"imagePullPolicy":{"type":"string"},"lifecycle":{"$ref":"#/components/schemas/V1Lifecycle"},"livenessProbe":{"$ref":"#/components/schemas/V1Probe"},"name":{"type":"string"},"ports":{"type":"array","items":{"$ref":"#/components/schemas/V1ContainerPort"}},"readinessProbe":{"$ref":"#/components/schemas/V1Probe"},"resizePolicy":{"type":"array","items":{"$ref":"#/components/schemas/V1ContainerResizePolicy"}},"resources":{"$ref":"#/components/schemas/V1ResourceRequirements"},"restartPolicy":{"type":"string","nullable":true},"securityContext":{"$ref":"#/components/schemas/V1SecurityContext"},"startupProbe":{"$ref":"#/components/schemas/V1Probe"},"stdin":{"type":"boolean"},"stdinOnce":{"type":"boolean"},"targetContainerName":{"type":"string"},"terminationMessagePath":{"type":"string"},"terminationMessagePolicy":{"type":"string"},"tty":{"type":"boolean"},"volumeDevices":{"type":"array","items":{"$ref":"#/components/schemas/V1VolumeDevice"}},"volumeMounts":{"type":"array","items":{"$ref":"#/components/schemas/V1VolumeMount"}},"workingDir":{"type":"string"}}},"V1EphemeralVolumeSource":{"type":"object","properties":{"volumeClaimTemplate":{"$ref":"#/components/schemas/V1PersistentVolumeClaimTemplate"}}},"V1ExecAction":{"type":"object","properties":{"command":{"type":"array","items":{"type":"string"}}}},"V1FCVolumeSource":{"type":"object","properties":{"fsType":{"type":"string"},"lun":{"type":"integer","nullable":true},"readOnly":{"type":"boolean"},"targetWWNs":{"type":"array","items":{"type":"string"}},"wwids":{"type":"array","items":{"type":"string"}}}},"V1FieldsV1":{"type":"object"},"V1FlexVolumeSource":{"type":"object","properties":{"driver":{"type":"string"},"fsType":{"type":"string"},"options":{"type":"object","additionalProperties":{"type":"string"}},"readOnly":{"type":"boolean"},"secretRef":{"$ref":"#/components/schemas/V1LocalObjectReference"}}},"V1FlockerVolumeSource":{"type":"object","properties":{"datasetName":{"type":"string"},"datasetUUID":{"type":"string"}}},"V1GCEPersistentDiskVolumeSource":{"type":"object","properties":{"fsType":{"type":"string"},"partition":{"type":"integer"},"pdName":{"type":"string"},"readOnly":{"type":"boolean"}}},"V1GRPCAction":{"type":"object","properties":{"port":{"type":"integer"},"service":{"type":"string","nullable":true}}},"V1GitRepoVolumeSource":{"type":"object","properties":{"directory":{"type":"string"},"repository":{"type":"string"},"revision":{"type":"string"}}},"V1GlusterfsVolumeSource":{"type":"object","properties":{"endpoints":{"type":"string"},"path":{"type":"string"},"readOnly":{"type":"boolean"}}},"V1HTTPGetAction":{"type":"object","properties":{"host":{"type":"string"},"httpHeaders":{"type":"array","items":{"$ref":"#/components/schemas/V1HTTPHeader"}},"path":{"type":"string"},"port":{"$ref":"#/components/schemas/IntstrIntOrString"},"scheme":{"type":"string"}}},"V1HTTPHeader":{"type":"object","properties":{"name":{"type":"string"},"value":{"type":"string"}}},"V1HostAlias":{"type":"object","properties":{"hostnames":{"type":"array","items":{"type":"string"}},"ip":{"type":"string"}}},"V1HostIP":{"type":"object","properties":{"ip":{"type":"string"}}},"V1HostPathVolumeSource":{"type":"object","properties":{"path":{"type":"string"},"type":{"type":"string","nullable":true}}},"V1ISCSIVolumeSource":{"type":"object","properties":{"chapAuthDiscovery":{"type":"boolean"},"chapAuthSession":{"type":"boolean"},"fsType":{"type":"string"},"initiatorName":{"type":"string","nullable":true},"iqn":{"type":"string"},"iscsiInterface":{"type":"string"},"lun":{"type":"integer"},"portals":{"type":"array","items":{"type":"string"}},"readOnly":{"type":"boolean"},"secretRef":{"$ref":"#/components/schemas/V1LocalObjectReference"},"targetPortal":{"type":"string"}}},"V1ImageVolumeSource":{"type":"object","properties":{"pullPolicy":{"type":"string"},"reference":{"type":"string"}}},"V1KeyToPath":{"type":"object","properties":{"key":{"type":"string"},"mode":{"type":"integer","nullable":true},"path":{"type":"string"}}},"V1LabelSelector":{"type":"object","properties":{"matchExpressions":{"type":"array","items":{"$ref":"#/components/schemas/V1LabelSelectorRequirement"}},"matchLabels":{"type":"object","additionalProperties":{"type":"string"}}}},"V1LabelSelectorRequirement":{"type":"object","properties":{"key":{"type":"string"},"operator":{"type":"string"},"values":{"type":"array","items":{"type":"string"}}}},"V1Lifecycle":{"type":"object","properties":{"postStart":{"$ref":"#/components/schemas/V1LifecycleHandler"},"preStop":{"$ref":"#/components/schemas/V1LifecycleHandler"},"stopSignal":{"type":"string","nullable":true}}},"V1LifecycleHandler":{"type":"object","properties":{"exec":{"$ref":"#/components/schemas/V1ExecAction"},"httpGet":{"$ref":"#/components/schemas/V1HTTPGetAction"},"sleep":{"$ref":"#/components/schemas/V1SleepAction"},"tcpSocket":{"$ref":"#/components/schemas/V1TCPSocketAction"}}},"V1LinuxContainerUser":{"type":"object","properties":{"gid":{"type":"integer"},"supplementalGroups":{"type":"array","items":{"type":"integer"}},"uid":{"type":"integer"}}},"V1LocalObjectReference":{"type":"object","properties":{"name":{"type":"string"}}},"V1ManagedFieldsEntry":{"type":"object","properties":{"apiVersion":{"type":"string"},"fieldsType":{"type":"string"},"fieldsV1":{"$ref":"#/components/schemas/V1FieldsV1"},"manager":{"type":"string"},"operation":{"type":"string"},"subresource":{"type":"string"},"time":{"type":"string"}}},"V1NFSVolumeSource":{"type":"object","properties":{"path":{"type":"string"},"readOnly":{"type":"boolean"},"server":{"type":"string"}}},"V1NodeAffinity":{"type":"object","properties":{"preferredDuringSchedulingIgnoredDuringExecution":{"type":"array","items":{"$ref":"#/components/schemas/V1PreferredSchedulingTerm"}},"requiredDuringSchedulingIgnoredDuringExecution":{"$ref":"#/components/schemas/V1NodeSelector"}}},"V1NodeSelector":{"type":"object","properties":{"nodeSelectorTerms":{"type":"array","items":{"$ref":"#/components/schemas/V1NodeSelectorTerm"},"nullable":true}}},"V1NodeSelectorRequirement":{"type":"object","properties":{"key":{"type":"string"},"operator":{"type":"string"},"values":{"type":"array","items":{"type":"string"}}}},"V1NodeSelectorTerm":{"type":"object","properties":{"matchExpressions":{"type":"array","items":{"$ref":"#/components/schemas/V1NodeSelectorRequirement"}},"matchFields":{"type":"array","items":{"$ref":"#/components/schemas/V1NodeSelectorRequirement"}}}},"V1ObjectFieldSelector":{"type":"object","properties":{"apiVersion":{"type":"string"},"fieldPath":{"type":"string"}}},"V1ObjectMeta":{"type":"object","properties":{"annotations":{"type":"object","additionalProperties":{"type":"string"}},"creationTimestamp":{"type":"string"},"deletionGracePeriodSeconds":{"type":"integer","nullable":true},"deletionTimestamp":{"type":"string"},"finalizers":{"type":"array","items":{"type":"string"}},"generateName":{"type":"string"},"generation":{"type":"integer"},"labels":{"type":"object","additionalProperties":{"type":"string"}},"managedFields":{"type":"array","items":{"$ref":"#/components/schemas/V1ManagedFieldsEntry"}},"name":{"type":"string"},"namespace":{"type":"string"},"ownerReferences":{"type":"array","items":{"$ref":"#/components/schemas/V1OwnerReference"}},"resourceVersion":{"type":"string"},"selfLink":{"type":"string"},"uid":{"type":"string"}}},"V1OwnerReference":{"type":"object","properties":{"apiVersion":{"type":"string"},"blockOwnerDeletion":{"type":"boolean","nullable":true},"controller":{"type":"boolean","nullable":true},"kind":{"type":"string"},"name":{"type":"string"},"uid":{"type":"string"}}},"V1PersistentVolumeClaimSpec":{"type":"object","properties":{"accessModes":{"type":"array","items":{"type":"string"}},"dataSource":{"$ref":"#/components/schemas/V1TypedLocalObjectReference"},"dataSourceRef":{"$ref":"#/components/schemas/V1TypedObjectReference"},"resources":{"$ref":"#/components/schemas/V1VolumeResourceRequirements"},"selector":{"$ref":"#/components/schemas/V1LabelSelector"},"storageClassName":{"type":"string","nullable":true},"volumeAttributesClassName":{"type":"string","nullable":true},"volumeMode":{"type":"string","nullable":true},"volumeName":{"type":"string"}}},"V1PersistentVolumeClaimTemplate":{"type":"object","properties":{"metadata":{"$ref":"#/components/schemas/V1ObjectMeta"},"spec":{"$ref":"#/components/schemas/V1PersistentVolumeClaimSpec"}}},"V1PersistentVolumeClaimVolumeSource":{"type":"object","properties":{"claimName":{"type":"string"},"readOnly":{"type":"boolean"}}},"V1PhotonPersistentDiskVolumeSource":{"type":"object","properties":{"fsType":{"type":"string"},"pdID":{"type":"string"}}},"V1Pod":{"type":"object","properties":{"apiVersion":{"type":"string"},"kind":{"type":"string"},"metadata":{"$ref":"#/components/schemas/V1ObjectMeta"},"spec":{"$ref":"#/components/schemas/V1PodSpec"},"status":{"$ref":"#/components/schemas/V1PodStatus"}}},"V1PodAffinity":{"type":"object","properties":{"preferredDuringSchedulingIgnoredDuringExecution":{"type":"array","items":{"$ref":"#/components/schemas/V1WeightedPodAffinityTerm"}},"requiredDuringSchedulingIgnoredDuringExecution":{"type":"array","items":{"$ref":"#/components/schemas/V1PodAffinityTerm"}}}},"V1PodAffinityTerm":{"type":"object","properties":{"labelSelector":{"$ref":"#/components/schemas/V1LabelSelector"},"matchLabelKeys":{"type":"array","items":{"type":"string"}},"mismatchLabelKeys":{"type":"array","items":{"type":"string"}},"namespaceSelector":{"$ref":"#/components/schemas/V1LabelSelector"},"namespaces":{"type":"array","items":{"type":"string"}},"topologyKey":{"type":"string"}}},"V1PodAntiAffinity":{"type":"object","properties":{"preferredDuringSchedulingIgnoredDuringExecution":{"type":"array","items":{"$ref":"#/components/schemas/V1WeightedPodAffinityTerm"}},"requiredDuringSchedulingIgnoredDuringExecution":{"type":"array","items":{"$ref":"#/components/schemas/V1PodAffinityTerm"}}}},"V1PodCondition":{"type":"object","properties":{"lastProbeTime":{"type":"string"},"lastTransitionTime":{"type":"string"},"message":{"type":"string"},"observedGeneration":{"type":"integer"},"reason":{"type":"string"},"status":{"type":"string"},"type":{"type":"string"}}},"V1PodDNSConfig":{"type":"object","properties":{"nameservers":{"type":"array","items":{"type":"string"}},"options":{"type":"array","items":{"$ref":"#/components/schemas/V1PodDNSConfigOption"}},"searches":{"type":"array","items":{"type":"string"}}}},"V1PodDNSConfigOption":{"type":"object","properties":{"name":{"type":"string"},"value":{"type":"string","nullable":true}}},"V1PodIP":{"type":"object","properties":{"ip":{"type":"string"}}},"V1PodOS":{"type":"object","properties":{"name":{"type":"string"}}},"V1PodReadinessGate":{"type":"object","properties":{"conditionType":{"type":"string"}}},"V1PodResourceClaim":{"type":"object","properties":{"name":{"type":"string"},"resourceClaimName":{"type":"string","nullable":true},"resourceClaimTemplateName":{"type":"string","nullable":true}}},"V1PodResourceClaimStatus":{"type":"object","properties":{"name":{"type":"string"},"resourceClaimName":{"type":"string","nullable":true}}},"V1PodSchedulingGate":{"type":"object","properties":{"name":{"type":"string"}}},"V1PodSecurityContext":{"type":"object","properties":{"appArmorProfile":{"$ref":"#/components/schemas/V1AppArmorProfile"},"fsGroup":{"type":"integer","nullable":true},"fsGroupChangePolicy":{"type":"string","nullable":true},"runAsGroup":{"type":"integer","nullable":true},"runAsNonRoot":{"type":"boolean","nullable":true},"runAsUser":{"type":"integer","nullable":true},"seLinuxChangePolicy":{"type":"string","nullable":true},"seLinuxOptions":{"$ref":"#/components/schemas/V1SELinuxOptions"},"seccompProfile":{"$ref":"#/components/schemas/V1SeccompProfile"},"supplementalGroups":{"type":"array","items":{"type":"integer"}},"supplementalGroupsPolicy":{"type":"string","nullable":true},"sysctls":{"type":"array","items":{"$ref":"#/components/schemas/V1Sysctl"}},"windowsOptions":{"$ref":"#/components/schemas/V1WindowsSecurityContextOptions"}}},"V1PodSpec":{"type":"object","properties":{"activeDeadlineSeconds":{"type":"integer","nullable":true},"affinity":{"$ref":"#/components/schemas/V1Affinity"},"automountServiceAccountToken":{"type":"boolean","nullable":true},"containers":{"type":"array","items":{"$ref":"#/components/schemas/V1Container"},"nullable":true},"dnsConfig":{"$ref":"#/components/schemas/V1PodDNSConfig"},"dnsPolicy":{"type":"string"},"enableServiceLinks":{"type":"boolean","nullable":true},"ephemeralContainers":{"type":"array","items":{"$ref":"#/components/schemas/V1EphemeralContainer"}},"hostAliases":{"type":"array","items":{"$ref":"#/components/schemas/V1HostAlias"}},"hostIPC":{"type":"boolean"},"hostNetwork":{"type":"boolean"},"hostPID":{"type":"boolean"},"hostUsers":{"type":"boolean","nullable":true},"hostname":{"type":"string"},"imagePullSecrets":{"type":"array","items":{"$ref":"#/components/schemas/V1LocalObjectReference"}},"initContainers":{"type":"array","items":{"$ref":"#/components/schemas/V1Container"}},"nodeName":{"type":"string"},"nodeSelector":{"type":"object","additionalProperties":{"type":"string"}},"os":{"$ref":"#/components/schemas/V1PodOS"},"overhead":{"$ref":"#/components/schemas/V1ResourceList"},"preemptionPolicy":{"type":"string","nullable":true},"priority":{"type":"integer","nullable":true},"priorityClassName":{"type":"string"},"readinessGates":{"type":"array","items":{"$ref":"#/components/schemas/V1PodReadinessGate"}},"resourceClaims":{"type":"array","items":{"$ref":"#/components/schemas/V1PodResourceClaim"}},"resources":{"$ref":"#/components/schemas/V1ResourceRequirements"},"restartPolicy":{"type":"string"},"runtimeClassName":{"type":"string","nullable":true},"schedulerName":{"type":"string"},"schedulingGates":{"type":"array","items":{"$ref":"#/components/schemas/V1PodSchedulingGate"}},"securityContext":{"$ref":"#/components/schemas/V1PodSecurityContext"},"serviceAccount":{"type":"string"},"serviceAccountName":{"type":"string"},"setHostnameAsFQDN":{"type":"boolean","nullable":true},"shareProcessNamespace":{"type":"boolean","nullable":true},"subdomain":{"type":"string"},"terminationGracePeriodSeconds":{"type":"integer","nullable":true},"tolerations":{"type":"array","items":{"$ref":"#/components/schemas/V1Toleration"}},"topologySpreadConstraints":{"type":"array","items":{"$ref":"#/components/schemas/V1TopologySpreadConstraint"}},"volumes":{"type":"array","items":{"$ref":"#/components/schemas/V1Volume"}}}},"V1PodStatus":{"type":"object","properties":{"conditions":{"type":"array","items":{"$ref":"#/components/schemas/V1PodCondition"}},"containerStatuses":{"type":"array","items":{"$ref":"#/components/schemas/V1ContainerStatus"}},"ephemeralContainerStatuses":{"type":"array","items":{"$ref":"#/components/schemas/V1ContainerStatus"}},"hostIP":{"type":"string"},"hostIPs":{"type":"array","items":{"$ref":"#/components/schemas/V1HostIP"}},"initContainerStatuses":{"type":"array","items":{"$ref":"#/components/schemas/V1ContainerStatus"}},"message":{"type":"string"},"nominatedNodeName":{"type":"string"},"observedGeneration":{"type":"integer"},"phase":{"type":"string"},"podIP":{"type":"string"},"podIPs":{"type":"array","items":{"$ref":"#/components/schemas/V1PodIP"}},"qosClass":{"type":"string"},"reason":{"type":"string"},"resize":{"type":"string"},"resourceClaimStatuses":{"type":"array","items":{"$ref":"#/components/schemas/V1PodResourceClaimStatus"}},"startTime":{"type":"string"}}},"V1PortworxVolumeSource":{"type":"object","properties":{"fsType":{"type":"string"},"readOnly":{"type":"boolean"},"volumeID":{"type":"string"}}},"V1PreferredSchedulingTerm":{"type":"object","properties":{"preference":{"$ref":"#/components/schemas/V1NodeSelectorTerm"},"weight":{"type":"integer"}}},"V1Probe":{"type":"object","properties":{"exec":{"$ref":"#/components/schemas/V1ExecAction"},"failureThreshold":{"type":"integer"},"grpc":{"$ref":"#/components/schemas/V1GRPCAction"},"httpGet":{"$ref":"#/components/schemas/V1HTTPGetAction"},"initialDelaySeconds":{"type":"integer"},"periodSeconds":{"type":"integer"},"successThreshold":{"type":"integer"},"tcpSocket":{"$ref":"#/components/schemas/V1TCPSocketAction"},"terminationGracePeriodSeconds":{"type":"integer","nullable":true},"timeoutSeconds":{"type":"integer"}}},"V1ProjectedVolumeSource":{"type":"object","properties":{"defaultMode":{"type":"integer","nullable":true},"sources":{"type":"array","items":{"$ref":"#/components/schemas/V1VolumeProjection"},"nullable":true}}},"V1QuobyteVolumeSource":{"type":"object","properties":{"group":{"type":"string"},"readOnly":{"type":"boolean"},"registry":{"type":"string"},"tenant":{"type":"string"},"user":{"type":"string"},"volume":{"type":"string"}}},"V1RBDVolumeSource":{"type":"object","properties":{"fsType":{"type":"string"},"image":{"type":"string"},"keyring":{"type":"string"},"monitors":{"type":"array","items":{"type":"string"},"nullable":true},"pool":{"type":"string"},"readOnly":{"type":"boolean"},"secretRef":{"$ref":"#/components/schemas/V1LocalObjectReference"},"user":{"type":"string"}}},"V1ResourceClaim":{"type":"object","properties":{"name":{"type":"string"},"request":{"type":"string"}}},"V1ResourceFieldSelector":{"type":"object","properties":{"containerName":{"type":"string"},"divisor":{"$ref":"#/components/schemas/ResourceQuantity"},"resource":{"type":"string"}}},"V1ResourceHealth":{"type":"object","properties":{"health":{"type":"string"},"resourceID":{"type":"string"}}},"V1ResourceList":{"type":"object","additionalProperties":{"$ref":"#/components/schemas/ResourceQuantity"}},"V1ResourceRequirements":{"type":"object","properties":{"claims":{"type":"array","items":{"$ref":"#/components/schemas/V1ResourceClaim"}},"limits":{"$ref":"#/components/schemas/V1ResourceList"},"requests":{"$ref":"#/components/schemas/V1ResourceList"}}},"V1ResourceStatus":{"type":"object","properties":{"name":{"type":"string"},"resources":{"type":"array","items":{"$ref":"#/components/schemas/V1ResourceHealth"}}}},"V1SELinuxOptions":{"type":"object","properties":{"level":{"type":"string"},"role":{"type":"string"},"type":{"type":"string"},"user":{"type":"string"}}},"V1ScaleIOVolumeSource":{"type":"object","properties":{"fsType":{"type":"string"},"gateway":{"type":"string"},"protectionDomain":{"type":"string"},"readOnly":{"type":"boolean"},"secretRef":{"$ref":"#/components/schemas/V1LocalObjectReference"},"sslEnabled":{"type":"boolean"},"storageMode":{"type":"string"},"storagePool":{"type":"string"},"system":{"type":"string"},"volumeName":{"type":"string"}}},"V1SeccompProfile":{"type":"object","properties":{"localhostProfile":{"type":"string","nullable":true},"type":{"type":"string"}}},"V1Secret":{"type":"object","properties":{"apiVersion":{"type":"string"},"data":{"type":"object","additionalProperties":{"type":"string","format":"base64"}},"immutable":{"type":"boolean","nullable":true},"kind":{"type":"string"},"metadata":{"$ref":"#/components/schemas/V1ObjectMeta"},"stringData":{"type":"object","additionalProperties":{"type":"string"}},"type":{"type":"string"}}},"V1SecretEnvSource":{"type":"object","properties":{"name":{"type":"string"},"optional":{"type":"boolean","nullable":true}}},"V1SecretKeySelector":{"type":"object","properties":{"key":{"type":"string"},"name":{"type":"string"},"optional":{"type":"boolean","nullable":true}}},"V1SecretProjection":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/V1KeyToPath"}},"name":{"type":"string"},"optional":{"type":"boolean","nullable":true}}},"V1SecretVolumeSource":{"type":"object","properties":{"defaultMode":{"type":"integer","nullable":true},"items":{"type":"array","items":{"$ref":"#/components/schemas/V1KeyToPath"}},"optional":{"type":"boolean","nullable":true},"secretName":{"type":"string"}}},"V1SecurityContext":{"type":"object","properties":{"allowPrivilegeEscalation":{"type":"boolean","nullable":true},"appArmorProfile":{"$ref":"#/components/schemas/V1AppArmorProfile"},"capabilities":{"$ref":"#/components/schemas/V1Capabilities"},"privileged":{"type":"boolean","nullable":true},"procMount":{"type":"string","nullable":true},"readOnlyRootFilesystem":{"type":"boolean","nullable":true},"runAsGroup":{"type":"integer","nullable":true},"runAsNonRoot":{"type":"boolean","nullable":true},"runAsUser":{"type":"integer","nullable":true},"seLinuxOptions":{"$ref":"#/components/schemas/V1SELinuxOptions"},"seccompProfile":{"$ref":"#/components/schemas/V1SeccompProfile"},"windowsOptions":{"$ref":"#/components/schemas/V1WindowsSecurityContextOptions"}}},"V1ServiceAccountTokenProjection":{"type":"object","properties":{"audience":{"type":"string"},"expirationSeconds":{"type":"integer","nullable":true},"path":{"type":"string"}}},"V1SleepAction":{"type":"object","properties":{"seconds":{"type":"integer"}}},"V1StorageOSVolumeSource":{"type":"object","properties":{"fsType":{"type":"string"},"readOnly":{"type":"boolean"},"secretRef":{"$ref":"#/components/schemas/V1LocalObjectReference"},"volumeName":{"type":"string"},"volumeNamespace":{"type":"string"}}},"V1Sysctl":{"type":"object","properties":{"name":{"type":"string"},"value":{"type":"string"}}},"V1TCPSocketAction":{"type":"object","properties":{"host":{"type":"string"},"port":{"$ref":"#/components/schemas/IntstrIntOrString"}}},"V1Toleration":{"type":"object","properties":{"effect":{"type":"string"},"key":{"type":"string"},"operator":{"type":"string"},"tolerationSeconds":{"type":"integer","nullable":true},"value":{"type":"string"}}},"V1TopologySpreadConstraint":{"type":"object","properties":{"labelSelector":{"$ref":"#/components/schemas/V1LabelSelector"},"matchLabelKeys":{"type":"array","items":{"type":"string"}},"maxSkew":{"type":"integer"},"minDomains":{"type":"integer","nullable":true},"nodeAffinityPolicy":{"type":"string","nullable":true},"nodeTaintsPolicy":{"type":"string","nullable":true},"topologyKey":{"type":"string"},"whenUnsatisfiable":{"type":"string"}}},"V1TypedLocalObjectReference":{"type":"object","properties":{"apiGroup":{"type":"string","nullable":true},"kind":{"type":"string"},"name":{"type":"string"}}},"V1TypedObjectReference":{"type":"object","properties":{"apiGroup":{"type":"string","nullable":true},"kind":{"type":"string"},"name":{"type":"string"},"namespace":{"type":"string","nullable":true}}},"V1Volume":{"type":"object","properties":{"awsElasticBlockStore":{"$ref":"#/components/schemas/V1AWSElasticBlockStoreVolumeSource"},"azureDisk":{"$ref":"#/components/schemas/V1AzureDiskVolumeSource"},"azureFile":{"$ref":"#/components/schemas/V1AzureFileVolumeSource"},"cephfs":{"$ref":"#/components/schemas/V1CephFSVolumeSource"},"cinder":{"$ref":"#/components/schemas/V1CinderVolumeSource"},"configMap":{"$ref":"#/components/schemas/V1ConfigMapVolumeSource"},"csi":{"$ref":"#/components/schemas/V1CSIVolumeSource"},"downwardAPI":{"$ref":"#/components/schemas/V1DownwardAPIVolumeSource"},"emptyDir":{"$ref":"#/components/schemas/V1EmptyDirVolumeSource"},"ephemeral":{"$ref":"#/components/schemas/V1EphemeralVolumeSource"},"fc":{"$ref":"#/components/schemas/V1FCVolumeSource"},"flexVolume":{"$ref":"#/components/schemas/V1FlexVolumeSource"},"flocker":{"$ref":"#/components/schemas/V1FlockerVolumeSource"},"gcePersistentDisk":{"$ref":"#/components/schemas/V1GCEPersistentDiskVolumeSource"},"gitRepo":{"$ref":"#/components/schemas/V1GitRepoVolumeSource"},"glusterfs":{"$ref":"#/components/schemas/V1GlusterfsVolumeSource"},"hostPath":{"$ref":"#/components/schemas/V1HostPathVolumeSource"},"image":{"$ref":"#/components/schemas/V1ImageVolumeSource"},"iscsi":{"$ref":"#/components/schemas/V1ISCSIVolumeSource"},"name":{"type":"string"},"nfs":{"$ref":"#/components/schemas/V1NFSVolumeSource"},"persistentVolumeClaim":{"$ref":"#/components/schemas/V1PersistentVolumeClaimVolumeSource"},"photonPersistentDisk":{"$ref":"#/components/schemas/V1PhotonPersistentDiskVolumeSource"},"portworxVolume":{"$ref":"#/components/schemas/V1PortworxVolumeSource"},"projected":{"$ref":"#/components/schemas/V1ProjectedVolumeSource"},"quobyte":{"$ref":"#/components/schemas/V1QuobyteVolumeSource"},"rbd":{"$ref":"#/components/schemas/V1RBDVolumeSource"},"scaleIO":{"$ref":"#/components/schemas/V1ScaleIOVolumeSource"},"secret":{"$ref":"#/components/schemas/V1SecretVolumeSource"},"storageos":{"$ref":"#/components/schemas/V1StorageOSVolumeSource"},"vsphereVolume":{"$ref":"#/components/schemas/V1VsphereVirtualDiskVolumeSource"}}},"V1VolumeDevice":{"type":"object","properties":{"devicePath":{"type":"string"},"name":{"type":"string"}}},"V1VolumeMount":{"type":"object","properties":{"mountPath":{"type":"string"},"mountPropagation":{"type":"string","nullable":true},"name":{"type":"string"},"readOnly":{"type":"boolean"},"recursiveReadOnly":{"type":"string","nullable":true},"subPath":{"type":"string"},"subPathExpr":{"type":"string"}}},"V1VolumeMountStatus":{"type":"object","properties":{"mountPath":{"type":"string"},"name":{"type":"string"},"readOnly":{"type":"boolean"},"recursiveReadOnly":{"type":"string","nullable":true}}},"V1VolumeProjection":{"type":"object","properties":{"clusterTrustBundle":{"$ref":"#/components/schemas/V1ClusterTrustBundleProjection"},"configMap":{"$ref":"#/components/schemas/V1ConfigMapProjection"},"downwardAPI":{"$ref":"#/components/schemas/V1DownwardAPIProjection"},"secret":{"$ref":"#/components/schemas/V1SecretProjection"},"serviceAccountToken":{"$ref":"#/components/schemas/V1ServiceAccountTokenProjection"}}},"V1VolumeResourceRequirements":{"type":"object","properties":{"limits":{"$ref":"#/components/schemas/V1ResourceList"},"requests":{"$ref":"#/components/schemas/V1ResourceList"}}},"V1VsphereVirtualDiskVolumeSource":{"type":"object","properties":{"fsType":{"type":"string"},"storagePolicyID":{"type":"string"},"storagePolicyName":{"type":"string"},"volumePath":{"type":"string"}}},"V1WeightedPodAffinityTerm":{"type":"object","properties":{"podAffinityTerm":{"$ref":"#/components/schemas/V1PodAffinityTerm"},"weight":{"type":"integer"}}},"V1WindowsSecurityContextOptions":{"type":"object","properties":{"gmsaCredentialSpec":{"type":"string","nullable":true},"gmsaCredentialSpecName":{"type":"string","nullable":true},"hostProcess":{"type":"boolean","nullable":true},"runAsUserName":{"type":"string","nullable":true}}}}}} \ No newline at end of file diff --git a/docs/openapi/plugin-openapi.json b/docs/openapi/plugin-openapi.json index 825ec318..6adc9c9e 100644 --- a/docs/openapi/plugin-openapi.json +++ b/docs/openapi/plugin-openapi.json @@ -44,6 +44,48 @@ } } }, + "/createPodSets": { + "post": { + "summary": "Create Pod Sets", + "operationId": "create_pod_sets_createPodSets_post", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RetrievedPodSetsData" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "items": { + "$ref": "#/components/schemas/CreateStruct" + }, + "type": "array", + "title": "Response Create Pod Sets Createpodsets Post" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, "/delete": { "post": { "summary": "Delete Pod", @@ -984,6 +1026,48 @@ ], "title": "PodVolume" }, + "RetrievedPodSetData": { + "properties": { + "name": { + "type": "string", + "title": "Name" + }, + "pods": { + "items": { + "$ref": "#/components/schemas/Pod" + }, + "type": "array", + "title": "Pods" + } + }, + "type": "object", + "required": [ + "name", + "pods" + ], + "title": "RetrievedPodSetData" + }, + "RetrievedPodSetsData": { + "properties": { + "groupUID": { + "type": "string", + "title": "Group Uid" + }, + "podSets": { + "items": { + "$ref": "#/components/schemas/RetrievedPodSetData" + }, + "type": "array", + "title": "Pod Sets" + } + }, + "type": "object", + "required": [ + "groupUID", + "podSets" + ], + "title": "RetrievedPodSetsData" + }, "Secret": { "properties": { "metadata": { diff --git a/pkg/interlink/api/create.go b/pkg/interlink/api/create.go index e5d38a7e..6800a86d 100644 --- a/pkg/interlink/api/create.go +++ b/pkg/interlink/api/create.go @@ -148,18 +148,7 @@ func (h *InterLinkHandler) CreateHandler(w http.ResponseWriter, r *http.Request) log.G(h.Ctx).Infof("Updated JobScript successfully (len=%d)", len(data.JobScript)) case h.Config.JobScriptTemplate != "": - - tmp, err := template.ParseFiles(h.Config.JobScriptTemplate) - if err != nil { - statusCode = http.StatusInternalServerError - log.G(h.Ctx).Error(err) - w.WriteHeader(statusCode) - types.SetSpanError(span, statusCode, err) - return - } - - var tpl bytes.Buffer - err = tmp.Execute(&tpl, data) + data.JobScript, err = renderJobScript(h.Config.JobScriptTemplate, data) if err != nil { statusCode = http.StatusInternalServerError log.G(h.Ctx).Error(err) @@ -167,8 +156,6 @@ func (h *InterLinkHandler) CreateHandler(w http.ResponseWriter, r *http.Request) types.SetSpanError(span, statusCode, err) return } - - data.JobScript = tpl.String() } // updated to handle single data @@ -213,3 +200,17 @@ func (h *InterLinkHandler) CreateHandler(w http.ResponseWriter, r *http.Request) // is set here. types.SetSpanOK(span, 0) } + +// renderJobScript fills the job script template at path with the data of one pod. +func renderJobScript(path string, data types.RetrievedPodData) (string, error) { + tmp, err := template.ParseFiles(path) + if err != nil { + return "", err + } + + var tpl bytes.Buffer + if err := tmp.Execute(&tpl, data); err != nil { + return "", err + } + return tpl.String(), nil +} diff --git a/pkg/interlink/api/createpodsets.go b/pkg/interlink/api/createpodsets.go new file mode 100644 index 00000000..b1bff892 --- /dev/null +++ b/pkg/interlink/api/createpodsets.go @@ -0,0 +1,232 @@ +package api + +import ( + "bytes" + "context" + "encoding/json" + "errors" + "fmt" + "html" + "io" + "net/http" + "time" + + "github.com/containerd/containerd/log" + + types "github.com/interlink-hq/interlink/pkg/interlink" + + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/attribute" + trace "go.opentelemetry.io/otel/trace" +) + +// CreatePodSetsHandler handles HTTP POST requests to create the pods of one group in a +// single call, so that the sidecar plugin can run the whole group as one job. +// Every pod is prepared as CreateHandler prepares it (ConfigMaps, Secrets and projected +// volumes split by container, optional job script), and the group is forwarded to the +// plugin's /createPodSets. It is meant for plugins that list "podset-create" in their +// ping answer; status, logs and delete stay per pod. +// +// Request body: JSON-encoded PodSetsCreateRequests +// Response: JSON-encoded CreateStruct array, one entry per pod in the order of the +// request, all with the job ID of the group +// +// HTTP Status Codes: +// - 200: Group creation request processed successfully +// - 400: Bad request (malformed body, no pods, or a permanent refusal of the plugin) +// - 503: The plugin cannot take the group now +// - 500: Internal server error (job script or sidecar communication failures) +func (h *InterLinkHandler) CreatePodSetsHandler(w http.ResponseWriter, r *http.Request) { + start := time.Now().UnixMicro() + tracer := otel.Tracer("interlink-API") + _, span := tracer.Start(h.Ctx, "CreatePodSetsAPI", trace.WithAttributes( + attribute.Int64("start.timestamp", start), + )) + defer span.End() + defer types.SetDurationSpan(start, span) + defer types.SetInfoFromHeaders(span, &r.Header) + + log.G(h.Ctx).Info("InterLink: received CreatePodSets call") + + bodyBytes, err := io.ReadAll(r.Body) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + log.G(h.Ctx).Error(err) + return + } + + var request types.PodSetsCreateRequests + if err := json.Unmarshal(bodyBytes, &request); err != nil { + writePodSetsError(h.Ctx, w, http.StatusBadRequest, fmt.Errorf("invalid request body: %w", err)) + return + } + + pods := 0 + for _, podSet := range request.PodSets { + pods += len(podSet.Pods) + } + if pods == 0 { + writePodSetsError(h.Ctx, w, http.StatusBadRequest, fmt.Errorf("group %s has no pods", request.GroupUID)) + return + } + + span.SetAttributes( + attribute.String("group.uid", request.GroupUID), + attribute.Int("group.podsets", len(request.PodSets)), + attribute.Int("group.pods", pods), + ) + + // One session for the whole group: GetSessionContext makes up a new one on every + // call when the request has none. + sessionContext := GetSessionContext(r) + + retrievedData := types.RetrievedPodSetsData{ + GroupUID: request.GroupUID, + PodSets: make([]types.RetrievedPodSetData, 0, len(request.PodSets)), + } + for _, podSet := range request.PodSets { + retrievedPodSet := types.RetrievedPodSetData{ + Name: podSet.Name, + Pods: make([]types.RetrievedPodData, 0, len(podSet.Pods)), + } + for _, pod := range podSet.Pods { + log.G(h.Ctx).Infof("InterLink: preparing pod %s/%s of PodSet %s", pod.Pod.Namespace, pod.Pod.Name, podSet.Name) + + data, err := getData(h.Ctx, h.Config, pod, span) + if err != nil { + writePodSetsError(h.Ctx, w, http.StatusInternalServerError, fmt.Errorf("pod %s/%s: %w", pod.Pod.Namespace, pod.Pod.Name, err)) + return + } + + statusCode, err := h.podSetJobScript(pod, &data, start, span, sessionContext) + if err != nil { + writePodSetsError(h.Ctx, w, statusCode, fmt.Errorf("pod %s/%s: job script: %w", pod.Pod.Namespace, pod.Pod.Name, err)) + return + } + + retrievedPodSet.Pods = append(retrievedPodSet.Pods, data) + } + retrievedData.PodSets = append(retrievedData.PodSets, retrievedPodSet) + } + + bodyBytes, err = json.Marshal(retrievedData) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + log.G(h.Ctx).Error(err) + return + } + log.G(h.Ctx).Debug(string(bodyBytes)) + + req, err := http.NewRequest(http.MethodPost, h.SidecarEndpoint+"/createPodSets", bytes.NewReader(bodyBytes)) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + log.G(h.Ctx).Error(err) + return + } + + log.G(h.Ctx).Infof("InterLink: forwarding CreatePodSets call to sidecar (group %s, %d pods)", request.GroupUID, pods) + + ForwardAccessToken(r, req) + _, err = ReqWithError(h.Ctx, req, w, start, span, true, false, sessionContext, h.ClientHTTP) + if err != nil { + log.L.Error(err) + return + } +} + +// podSetJobScript fills the job script of one pod of a group as CreateHandler does, from +// the JobScriptBuilderURL of the pod or from the configured JobScriptTemplate. Unlike +// CreateHandler it does not answer the caller: the group gets one answer, so it returns +// the status code to answer with when it fails. +func (h *InterLinkHandler) podSetJobScript( + pod types.PodCreateRequests, + data *types.RetrievedPodData, + start int64, + span trace.Span, + sessionContext string, +) (int, error) { + switch { + case pod.JobScriptBuilderURL != "": + log.G(h.Ctx).Info("JobScriptBuilderURL: ", pod.JobScriptBuilderURL) + if h.Config.JobScriptBuildConfig == nil { + return http.StatusInternalServerError, errors.New("JobScript URL requested, but interlink does not have any Script build config set") + } + + data.JobScriptBuild = *h.Config.JobScriptBuildConfig + + bodyBytes, err := json.Marshal(data) + if err != nil { + return http.StatusInternalServerError, err + } + + req, err := http.NewRequest(http.MethodPost, pod.JobScriptBuilderURL, bytes.NewReader(bodyBytes)) + if err != nil { + return http.StatusInternalServerError, err + } + req.Header.Set("Content-Type", "application/json") + + // No ForwardAccessToken here, as in CreateHandler: JobScriptBuilderURL comes from + // the pod spec and can point outside the deployment. + // ReqWithError writes the builder's status code to its ResponseWriter, which must + // not be the caller's: the caller is answered once for the whole group. + var builderStatus statusRecorder + script, err := ReqWithError(h.Ctx, req, &builderStatus, start, span, false, true, sessionContext, http.DefaultClient) + if err != nil { + statusCode := builderStatus.code + if statusCode == 0 || statusCode == http.StatusOK { + statusCode = http.StatusInternalServerError + } + return statusCode, err + } + + data.JobScript = string(script) + log.G(h.Ctx).Infof("Updated JobScript of pod %s successfully (len=%d)", data.Pod.Name, len(data.JobScript)) + + case h.Config.JobScriptTemplate != "": + script, err := renderJobScript(h.Config.JobScriptTemplate, *data) + if err != nil { + return http.StatusInternalServerError, err + } + data.JobScript = script + } + return http.StatusOK, nil +} + +// writePodSetsError answers the caller with statusCode and the error text, escaped as +// ReqWithError escapes the errors of the sidecar. +func writePodSetsError(ctx context.Context, w http.ResponseWriter, statusCode int, err error) { + log.G(ctx).Error(err) + w.WriteHeader(statusCode) + if _, werr := w.Write([]byte(html.EscapeString(err.Error()))); werr != nil { + log.G(ctx).Error(werr) + } +} + +// statusRecorder is a ResponseWriter that keeps only the status code written to it, for +// calls whose answer must not reach the caller directly. +type statusRecorder struct { + header http.Header + code int +} + +func (s *statusRecorder) Header() http.Header { + if s.header == nil { + s.header = http.Header{} + } + return s.header +} + +// WriteHeader keeps the first status code only, as net/http does. +func (s *statusRecorder) WriteHeader(statusCode int) { + if s.code == 0 { + s.code = statusCode + } +} + +func (s *statusRecorder) Write(p []byte) (int, error) { + s.WriteHeader(http.StatusOK) + return len(p), nil +} + +// Flush makes statusRecorder an http.Flusher, which ReqWithError otherwise warns about. +func (s *statusRecorder) Flush() {} diff --git a/pkg/interlink/api/createpodsets_test.go b/pkg/interlink/api/createpodsets_test.go new file mode 100644 index 00000000..d20a4488 --- /dev/null +++ b/pkg/interlink/api/createpodsets_test.go @@ -0,0 +1,364 @@ +package api + +import ( + "bytes" + "context" + "encoding/json" + "io" + "net/http" + "net/http/httptest" + "os" + "path/filepath" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + v1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + k8stypes "k8s.io/apimachinery/pkg/types" + + types "github.com/interlink-hq/interlink/pkg/interlink" +) + +const ( + testGroupUID = "0b7c6f6e-5c1d-4d6a-9f3e-2a1b0c9d8e7f" + testPodSetsSession = "Request-podsets-123" + testPodSetLauncher = "launcher" + testPodSetWorker = "worker" + testMainContainer = "main" + testSidecarContainer = "sidecar" + testConfigMapName = "settings" + testSecretName = "creds" + testProjectedVolume = "token" + testCreatePodSetsPath = "/createPodSets" +) + +// sidecarCall is what the fake sidecar received in one call. +type sidecarCall struct { + path string + session string + token string + body types.RetrievedPodSetsData +} + +// newPodSetsSidecar starts a fake sidecar that records every call and answers each one +// with statusCode and answer, and returns an InterLinkHandler pointed at it. +func newPodSetsSidecar(t *testing.T, statusCode int, answer string) (*InterLinkHandler, chan sidecarCall) { + t.Helper() + calls := make(chan sidecarCall, 4) + sidecar := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + call := sidecarCall{path: r.URL.Path, session: r.Header.Get("InterLink-Http-Session"), token: r.Header.Get(accessTokenHeader)} + assert.NoError(t, json.NewDecoder(r.Body).Decode(&call.body)) + calls <- call + + w.WriteHeader(statusCode) + if _, err := io.WriteString(w, answer); err != nil { + panic(err) + } + })) + t.Cleanup(sidecar.Close) + + handler := &InterLinkHandler{ + Ctx: context.Background(), + SidecarEndpoint: sidecar.URL, + ClientHTTP: sidecar.Client(), + } + return handler, calls +} + +// podSetPod returns a pod of the test group with two containers: the main one mounts +// the ConfigMap and the projected volume, the sidecar one mounts the Secret only. The +// projected volume holds the pod name, as downward API files do. +func podSetPod(name string) types.PodCreateRequests { + return types.PodCreateRequests{ + Pod: v1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: testDefaultNamespace, + UID: k8stypes.UID("uid-" + name), + }, + Spec: v1.PodSpec{ + Containers: []v1.Container{ + {Name: testMainContainer, VolumeMounts: []v1.VolumeMount{{Name: "config"}, {Name: testProjectedVolume}}}, + {Name: testSidecarContainer, VolumeMounts: []v1.VolumeMount{{Name: "secret"}}}, + }, + Volumes: []v1.Volume{ + {Name: "config", VolumeSource: v1.VolumeSource{ConfigMap: &v1.ConfigMapVolumeSource{ + LocalObjectReference: v1.LocalObjectReference{Name: testConfigMapName}, + }}}, + {Name: "secret", VolumeSource: v1.VolumeSource{Secret: &v1.SecretVolumeSource{SecretName: testSecretName}}}, + {Name: testProjectedVolume, VolumeSource: v1.VolumeSource{Projected: &v1.ProjectedVolumeSource{}}}, + }, + }, + }, + ConfigMaps: []v1.ConfigMap{{ObjectMeta: metav1.ObjectMeta{Name: testConfigMapName}}}, + Secrets: []v1.Secret{{ObjectMeta: metav1.ObjectMeta{Name: testSecretName}}}, + ProjectedVolumeMaps: []v1.ConfigMap{{ + ObjectMeta: metav1.ObjectMeta{Name: testProjectedVolume}, + Data: map[string]string{"pod-name": name}, + }}, + } +} + +// podSetsRequest returns a POST /createPodSets of the test group: a launcher PodSet with +// one pod and a worker PodSet with two. +func podSetsRequest(t *testing.T, mutate func(*types.PodCreateRequests)) *http.Request { + t.Helper() + request := types.PodSetsCreateRequests{ + GroupUID: testGroupUID, + PodSets: []types.PodSetCreateRequests{ + {Name: testPodSetLauncher, Pods: []types.PodCreateRequests{podSetPod("launcher-0")}}, + {Name: testPodSetWorker, Pods: []types.PodCreateRequests{podSetPod("worker-0"), podSetPod("worker-1")}}, + }, + } + if mutate != nil { + for i := range request.PodSets { + for j := range request.PodSets[i].Pods { + mutate(&request.PodSets[i].Pods[j]) + } + } + } + body, err := json.Marshal(request) + require.NoError(t, err) + + r := httptest.NewRequest(http.MethodPost, testCreatePodSetsPath, bytes.NewReader(body)) + r.Header.Set("InterLink-Http-Session", testPodSetsSession) + return r +} + +// groupPods flattens the PodSets the sidecar received, in their order. +func groupPods(data types.RetrievedPodSetsData) []types.RetrievedPodData { + var pods []types.RetrievedPodData + for _, podSet := range data.PodSets { + pods = append(pods, podSet.Pods...) + } + return pods +} + +func TestCreatePodSetsHandler_ForwardsGroup(t *testing.T) { + _, cleanup := setupTestTracer() + defer cleanup() + + answer := `[{"PodUID":"uid-launcher-0","PodJID":"42"},{"PodUID":"uid-worker-0","PodJID":"42"},{"PodUID":"uid-worker-1","PodJID":"42"}]` + handler, calls := newPodSetsSidecar(t, http.StatusOK, answer) + + w := httptest.NewRecorder() + handler.CreatePodSetsHandler(w, podSetsRequest(t, nil)) + + assert.Equal(t, http.StatusOK, w.Code) + assert.Equal(t, answer, w.Body.String()) + + require.Len(t, calls, 1) + call := <-calls + assert.Equal(t, testCreatePodSetsPath, call.path) + assert.Equal(t, testPodSetsSession, call.session) + assert.Equal(t, testGroupUID, call.body.GroupUID) + + require.Len(t, call.body.PodSets, 2) + assert.Equal(t, testPodSetLauncher, call.body.PodSets[0].Name) + assert.Equal(t, testPodSetWorker, call.body.PodSets[1].Name) + + pods := groupPods(call.body) + require.Len(t, pods, 3) + for i, name := range []string{"launcher-0", "worker-0", "worker-1"} { + pod := pods[i] + assert.Equal(t, name, pod.Pod.Name) + require.Len(t, pod.Containers, 2) + + mainData, sidecarData := pod.Containers[0], pod.Containers[1] + assert.Equal(t, testMainContainer, mainData.Name) + require.Len(t, mainData.ConfigMaps, 1) + assert.Equal(t, testConfigMapName, mainData.ConfigMaps[0].Name) + assert.Empty(t, mainData.Secrets) + require.Len(t, mainData.ProjectedVolumeMaps, 1) + assert.Equal(t, name, mainData.ProjectedVolumeMaps[0].Data["pod-name"], "each pod keeps its own projected volume") + + assert.Equal(t, testSidecarContainer, sidecarData.Name) + require.Len(t, sidecarData.Secrets, 1) + assert.Equal(t, testSecretName, sidecarData.Secrets[0].Name) + assert.Empty(t, sidecarData.ConfigMaps) + assert.Empty(t, sidecarData.ProjectedVolumeMaps) + } +} + +func TestCreatePodSetsHandler_RelaysSidecarErrors(t *testing.T) { + _, cleanup := setupTestTracer() + defer cleanup() + + tests := []struct { + name string + statusCode int + answer string + }{ + { + name: "transient refusal", + statusCode: http.StatusServiceUnavailable, + answer: "group still runs as job 42", + }, + { + name: "permanent refusal", + statusCode: http.StatusBadRequest, + answer: "this plugin does not run pod groups", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + handler, calls := newPodSetsSidecar(t, tt.statusCode, tt.answer) + + w := httptest.NewRecorder() + handler.CreatePodSetsHandler(w, podSetsRequest(t, nil)) + + assert.Equal(t, tt.statusCode, w.Code) + assert.Contains(t, w.Body.String(), tt.answer) + assert.Len(t, calls, 1) + }) + } +} + +func TestCreatePodSetsHandler_RefusesBadRequests(t *testing.T) { + _, cleanup := setupTestTracer() + defer cleanup() + + tests := []struct { + name string + body string + }{ + { + name: "malformed body", + body: `{"groupUID":`, + }, + { + name: "no PodSets", + body: `{"groupUID":"` + testGroupUID + `"}`, + }, + { + name: "PodSets without pods", + body: `{"groupUID":"` + testGroupUID + `","podSets":[{"name":"worker","pods":[]}]}`, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + handler, calls := newPodSetsSidecar(t, http.StatusOK, "[]") + + r := httptest.NewRequest(http.MethodPost, testCreatePodSetsPath, bytes.NewBufferString(tt.body)) + w := httptest.NewRecorder() + handler.CreatePodSetsHandler(w, r) + + assert.Equal(t, http.StatusBadRequest, w.Code) + assert.NotEmpty(t, w.Body.String()) + assert.Empty(t, calls, "the sidecar must not be called") + }) + } +} + +func TestCreatePodSetsHandler_JobScriptTemplate(t *testing.T) { + _, cleanup := setupTestTracer() + defer cleanup() + + templatePath := filepath.Join(t.TempDir(), "job.sh.tmpl") + require.NoError(t, os.WriteFile(templatePath, []byte("run {{.Pod.Name}}"), 0o600)) + + handler, calls := newPodSetsSidecar(t, http.StatusOK, "[]") + handler.Config.JobScriptTemplate = templatePath + + w := httptest.NewRecorder() + handler.CreatePodSetsHandler(w, podSetsRequest(t, nil)) + + assert.Equal(t, http.StatusOK, w.Code) + require.Len(t, calls, 1) + for _, pod := range groupPods((<-calls).body) { + assert.Equal(t, "run "+pod.Pod.Name, pod.JobScript) + } +} + +func TestCreatePodSetsHandler_JobScriptBuilder(t *testing.T) { + _, cleanup := setupTestTracer() + defer cleanup() + + tests := []struct { + name string + builderStatus int + wantStatus int + }{ + { + name: "builder answers", + builderStatus: http.StatusOK, + wantStatus: http.StatusOK, + }, + { + name: "builder unavailable", + builderStatus: http.StatusServiceUnavailable, + wantStatus: http.StatusServiceUnavailable, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + builder := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + var data types.RetrievedPodData + assert.NoError(t, json.NewDecoder(r.Body).Decode(&data)) + assert.Equal(t, testPodSetsSession, r.Header.Get("InterLink-Http-Session")) + + w.WriteHeader(tt.builderStatus) + if _, err := io.WriteString(w, "built for "+data.Pod.Name); err != nil { + panic(err) + } + })) + defer builder.Close() + + handler, calls := newPodSetsSidecar(t, http.StatusOK, "[]") + handler.Config.JobScriptBuildConfig = &types.ScriptBuildConfig{} + + w := httptest.NewRecorder() + handler.CreatePodSetsHandler(w, podSetsRequest(t, func(pod *types.PodCreateRequests) { + pod.JobScriptBuilderURL = builder.URL + })) + + assert.Equal(t, tt.wantStatus, w.Code) + if tt.wantStatus != http.StatusOK { + assert.Contains(t, w.Body.String(), "built for launcher-0") + assert.Empty(t, calls, "the sidecar must not be called") + return + } + require.Len(t, calls, 1) + for _, pod := range groupPods((<-calls).body) { + assert.Equal(t, "built for "+pod.Pod.Name, pod.JobScript) + } + }) + } +} + +// The caller's access token reaches the sidecar, as on /create, and never the job script +// builder, whose URL comes from the pod spec. +func TestCreatePodSetsHandler_ForwardsAccessTokenToSidecarOnly(t *testing.T) { + _, cleanup := setupTestTracer() + defer cleanup() + + var builderTokens tokenLog + builder := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + builderTokens.record(r) + if _, err := io.WriteString(w, "script"); err != nil { + panic(err) + } + })) + defer builder.Close() + + handler, calls := newPodSetsSidecar(t, http.StatusOK, "[]") + handler.Config.JobScriptBuildConfig = &types.ScriptBuildConfig{} + + r := podSetsRequest(t, func(pod *types.PodCreateRequests) { + pod.JobScriptBuilderURL = builder.URL + }) + r.Header.Set(accessTokenHeader, testAccessToken) + w := httptest.NewRecorder() + handler.CreatePodSetsHandler(w, r) + + assert.Equal(t, http.StatusOK, w.Code) + require.Len(t, calls, 1) + assert.Equal(t, testAccessToken, (<-calls).token) + token, called := builderTokens.at("/") + require.True(t, called) + assert.Empty(t, token, "the builder must not get the caller's token") +} diff --git a/pkg/interlink/types.go b/pkg/interlink/types.go index bbf9bfd9..c3d9bbff 100644 --- a/pkg/interlink/types.go +++ b/pkg/interlink/types.go @@ -101,6 +101,45 @@ type RetrievedPodData struct { JobScript string `json:"jobScript,omitempty"` } +// PodSetsCreateRequests represents a request to create the pods of one group in a single call, +// so that the plugin can run them as one job. The pods are grouped by PodSet; their order, +// PodSet after PodSet, gives their ranks, and rank 0 is the head. +type PodSetsCreateRequests struct { + // GroupUID identifies the group (the Workload UID), and is also the value of the + // pod-group label set on its pods + GroupUID string `json:"groupUID"` + // PodSets contains the PodSets of the group, in rank order + PodSets []PodSetCreateRequests `json:"podSets"` +} + +// PodSetCreateRequests represents one PodSet of a group to create. +// Each pod carries its own ConfigMaps, Secrets and projected volumes, because projected +// and downward API files differ from pod to pod (pod name, UID, rank annotations). +type PodSetCreateRequests struct { + // Name is the name of the PodSet + Name string `json:"name"` + // Pods contains the pods of the PodSet, each as it is sent to /create + Pods []PodCreateRequests `json:"pods"` +} + +// RetrievedPodSetsData represents the pods of one group with all their associated data. +// It is what interLink sends to the plugin's /createPodSets: each pod is prepared as for +// /create, and the order of the pods is the one of the PodSetsCreateRequests. +type RetrievedPodSetsData struct { + // GroupUID identifies the group, as in PodSetsCreateRequests + GroupUID string `json:"groupUID"` + // PodSets contains the PodSets of the group, in rank order + PodSets []RetrievedPodSetData `json:"podSets"` +} + +// RetrievedPodSetData represents one PodSet of a group with the data of its pods. +type RetrievedPodSetData struct { + // Name is the name of the PodSet + Name string `json:"name"` + // Pods contains the pods of the PodSet, each as it is sent to the plugin's /create + Pods []RetrievedPodData `json:"pods"` +} + // ContainerLogOpts specifies options for retrieving container logs from sidecar plugins. // These options control how logs are fetched, filtered, and formatted, matching // Kubernetes' standard log retrieval parameters. From cd410baf86c876dfc4e13cd23cbc2585f8bf4611 Mon Sep 17 00:00:00 2001 From: Matteo Bunino <48362942+matbun@users.noreply.github.com> Date: Fri, 30 Oct 2026 13:47:27 +0100 Subject: [PATCH 2/3] feat: name the group call after Kubernetes' PodGroup /createPodSets becomes /createPodGroup and takes a flat list of pods in rank order instead of named PodSets. PodGroup is what Kubernetes calls a set of pods scheduled together (scheduling.k8s.io Workload and PodGroup, KEP-4671), while PodSet is Kueue's own term. No plugin needs the inner names, and each pod still carries its own labels. - PodGroupCreateRequests and RetrievedPodGroupData replace the four PodSet types; plugins list the capability "pod-group-create". - Handler, route, OpenAPI specs, plugin guide and tests follow. Co-Authored-By: Claude Opus 5.5 Signed-off-by: Matteo Bunino <48362942+matbun@users.noreply.github.com> --- cmd/interlink/main.go | 2 +- cmd/openapi-gen/main.go | 10 +- docs/docs/guides/02-develop-a-plugin.md | 23 ++-- docs/openapi/interlink-openapi.json | 2 +- docs/openapi/plugin-openapi.json | 41 ++---- .../{createpodsets.go => createpodgroup.go} | 79 +++++------- ...podsets_test.go => createpodgroup_test.go} | 122 ++++++++---------- pkg/interlink/types.go | 44 ++----- 8 files changed, 124 insertions(+), 199 deletions(-) rename pkg/interlink/api/{createpodsets.go => createpodgroup.go} (70%) rename pkg/interlink/api/{createpodsets_test.go => createpodgroup_test.go} (67%) diff --git a/cmd/interlink/main.go b/cmd/interlink/main.go index b26c3615..b5811657 100644 --- a/cmd/interlink/main.go +++ b/cmd/interlink/main.go @@ -190,7 +190,7 @@ func main() { mutex := http.NewServeMux() mutex.HandleFunc("/status", interLinkAPIs.StatusHandler) mutex.HandleFunc("/create", interLinkAPIs.CreateHandler) - mutex.HandleFunc("/createPodSets", interLinkAPIs.CreatePodSetsHandler) + mutex.HandleFunc("/createPodGroup", interLinkAPIs.CreatePodGroupHandler) mutex.HandleFunc("/delete", interLinkAPIs.DeleteHandler) mutex.HandleFunc("/pinglink", interLinkAPIs.Ping) mutex.HandleFunc("/getLogs", interLinkAPIs.GetLogsHandler) diff --git a/cmd/openapi-gen/main.go b/cmd/openapi-gen/main.go index 0e11aadc..bfdefa17 100644 --- a/cmd/openapi-gen/main.go +++ b/cmd/openapi-gen/main.go @@ -38,16 +38,16 @@ func main() { panic(err) } - // CREATE PODSETS - createPodSetsOp, err := reflector.NewOperationContext(http.MethodPost, "/createPodSets") + // CREATE POD GROUP + createPodGroupOp, err := reflector.NewOperationContext(http.MethodPost, "/createPodGroup") if err != nil { panic(err) } - createPodSetsOp.AddReqStructure(new(interlink.PodSetsCreateRequests)) - createPodSetsOp.AddRespStructure(new([]interlink.CreateStruct), func(cu *openapi.ContentUnit) { cu.HTTPStatus = http.StatusOK }) + createPodGroupOp.AddReqStructure(new(interlink.PodGroupCreateRequests)) + createPodGroupOp.AddRespStructure(new([]interlink.CreateStruct), func(cu *openapi.ContentUnit) { cu.HTTPStatus = http.StatusOK }) - err = reflector.AddOperation(createPodSetsOp) + err = reflector.AddOperation(createPodGroupOp) if err != nil { panic(err) } diff --git a/docs/docs/guides/02-develop-a-plugin.md b/docs/docs/guides/02-develop-a-plugin.md index 420da0c7..48570414 100644 --- a/docs/docs/guides/02-develop-a-plugin.md +++ b/docs/docs/guides/02-develop-a-plugin.md @@ -151,28 +151,25 @@ Retrieves logs from a specific container. **Query Parameters**: Pod UID, container name, log options **Response**: Container logs (plain text) -### POST /createPodSets (optional) +### POST /createPodGroup (optional) -Creates the pods of one group as a single job. A plugin that implements it lists -`podset-create` in the `capabilities` of its ping answer, and it is used only -then; status, logs and delete stay per pod. +Creates the pods of one pod group as a single job. A plugin that implements it +lists `pod-group-create` in the `capabilities` of its ping answer, and it is +used only then; status, logs and delete stay per pod. -**Request Body**: `RetrievedPodSetsData` **Response**: `List[CreateStruct]`, one -per pod in the order of the request, all with the job ID of the group +**Request Body**: `RetrievedPodGroupData` **Response**: `List[CreateStruct]`, +one per pod in the order of the request, all with the job ID of the group ```json { "groupUID": "workload-uid", - "podSets": [ - { "name": "launcher", "pods": [...] }, // each pod as sent to /create - { "name": "worker", "pods": [...] } - ] + "pods": [...] // each pod as sent to /create, in rank order } ``` -The order of the pods, PodSet after PodSet, gives their ranks; rank 0 is the -head. Answer 400 with a text body for a bad request or a permanent refusal, and -503 when the group cannot be taken now. +The order of the pods gives their ranks; rank 0 is the head. Answer 400 with a +text body for a bad request or a permanent refusal, and 503 when the group +cannot be taken now. ## Developing with the Python SDK diff --git a/docs/openapi/interlink-openapi.json b/docs/openapi/interlink-openapi.json index 55743ce7..fd1881e8 100644 --- a/docs/openapi/interlink-openapi.json +++ b/docs/openapi/interlink-openapi.json @@ -1 +1 @@ -{"openapi":"3.0.3","info":{"title":"interLink server API","description":"This is the API spec for the Virtual Kubelet to interLink API server communication","version":"0.4.0"},"paths":{"/create":{"post":{"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/InterlinkPodCreateRequests"}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InterlinkRetrievedPodData"}}}}}}},"/createPodSets":{"post":{"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/InterlinkPodSetsCreateRequests"}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/InterlinkCreateStruct"}}}}}}}},"/delete":{"post":{"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Pod"}}}},"responses":{"200":{"description":"OK"}}}},"/getLogs":{"post":{"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/InterlinkLogStruct"}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"string"}}}}}}},"/pinglink":{"post":{"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InterlinkPingResponse"}}}}}}},"/status":{"post":{"requestBody":{"content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/V1Pod"},"nullable":true}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/InterlinkPodStatus"}}}}}}}}},"components":{"schemas":{"InterlinkAcceleratorResponse":{"type":"object","properties":{"available":{"type":"string"},"resourceType":{"type":"string"}}},"InterlinkApptainerOptions":{"type":"object","properties":{"cleanenv":{"type":"boolean"},"containall":{"type":"boolean"},"executable":{"type":"string"},"fakeroot":{"type":"boolean"},"fuseMode":{"type":"string"},"noHome":{"type":"boolean"},"noInit":{"type":"boolean"},"noPrivs":{"type":"boolean"},"nvidiaSupport":{"type":"boolean"},"unsquash":{"type":"boolean"}}},"InterlinkContainerLogOpts":{"type":"object","properties":{"Bytes":{"type":"integer"},"Follow":{"type":"boolean"},"Previous":{"type":"boolean"},"SinceSeconds":{"type":"integer"},"SinceTime":{"type":"string","format":"date-time"},"Tail":{"type":"integer"},"Timestamps":{"type":"boolean"}}},"InterlinkCreateStruct":{"type":"object","properties":{"PodJID":{"type":"string"},"PodUID":{"type":"string"}}},"InterlinkLogStruct":{"type":"object","properties":{"ContainerName":{"type":"string"},"Namespace":{"type":"string"},"Opts":{"$ref":"#/components/schemas/InterlinkContainerLogOpts"},"PodName":{"type":"string"},"PodUID":{"type":"string"}}},"InterlinkPingResponse":{"type":"object","properties":{"resources":{"$ref":"#/components/schemas/InterlinkResourcesResponse"},"status":{"type":"string"},"taints":{"type":"array","items":{"$ref":"#/components/schemas/InterlinkTaintResponse"},"nullable":true}}},"InterlinkPodCreateRequests":{"type":"object","properties":{"configmaps":{"type":"array","items":{"$ref":"#/components/schemas/V1ConfigMap"},"nullable":true},"jobscriptURL":{"type":"string"},"pod":{"$ref":"#/components/schemas/V1Pod"},"projectedvolumesmaps":{"type":"array","items":{"$ref":"#/components/schemas/V1ConfigMap"},"nullable":true},"secrets":{"type":"array","items":{"$ref":"#/components/schemas/V1Secret"},"nullable":true}}},"InterlinkPodSetCreateRequests":{"type":"object","properties":{"name":{"type":"string"},"pods":{"type":"array","items":{"$ref":"#/components/schemas/InterlinkPodCreateRequests"},"nullable":true}}},"InterlinkPodSetsCreateRequests":{"type":"object","properties":{"groupUID":{"type":"string"},"podSets":{"type":"array","items":{"$ref":"#/components/schemas/InterlinkPodSetCreateRequests"},"nullable":true}}},"InterlinkPodStatus":{"type":"object","properties":{"JID":{"type":"string"},"UID":{"type":"string"},"containers":{"type":"array","items":{"$ref":"#/components/schemas/V1ContainerStatus"},"nullable":true},"initContainers":{"type":"array","items":{"$ref":"#/components/schemas/V1ContainerStatus"},"nullable":true},"name":{"type":"string"},"namespace":{"type":"string"},"nodeName":{"type":"string"},"socketDir":{"type":"string"}}},"InterlinkResourcesResponse":{"type":"object","properties":{"accelerators":{"type":"array","items":{"$ref":"#/components/schemas/InterlinkAcceleratorResponse"}},"cpu":{"type":"string"},"memory":{"type":"string"},"pods":{"type":"string"}}},"InterlinkRetrievedContainer":{"type":"object","properties":{"configMaps":{"type":"array","items":{"$ref":"#/components/schemas/V1ConfigMap"},"nullable":true},"emptyDirs":{"type":"array","items":{"type":"string"},"nullable":true},"name":{"type":"string"},"projectedvolumemaps":{"type":"array","items":{"$ref":"#/components/schemas/V1ConfigMap"},"nullable":true},"secrets":{"type":"array","items":{"$ref":"#/components/schemas/V1Secret"},"nullable":true}}},"InterlinkRetrievedPodData":{"type":"object","properties":{"container":{"type":"array","items":{"$ref":"#/components/schemas/InterlinkRetrievedContainer"},"nullable":true},"jobConfig":{"$ref":"#/components/schemas/InterlinkScriptBuildConfig"},"jobScript":{"type":"string"},"pod":{"$ref":"#/components/schemas/V1Pod"}}},"InterlinkScriptBuildConfig":{"type":"object","properties":{"ApptainerOptions":{"$ref":"#/components/schemas/InterlinkApptainerOptions"},"SingularityHubProxy":{"$ref":"#/components/schemas/InterlinkSingularityHubConfig"},"Volumes":{"$ref":"#/components/schemas/InterlinkVolumesOptions"}}},"InterlinkSingularityHubConfig":{"type":"object","properties":{"cache_validity_seconds":{"type":"integer"},"master_token":{"type":"string"},"server":{"type":"string"}}},"InterlinkTaintResponse":{"type":"object","properties":{"effect":{"type":"string"},"key":{"type":"string"},"value":{"type":"string"}}},"InterlinkVolumesOptions":{"type":"object","properties":{"additional_directories_in_path":{"type":"array","items":{"type":"string"},"nullable":true},"apptainer_cachedir":{"type":"string"},"fuse_sleep_seconds":{"type":"integer"},"image_dir":{"type":"string"},"scratch_area":{"type":"string"}}},"IntstrIntOrString":{"type":"object"},"ResourceQuantity":{"type":"object"},"V1AWSElasticBlockStoreVolumeSource":{"type":"object","properties":{"fsType":{"type":"string"},"partition":{"type":"integer"},"readOnly":{"type":"boolean"},"volumeID":{"type":"string"}}},"V1Affinity":{"type":"object","properties":{"nodeAffinity":{"$ref":"#/components/schemas/V1NodeAffinity"},"podAffinity":{"$ref":"#/components/schemas/V1PodAffinity"},"podAntiAffinity":{"$ref":"#/components/schemas/V1PodAntiAffinity"}}},"V1AppArmorProfile":{"type":"object","properties":{"localhostProfile":{"type":"string","nullable":true},"type":{"type":"string"}}},"V1AzureDiskVolumeSource":{"type":"object","properties":{"cachingMode":{"type":"string","nullable":true},"diskName":{"type":"string"},"diskURI":{"type":"string"},"fsType":{"type":"string","nullable":true},"kind":{"type":"string","nullable":true},"readOnly":{"type":"boolean","nullable":true}}},"V1AzureFileVolumeSource":{"type":"object","properties":{"readOnly":{"type":"boolean"},"secretName":{"type":"string"},"shareName":{"type":"string"}}},"V1CSIVolumeSource":{"type":"object","properties":{"driver":{"type":"string"},"fsType":{"type":"string","nullable":true},"nodePublishSecretRef":{"$ref":"#/components/schemas/V1LocalObjectReference"},"readOnly":{"type":"boolean","nullable":true},"volumeAttributes":{"type":"object","additionalProperties":{"type":"string"}}}},"V1Capabilities":{"type":"object","properties":{"add":{"type":"array","items":{"type":"string"}},"drop":{"type":"array","items":{"type":"string"}}}},"V1CephFSVolumeSource":{"type":"object","properties":{"monitors":{"type":"array","items":{"type":"string"},"nullable":true},"path":{"type":"string"},"readOnly":{"type":"boolean"},"secretFile":{"type":"string"},"secretRef":{"$ref":"#/components/schemas/V1LocalObjectReference"},"user":{"type":"string"}}},"V1CinderVolumeSource":{"type":"object","properties":{"fsType":{"type":"string"},"readOnly":{"type":"boolean"},"secretRef":{"$ref":"#/components/schemas/V1LocalObjectReference"},"volumeID":{"type":"string"}}},"V1ClusterTrustBundleProjection":{"type":"object","properties":{"labelSelector":{"$ref":"#/components/schemas/V1LabelSelector"},"name":{"type":"string","nullable":true},"optional":{"type":"boolean","nullable":true},"path":{"type":"string"},"signerName":{"type":"string","nullable":true}}},"V1ConfigMap":{"type":"object","properties":{"apiVersion":{"type":"string"},"binaryData":{"type":"object","additionalProperties":{"type":"string","format":"base64"}},"data":{"type":"object","additionalProperties":{"type":"string"}},"immutable":{"type":"boolean","nullable":true},"kind":{"type":"string"},"metadata":{"$ref":"#/components/schemas/V1ObjectMeta"}}},"V1ConfigMapEnvSource":{"type":"object","properties":{"name":{"type":"string"},"optional":{"type":"boolean","nullable":true}}},"V1ConfigMapKeySelector":{"type":"object","properties":{"key":{"type":"string"},"name":{"type":"string"},"optional":{"type":"boolean","nullable":true}}},"V1ConfigMapProjection":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/V1KeyToPath"}},"name":{"type":"string"},"optional":{"type":"boolean","nullable":true}}},"V1ConfigMapVolumeSource":{"type":"object","properties":{"defaultMode":{"type":"integer","nullable":true},"items":{"type":"array","items":{"$ref":"#/components/schemas/V1KeyToPath"}},"name":{"type":"string"},"optional":{"type":"boolean","nullable":true}}},"V1Container":{"type":"object","properties":{"args":{"type":"array","items":{"type":"string"}},"command":{"type":"array","items":{"type":"string"}},"env":{"type":"array","items":{"$ref":"#/components/schemas/V1EnvVar"}},"envFrom":{"type":"array","items":{"$ref":"#/components/schemas/V1EnvFromSource"}},"image":{"type":"string"},"imagePullPolicy":{"type":"string"},"lifecycle":{"$ref":"#/components/schemas/V1Lifecycle"},"livenessProbe":{"$ref":"#/components/schemas/V1Probe"},"name":{"type":"string"},"ports":{"type":"array","items":{"$ref":"#/components/schemas/V1ContainerPort"}},"readinessProbe":{"$ref":"#/components/schemas/V1Probe"},"resizePolicy":{"type":"array","items":{"$ref":"#/components/schemas/V1ContainerResizePolicy"}},"resources":{"$ref":"#/components/schemas/V1ResourceRequirements"},"restartPolicy":{"type":"string","nullable":true},"securityContext":{"$ref":"#/components/schemas/V1SecurityContext"},"startupProbe":{"$ref":"#/components/schemas/V1Probe"},"stdin":{"type":"boolean"},"stdinOnce":{"type":"boolean"},"terminationMessagePath":{"type":"string"},"terminationMessagePolicy":{"type":"string"},"tty":{"type":"boolean"},"volumeDevices":{"type":"array","items":{"$ref":"#/components/schemas/V1VolumeDevice"}},"volumeMounts":{"type":"array","items":{"$ref":"#/components/schemas/V1VolumeMount"}},"workingDir":{"type":"string"}}},"V1ContainerPort":{"type":"object","properties":{"containerPort":{"type":"integer"},"hostIP":{"type":"string"},"hostPort":{"type":"integer"},"name":{"type":"string"},"protocol":{"type":"string"}}},"V1ContainerResizePolicy":{"type":"object","properties":{"resourceName":{"type":"string"},"restartPolicy":{"type":"string"}}},"V1ContainerState":{"type":"object","properties":{"running":{"$ref":"#/components/schemas/V1ContainerStateRunning"},"terminated":{"$ref":"#/components/schemas/V1ContainerStateTerminated"},"waiting":{"$ref":"#/components/schemas/V1ContainerStateWaiting"}}},"V1ContainerStateRunning":{"type":"object","properties":{"startedAt":{"type":"string"}}},"V1ContainerStateTerminated":{"type":"object","properties":{"containerID":{"type":"string"},"exitCode":{"type":"integer"},"finishedAt":{"type":"string"},"message":{"type":"string"},"reason":{"type":"string"},"signal":{"type":"integer"},"startedAt":{"type":"string"}}},"V1ContainerStateWaiting":{"type":"object","properties":{"message":{"type":"string"},"reason":{"type":"string"}}},"V1ContainerStatus":{"type":"object","properties":{"allocatedResources":{"$ref":"#/components/schemas/V1ResourceList"},"allocatedResourcesStatus":{"type":"array","items":{"$ref":"#/components/schemas/V1ResourceStatus"}},"containerID":{"type":"string"},"image":{"type":"string"},"imageID":{"type":"string"},"lastState":{"$ref":"#/components/schemas/V1ContainerState"},"name":{"type":"string"},"ready":{"type":"boolean"},"resources":{"$ref":"#/components/schemas/V1ResourceRequirements"},"restartCount":{"type":"integer"},"started":{"type":"boolean","nullable":true},"state":{"$ref":"#/components/schemas/V1ContainerState"},"stopSignal":{"type":"string","nullable":true},"user":{"$ref":"#/components/schemas/V1ContainerUser"},"volumeMounts":{"type":"array","items":{"$ref":"#/components/schemas/V1VolumeMountStatus"}}}},"V1ContainerUser":{"type":"object","properties":{"linux":{"$ref":"#/components/schemas/V1LinuxContainerUser"}}},"V1DownwardAPIProjection":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/V1DownwardAPIVolumeFile"}}}},"V1DownwardAPIVolumeFile":{"type":"object","properties":{"fieldRef":{"$ref":"#/components/schemas/V1ObjectFieldSelector"},"mode":{"type":"integer","nullable":true},"path":{"type":"string"},"resourceFieldRef":{"$ref":"#/components/schemas/V1ResourceFieldSelector"}}},"V1DownwardAPIVolumeSource":{"type":"object","properties":{"defaultMode":{"type":"integer","nullable":true},"items":{"type":"array","items":{"$ref":"#/components/schemas/V1DownwardAPIVolumeFile"}}}},"V1EmptyDirVolumeSource":{"type":"object","properties":{"medium":{"type":"string"},"sizeLimit":{"$ref":"#/components/schemas/ResourceQuantity"}}},"V1EnvFromSource":{"type":"object","properties":{"configMapRef":{"$ref":"#/components/schemas/V1ConfigMapEnvSource"},"prefix":{"type":"string"},"secretRef":{"$ref":"#/components/schemas/V1SecretEnvSource"}}},"V1EnvVar":{"type":"object","properties":{"name":{"type":"string"},"value":{"type":"string"},"valueFrom":{"$ref":"#/components/schemas/V1EnvVarSource"}}},"V1EnvVarSource":{"type":"object","properties":{"configMapKeyRef":{"$ref":"#/components/schemas/V1ConfigMapKeySelector"},"fieldRef":{"$ref":"#/components/schemas/V1ObjectFieldSelector"},"resourceFieldRef":{"$ref":"#/components/schemas/V1ResourceFieldSelector"},"secretKeyRef":{"$ref":"#/components/schemas/V1SecretKeySelector"}}},"V1EphemeralContainer":{"type":"object","properties":{"args":{"type":"array","items":{"type":"string"}},"command":{"type":"array","items":{"type":"string"}},"env":{"type":"array","items":{"$ref":"#/components/schemas/V1EnvVar"}},"envFrom":{"type":"array","items":{"$ref":"#/components/schemas/V1EnvFromSource"}},"image":{"type":"string"},"imagePullPolicy":{"type":"string"},"lifecycle":{"$ref":"#/components/schemas/V1Lifecycle"},"livenessProbe":{"$ref":"#/components/schemas/V1Probe"},"name":{"type":"string"},"ports":{"type":"array","items":{"$ref":"#/components/schemas/V1ContainerPort"}},"readinessProbe":{"$ref":"#/components/schemas/V1Probe"},"resizePolicy":{"type":"array","items":{"$ref":"#/components/schemas/V1ContainerResizePolicy"}},"resources":{"$ref":"#/components/schemas/V1ResourceRequirements"},"restartPolicy":{"type":"string","nullable":true},"securityContext":{"$ref":"#/components/schemas/V1SecurityContext"},"startupProbe":{"$ref":"#/components/schemas/V1Probe"},"stdin":{"type":"boolean"},"stdinOnce":{"type":"boolean"},"targetContainerName":{"type":"string"},"terminationMessagePath":{"type":"string"},"terminationMessagePolicy":{"type":"string"},"tty":{"type":"boolean"},"volumeDevices":{"type":"array","items":{"$ref":"#/components/schemas/V1VolumeDevice"}},"volumeMounts":{"type":"array","items":{"$ref":"#/components/schemas/V1VolumeMount"}},"workingDir":{"type":"string"}}},"V1EphemeralVolumeSource":{"type":"object","properties":{"volumeClaimTemplate":{"$ref":"#/components/schemas/V1PersistentVolumeClaimTemplate"}}},"V1ExecAction":{"type":"object","properties":{"command":{"type":"array","items":{"type":"string"}}}},"V1FCVolumeSource":{"type":"object","properties":{"fsType":{"type":"string"},"lun":{"type":"integer","nullable":true},"readOnly":{"type":"boolean"},"targetWWNs":{"type":"array","items":{"type":"string"}},"wwids":{"type":"array","items":{"type":"string"}}}},"V1FieldsV1":{"type":"object"},"V1FlexVolumeSource":{"type":"object","properties":{"driver":{"type":"string"},"fsType":{"type":"string"},"options":{"type":"object","additionalProperties":{"type":"string"}},"readOnly":{"type":"boolean"},"secretRef":{"$ref":"#/components/schemas/V1LocalObjectReference"}}},"V1FlockerVolumeSource":{"type":"object","properties":{"datasetName":{"type":"string"},"datasetUUID":{"type":"string"}}},"V1GCEPersistentDiskVolumeSource":{"type":"object","properties":{"fsType":{"type":"string"},"partition":{"type":"integer"},"pdName":{"type":"string"},"readOnly":{"type":"boolean"}}},"V1GRPCAction":{"type":"object","properties":{"port":{"type":"integer"},"service":{"type":"string","nullable":true}}},"V1GitRepoVolumeSource":{"type":"object","properties":{"directory":{"type":"string"},"repository":{"type":"string"},"revision":{"type":"string"}}},"V1GlusterfsVolumeSource":{"type":"object","properties":{"endpoints":{"type":"string"},"path":{"type":"string"},"readOnly":{"type":"boolean"}}},"V1HTTPGetAction":{"type":"object","properties":{"host":{"type":"string"},"httpHeaders":{"type":"array","items":{"$ref":"#/components/schemas/V1HTTPHeader"}},"path":{"type":"string"},"port":{"$ref":"#/components/schemas/IntstrIntOrString"},"scheme":{"type":"string"}}},"V1HTTPHeader":{"type":"object","properties":{"name":{"type":"string"},"value":{"type":"string"}}},"V1HostAlias":{"type":"object","properties":{"hostnames":{"type":"array","items":{"type":"string"}},"ip":{"type":"string"}}},"V1HostIP":{"type":"object","properties":{"ip":{"type":"string"}}},"V1HostPathVolumeSource":{"type":"object","properties":{"path":{"type":"string"},"type":{"type":"string","nullable":true}}},"V1ISCSIVolumeSource":{"type":"object","properties":{"chapAuthDiscovery":{"type":"boolean"},"chapAuthSession":{"type":"boolean"},"fsType":{"type":"string"},"initiatorName":{"type":"string","nullable":true},"iqn":{"type":"string"},"iscsiInterface":{"type":"string"},"lun":{"type":"integer"},"portals":{"type":"array","items":{"type":"string"}},"readOnly":{"type":"boolean"},"secretRef":{"$ref":"#/components/schemas/V1LocalObjectReference"},"targetPortal":{"type":"string"}}},"V1ImageVolumeSource":{"type":"object","properties":{"pullPolicy":{"type":"string"},"reference":{"type":"string"}}},"V1KeyToPath":{"type":"object","properties":{"key":{"type":"string"},"mode":{"type":"integer","nullable":true},"path":{"type":"string"}}},"V1LabelSelector":{"type":"object","properties":{"matchExpressions":{"type":"array","items":{"$ref":"#/components/schemas/V1LabelSelectorRequirement"}},"matchLabels":{"type":"object","additionalProperties":{"type":"string"}}}},"V1LabelSelectorRequirement":{"type":"object","properties":{"key":{"type":"string"},"operator":{"type":"string"},"values":{"type":"array","items":{"type":"string"}}}},"V1Lifecycle":{"type":"object","properties":{"postStart":{"$ref":"#/components/schemas/V1LifecycleHandler"},"preStop":{"$ref":"#/components/schemas/V1LifecycleHandler"},"stopSignal":{"type":"string","nullable":true}}},"V1LifecycleHandler":{"type":"object","properties":{"exec":{"$ref":"#/components/schemas/V1ExecAction"},"httpGet":{"$ref":"#/components/schemas/V1HTTPGetAction"},"sleep":{"$ref":"#/components/schemas/V1SleepAction"},"tcpSocket":{"$ref":"#/components/schemas/V1TCPSocketAction"}}},"V1LinuxContainerUser":{"type":"object","properties":{"gid":{"type":"integer"},"supplementalGroups":{"type":"array","items":{"type":"integer"}},"uid":{"type":"integer"}}},"V1LocalObjectReference":{"type":"object","properties":{"name":{"type":"string"}}},"V1ManagedFieldsEntry":{"type":"object","properties":{"apiVersion":{"type":"string"},"fieldsType":{"type":"string"},"fieldsV1":{"$ref":"#/components/schemas/V1FieldsV1"},"manager":{"type":"string"},"operation":{"type":"string"},"subresource":{"type":"string"},"time":{"type":"string"}}},"V1NFSVolumeSource":{"type":"object","properties":{"path":{"type":"string"},"readOnly":{"type":"boolean"},"server":{"type":"string"}}},"V1NodeAffinity":{"type":"object","properties":{"preferredDuringSchedulingIgnoredDuringExecution":{"type":"array","items":{"$ref":"#/components/schemas/V1PreferredSchedulingTerm"}},"requiredDuringSchedulingIgnoredDuringExecution":{"$ref":"#/components/schemas/V1NodeSelector"}}},"V1NodeSelector":{"type":"object","properties":{"nodeSelectorTerms":{"type":"array","items":{"$ref":"#/components/schemas/V1NodeSelectorTerm"},"nullable":true}}},"V1NodeSelectorRequirement":{"type":"object","properties":{"key":{"type":"string"},"operator":{"type":"string"},"values":{"type":"array","items":{"type":"string"}}}},"V1NodeSelectorTerm":{"type":"object","properties":{"matchExpressions":{"type":"array","items":{"$ref":"#/components/schemas/V1NodeSelectorRequirement"}},"matchFields":{"type":"array","items":{"$ref":"#/components/schemas/V1NodeSelectorRequirement"}}}},"V1ObjectFieldSelector":{"type":"object","properties":{"apiVersion":{"type":"string"},"fieldPath":{"type":"string"}}},"V1ObjectMeta":{"type":"object","properties":{"annotations":{"type":"object","additionalProperties":{"type":"string"}},"creationTimestamp":{"type":"string"},"deletionGracePeriodSeconds":{"type":"integer","nullable":true},"deletionTimestamp":{"type":"string"},"finalizers":{"type":"array","items":{"type":"string"}},"generateName":{"type":"string"},"generation":{"type":"integer"},"labels":{"type":"object","additionalProperties":{"type":"string"}},"managedFields":{"type":"array","items":{"$ref":"#/components/schemas/V1ManagedFieldsEntry"}},"name":{"type":"string"},"namespace":{"type":"string"},"ownerReferences":{"type":"array","items":{"$ref":"#/components/schemas/V1OwnerReference"}},"resourceVersion":{"type":"string"},"selfLink":{"type":"string"},"uid":{"type":"string"}}},"V1OwnerReference":{"type":"object","properties":{"apiVersion":{"type":"string"},"blockOwnerDeletion":{"type":"boolean","nullable":true},"controller":{"type":"boolean","nullable":true},"kind":{"type":"string"},"name":{"type":"string"},"uid":{"type":"string"}}},"V1PersistentVolumeClaimSpec":{"type":"object","properties":{"accessModes":{"type":"array","items":{"type":"string"}},"dataSource":{"$ref":"#/components/schemas/V1TypedLocalObjectReference"},"dataSourceRef":{"$ref":"#/components/schemas/V1TypedObjectReference"},"resources":{"$ref":"#/components/schemas/V1VolumeResourceRequirements"},"selector":{"$ref":"#/components/schemas/V1LabelSelector"},"storageClassName":{"type":"string","nullable":true},"volumeAttributesClassName":{"type":"string","nullable":true},"volumeMode":{"type":"string","nullable":true},"volumeName":{"type":"string"}}},"V1PersistentVolumeClaimTemplate":{"type":"object","properties":{"metadata":{"$ref":"#/components/schemas/V1ObjectMeta"},"spec":{"$ref":"#/components/schemas/V1PersistentVolumeClaimSpec"}}},"V1PersistentVolumeClaimVolumeSource":{"type":"object","properties":{"claimName":{"type":"string"},"readOnly":{"type":"boolean"}}},"V1PhotonPersistentDiskVolumeSource":{"type":"object","properties":{"fsType":{"type":"string"},"pdID":{"type":"string"}}},"V1Pod":{"type":"object","properties":{"apiVersion":{"type":"string"},"kind":{"type":"string"},"metadata":{"$ref":"#/components/schemas/V1ObjectMeta"},"spec":{"$ref":"#/components/schemas/V1PodSpec"},"status":{"$ref":"#/components/schemas/V1PodStatus"}}},"V1PodAffinity":{"type":"object","properties":{"preferredDuringSchedulingIgnoredDuringExecution":{"type":"array","items":{"$ref":"#/components/schemas/V1WeightedPodAffinityTerm"}},"requiredDuringSchedulingIgnoredDuringExecution":{"type":"array","items":{"$ref":"#/components/schemas/V1PodAffinityTerm"}}}},"V1PodAffinityTerm":{"type":"object","properties":{"labelSelector":{"$ref":"#/components/schemas/V1LabelSelector"},"matchLabelKeys":{"type":"array","items":{"type":"string"}},"mismatchLabelKeys":{"type":"array","items":{"type":"string"}},"namespaceSelector":{"$ref":"#/components/schemas/V1LabelSelector"},"namespaces":{"type":"array","items":{"type":"string"}},"topologyKey":{"type":"string"}}},"V1PodAntiAffinity":{"type":"object","properties":{"preferredDuringSchedulingIgnoredDuringExecution":{"type":"array","items":{"$ref":"#/components/schemas/V1WeightedPodAffinityTerm"}},"requiredDuringSchedulingIgnoredDuringExecution":{"type":"array","items":{"$ref":"#/components/schemas/V1PodAffinityTerm"}}}},"V1PodCondition":{"type":"object","properties":{"lastProbeTime":{"type":"string"},"lastTransitionTime":{"type":"string"},"message":{"type":"string"},"observedGeneration":{"type":"integer"},"reason":{"type":"string"},"status":{"type":"string"},"type":{"type":"string"}}},"V1PodDNSConfig":{"type":"object","properties":{"nameservers":{"type":"array","items":{"type":"string"}},"options":{"type":"array","items":{"$ref":"#/components/schemas/V1PodDNSConfigOption"}},"searches":{"type":"array","items":{"type":"string"}}}},"V1PodDNSConfigOption":{"type":"object","properties":{"name":{"type":"string"},"value":{"type":"string","nullable":true}}},"V1PodIP":{"type":"object","properties":{"ip":{"type":"string"}}},"V1PodOS":{"type":"object","properties":{"name":{"type":"string"}}},"V1PodReadinessGate":{"type":"object","properties":{"conditionType":{"type":"string"}}},"V1PodResourceClaim":{"type":"object","properties":{"name":{"type":"string"},"resourceClaimName":{"type":"string","nullable":true},"resourceClaimTemplateName":{"type":"string","nullable":true}}},"V1PodResourceClaimStatus":{"type":"object","properties":{"name":{"type":"string"},"resourceClaimName":{"type":"string","nullable":true}}},"V1PodSchedulingGate":{"type":"object","properties":{"name":{"type":"string"}}},"V1PodSecurityContext":{"type":"object","properties":{"appArmorProfile":{"$ref":"#/components/schemas/V1AppArmorProfile"},"fsGroup":{"type":"integer","nullable":true},"fsGroupChangePolicy":{"type":"string","nullable":true},"runAsGroup":{"type":"integer","nullable":true},"runAsNonRoot":{"type":"boolean","nullable":true},"runAsUser":{"type":"integer","nullable":true},"seLinuxChangePolicy":{"type":"string","nullable":true},"seLinuxOptions":{"$ref":"#/components/schemas/V1SELinuxOptions"},"seccompProfile":{"$ref":"#/components/schemas/V1SeccompProfile"},"supplementalGroups":{"type":"array","items":{"type":"integer"}},"supplementalGroupsPolicy":{"type":"string","nullable":true},"sysctls":{"type":"array","items":{"$ref":"#/components/schemas/V1Sysctl"}},"windowsOptions":{"$ref":"#/components/schemas/V1WindowsSecurityContextOptions"}}},"V1PodSpec":{"type":"object","properties":{"activeDeadlineSeconds":{"type":"integer","nullable":true},"affinity":{"$ref":"#/components/schemas/V1Affinity"},"automountServiceAccountToken":{"type":"boolean","nullable":true},"containers":{"type":"array","items":{"$ref":"#/components/schemas/V1Container"},"nullable":true},"dnsConfig":{"$ref":"#/components/schemas/V1PodDNSConfig"},"dnsPolicy":{"type":"string"},"enableServiceLinks":{"type":"boolean","nullable":true},"ephemeralContainers":{"type":"array","items":{"$ref":"#/components/schemas/V1EphemeralContainer"}},"hostAliases":{"type":"array","items":{"$ref":"#/components/schemas/V1HostAlias"}},"hostIPC":{"type":"boolean"},"hostNetwork":{"type":"boolean"},"hostPID":{"type":"boolean"},"hostUsers":{"type":"boolean","nullable":true},"hostname":{"type":"string"},"imagePullSecrets":{"type":"array","items":{"$ref":"#/components/schemas/V1LocalObjectReference"}},"initContainers":{"type":"array","items":{"$ref":"#/components/schemas/V1Container"}},"nodeName":{"type":"string"},"nodeSelector":{"type":"object","additionalProperties":{"type":"string"}},"os":{"$ref":"#/components/schemas/V1PodOS"},"overhead":{"$ref":"#/components/schemas/V1ResourceList"},"preemptionPolicy":{"type":"string","nullable":true},"priority":{"type":"integer","nullable":true},"priorityClassName":{"type":"string"},"readinessGates":{"type":"array","items":{"$ref":"#/components/schemas/V1PodReadinessGate"}},"resourceClaims":{"type":"array","items":{"$ref":"#/components/schemas/V1PodResourceClaim"}},"resources":{"$ref":"#/components/schemas/V1ResourceRequirements"},"restartPolicy":{"type":"string"},"runtimeClassName":{"type":"string","nullable":true},"schedulerName":{"type":"string"},"schedulingGates":{"type":"array","items":{"$ref":"#/components/schemas/V1PodSchedulingGate"}},"securityContext":{"$ref":"#/components/schemas/V1PodSecurityContext"},"serviceAccount":{"type":"string"},"serviceAccountName":{"type":"string"},"setHostnameAsFQDN":{"type":"boolean","nullable":true},"shareProcessNamespace":{"type":"boolean","nullable":true},"subdomain":{"type":"string"},"terminationGracePeriodSeconds":{"type":"integer","nullable":true},"tolerations":{"type":"array","items":{"$ref":"#/components/schemas/V1Toleration"}},"topologySpreadConstraints":{"type":"array","items":{"$ref":"#/components/schemas/V1TopologySpreadConstraint"}},"volumes":{"type":"array","items":{"$ref":"#/components/schemas/V1Volume"}}}},"V1PodStatus":{"type":"object","properties":{"conditions":{"type":"array","items":{"$ref":"#/components/schemas/V1PodCondition"}},"containerStatuses":{"type":"array","items":{"$ref":"#/components/schemas/V1ContainerStatus"}},"ephemeralContainerStatuses":{"type":"array","items":{"$ref":"#/components/schemas/V1ContainerStatus"}},"hostIP":{"type":"string"},"hostIPs":{"type":"array","items":{"$ref":"#/components/schemas/V1HostIP"}},"initContainerStatuses":{"type":"array","items":{"$ref":"#/components/schemas/V1ContainerStatus"}},"message":{"type":"string"},"nominatedNodeName":{"type":"string"},"observedGeneration":{"type":"integer"},"phase":{"type":"string"},"podIP":{"type":"string"},"podIPs":{"type":"array","items":{"$ref":"#/components/schemas/V1PodIP"}},"qosClass":{"type":"string"},"reason":{"type":"string"},"resize":{"type":"string"},"resourceClaimStatuses":{"type":"array","items":{"$ref":"#/components/schemas/V1PodResourceClaimStatus"}},"startTime":{"type":"string"}}},"V1PortworxVolumeSource":{"type":"object","properties":{"fsType":{"type":"string"},"readOnly":{"type":"boolean"},"volumeID":{"type":"string"}}},"V1PreferredSchedulingTerm":{"type":"object","properties":{"preference":{"$ref":"#/components/schemas/V1NodeSelectorTerm"},"weight":{"type":"integer"}}},"V1Probe":{"type":"object","properties":{"exec":{"$ref":"#/components/schemas/V1ExecAction"},"failureThreshold":{"type":"integer"},"grpc":{"$ref":"#/components/schemas/V1GRPCAction"},"httpGet":{"$ref":"#/components/schemas/V1HTTPGetAction"},"initialDelaySeconds":{"type":"integer"},"periodSeconds":{"type":"integer"},"successThreshold":{"type":"integer"},"tcpSocket":{"$ref":"#/components/schemas/V1TCPSocketAction"},"terminationGracePeriodSeconds":{"type":"integer","nullable":true},"timeoutSeconds":{"type":"integer"}}},"V1ProjectedVolumeSource":{"type":"object","properties":{"defaultMode":{"type":"integer","nullable":true},"sources":{"type":"array","items":{"$ref":"#/components/schemas/V1VolumeProjection"},"nullable":true}}},"V1QuobyteVolumeSource":{"type":"object","properties":{"group":{"type":"string"},"readOnly":{"type":"boolean"},"registry":{"type":"string"},"tenant":{"type":"string"},"user":{"type":"string"},"volume":{"type":"string"}}},"V1RBDVolumeSource":{"type":"object","properties":{"fsType":{"type":"string"},"image":{"type":"string"},"keyring":{"type":"string"},"monitors":{"type":"array","items":{"type":"string"},"nullable":true},"pool":{"type":"string"},"readOnly":{"type":"boolean"},"secretRef":{"$ref":"#/components/schemas/V1LocalObjectReference"},"user":{"type":"string"}}},"V1ResourceClaim":{"type":"object","properties":{"name":{"type":"string"},"request":{"type":"string"}}},"V1ResourceFieldSelector":{"type":"object","properties":{"containerName":{"type":"string"},"divisor":{"$ref":"#/components/schemas/ResourceQuantity"},"resource":{"type":"string"}}},"V1ResourceHealth":{"type":"object","properties":{"health":{"type":"string"},"resourceID":{"type":"string"}}},"V1ResourceList":{"type":"object","additionalProperties":{"$ref":"#/components/schemas/ResourceQuantity"}},"V1ResourceRequirements":{"type":"object","properties":{"claims":{"type":"array","items":{"$ref":"#/components/schemas/V1ResourceClaim"}},"limits":{"$ref":"#/components/schemas/V1ResourceList"},"requests":{"$ref":"#/components/schemas/V1ResourceList"}}},"V1ResourceStatus":{"type":"object","properties":{"name":{"type":"string"},"resources":{"type":"array","items":{"$ref":"#/components/schemas/V1ResourceHealth"}}}},"V1SELinuxOptions":{"type":"object","properties":{"level":{"type":"string"},"role":{"type":"string"},"type":{"type":"string"},"user":{"type":"string"}}},"V1ScaleIOVolumeSource":{"type":"object","properties":{"fsType":{"type":"string"},"gateway":{"type":"string"},"protectionDomain":{"type":"string"},"readOnly":{"type":"boolean"},"secretRef":{"$ref":"#/components/schemas/V1LocalObjectReference"},"sslEnabled":{"type":"boolean"},"storageMode":{"type":"string"},"storagePool":{"type":"string"},"system":{"type":"string"},"volumeName":{"type":"string"}}},"V1SeccompProfile":{"type":"object","properties":{"localhostProfile":{"type":"string","nullable":true},"type":{"type":"string"}}},"V1Secret":{"type":"object","properties":{"apiVersion":{"type":"string"},"data":{"type":"object","additionalProperties":{"type":"string","format":"base64"}},"immutable":{"type":"boolean","nullable":true},"kind":{"type":"string"},"metadata":{"$ref":"#/components/schemas/V1ObjectMeta"},"stringData":{"type":"object","additionalProperties":{"type":"string"}},"type":{"type":"string"}}},"V1SecretEnvSource":{"type":"object","properties":{"name":{"type":"string"},"optional":{"type":"boolean","nullable":true}}},"V1SecretKeySelector":{"type":"object","properties":{"key":{"type":"string"},"name":{"type":"string"},"optional":{"type":"boolean","nullable":true}}},"V1SecretProjection":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/V1KeyToPath"}},"name":{"type":"string"},"optional":{"type":"boolean","nullable":true}}},"V1SecretVolumeSource":{"type":"object","properties":{"defaultMode":{"type":"integer","nullable":true},"items":{"type":"array","items":{"$ref":"#/components/schemas/V1KeyToPath"}},"optional":{"type":"boolean","nullable":true},"secretName":{"type":"string"}}},"V1SecurityContext":{"type":"object","properties":{"allowPrivilegeEscalation":{"type":"boolean","nullable":true},"appArmorProfile":{"$ref":"#/components/schemas/V1AppArmorProfile"},"capabilities":{"$ref":"#/components/schemas/V1Capabilities"},"privileged":{"type":"boolean","nullable":true},"procMount":{"type":"string","nullable":true},"readOnlyRootFilesystem":{"type":"boolean","nullable":true},"runAsGroup":{"type":"integer","nullable":true},"runAsNonRoot":{"type":"boolean","nullable":true},"runAsUser":{"type":"integer","nullable":true},"seLinuxOptions":{"$ref":"#/components/schemas/V1SELinuxOptions"},"seccompProfile":{"$ref":"#/components/schemas/V1SeccompProfile"},"windowsOptions":{"$ref":"#/components/schemas/V1WindowsSecurityContextOptions"}}},"V1ServiceAccountTokenProjection":{"type":"object","properties":{"audience":{"type":"string"},"expirationSeconds":{"type":"integer","nullable":true},"path":{"type":"string"}}},"V1SleepAction":{"type":"object","properties":{"seconds":{"type":"integer"}}},"V1StorageOSVolumeSource":{"type":"object","properties":{"fsType":{"type":"string"},"readOnly":{"type":"boolean"},"secretRef":{"$ref":"#/components/schemas/V1LocalObjectReference"},"volumeName":{"type":"string"},"volumeNamespace":{"type":"string"}}},"V1Sysctl":{"type":"object","properties":{"name":{"type":"string"},"value":{"type":"string"}}},"V1TCPSocketAction":{"type":"object","properties":{"host":{"type":"string"},"port":{"$ref":"#/components/schemas/IntstrIntOrString"}}},"V1Toleration":{"type":"object","properties":{"effect":{"type":"string"},"key":{"type":"string"},"operator":{"type":"string"},"tolerationSeconds":{"type":"integer","nullable":true},"value":{"type":"string"}}},"V1TopologySpreadConstraint":{"type":"object","properties":{"labelSelector":{"$ref":"#/components/schemas/V1LabelSelector"},"matchLabelKeys":{"type":"array","items":{"type":"string"}},"maxSkew":{"type":"integer"},"minDomains":{"type":"integer","nullable":true},"nodeAffinityPolicy":{"type":"string","nullable":true},"nodeTaintsPolicy":{"type":"string","nullable":true},"topologyKey":{"type":"string"},"whenUnsatisfiable":{"type":"string"}}},"V1TypedLocalObjectReference":{"type":"object","properties":{"apiGroup":{"type":"string","nullable":true},"kind":{"type":"string"},"name":{"type":"string"}}},"V1TypedObjectReference":{"type":"object","properties":{"apiGroup":{"type":"string","nullable":true},"kind":{"type":"string"},"name":{"type":"string"},"namespace":{"type":"string","nullable":true}}},"V1Volume":{"type":"object","properties":{"awsElasticBlockStore":{"$ref":"#/components/schemas/V1AWSElasticBlockStoreVolumeSource"},"azureDisk":{"$ref":"#/components/schemas/V1AzureDiskVolumeSource"},"azureFile":{"$ref":"#/components/schemas/V1AzureFileVolumeSource"},"cephfs":{"$ref":"#/components/schemas/V1CephFSVolumeSource"},"cinder":{"$ref":"#/components/schemas/V1CinderVolumeSource"},"configMap":{"$ref":"#/components/schemas/V1ConfigMapVolumeSource"},"csi":{"$ref":"#/components/schemas/V1CSIVolumeSource"},"downwardAPI":{"$ref":"#/components/schemas/V1DownwardAPIVolumeSource"},"emptyDir":{"$ref":"#/components/schemas/V1EmptyDirVolumeSource"},"ephemeral":{"$ref":"#/components/schemas/V1EphemeralVolumeSource"},"fc":{"$ref":"#/components/schemas/V1FCVolumeSource"},"flexVolume":{"$ref":"#/components/schemas/V1FlexVolumeSource"},"flocker":{"$ref":"#/components/schemas/V1FlockerVolumeSource"},"gcePersistentDisk":{"$ref":"#/components/schemas/V1GCEPersistentDiskVolumeSource"},"gitRepo":{"$ref":"#/components/schemas/V1GitRepoVolumeSource"},"glusterfs":{"$ref":"#/components/schemas/V1GlusterfsVolumeSource"},"hostPath":{"$ref":"#/components/schemas/V1HostPathVolumeSource"},"image":{"$ref":"#/components/schemas/V1ImageVolumeSource"},"iscsi":{"$ref":"#/components/schemas/V1ISCSIVolumeSource"},"name":{"type":"string"},"nfs":{"$ref":"#/components/schemas/V1NFSVolumeSource"},"persistentVolumeClaim":{"$ref":"#/components/schemas/V1PersistentVolumeClaimVolumeSource"},"photonPersistentDisk":{"$ref":"#/components/schemas/V1PhotonPersistentDiskVolumeSource"},"portworxVolume":{"$ref":"#/components/schemas/V1PortworxVolumeSource"},"projected":{"$ref":"#/components/schemas/V1ProjectedVolumeSource"},"quobyte":{"$ref":"#/components/schemas/V1QuobyteVolumeSource"},"rbd":{"$ref":"#/components/schemas/V1RBDVolumeSource"},"scaleIO":{"$ref":"#/components/schemas/V1ScaleIOVolumeSource"},"secret":{"$ref":"#/components/schemas/V1SecretVolumeSource"},"storageos":{"$ref":"#/components/schemas/V1StorageOSVolumeSource"},"vsphereVolume":{"$ref":"#/components/schemas/V1VsphereVirtualDiskVolumeSource"}}},"V1VolumeDevice":{"type":"object","properties":{"devicePath":{"type":"string"},"name":{"type":"string"}}},"V1VolumeMount":{"type":"object","properties":{"mountPath":{"type":"string"},"mountPropagation":{"type":"string","nullable":true},"name":{"type":"string"},"readOnly":{"type":"boolean"},"recursiveReadOnly":{"type":"string","nullable":true},"subPath":{"type":"string"},"subPathExpr":{"type":"string"}}},"V1VolumeMountStatus":{"type":"object","properties":{"mountPath":{"type":"string"},"name":{"type":"string"},"readOnly":{"type":"boolean"},"recursiveReadOnly":{"type":"string","nullable":true}}},"V1VolumeProjection":{"type":"object","properties":{"clusterTrustBundle":{"$ref":"#/components/schemas/V1ClusterTrustBundleProjection"},"configMap":{"$ref":"#/components/schemas/V1ConfigMapProjection"},"downwardAPI":{"$ref":"#/components/schemas/V1DownwardAPIProjection"},"secret":{"$ref":"#/components/schemas/V1SecretProjection"},"serviceAccountToken":{"$ref":"#/components/schemas/V1ServiceAccountTokenProjection"}}},"V1VolumeResourceRequirements":{"type":"object","properties":{"limits":{"$ref":"#/components/schemas/V1ResourceList"},"requests":{"$ref":"#/components/schemas/V1ResourceList"}}},"V1VsphereVirtualDiskVolumeSource":{"type":"object","properties":{"fsType":{"type":"string"},"storagePolicyID":{"type":"string"},"storagePolicyName":{"type":"string"},"volumePath":{"type":"string"}}},"V1WeightedPodAffinityTerm":{"type":"object","properties":{"podAffinityTerm":{"$ref":"#/components/schemas/V1PodAffinityTerm"},"weight":{"type":"integer"}}},"V1WindowsSecurityContextOptions":{"type":"object","properties":{"gmsaCredentialSpec":{"type":"string","nullable":true},"gmsaCredentialSpecName":{"type":"string","nullable":true},"hostProcess":{"type":"boolean","nullable":true},"runAsUserName":{"type":"string","nullable":true}}}}}} \ No newline at end of file +{"openapi":"3.0.3","info":{"title":"interLink server API","description":"This is the API spec for the Virtual Kubelet to interLink API server communication","version":"0.4.0"},"paths":{"/create":{"post":{"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/InterlinkPodCreateRequests"}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InterlinkRetrievedPodData"}}}}}}},"/createPodGroup":{"post":{"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/InterlinkPodGroupCreateRequests"}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/InterlinkCreateStruct"}}}}}}}},"/delete":{"post":{"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Pod"}}}},"responses":{"200":{"description":"OK"}}}},"/getLogs":{"post":{"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/InterlinkLogStruct"}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"string"}}}}}}},"/pinglink":{"post":{"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InterlinkPingResponse"}}}}}}},"/status":{"post":{"requestBody":{"content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/V1Pod"},"nullable":true}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/InterlinkPodStatus"}}}}}}}}},"components":{"schemas":{"InterlinkAcceleratorResponse":{"type":"object","properties":{"available":{"type":"string"},"resourceType":{"type":"string"}}},"InterlinkApptainerOptions":{"type":"object","properties":{"cleanenv":{"type":"boolean"},"containall":{"type":"boolean"},"executable":{"type":"string"},"fakeroot":{"type":"boolean"},"fuseMode":{"type":"string"},"noHome":{"type":"boolean"},"noInit":{"type":"boolean"},"noPrivs":{"type":"boolean"},"nvidiaSupport":{"type":"boolean"},"unsquash":{"type":"boolean"}}},"InterlinkContainerLogOpts":{"type":"object","properties":{"Bytes":{"type":"integer"},"Follow":{"type":"boolean"},"Previous":{"type":"boolean"},"SinceSeconds":{"type":"integer"},"SinceTime":{"type":"string","format":"date-time"},"Tail":{"type":"integer"},"Timestamps":{"type":"boolean"}}},"InterlinkCreateStruct":{"type":"object","properties":{"PodJID":{"type":"string"},"PodUID":{"type":"string"}}},"InterlinkLogStruct":{"type":"object","properties":{"ContainerName":{"type":"string"},"Namespace":{"type":"string"},"Opts":{"$ref":"#/components/schemas/InterlinkContainerLogOpts"},"PodName":{"type":"string"},"PodUID":{"type":"string"}}},"InterlinkPingResponse":{"type":"object","properties":{"resources":{"$ref":"#/components/schemas/InterlinkResourcesResponse"},"status":{"type":"string"},"taints":{"type":"array","items":{"$ref":"#/components/schemas/InterlinkTaintResponse"},"nullable":true}}},"InterlinkPodCreateRequests":{"type":"object","properties":{"configmaps":{"type":"array","items":{"$ref":"#/components/schemas/V1ConfigMap"},"nullable":true},"jobscriptURL":{"type":"string"},"pod":{"$ref":"#/components/schemas/V1Pod"},"projectedvolumesmaps":{"type":"array","items":{"$ref":"#/components/schemas/V1ConfigMap"},"nullable":true},"secrets":{"type":"array","items":{"$ref":"#/components/schemas/V1Secret"},"nullable":true}}},"InterlinkPodGroupCreateRequests":{"type":"object","properties":{"groupUID":{"type":"string"},"pods":{"type":"array","items":{"$ref":"#/components/schemas/InterlinkPodCreateRequests"},"nullable":true}}},"InterlinkPodStatus":{"type":"object","properties":{"JID":{"type":"string"},"UID":{"type":"string"},"containers":{"type":"array","items":{"$ref":"#/components/schemas/V1ContainerStatus"},"nullable":true},"initContainers":{"type":"array","items":{"$ref":"#/components/schemas/V1ContainerStatus"},"nullable":true},"name":{"type":"string"},"namespace":{"type":"string"},"nodeName":{"type":"string"},"socketDir":{"type":"string"}}},"InterlinkResourcesResponse":{"type":"object","properties":{"accelerators":{"type":"array","items":{"$ref":"#/components/schemas/InterlinkAcceleratorResponse"}},"cpu":{"type":"string"},"memory":{"type":"string"},"pods":{"type":"string"}}},"InterlinkRetrievedContainer":{"type":"object","properties":{"configMaps":{"type":"array","items":{"$ref":"#/components/schemas/V1ConfigMap"},"nullable":true},"emptyDirs":{"type":"array","items":{"type":"string"},"nullable":true},"name":{"type":"string"},"projectedvolumemaps":{"type":"array","items":{"$ref":"#/components/schemas/V1ConfigMap"},"nullable":true},"secrets":{"type":"array","items":{"$ref":"#/components/schemas/V1Secret"},"nullable":true}}},"InterlinkRetrievedPodData":{"type":"object","properties":{"container":{"type":"array","items":{"$ref":"#/components/schemas/InterlinkRetrievedContainer"},"nullable":true},"jobConfig":{"$ref":"#/components/schemas/InterlinkScriptBuildConfig"},"jobScript":{"type":"string"},"pod":{"$ref":"#/components/schemas/V1Pod"}}},"InterlinkScriptBuildConfig":{"type":"object","properties":{"ApptainerOptions":{"$ref":"#/components/schemas/InterlinkApptainerOptions"},"SingularityHubProxy":{"$ref":"#/components/schemas/InterlinkSingularityHubConfig"},"Volumes":{"$ref":"#/components/schemas/InterlinkVolumesOptions"}}},"InterlinkSingularityHubConfig":{"type":"object","properties":{"cache_validity_seconds":{"type":"integer"},"master_token":{"type":"string"},"server":{"type":"string"}}},"InterlinkTaintResponse":{"type":"object","properties":{"effect":{"type":"string"},"key":{"type":"string"},"value":{"type":"string"}}},"InterlinkVolumesOptions":{"type":"object","properties":{"additional_directories_in_path":{"type":"array","items":{"type":"string"},"nullable":true},"apptainer_cachedir":{"type":"string"},"fuse_sleep_seconds":{"type":"integer"},"image_dir":{"type":"string"},"scratch_area":{"type":"string"}}},"IntstrIntOrString":{"type":"object"},"ResourceQuantity":{"type":"object"},"V1AWSElasticBlockStoreVolumeSource":{"type":"object","properties":{"fsType":{"type":"string"},"partition":{"type":"integer"},"readOnly":{"type":"boolean"},"volumeID":{"type":"string"}}},"V1Affinity":{"type":"object","properties":{"nodeAffinity":{"$ref":"#/components/schemas/V1NodeAffinity"},"podAffinity":{"$ref":"#/components/schemas/V1PodAffinity"},"podAntiAffinity":{"$ref":"#/components/schemas/V1PodAntiAffinity"}}},"V1AppArmorProfile":{"type":"object","properties":{"localhostProfile":{"type":"string","nullable":true},"type":{"type":"string"}}},"V1AzureDiskVolumeSource":{"type":"object","properties":{"cachingMode":{"type":"string","nullable":true},"diskName":{"type":"string"},"diskURI":{"type":"string"},"fsType":{"type":"string","nullable":true},"kind":{"type":"string","nullable":true},"readOnly":{"type":"boolean","nullable":true}}},"V1AzureFileVolumeSource":{"type":"object","properties":{"readOnly":{"type":"boolean"},"secretName":{"type":"string"},"shareName":{"type":"string"}}},"V1CSIVolumeSource":{"type":"object","properties":{"driver":{"type":"string"},"fsType":{"type":"string","nullable":true},"nodePublishSecretRef":{"$ref":"#/components/schemas/V1LocalObjectReference"},"readOnly":{"type":"boolean","nullable":true},"volumeAttributes":{"type":"object","additionalProperties":{"type":"string"}}}},"V1Capabilities":{"type":"object","properties":{"add":{"type":"array","items":{"type":"string"}},"drop":{"type":"array","items":{"type":"string"}}}},"V1CephFSVolumeSource":{"type":"object","properties":{"monitors":{"type":"array","items":{"type":"string"},"nullable":true},"path":{"type":"string"},"readOnly":{"type":"boolean"},"secretFile":{"type":"string"},"secretRef":{"$ref":"#/components/schemas/V1LocalObjectReference"},"user":{"type":"string"}}},"V1CinderVolumeSource":{"type":"object","properties":{"fsType":{"type":"string"},"readOnly":{"type":"boolean"},"secretRef":{"$ref":"#/components/schemas/V1LocalObjectReference"},"volumeID":{"type":"string"}}},"V1ClusterTrustBundleProjection":{"type":"object","properties":{"labelSelector":{"$ref":"#/components/schemas/V1LabelSelector"},"name":{"type":"string","nullable":true},"optional":{"type":"boolean","nullable":true},"path":{"type":"string"},"signerName":{"type":"string","nullable":true}}},"V1ConfigMap":{"type":"object","properties":{"apiVersion":{"type":"string"},"binaryData":{"type":"object","additionalProperties":{"type":"string","format":"base64"}},"data":{"type":"object","additionalProperties":{"type":"string"}},"immutable":{"type":"boolean","nullable":true},"kind":{"type":"string"},"metadata":{"$ref":"#/components/schemas/V1ObjectMeta"}}},"V1ConfigMapEnvSource":{"type":"object","properties":{"name":{"type":"string"},"optional":{"type":"boolean","nullable":true}}},"V1ConfigMapKeySelector":{"type":"object","properties":{"key":{"type":"string"},"name":{"type":"string"},"optional":{"type":"boolean","nullable":true}}},"V1ConfigMapProjection":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/V1KeyToPath"}},"name":{"type":"string"},"optional":{"type":"boolean","nullable":true}}},"V1ConfigMapVolumeSource":{"type":"object","properties":{"defaultMode":{"type":"integer","nullable":true},"items":{"type":"array","items":{"$ref":"#/components/schemas/V1KeyToPath"}},"name":{"type":"string"},"optional":{"type":"boolean","nullable":true}}},"V1Container":{"type":"object","properties":{"args":{"type":"array","items":{"type":"string"}},"command":{"type":"array","items":{"type":"string"}},"env":{"type":"array","items":{"$ref":"#/components/schemas/V1EnvVar"}},"envFrom":{"type":"array","items":{"$ref":"#/components/schemas/V1EnvFromSource"}},"image":{"type":"string"},"imagePullPolicy":{"type":"string"},"lifecycle":{"$ref":"#/components/schemas/V1Lifecycle"},"livenessProbe":{"$ref":"#/components/schemas/V1Probe"},"name":{"type":"string"},"ports":{"type":"array","items":{"$ref":"#/components/schemas/V1ContainerPort"}},"readinessProbe":{"$ref":"#/components/schemas/V1Probe"},"resizePolicy":{"type":"array","items":{"$ref":"#/components/schemas/V1ContainerResizePolicy"}},"resources":{"$ref":"#/components/schemas/V1ResourceRequirements"},"restartPolicy":{"type":"string","nullable":true},"securityContext":{"$ref":"#/components/schemas/V1SecurityContext"},"startupProbe":{"$ref":"#/components/schemas/V1Probe"},"stdin":{"type":"boolean"},"stdinOnce":{"type":"boolean"},"terminationMessagePath":{"type":"string"},"terminationMessagePolicy":{"type":"string"},"tty":{"type":"boolean"},"volumeDevices":{"type":"array","items":{"$ref":"#/components/schemas/V1VolumeDevice"}},"volumeMounts":{"type":"array","items":{"$ref":"#/components/schemas/V1VolumeMount"}},"workingDir":{"type":"string"}}},"V1ContainerPort":{"type":"object","properties":{"containerPort":{"type":"integer"},"hostIP":{"type":"string"},"hostPort":{"type":"integer"},"name":{"type":"string"},"protocol":{"type":"string"}}},"V1ContainerResizePolicy":{"type":"object","properties":{"resourceName":{"type":"string"},"restartPolicy":{"type":"string"}}},"V1ContainerState":{"type":"object","properties":{"running":{"$ref":"#/components/schemas/V1ContainerStateRunning"},"terminated":{"$ref":"#/components/schemas/V1ContainerStateTerminated"},"waiting":{"$ref":"#/components/schemas/V1ContainerStateWaiting"}}},"V1ContainerStateRunning":{"type":"object","properties":{"startedAt":{"type":"string"}}},"V1ContainerStateTerminated":{"type":"object","properties":{"containerID":{"type":"string"},"exitCode":{"type":"integer"},"finishedAt":{"type":"string"},"message":{"type":"string"},"reason":{"type":"string"},"signal":{"type":"integer"},"startedAt":{"type":"string"}}},"V1ContainerStateWaiting":{"type":"object","properties":{"message":{"type":"string"},"reason":{"type":"string"}}},"V1ContainerStatus":{"type":"object","properties":{"allocatedResources":{"$ref":"#/components/schemas/V1ResourceList"},"allocatedResourcesStatus":{"type":"array","items":{"$ref":"#/components/schemas/V1ResourceStatus"}},"containerID":{"type":"string"},"image":{"type":"string"},"imageID":{"type":"string"},"lastState":{"$ref":"#/components/schemas/V1ContainerState"},"name":{"type":"string"},"ready":{"type":"boolean"},"resources":{"$ref":"#/components/schemas/V1ResourceRequirements"},"restartCount":{"type":"integer"},"started":{"type":"boolean","nullable":true},"state":{"$ref":"#/components/schemas/V1ContainerState"},"stopSignal":{"type":"string","nullable":true},"user":{"$ref":"#/components/schemas/V1ContainerUser"},"volumeMounts":{"type":"array","items":{"$ref":"#/components/schemas/V1VolumeMountStatus"}}}},"V1ContainerUser":{"type":"object","properties":{"linux":{"$ref":"#/components/schemas/V1LinuxContainerUser"}}},"V1DownwardAPIProjection":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/V1DownwardAPIVolumeFile"}}}},"V1DownwardAPIVolumeFile":{"type":"object","properties":{"fieldRef":{"$ref":"#/components/schemas/V1ObjectFieldSelector"},"mode":{"type":"integer","nullable":true},"path":{"type":"string"},"resourceFieldRef":{"$ref":"#/components/schemas/V1ResourceFieldSelector"}}},"V1DownwardAPIVolumeSource":{"type":"object","properties":{"defaultMode":{"type":"integer","nullable":true},"items":{"type":"array","items":{"$ref":"#/components/schemas/V1DownwardAPIVolumeFile"}}}},"V1EmptyDirVolumeSource":{"type":"object","properties":{"medium":{"type":"string"},"sizeLimit":{"$ref":"#/components/schemas/ResourceQuantity"}}},"V1EnvFromSource":{"type":"object","properties":{"configMapRef":{"$ref":"#/components/schemas/V1ConfigMapEnvSource"},"prefix":{"type":"string"},"secretRef":{"$ref":"#/components/schemas/V1SecretEnvSource"}}},"V1EnvVar":{"type":"object","properties":{"name":{"type":"string"},"value":{"type":"string"},"valueFrom":{"$ref":"#/components/schemas/V1EnvVarSource"}}},"V1EnvVarSource":{"type":"object","properties":{"configMapKeyRef":{"$ref":"#/components/schemas/V1ConfigMapKeySelector"},"fieldRef":{"$ref":"#/components/schemas/V1ObjectFieldSelector"},"resourceFieldRef":{"$ref":"#/components/schemas/V1ResourceFieldSelector"},"secretKeyRef":{"$ref":"#/components/schemas/V1SecretKeySelector"}}},"V1EphemeralContainer":{"type":"object","properties":{"args":{"type":"array","items":{"type":"string"}},"command":{"type":"array","items":{"type":"string"}},"env":{"type":"array","items":{"$ref":"#/components/schemas/V1EnvVar"}},"envFrom":{"type":"array","items":{"$ref":"#/components/schemas/V1EnvFromSource"}},"image":{"type":"string"},"imagePullPolicy":{"type":"string"},"lifecycle":{"$ref":"#/components/schemas/V1Lifecycle"},"livenessProbe":{"$ref":"#/components/schemas/V1Probe"},"name":{"type":"string"},"ports":{"type":"array","items":{"$ref":"#/components/schemas/V1ContainerPort"}},"readinessProbe":{"$ref":"#/components/schemas/V1Probe"},"resizePolicy":{"type":"array","items":{"$ref":"#/components/schemas/V1ContainerResizePolicy"}},"resources":{"$ref":"#/components/schemas/V1ResourceRequirements"},"restartPolicy":{"type":"string","nullable":true},"securityContext":{"$ref":"#/components/schemas/V1SecurityContext"},"startupProbe":{"$ref":"#/components/schemas/V1Probe"},"stdin":{"type":"boolean"},"stdinOnce":{"type":"boolean"},"targetContainerName":{"type":"string"},"terminationMessagePath":{"type":"string"},"terminationMessagePolicy":{"type":"string"},"tty":{"type":"boolean"},"volumeDevices":{"type":"array","items":{"$ref":"#/components/schemas/V1VolumeDevice"}},"volumeMounts":{"type":"array","items":{"$ref":"#/components/schemas/V1VolumeMount"}},"workingDir":{"type":"string"}}},"V1EphemeralVolumeSource":{"type":"object","properties":{"volumeClaimTemplate":{"$ref":"#/components/schemas/V1PersistentVolumeClaimTemplate"}}},"V1ExecAction":{"type":"object","properties":{"command":{"type":"array","items":{"type":"string"}}}},"V1FCVolumeSource":{"type":"object","properties":{"fsType":{"type":"string"},"lun":{"type":"integer","nullable":true},"readOnly":{"type":"boolean"},"targetWWNs":{"type":"array","items":{"type":"string"}},"wwids":{"type":"array","items":{"type":"string"}}}},"V1FieldsV1":{"type":"object"},"V1FlexVolumeSource":{"type":"object","properties":{"driver":{"type":"string"},"fsType":{"type":"string"},"options":{"type":"object","additionalProperties":{"type":"string"}},"readOnly":{"type":"boolean"},"secretRef":{"$ref":"#/components/schemas/V1LocalObjectReference"}}},"V1FlockerVolumeSource":{"type":"object","properties":{"datasetName":{"type":"string"},"datasetUUID":{"type":"string"}}},"V1GCEPersistentDiskVolumeSource":{"type":"object","properties":{"fsType":{"type":"string"},"partition":{"type":"integer"},"pdName":{"type":"string"},"readOnly":{"type":"boolean"}}},"V1GRPCAction":{"type":"object","properties":{"port":{"type":"integer"},"service":{"type":"string","nullable":true}}},"V1GitRepoVolumeSource":{"type":"object","properties":{"directory":{"type":"string"},"repository":{"type":"string"},"revision":{"type":"string"}}},"V1GlusterfsVolumeSource":{"type":"object","properties":{"endpoints":{"type":"string"},"path":{"type":"string"},"readOnly":{"type":"boolean"}}},"V1HTTPGetAction":{"type":"object","properties":{"host":{"type":"string"},"httpHeaders":{"type":"array","items":{"$ref":"#/components/schemas/V1HTTPHeader"}},"path":{"type":"string"},"port":{"$ref":"#/components/schemas/IntstrIntOrString"},"scheme":{"type":"string"}}},"V1HTTPHeader":{"type":"object","properties":{"name":{"type":"string"},"value":{"type":"string"}}},"V1HostAlias":{"type":"object","properties":{"hostnames":{"type":"array","items":{"type":"string"}},"ip":{"type":"string"}}},"V1HostIP":{"type":"object","properties":{"ip":{"type":"string"}}},"V1HostPathVolumeSource":{"type":"object","properties":{"path":{"type":"string"},"type":{"type":"string","nullable":true}}},"V1ISCSIVolumeSource":{"type":"object","properties":{"chapAuthDiscovery":{"type":"boolean"},"chapAuthSession":{"type":"boolean"},"fsType":{"type":"string"},"initiatorName":{"type":"string","nullable":true},"iqn":{"type":"string"},"iscsiInterface":{"type":"string"},"lun":{"type":"integer"},"portals":{"type":"array","items":{"type":"string"}},"readOnly":{"type":"boolean"},"secretRef":{"$ref":"#/components/schemas/V1LocalObjectReference"},"targetPortal":{"type":"string"}}},"V1ImageVolumeSource":{"type":"object","properties":{"pullPolicy":{"type":"string"},"reference":{"type":"string"}}},"V1KeyToPath":{"type":"object","properties":{"key":{"type":"string"},"mode":{"type":"integer","nullable":true},"path":{"type":"string"}}},"V1LabelSelector":{"type":"object","properties":{"matchExpressions":{"type":"array","items":{"$ref":"#/components/schemas/V1LabelSelectorRequirement"}},"matchLabels":{"type":"object","additionalProperties":{"type":"string"}}}},"V1LabelSelectorRequirement":{"type":"object","properties":{"key":{"type":"string"},"operator":{"type":"string"},"values":{"type":"array","items":{"type":"string"}}}},"V1Lifecycle":{"type":"object","properties":{"postStart":{"$ref":"#/components/schemas/V1LifecycleHandler"},"preStop":{"$ref":"#/components/schemas/V1LifecycleHandler"},"stopSignal":{"type":"string","nullable":true}}},"V1LifecycleHandler":{"type":"object","properties":{"exec":{"$ref":"#/components/schemas/V1ExecAction"},"httpGet":{"$ref":"#/components/schemas/V1HTTPGetAction"},"sleep":{"$ref":"#/components/schemas/V1SleepAction"},"tcpSocket":{"$ref":"#/components/schemas/V1TCPSocketAction"}}},"V1LinuxContainerUser":{"type":"object","properties":{"gid":{"type":"integer"},"supplementalGroups":{"type":"array","items":{"type":"integer"}},"uid":{"type":"integer"}}},"V1LocalObjectReference":{"type":"object","properties":{"name":{"type":"string"}}},"V1ManagedFieldsEntry":{"type":"object","properties":{"apiVersion":{"type":"string"},"fieldsType":{"type":"string"},"fieldsV1":{"$ref":"#/components/schemas/V1FieldsV1"},"manager":{"type":"string"},"operation":{"type":"string"},"subresource":{"type":"string"},"time":{"type":"string"}}},"V1NFSVolumeSource":{"type":"object","properties":{"path":{"type":"string"},"readOnly":{"type":"boolean"},"server":{"type":"string"}}},"V1NodeAffinity":{"type":"object","properties":{"preferredDuringSchedulingIgnoredDuringExecution":{"type":"array","items":{"$ref":"#/components/schemas/V1PreferredSchedulingTerm"}},"requiredDuringSchedulingIgnoredDuringExecution":{"$ref":"#/components/schemas/V1NodeSelector"}}},"V1NodeSelector":{"type":"object","properties":{"nodeSelectorTerms":{"type":"array","items":{"$ref":"#/components/schemas/V1NodeSelectorTerm"},"nullable":true}}},"V1NodeSelectorRequirement":{"type":"object","properties":{"key":{"type":"string"},"operator":{"type":"string"},"values":{"type":"array","items":{"type":"string"}}}},"V1NodeSelectorTerm":{"type":"object","properties":{"matchExpressions":{"type":"array","items":{"$ref":"#/components/schemas/V1NodeSelectorRequirement"}},"matchFields":{"type":"array","items":{"$ref":"#/components/schemas/V1NodeSelectorRequirement"}}}},"V1ObjectFieldSelector":{"type":"object","properties":{"apiVersion":{"type":"string"},"fieldPath":{"type":"string"}}},"V1ObjectMeta":{"type":"object","properties":{"annotations":{"type":"object","additionalProperties":{"type":"string"}},"creationTimestamp":{"type":"string"},"deletionGracePeriodSeconds":{"type":"integer","nullable":true},"deletionTimestamp":{"type":"string"},"finalizers":{"type":"array","items":{"type":"string"}},"generateName":{"type":"string"},"generation":{"type":"integer"},"labels":{"type":"object","additionalProperties":{"type":"string"}},"managedFields":{"type":"array","items":{"$ref":"#/components/schemas/V1ManagedFieldsEntry"}},"name":{"type":"string"},"namespace":{"type":"string"},"ownerReferences":{"type":"array","items":{"$ref":"#/components/schemas/V1OwnerReference"}},"resourceVersion":{"type":"string"},"selfLink":{"type":"string"},"uid":{"type":"string"}}},"V1OwnerReference":{"type":"object","properties":{"apiVersion":{"type":"string"},"blockOwnerDeletion":{"type":"boolean","nullable":true},"controller":{"type":"boolean","nullable":true},"kind":{"type":"string"},"name":{"type":"string"},"uid":{"type":"string"}}},"V1PersistentVolumeClaimSpec":{"type":"object","properties":{"accessModes":{"type":"array","items":{"type":"string"}},"dataSource":{"$ref":"#/components/schemas/V1TypedLocalObjectReference"},"dataSourceRef":{"$ref":"#/components/schemas/V1TypedObjectReference"},"resources":{"$ref":"#/components/schemas/V1VolumeResourceRequirements"},"selector":{"$ref":"#/components/schemas/V1LabelSelector"},"storageClassName":{"type":"string","nullable":true},"volumeAttributesClassName":{"type":"string","nullable":true},"volumeMode":{"type":"string","nullable":true},"volumeName":{"type":"string"}}},"V1PersistentVolumeClaimTemplate":{"type":"object","properties":{"metadata":{"$ref":"#/components/schemas/V1ObjectMeta"},"spec":{"$ref":"#/components/schemas/V1PersistentVolumeClaimSpec"}}},"V1PersistentVolumeClaimVolumeSource":{"type":"object","properties":{"claimName":{"type":"string"},"readOnly":{"type":"boolean"}}},"V1PhotonPersistentDiskVolumeSource":{"type":"object","properties":{"fsType":{"type":"string"},"pdID":{"type":"string"}}},"V1Pod":{"type":"object","properties":{"apiVersion":{"type":"string"},"kind":{"type":"string"},"metadata":{"$ref":"#/components/schemas/V1ObjectMeta"},"spec":{"$ref":"#/components/schemas/V1PodSpec"},"status":{"$ref":"#/components/schemas/V1PodStatus"}}},"V1PodAffinity":{"type":"object","properties":{"preferredDuringSchedulingIgnoredDuringExecution":{"type":"array","items":{"$ref":"#/components/schemas/V1WeightedPodAffinityTerm"}},"requiredDuringSchedulingIgnoredDuringExecution":{"type":"array","items":{"$ref":"#/components/schemas/V1PodAffinityTerm"}}}},"V1PodAffinityTerm":{"type":"object","properties":{"labelSelector":{"$ref":"#/components/schemas/V1LabelSelector"},"matchLabelKeys":{"type":"array","items":{"type":"string"}},"mismatchLabelKeys":{"type":"array","items":{"type":"string"}},"namespaceSelector":{"$ref":"#/components/schemas/V1LabelSelector"},"namespaces":{"type":"array","items":{"type":"string"}},"topologyKey":{"type":"string"}}},"V1PodAntiAffinity":{"type":"object","properties":{"preferredDuringSchedulingIgnoredDuringExecution":{"type":"array","items":{"$ref":"#/components/schemas/V1WeightedPodAffinityTerm"}},"requiredDuringSchedulingIgnoredDuringExecution":{"type":"array","items":{"$ref":"#/components/schemas/V1PodAffinityTerm"}}}},"V1PodCondition":{"type":"object","properties":{"lastProbeTime":{"type":"string"},"lastTransitionTime":{"type":"string"},"message":{"type":"string"},"observedGeneration":{"type":"integer"},"reason":{"type":"string"},"status":{"type":"string"},"type":{"type":"string"}}},"V1PodDNSConfig":{"type":"object","properties":{"nameservers":{"type":"array","items":{"type":"string"}},"options":{"type":"array","items":{"$ref":"#/components/schemas/V1PodDNSConfigOption"}},"searches":{"type":"array","items":{"type":"string"}}}},"V1PodDNSConfigOption":{"type":"object","properties":{"name":{"type":"string"},"value":{"type":"string","nullable":true}}},"V1PodIP":{"type":"object","properties":{"ip":{"type":"string"}}},"V1PodOS":{"type":"object","properties":{"name":{"type":"string"}}},"V1PodReadinessGate":{"type":"object","properties":{"conditionType":{"type":"string"}}},"V1PodResourceClaim":{"type":"object","properties":{"name":{"type":"string"},"resourceClaimName":{"type":"string","nullable":true},"resourceClaimTemplateName":{"type":"string","nullable":true}}},"V1PodResourceClaimStatus":{"type":"object","properties":{"name":{"type":"string"},"resourceClaimName":{"type":"string","nullable":true}}},"V1PodSchedulingGate":{"type":"object","properties":{"name":{"type":"string"}}},"V1PodSecurityContext":{"type":"object","properties":{"appArmorProfile":{"$ref":"#/components/schemas/V1AppArmorProfile"},"fsGroup":{"type":"integer","nullable":true},"fsGroupChangePolicy":{"type":"string","nullable":true},"runAsGroup":{"type":"integer","nullable":true},"runAsNonRoot":{"type":"boolean","nullable":true},"runAsUser":{"type":"integer","nullable":true},"seLinuxChangePolicy":{"type":"string","nullable":true},"seLinuxOptions":{"$ref":"#/components/schemas/V1SELinuxOptions"},"seccompProfile":{"$ref":"#/components/schemas/V1SeccompProfile"},"supplementalGroups":{"type":"array","items":{"type":"integer"}},"supplementalGroupsPolicy":{"type":"string","nullable":true},"sysctls":{"type":"array","items":{"$ref":"#/components/schemas/V1Sysctl"}},"windowsOptions":{"$ref":"#/components/schemas/V1WindowsSecurityContextOptions"}}},"V1PodSpec":{"type":"object","properties":{"activeDeadlineSeconds":{"type":"integer","nullable":true},"affinity":{"$ref":"#/components/schemas/V1Affinity"},"automountServiceAccountToken":{"type":"boolean","nullable":true},"containers":{"type":"array","items":{"$ref":"#/components/schemas/V1Container"},"nullable":true},"dnsConfig":{"$ref":"#/components/schemas/V1PodDNSConfig"},"dnsPolicy":{"type":"string"},"enableServiceLinks":{"type":"boolean","nullable":true},"ephemeralContainers":{"type":"array","items":{"$ref":"#/components/schemas/V1EphemeralContainer"}},"hostAliases":{"type":"array","items":{"$ref":"#/components/schemas/V1HostAlias"}},"hostIPC":{"type":"boolean"},"hostNetwork":{"type":"boolean"},"hostPID":{"type":"boolean"},"hostUsers":{"type":"boolean","nullable":true},"hostname":{"type":"string"},"imagePullSecrets":{"type":"array","items":{"$ref":"#/components/schemas/V1LocalObjectReference"}},"initContainers":{"type":"array","items":{"$ref":"#/components/schemas/V1Container"}},"nodeName":{"type":"string"},"nodeSelector":{"type":"object","additionalProperties":{"type":"string"}},"os":{"$ref":"#/components/schemas/V1PodOS"},"overhead":{"$ref":"#/components/schemas/V1ResourceList"},"preemptionPolicy":{"type":"string","nullable":true},"priority":{"type":"integer","nullable":true},"priorityClassName":{"type":"string"},"readinessGates":{"type":"array","items":{"$ref":"#/components/schemas/V1PodReadinessGate"}},"resourceClaims":{"type":"array","items":{"$ref":"#/components/schemas/V1PodResourceClaim"}},"resources":{"$ref":"#/components/schemas/V1ResourceRequirements"},"restartPolicy":{"type":"string"},"runtimeClassName":{"type":"string","nullable":true},"schedulerName":{"type":"string"},"schedulingGates":{"type":"array","items":{"$ref":"#/components/schemas/V1PodSchedulingGate"}},"securityContext":{"$ref":"#/components/schemas/V1PodSecurityContext"},"serviceAccount":{"type":"string"},"serviceAccountName":{"type":"string"},"setHostnameAsFQDN":{"type":"boolean","nullable":true},"shareProcessNamespace":{"type":"boolean","nullable":true},"subdomain":{"type":"string"},"terminationGracePeriodSeconds":{"type":"integer","nullable":true},"tolerations":{"type":"array","items":{"$ref":"#/components/schemas/V1Toleration"}},"topologySpreadConstraints":{"type":"array","items":{"$ref":"#/components/schemas/V1TopologySpreadConstraint"}},"volumes":{"type":"array","items":{"$ref":"#/components/schemas/V1Volume"}}}},"V1PodStatus":{"type":"object","properties":{"conditions":{"type":"array","items":{"$ref":"#/components/schemas/V1PodCondition"}},"containerStatuses":{"type":"array","items":{"$ref":"#/components/schemas/V1ContainerStatus"}},"ephemeralContainerStatuses":{"type":"array","items":{"$ref":"#/components/schemas/V1ContainerStatus"}},"hostIP":{"type":"string"},"hostIPs":{"type":"array","items":{"$ref":"#/components/schemas/V1HostIP"}},"initContainerStatuses":{"type":"array","items":{"$ref":"#/components/schemas/V1ContainerStatus"}},"message":{"type":"string"},"nominatedNodeName":{"type":"string"},"observedGeneration":{"type":"integer"},"phase":{"type":"string"},"podIP":{"type":"string"},"podIPs":{"type":"array","items":{"$ref":"#/components/schemas/V1PodIP"}},"qosClass":{"type":"string"},"reason":{"type":"string"},"resize":{"type":"string"},"resourceClaimStatuses":{"type":"array","items":{"$ref":"#/components/schemas/V1PodResourceClaimStatus"}},"startTime":{"type":"string"}}},"V1PortworxVolumeSource":{"type":"object","properties":{"fsType":{"type":"string"},"readOnly":{"type":"boolean"},"volumeID":{"type":"string"}}},"V1PreferredSchedulingTerm":{"type":"object","properties":{"preference":{"$ref":"#/components/schemas/V1NodeSelectorTerm"},"weight":{"type":"integer"}}},"V1Probe":{"type":"object","properties":{"exec":{"$ref":"#/components/schemas/V1ExecAction"},"failureThreshold":{"type":"integer"},"grpc":{"$ref":"#/components/schemas/V1GRPCAction"},"httpGet":{"$ref":"#/components/schemas/V1HTTPGetAction"},"initialDelaySeconds":{"type":"integer"},"periodSeconds":{"type":"integer"},"successThreshold":{"type":"integer"},"tcpSocket":{"$ref":"#/components/schemas/V1TCPSocketAction"},"terminationGracePeriodSeconds":{"type":"integer","nullable":true},"timeoutSeconds":{"type":"integer"}}},"V1ProjectedVolumeSource":{"type":"object","properties":{"defaultMode":{"type":"integer","nullable":true},"sources":{"type":"array","items":{"$ref":"#/components/schemas/V1VolumeProjection"},"nullable":true}}},"V1QuobyteVolumeSource":{"type":"object","properties":{"group":{"type":"string"},"readOnly":{"type":"boolean"},"registry":{"type":"string"},"tenant":{"type":"string"},"user":{"type":"string"},"volume":{"type":"string"}}},"V1RBDVolumeSource":{"type":"object","properties":{"fsType":{"type":"string"},"image":{"type":"string"},"keyring":{"type":"string"},"monitors":{"type":"array","items":{"type":"string"},"nullable":true},"pool":{"type":"string"},"readOnly":{"type":"boolean"},"secretRef":{"$ref":"#/components/schemas/V1LocalObjectReference"},"user":{"type":"string"}}},"V1ResourceClaim":{"type":"object","properties":{"name":{"type":"string"},"request":{"type":"string"}}},"V1ResourceFieldSelector":{"type":"object","properties":{"containerName":{"type":"string"},"divisor":{"$ref":"#/components/schemas/ResourceQuantity"},"resource":{"type":"string"}}},"V1ResourceHealth":{"type":"object","properties":{"health":{"type":"string"},"resourceID":{"type":"string"}}},"V1ResourceList":{"type":"object","additionalProperties":{"$ref":"#/components/schemas/ResourceQuantity"}},"V1ResourceRequirements":{"type":"object","properties":{"claims":{"type":"array","items":{"$ref":"#/components/schemas/V1ResourceClaim"}},"limits":{"$ref":"#/components/schemas/V1ResourceList"},"requests":{"$ref":"#/components/schemas/V1ResourceList"}}},"V1ResourceStatus":{"type":"object","properties":{"name":{"type":"string"},"resources":{"type":"array","items":{"$ref":"#/components/schemas/V1ResourceHealth"}}}},"V1SELinuxOptions":{"type":"object","properties":{"level":{"type":"string"},"role":{"type":"string"},"type":{"type":"string"},"user":{"type":"string"}}},"V1ScaleIOVolumeSource":{"type":"object","properties":{"fsType":{"type":"string"},"gateway":{"type":"string"},"protectionDomain":{"type":"string"},"readOnly":{"type":"boolean"},"secretRef":{"$ref":"#/components/schemas/V1LocalObjectReference"},"sslEnabled":{"type":"boolean"},"storageMode":{"type":"string"},"storagePool":{"type":"string"},"system":{"type":"string"},"volumeName":{"type":"string"}}},"V1SeccompProfile":{"type":"object","properties":{"localhostProfile":{"type":"string","nullable":true},"type":{"type":"string"}}},"V1Secret":{"type":"object","properties":{"apiVersion":{"type":"string"},"data":{"type":"object","additionalProperties":{"type":"string","format":"base64"}},"immutable":{"type":"boolean","nullable":true},"kind":{"type":"string"},"metadata":{"$ref":"#/components/schemas/V1ObjectMeta"},"stringData":{"type":"object","additionalProperties":{"type":"string"}},"type":{"type":"string"}}},"V1SecretEnvSource":{"type":"object","properties":{"name":{"type":"string"},"optional":{"type":"boolean","nullable":true}}},"V1SecretKeySelector":{"type":"object","properties":{"key":{"type":"string"},"name":{"type":"string"},"optional":{"type":"boolean","nullable":true}}},"V1SecretProjection":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/V1KeyToPath"}},"name":{"type":"string"},"optional":{"type":"boolean","nullable":true}}},"V1SecretVolumeSource":{"type":"object","properties":{"defaultMode":{"type":"integer","nullable":true},"items":{"type":"array","items":{"$ref":"#/components/schemas/V1KeyToPath"}},"optional":{"type":"boolean","nullable":true},"secretName":{"type":"string"}}},"V1SecurityContext":{"type":"object","properties":{"allowPrivilegeEscalation":{"type":"boolean","nullable":true},"appArmorProfile":{"$ref":"#/components/schemas/V1AppArmorProfile"},"capabilities":{"$ref":"#/components/schemas/V1Capabilities"},"privileged":{"type":"boolean","nullable":true},"procMount":{"type":"string","nullable":true},"readOnlyRootFilesystem":{"type":"boolean","nullable":true},"runAsGroup":{"type":"integer","nullable":true},"runAsNonRoot":{"type":"boolean","nullable":true},"runAsUser":{"type":"integer","nullable":true},"seLinuxOptions":{"$ref":"#/components/schemas/V1SELinuxOptions"},"seccompProfile":{"$ref":"#/components/schemas/V1SeccompProfile"},"windowsOptions":{"$ref":"#/components/schemas/V1WindowsSecurityContextOptions"}}},"V1ServiceAccountTokenProjection":{"type":"object","properties":{"audience":{"type":"string"},"expirationSeconds":{"type":"integer","nullable":true},"path":{"type":"string"}}},"V1SleepAction":{"type":"object","properties":{"seconds":{"type":"integer"}}},"V1StorageOSVolumeSource":{"type":"object","properties":{"fsType":{"type":"string"},"readOnly":{"type":"boolean"},"secretRef":{"$ref":"#/components/schemas/V1LocalObjectReference"},"volumeName":{"type":"string"},"volumeNamespace":{"type":"string"}}},"V1Sysctl":{"type":"object","properties":{"name":{"type":"string"},"value":{"type":"string"}}},"V1TCPSocketAction":{"type":"object","properties":{"host":{"type":"string"},"port":{"$ref":"#/components/schemas/IntstrIntOrString"}}},"V1Toleration":{"type":"object","properties":{"effect":{"type":"string"},"key":{"type":"string"},"operator":{"type":"string"},"tolerationSeconds":{"type":"integer","nullable":true},"value":{"type":"string"}}},"V1TopologySpreadConstraint":{"type":"object","properties":{"labelSelector":{"$ref":"#/components/schemas/V1LabelSelector"},"matchLabelKeys":{"type":"array","items":{"type":"string"}},"maxSkew":{"type":"integer"},"minDomains":{"type":"integer","nullable":true},"nodeAffinityPolicy":{"type":"string","nullable":true},"nodeTaintsPolicy":{"type":"string","nullable":true},"topologyKey":{"type":"string"},"whenUnsatisfiable":{"type":"string"}}},"V1TypedLocalObjectReference":{"type":"object","properties":{"apiGroup":{"type":"string","nullable":true},"kind":{"type":"string"},"name":{"type":"string"}}},"V1TypedObjectReference":{"type":"object","properties":{"apiGroup":{"type":"string","nullable":true},"kind":{"type":"string"},"name":{"type":"string"},"namespace":{"type":"string","nullable":true}}},"V1Volume":{"type":"object","properties":{"awsElasticBlockStore":{"$ref":"#/components/schemas/V1AWSElasticBlockStoreVolumeSource"},"azureDisk":{"$ref":"#/components/schemas/V1AzureDiskVolumeSource"},"azureFile":{"$ref":"#/components/schemas/V1AzureFileVolumeSource"},"cephfs":{"$ref":"#/components/schemas/V1CephFSVolumeSource"},"cinder":{"$ref":"#/components/schemas/V1CinderVolumeSource"},"configMap":{"$ref":"#/components/schemas/V1ConfigMapVolumeSource"},"csi":{"$ref":"#/components/schemas/V1CSIVolumeSource"},"downwardAPI":{"$ref":"#/components/schemas/V1DownwardAPIVolumeSource"},"emptyDir":{"$ref":"#/components/schemas/V1EmptyDirVolumeSource"},"ephemeral":{"$ref":"#/components/schemas/V1EphemeralVolumeSource"},"fc":{"$ref":"#/components/schemas/V1FCVolumeSource"},"flexVolume":{"$ref":"#/components/schemas/V1FlexVolumeSource"},"flocker":{"$ref":"#/components/schemas/V1FlockerVolumeSource"},"gcePersistentDisk":{"$ref":"#/components/schemas/V1GCEPersistentDiskVolumeSource"},"gitRepo":{"$ref":"#/components/schemas/V1GitRepoVolumeSource"},"glusterfs":{"$ref":"#/components/schemas/V1GlusterfsVolumeSource"},"hostPath":{"$ref":"#/components/schemas/V1HostPathVolumeSource"},"image":{"$ref":"#/components/schemas/V1ImageVolumeSource"},"iscsi":{"$ref":"#/components/schemas/V1ISCSIVolumeSource"},"name":{"type":"string"},"nfs":{"$ref":"#/components/schemas/V1NFSVolumeSource"},"persistentVolumeClaim":{"$ref":"#/components/schemas/V1PersistentVolumeClaimVolumeSource"},"photonPersistentDisk":{"$ref":"#/components/schemas/V1PhotonPersistentDiskVolumeSource"},"portworxVolume":{"$ref":"#/components/schemas/V1PortworxVolumeSource"},"projected":{"$ref":"#/components/schemas/V1ProjectedVolumeSource"},"quobyte":{"$ref":"#/components/schemas/V1QuobyteVolumeSource"},"rbd":{"$ref":"#/components/schemas/V1RBDVolumeSource"},"scaleIO":{"$ref":"#/components/schemas/V1ScaleIOVolumeSource"},"secret":{"$ref":"#/components/schemas/V1SecretVolumeSource"},"storageos":{"$ref":"#/components/schemas/V1StorageOSVolumeSource"},"vsphereVolume":{"$ref":"#/components/schemas/V1VsphereVirtualDiskVolumeSource"}}},"V1VolumeDevice":{"type":"object","properties":{"devicePath":{"type":"string"},"name":{"type":"string"}}},"V1VolumeMount":{"type":"object","properties":{"mountPath":{"type":"string"},"mountPropagation":{"type":"string","nullable":true},"name":{"type":"string"},"readOnly":{"type":"boolean"},"recursiveReadOnly":{"type":"string","nullable":true},"subPath":{"type":"string"},"subPathExpr":{"type":"string"}}},"V1VolumeMountStatus":{"type":"object","properties":{"mountPath":{"type":"string"},"name":{"type":"string"},"readOnly":{"type":"boolean"},"recursiveReadOnly":{"type":"string","nullable":true}}},"V1VolumeProjection":{"type":"object","properties":{"clusterTrustBundle":{"$ref":"#/components/schemas/V1ClusterTrustBundleProjection"},"configMap":{"$ref":"#/components/schemas/V1ConfigMapProjection"},"downwardAPI":{"$ref":"#/components/schemas/V1DownwardAPIProjection"},"secret":{"$ref":"#/components/schemas/V1SecretProjection"},"serviceAccountToken":{"$ref":"#/components/schemas/V1ServiceAccountTokenProjection"}}},"V1VolumeResourceRequirements":{"type":"object","properties":{"limits":{"$ref":"#/components/schemas/V1ResourceList"},"requests":{"$ref":"#/components/schemas/V1ResourceList"}}},"V1VsphereVirtualDiskVolumeSource":{"type":"object","properties":{"fsType":{"type":"string"},"storagePolicyID":{"type":"string"},"storagePolicyName":{"type":"string"},"volumePath":{"type":"string"}}},"V1WeightedPodAffinityTerm":{"type":"object","properties":{"podAffinityTerm":{"$ref":"#/components/schemas/V1PodAffinityTerm"},"weight":{"type":"integer"}}},"V1WindowsSecurityContextOptions":{"type":"object","properties":{"gmsaCredentialSpec":{"type":"string","nullable":true},"gmsaCredentialSpecName":{"type":"string","nullable":true},"hostProcess":{"type":"boolean","nullable":true},"runAsUserName":{"type":"string","nullable":true}}}}}} \ No newline at end of file diff --git a/docs/openapi/plugin-openapi.json b/docs/openapi/plugin-openapi.json index 6adc9c9e..761b9510 100644 --- a/docs/openapi/plugin-openapi.json +++ b/docs/openapi/plugin-openapi.json @@ -44,15 +44,15 @@ } } }, - "/createPodSets": { + "/createPodGroup": { "post": { - "summary": "Create Pod Sets", - "operationId": "create_pod_sets_createPodSets_post", + "summary": "Create Pod Group", + "operationId": "create_pod_group_createPodGroup_post", "requestBody": { "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/RetrievedPodSetsData" + "$ref": "#/components/schemas/RetrievedPodGroupData" } } }, @@ -68,7 +68,7 @@ "$ref": "#/components/schemas/CreateStruct" }, "type": "array", - "title": "Response Create Pod Sets Createpodsets Post" + "title": "Response Create Pod Group Createpodgroup Post" } } } @@ -1026,11 +1026,11 @@ ], "title": "PodVolume" }, - "RetrievedPodSetData": { + "RetrievedPodGroupData": { "properties": { - "name": { + "groupUID": { "type": "string", - "title": "Name" + "title": "Group Uid" }, "pods": { "items": { @@ -1041,32 +1041,11 @@ } }, "type": "object", - "required": [ - "name", - "pods" - ], - "title": "RetrievedPodSetData" - }, - "RetrievedPodSetsData": { - "properties": { - "groupUID": { - "type": "string", - "title": "Group Uid" - }, - "podSets": { - "items": { - "$ref": "#/components/schemas/RetrievedPodSetData" - }, - "type": "array", - "title": "Pod Sets" - } - }, - "type": "object", "required": [ "groupUID", - "podSets" + "pods" ], - "title": "RetrievedPodSetsData" + "title": "RetrievedPodGroupData" }, "Secret": { "properties": { diff --git a/pkg/interlink/api/createpodsets.go b/pkg/interlink/api/createpodgroup.go similarity index 70% rename from pkg/interlink/api/createpodsets.go rename to pkg/interlink/api/createpodgroup.go index b1bff892..3b74f1c9 100644 --- a/pkg/interlink/api/createpodsets.go +++ b/pkg/interlink/api/createpodgroup.go @@ -20,14 +20,14 @@ import ( trace "go.opentelemetry.io/otel/trace" ) -// CreatePodSetsHandler handles HTTP POST requests to create the pods of one group in a -// single call, so that the sidecar plugin can run the whole group as one job. +// CreatePodGroupHandler handles HTTP POST requests to create the pods of one pod group in +// a single call, so that the sidecar plugin can run the whole group as one job. // Every pod is prepared as CreateHandler prepares it (ConfigMaps, Secrets and projected // volumes split by container, optional job script), and the group is forwarded to the -// plugin's /createPodSets. It is meant for plugins that list "podset-create" in their +// plugin's /createPodGroup. It is meant for plugins that list "pod-group-create" in their // ping answer; status, logs and delete stay per pod. // -// Request body: JSON-encoded PodSetsCreateRequests +// Request body: JSON-encoded PodGroupCreateRequests // Response: JSON-encoded CreateStruct array, one entry per pod in the order of the // request, all with the job ID of the group // @@ -36,17 +36,17 @@ import ( // - 400: Bad request (malformed body, no pods, or a permanent refusal of the plugin) // - 503: The plugin cannot take the group now // - 500: Internal server error (job script or sidecar communication failures) -func (h *InterLinkHandler) CreatePodSetsHandler(w http.ResponseWriter, r *http.Request) { +func (h *InterLinkHandler) CreatePodGroupHandler(w http.ResponseWriter, r *http.Request) { start := time.Now().UnixMicro() tracer := otel.Tracer("interlink-API") - _, span := tracer.Start(h.Ctx, "CreatePodSetsAPI", trace.WithAttributes( + _, span := tracer.Start(h.Ctx, "CreatePodGroupAPI", trace.WithAttributes( attribute.Int64("start.timestamp", start), )) defer span.End() defer types.SetDurationSpan(start, span) defer types.SetInfoFromHeaders(span, &r.Header) - log.G(h.Ctx).Info("InterLink: received CreatePodSets call") + log.G(h.Ctx).Info("InterLink: received CreatePodGroup call") bodyBytes, err := io.ReadAll(r.Body) if err != nil { @@ -55,58 +55,45 @@ func (h *InterLinkHandler) CreatePodSetsHandler(w http.ResponseWriter, r *http.R return } - var request types.PodSetsCreateRequests + var request types.PodGroupCreateRequests if err := json.Unmarshal(bodyBytes, &request); err != nil { - writePodSetsError(h.Ctx, w, http.StatusBadRequest, fmt.Errorf("invalid request body: %w", err)) + writePodGroupError(h.Ctx, w, http.StatusBadRequest, fmt.Errorf("invalid request body: %w", err)) return } - - pods := 0 - for _, podSet := range request.PodSets { - pods += len(podSet.Pods) - } - if pods == 0 { - writePodSetsError(h.Ctx, w, http.StatusBadRequest, fmt.Errorf("group %s has no pods", request.GroupUID)) + if len(request.Pods) == 0 { + writePodGroupError(h.Ctx, w, http.StatusBadRequest, fmt.Errorf("pod group %s has no pods", request.GroupUID)) return } span.SetAttributes( attribute.String("group.uid", request.GroupUID), - attribute.Int("group.podsets", len(request.PodSets)), - attribute.Int("group.pods", pods), + attribute.Int("group.pods", len(request.Pods)), ) // One session for the whole group: GetSessionContext makes up a new one on every // call when the request has none. sessionContext := GetSessionContext(r) - retrievedData := types.RetrievedPodSetsData{ + retrievedData := types.RetrievedPodGroupData{ GroupUID: request.GroupUID, - PodSets: make([]types.RetrievedPodSetData, 0, len(request.PodSets)), + Pods: make([]types.RetrievedPodData, 0, len(request.Pods)), } - for _, podSet := range request.PodSets { - retrievedPodSet := types.RetrievedPodSetData{ - Name: podSet.Name, - Pods: make([]types.RetrievedPodData, 0, len(podSet.Pods)), - } - for _, pod := range podSet.Pods { - log.G(h.Ctx).Infof("InterLink: preparing pod %s/%s of PodSet %s", pod.Pod.Namespace, pod.Pod.Name, podSet.Name) - - data, err := getData(h.Ctx, h.Config, pod, span) - if err != nil { - writePodSetsError(h.Ctx, w, http.StatusInternalServerError, fmt.Errorf("pod %s/%s: %w", pod.Pod.Namespace, pod.Pod.Name, err)) - return - } + for _, pod := range request.Pods { + log.G(h.Ctx).Infof("InterLink: preparing pod %s/%s of pod group %s", pod.Pod.Namespace, pod.Pod.Name, request.GroupUID) - statusCode, err := h.podSetJobScript(pod, &data, start, span, sessionContext) - if err != nil { - writePodSetsError(h.Ctx, w, statusCode, fmt.Errorf("pod %s/%s: job script: %w", pod.Pod.Namespace, pod.Pod.Name, err)) - return - } + data, err := getData(h.Ctx, h.Config, pod, span) + if err != nil { + writePodGroupError(h.Ctx, w, http.StatusInternalServerError, fmt.Errorf("pod %s/%s: %w", pod.Pod.Namespace, pod.Pod.Name, err)) + return + } - retrievedPodSet.Pods = append(retrievedPodSet.Pods, data) + statusCode, err := h.podGroupJobScript(pod, &data, start, span, sessionContext) + if err != nil { + writePodGroupError(h.Ctx, w, statusCode, fmt.Errorf("pod %s/%s: job script: %w", pod.Pod.Namespace, pod.Pod.Name, err)) + return } - retrievedData.PodSets = append(retrievedData.PodSets, retrievedPodSet) + + retrievedData.Pods = append(retrievedData.Pods, data) } bodyBytes, err = json.Marshal(retrievedData) @@ -117,14 +104,14 @@ func (h *InterLinkHandler) CreatePodSetsHandler(w http.ResponseWriter, r *http.R } log.G(h.Ctx).Debug(string(bodyBytes)) - req, err := http.NewRequest(http.MethodPost, h.SidecarEndpoint+"/createPodSets", bytes.NewReader(bodyBytes)) + req, err := http.NewRequest(http.MethodPost, h.SidecarEndpoint+"/createPodGroup", bytes.NewReader(bodyBytes)) if err != nil { w.WriteHeader(http.StatusInternalServerError) log.G(h.Ctx).Error(err) return } - log.G(h.Ctx).Infof("InterLink: forwarding CreatePodSets call to sidecar (group %s, %d pods)", request.GroupUID, pods) + log.G(h.Ctx).Infof("InterLink: forwarding CreatePodGroup call to sidecar (group %s, %d pods)", request.GroupUID, len(request.Pods)) ForwardAccessToken(r, req) _, err = ReqWithError(h.Ctx, req, w, start, span, true, false, sessionContext, h.ClientHTTP) @@ -134,11 +121,11 @@ func (h *InterLinkHandler) CreatePodSetsHandler(w http.ResponseWriter, r *http.R } } -// podSetJobScript fills the job script of one pod of a group as CreateHandler does, from +// podGroupJobScript fills the job script of one pod of a group as CreateHandler does, from // the JobScriptBuilderURL of the pod or from the configured JobScriptTemplate. Unlike // CreateHandler it does not answer the caller: the group gets one answer, so it returns // the status code to answer with when it fails. -func (h *InterLinkHandler) podSetJobScript( +func (h *InterLinkHandler) podGroupJobScript( pod types.PodCreateRequests, data *types.RetrievedPodData, start int64, @@ -192,9 +179,9 @@ func (h *InterLinkHandler) podSetJobScript( return http.StatusOK, nil } -// writePodSetsError answers the caller with statusCode and the error text, escaped as +// writePodGroupError answers the caller with statusCode and the error text, escaped as // ReqWithError escapes the errors of the sidecar. -func writePodSetsError(ctx context.Context, w http.ResponseWriter, statusCode int, err error) { +func writePodGroupError(ctx context.Context, w http.ResponseWriter, statusCode int, err error) { log.G(ctx).Error(err) w.WriteHeader(statusCode) if _, werr := w.Write([]byte(html.EscapeString(err.Error()))); werr != nil { diff --git a/pkg/interlink/api/createpodsets_test.go b/pkg/interlink/api/createpodgroup_test.go similarity index 67% rename from pkg/interlink/api/createpodsets_test.go rename to pkg/interlink/api/createpodgroup_test.go index d20a4488..91b8c450 100644 --- a/pkg/interlink/api/createpodsets_test.go +++ b/pkg/interlink/api/createpodgroup_test.go @@ -21,16 +21,14 @@ import ( ) const ( - testGroupUID = "0b7c6f6e-5c1d-4d6a-9f3e-2a1b0c9d8e7f" - testPodSetsSession = "Request-podsets-123" - testPodSetLauncher = "launcher" - testPodSetWorker = "worker" - testMainContainer = "main" - testSidecarContainer = "sidecar" - testConfigMapName = "settings" - testSecretName = "creds" - testProjectedVolume = "token" - testCreatePodSetsPath = "/createPodSets" + testGroupUID = "0b7c6f6e-5c1d-4d6a-9f3e-2a1b0c9d8e7f" + testPodGroupSession = "Request-podgroup-123" + testMainContainer = "main" + testSidecarContainer = "sidecar" + testConfigMapName = "settings" + testSecretName = "creds" + testProjectedVolume = "token" + testCreatePodGroupPath = "/createPodGroup" ) // sidecarCall is what the fake sidecar received in one call. @@ -38,12 +36,12 @@ type sidecarCall struct { path string session string token string - body types.RetrievedPodSetsData + body types.RetrievedPodGroupData } -// newPodSetsSidecar starts a fake sidecar that records every call and answers each one +// newPodGroupSidecar starts a fake sidecar that records every call and answers each one // with statusCode and answer, and returns an InterLinkHandler pointed at it. -func newPodSetsSidecar(t *testing.T, statusCode int, answer string) (*InterLinkHandler, chan sidecarCall) { +func newPodGroupSidecar(t *testing.T, statusCode int, answer string) (*InterLinkHandler, chan sidecarCall) { t.Helper() calls := make(chan sidecarCall, 4) sidecar := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { @@ -66,10 +64,10 @@ func newPodSetsSidecar(t *testing.T, statusCode int, answer string) (*InterLinkH return handler, calls } -// podSetPod returns a pod of the test group with two containers: the main one mounts +// podGroupPod returns a pod of the test group with two containers: the main one mounts // the ConfigMap and the projected volume, the sidecar one mounts the Secret only. The // projected volume holds the pod name, as downward API files do. -func podSetPod(name string) types.PodCreateRequests { +func podGroupPod(name string) types.PodCreateRequests { return types.PodCreateRequests{ Pod: v1.Pod{ ObjectMeta: metav1.ObjectMeta{ @@ -100,65 +98,47 @@ func podSetPod(name string) types.PodCreateRequests { } } -// podSetsRequest returns a POST /createPodSets of the test group: a launcher PodSet with -// one pod and a worker PodSet with two. -func podSetsRequest(t *testing.T, mutate func(*types.PodCreateRequests)) *http.Request { +// podGroupRequest returns a POST /createPodGroup of the test group: a launcher pod and two +// workers, in rank order. +func podGroupRequest(t *testing.T, mutate func(*types.PodCreateRequests)) *http.Request { t.Helper() - request := types.PodSetsCreateRequests{ + request := types.PodGroupCreateRequests{ GroupUID: testGroupUID, - PodSets: []types.PodSetCreateRequests{ - {Name: testPodSetLauncher, Pods: []types.PodCreateRequests{podSetPod("launcher-0")}}, - {Name: testPodSetWorker, Pods: []types.PodCreateRequests{podSetPod("worker-0"), podSetPod("worker-1")}}, - }, + Pods: []types.PodCreateRequests{podGroupPod("launcher-0"), podGroupPod("worker-0"), podGroupPod("worker-1")}, } if mutate != nil { - for i := range request.PodSets { - for j := range request.PodSets[i].Pods { - mutate(&request.PodSets[i].Pods[j]) - } + for i := range request.Pods { + mutate(&request.Pods[i]) } } body, err := json.Marshal(request) require.NoError(t, err) - r := httptest.NewRequest(http.MethodPost, testCreatePodSetsPath, bytes.NewReader(body)) - r.Header.Set("InterLink-Http-Session", testPodSetsSession) + r := httptest.NewRequest(http.MethodPost, testCreatePodGroupPath, bytes.NewReader(body)) + r.Header.Set("InterLink-Http-Session", testPodGroupSession) return r } -// groupPods flattens the PodSets the sidecar received, in their order. -func groupPods(data types.RetrievedPodSetsData) []types.RetrievedPodData { - var pods []types.RetrievedPodData - for _, podSet := range data.PodSets { - pods = append(pods, podSet.Pods...) - } - return pods -} - -func TestCreatePodSetsHandler_ForwardsGroup(t *testing.T) { +func TestCreatePodGroupHandler_ForwardsGroup(t *testing.T) { _, cleanup := setupTestTracer() defer cleanup() answer := `[{"PodUID":"uid-launcher-0","PodJID":"42"},{"PodUID":"uid-worker-0","PodJID":"42"},{"PodUID":"uid-worker-1","PodJID":"42"}]` - handler, calls := newPodSetsSidecar(t, http.StatusOK, answer) + handler, calls := newPodGroupSidecar(t, http.StatusOK, answer) w := httptest.NewRecorder() - handler.CreatePodSetsHandler(w, podSetsRequest(t, nil)) + handler.CreatePodGroupHandler(w, podGroupRequest(t, nil)) assert.Equal(t, http.StatusOK, w.Code) assert.Equal(t, answer, w.Body.String()) require.Len(t, calls, 1) call := <-calls - assert.Equal(t, testCreatePodSetsPath, call.path) - assert.Equal(t, testPodSetsSession, call.session) + assert.Equal(t, testCreatePodGroupPath, call.path) + assert.Equal(t, testPodGroupSession, call.session) assert.Equal(t, testGroupUID, call.body.GroupUID) - require.Len(t, call.body.PodSets, 2) - assert.Equal(t, testPodSetLauncher, call.body.PodSets[0].Name) - assert.Equal(t, testPodSetWorker, call.body.PodSets[1].Name) - - pods := groupPods(call.body) + pods := call.body.Pods require.Len(t, pods, 3) for i, name := range []string{"launcher-0", "worker-0", "worker-1"} { pod := pods[i] @@ -181,7 +161,7 @@ func TestCreatePodSetsHandler_ForwardsGroup(t *testing.T) { } } -func TestCreatePodSetsHandler_RelaysSidecarErrors(t *testing.T) { +func TestCreatePodGroupHandler_RelaysSidecarErrors(t *testing.T) { _, cleanup := setupTestTracer() defer cleanup() @@ -204,10 +184,10 @@ func TestCreatePodSetsHandler_RelaysSidecarErrors(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - handler, calls := newPodSetsSidecar(t, tt.statusCode, tt.answer) + handler, calls := newPodGroupSidecar(t, tt.statusCode, tt.answer) w := httptest.NewRecorder() - handler.CreatePodSetsHandler(w, podSetsRequest(t, nil)) + handler.CreatePodGroupHandler(w, podGroupRequest(t, nil)) assert.Equal(t, tt.statusCode, w.Code) assert.Contains(t, w.Body.String(), tt.answer) @@ -216,7 +196,7 @@ func TestCreatePodSetsHandler_RelaysSidecarErrors(t *testing.T) { } } -func TestCreatePodSetsHandler_RefusesBadRequests(t *testing.T) { +func TestCreatePodGroupHandler_RefusesBadRequests(t *testing.T) { _, cleanup := setupTestTracer() defer cleanup() @@ -229,22 +209,22 @@ func TestCreatePodSetsHandler_RefusesBadRequests(t *testing.T) { body: `{"groupUID":`, }, { - name: "no PodSets", + name: "no pods", body: `{"groupUID":"` + testGroupUID + `"}`, }, { - name: "PodSets without pods", - body: `{"groupUID":"` + testGroupUID + `","podSets":[{"name":"worker","pods":[]}]}`, + name: "empty pods", + body: `{"groupUID":"` + testGroupUID + `","pods":[]}`, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - handler, calls := newPodSetsSidecar(t, http.StatusOK, "[]") + handler, calls := newPodGroupSidecar(t, http.StatusOK, "[]") - r := httptest.NewRequest(http.MethodPost, testCreatePodSetsPath, bytes.NewBufferString(tt.body)) + r := httptest.NewRequest(http.MethodPost, testCreatePodGroupPath, bytes.NewBufferString(tt.body)) w := httptest.NewRecorder() - handler.CreatePodSetsHandler(w, r) + handler.CreatePodGroupHandler(w, r) assert.Equal(t, http.StatusBadRequest, w.Code) assert.NotEmpty(t, w.Body.String()) @@ -253,27 +233,27 @@ func TestCreatePodSetsHandler_RefusesBadRequests(t *testing.T) { } } -func TestCreatePodSetsHandler_JobScriptTemplate(t *testing.T) { +func TestCreatePodGroupHandler_JobScriptTemplate(t *testing.T) { _, cleanup := setupTestTracer() defer cleanup() templatePath := filepath.Join(t.TempDir(), "job.sh.tmpl") require.NoError(t, os.WriteFile(templatePath, []byte("run {{.Pod.Name}}"), 0o600)) - handler, calls := newPodSetsSidecar(t, http.StatusOK, "[]") + handler, calls := newPodGroupSidecar(t, http.StatusOK, "[]") handler.Config.JobScriptTemplate = templatePath w := httptest.NewRecorder() - handler.CreatePodSetsHandler(w, podSetsRequest(t, nil)) + handler.CreatePodGroupHandler(w, podGroupRequest(t, nil)) assert.Equal(t, http.StatusOK, w.Code) require.Len(t, calls, 1) - for _, pod := range groupPods((<-calls).body) { + for _, pod := range (<-calls).body.Pods { assert.Equal(t, "run "+pod.Pod.Name, pod.JobScript) } } -func TestCreatePodSetsHandler_JobScriptBuilder(t *testing.T) { +func TestCreatePodGroupHandler_JobScriptBuilder(t *testing.T) { _, cleanup := setupTestTracer() defer cleanup() @@ -299,7 +279,7 @@ func TestCreatePodSetsHandler_JobScriptBuilder(t *testing.T) { builder := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { var data types.RetrievedPodData assert.NoError(t, json.NewDecoder(r.Body).Decode(&data)) - assert.Equal(t, testPodSetsSession, r.Header.Get("InterLink-Http-Session")) + assert.Equal(t, testPodGroupSession, r.Header.Get("InterLink-Http-Session")) w.WriteHeader(tt.builderStatus) if _, err := io.WriteString(w, "built for "+data.Pod.Name); err != nil { @@ -308,11 +288,11 @@ func TestCreatePodSetsHandler_JobScriptBuilder(t *testing.T) { })) defer builder.Close() - handler, calls := newPodSetsSidecar(t, http.StatusOK, "[]") + handler, calls := newPodGroupSidecar(t, http.StatusOK, "[]") handler.Config.JobScriptBuildConfig = &types.ScriptBuildConfig{} w := httptest.NewRecorder() - handler.CreatePodSetsHandler(w, podSetsRequest(t, func(pod *types.PodCreateRequests) { + handler.CreatePodGroupHandler(w, podGroupRequest(t, func(pod *types.PodCreateRequests) { pod.JobScriptBuilderURL = builder.URL })) @@ -323,7 +303,7 @@ func TestCreatePodSetsHandler_JobScriptBuilder(t *testing.T) { return } require.Len(t, calls, 1) - for _, pod := range groupPods((<-calls).body) { + for _, pod := range (<-calls).body.Pods { assert.Equal(t, "built for "+pod.Pod.Name, pod.JobScript) } }) @@ -332,7 +312,7 @@ func TestCreatePodSetsHandler_JobScriptBuilder(t *testing.T) { // The caller's access token reaches the sidecar, as on /create, and never the job script // builder, whose URL comes from the pod spec. -func TestCreatePodSetsHandler_ForwardsAccessTokenToSidecarOnly(t *testing.T) { +func TestCreatePodGroupHandler_ForwardsAccessTokenToSidecarOnly(t *testing.T) { _, cleanup := setupTestTracer() defer cleanup() @@ -345,15 +325,15 @@ func TestCreatePodSetsHandler_ForwardsAccessTokenToSidecarOnly(t *testing.T) { })) defer builder.Close() - handler, calls := newPodSetsSidecar(t, http.StatusOK, "[]") + handler, calls := newPodGroupSidecar(t, http.StatusOK, "[]") handler.Config.JobScriptBuildConfig = &types.ScriptBuildConfig{} - r := podSetsRequest(t, func(pod *types.PodCreateRequests) { + r := podGroupRequest(t, func(pod *types.PodCreateRequests) { pod.JobScriptBuilderURL = builder.URL }) r.Header.Set(accessTokenHeader, testAccessToken) w := httptest.NewRecorder() - handler.CreatePodSetsHandler(w, r) + handler.CreatePodGroupHandler(w, r) assert.Equal(t, http.StatusOK, w.Code) require.Len(t, calls, 1) diff --git a/pkg/interlink/types.go b/pkg/interlink/types.go index c3d9bbff..1a20bd20 100644 --- a/pkg/interlink/types.go +++ b/pkg/interlink/types.go @@ -101,42 +101,24 @@ type RetrievedPodData struct { JobScript string `json:"jobScript,omitempty"` } -// PodSetsCreateRequests represents a request to create the pods of one group in a single call, -// so that the plugin can run them as one job. The pods are grouped by PodSet; their order, -// PodSet after PodSet, gives their ranks, and rank 0 is the head. -type PodSetsCreateRequests struct { - // GroupUID identifies the group (the Workload UID), and is also the value of the - // pod-group label set on its pods +// PodGroupCreateRequests represents a request to create the pods of one pod group in a +// single call, so that the plugin can run them as one job. The order of the pods gives their +// ranks, and rank 0 is the head. Each pod carries its own ConfigMaps, Secrets and projected +// volumes, because projected and downward API files differ from pod to pod. +type PodGroupCreateRequests struct { + // GroupUID identifies the pod group, for instance the UID of the workload it runs GroupUID string `json:"groupUID"` - // PodSets contains the PodSets of the group, in rank order - PodSets []PodSetCreateRequests `json:"podSets"` -} - -// PodSetCreateRequests represents one PodSet of a group to create. -// Each pod carries its own ConfigMaps, Secrets and projected volumes, because projected -// and downward API files differ from pod to pod (pod name, UID, rank annotations). -type PodSetCreateRequests struct { - // Name is the name of the PodSet - Name string `json:"name"` - // Pods contains the pods of the PodSet, each as it is sent to /create + // Pods contains the pods of the group in rank order, each as it is sent to /create Pods []PodCreateRequests `json:"pods"` } -// RetrievedPodSetsData represents the pods of one group with all their associated data. -// It is what interLink sends to the plugin's /createPodSets: each pod is prepared as for -// /create, and the order of the pods is the one of the PodSetsCreateRequests. -type RetrievedPodSetsData struct { - // GroupUID identifies the group, as in PodSetsCreateRequests +// RetrievedPodGroupData represents the pods of one pod group with all their associated data. +// It is what interLink sends to the plugin's /createPodGroup: each pod is prepared as for +// /create, in the order of the PodGroupCreateRequests. +type RetrievedPodGroupData struct { + // GroupUID identifies the pod group, as in PodGroupCreateRequests GroupUID string `json:"groupUID"` - // PodSets contains the PodSets of the group, in rank order - PodSets []RetrievedPodSetData `json:"podSets"` -} - -// RetrievedPodSetData represents one PodSet of a group with the data of its pods. -type RetrievedPodSetData struct { - // Name is the name of the PodSet - Name string `json:"name"` - // Pods contains the pods of the PodSet, each as it is sent to the plugin's /create + // Pods contains the pods of the group in rank order, each as it is sent to the plugin's /create Pods []RetrievedPodData `json:"pods"` } From 9d9606f660bee185d794a048af6b5c53062d6ff4 Mon Sep 17 00:00:00 2001 From: Matteo Bunino <48362942+matbun@users.noreply.github.com> Date: Tue, 10 Nov 2026 15:40:55 +0100 Subject: [PATCH 3/3] feat: trace /createPodGroup as #575 traces the other calls The group call now starts its span with startAPITrace, records the caller's X-Forwarded headers only with Tracing.Detailed, marks the span as failed on every error path and as ok once the sidecar takes the group, and passes the request context to getData, the job script builder and the sidecar. Co-Authored-By: Claude Opus 5.5 Signed-off-by: Matteo Bunino <48362942+matbun@users.noreply.github.com> --- pkg/interlink/api/createpodgroup.go | 53 ++++++++++++-------- pkg/interlink/api/createpodgroup_test.go | 64 ++++++++++++++++++++++++ 2 files changed, 95 insertions(+), 22 deletions(-) diff --git a/pkg/interlink/api/createpodgroup.go b/pkg/interlink/api/createpodgroup.go index 3b74f1c9..55e6c201 100644 --- a/pkg/interlink/api/createpodgroup.go +++ b/pkg/interlink/api/createpodgroup.go @@ -15,7 +15,6 @@ import ( types "github.com/interlink-hq/interlink/pkg/interlink" - "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/attribute" trace "go.opentelemetry.io/otel/trace" ) @@ -38,13 +37,16 @@ import ( // - 500: Internal server error (job script or sidecar communication failures) func (h *InterLinkHandler) CreatePodGroupHandler(w http.ResponseWriter, r *http.Request) { start := time.Now().UnixMicro() - tracer := otel.Tracer("interlink-API") - _, span := tracer.Start(h.Ctx, "CreatePodGroupAPI", trace.WithAttributes( - attribute.Int64("start.timestamp", start), - )) + // One session for the whole group: GetSessionContext, which startAPITrace calls, + // makes up a new one on every call when the request has none. + ctx, span, sessionContext := h.startAPITrace(r, "CreatePodGroupAPI", "/createPodGroup", start) defer span.End() defer types.SetDurationSpan(start, span) - defer types.SetInfoFromHeaders(span, &r.Header) + // X-Forwarded-Email and X-Forwarded-User identify a person, so they follow + // the same opt-in rule as the other client details. + if h.Config.Tracing.Detailed { + defer types.SetInfoFromHeaders(span, &r.Header) + } log.G(h.Ctx).Info("InterLink: received CreatePodGroup call") @@ -52,16 +54,18 @@ func (h *InterLinkHandler) CreatePodGroupHandler(w http.ResponseWriter, r *http. if err != nil { w.WriteHeader(http.StatusInternalServerError) log.G(h.Ctx).Error(err) + types.SetSpanError(span, http.StatusInternalServerError, err) return } + setRequestBodySize(span, bodyBytes) var request types.PodGroupCreateRequests if err := json.Unmarshal(bodyBytes, &request); err != nil { - writePodGroupError(h.Ctx, w, http.StatusBadRequest, fmt.Errorf("invalid request body: %w", err)) + writePodGroupError(h.Ctx, w, span, http.StatusBadRequest, fmt.Errorf("invalid request body: %w", err)) return } if len(request.Pods) == 0 { - writePodGroupError(h.Ctx, w, http.StatusBadRequest, fmt.Errorf("pod group %s has no pods", request.GroupUID)) + writePodGroupError(h.Ctx, w, span, http.StatusBadRequest, fmt.Errorf("pod group %s has no pods", request.GroupUID)) return } @@ -70,10 +74,6 @@ func (h *InterLinkHandler) CreatePodGroupHandler(w http.ResponseWriter, r *http. attribute.Int("group.pods", len(request.Pods)), ) - // One session for the whole group: GetSessionContext makes up a new one on every - // call when the request has none. - sessionContext := GetSessionContext(r) - retrievedData := types.RetrievedPodGroupData{ GroupUID: request.GroupUID, Pods: make([]types.RetrievedPodData, 0, len(request.Pods)), @@ -81,15 +81,15 @@ func (h *InterLinkHandler) CreatePodGroupHandler(w http.ResponseWriter, r *http. for _, pod := range request.Pods { log.G(h.Ctx).Infof("InterLink: preparing pod %s/%s of pod group %s", pod.Pod.Namespace, pod.Pod.Name, request.GroupUID) - data, err := getData(h.Ctx, h.Config, pod, span) + data, err := getData(ctx, h.Config, pod, span) if err != nil { - writePodGroupError(h.Ctx, w, http.StatusInternalServerError, fmt.Errorf("pod %s/%s: %w", pod.Pod.Namespace, pod.Pod.Name, err)) + writePodGroupError(h.Ctx, w, span, http.StatusInternalServerError, fmt.Errorf("pod %s/%s: %w", pod.Pod.Namespace, pod.Pod.Name, err)) return } - statusCode, err := h.podGroupJobScript(pod, &data, start, span, sessionContext) + statusCode, err := h.podGroupJobScript(ctx, pod, &data, start, span, sessionContext) if err != nil { - writePodGroupError(h.Ctx, w, statusCode, fmt.Errorf("pod %s/%s: job script: %w", pod.Pod.Namespace, pod.Pod.Name, err)) + writePodGroupError(h.Ctx, w, span, statusCode, fmt.Errorf("pod %s/%s: job script: %w", pod.Pod.Namespace, pod.Pod.Name, err)) return } @@ -100,25 +100,32 @@ func (h *InterLinkHandler) CreatePodGroupHandler(w http.ResponseWriter, r *http. if err != nil { w.WriteHeader(http.StatusInternalServerError) log.G(h.Ctx).Error(err) + types.SetSpanError(span, http.StatusInternalServerError, err) return } log.G(h.Ctx).Debug(string(bodyBytes)) - req, err := http.NewRequest(http.MethodPost, h.SidecarEndpoint+"/createPodGroup", bytes.NewReader(bodyBytes)) + req, err := http.NewRequestWithContext(ctx, http.MethodPost, h.SidecarEndpoint+"/createPodGroup", bytes.NewReader(bodyBytes)) if err != nil { w.WriteHeader(http.StatusInternalServerError) log.G(h.Ctx).Error(err) + types.SetSpanError(span, http.StatusInternalServerError, err) return } log.G(h.Ctx).Infof("InterLink: forwarding CreatePodGroup call to sidecar (group %s, %d pods)", request.GroupUID, len(request.Pods)) ForwardAccessToken(r, req) - _, err = ReqWithError(h.Ctx, req, w, start, span, true, false, sessionContext, h.ClientHTTP) + _, err = ReqWithError(ctx, req, w, start, span, true, false, sessionContext, h.ClientHTTP) if err != nil { + // ReqWithError has already marked the span as failed. log.L.Error(err) return } + + // The return code was already recorded by ReqWithError, so only the outcome + // is set here. + types.SetSpanOK(span, 0) } // podGroupJobScript fills the job script of one pod of a group as CreateHandler does, from @@ -126,6 +133,7 @@ func (h *InterLinkHandler) CreatePodGroupHandler(w http.ResponseWriter, r *http. // CreateHandler it does not answer the caller: the group gets one answer, so it returns // the status code to answer with when it fails. func (h *InterLinkHandler) podGroupJobScript( + ctx context.Context, pod types.PodCreateRequests, data *types.RetrievedPodData, start int64, @@ -146,7 +154,7 @@ func (h *InterLinkHandler) podGroupJobScript( return http.StatusInternalServerError, err } - req, err := http.NewRequest(http.MethodPost, pod.JobScriptBuilderURL, bytes.NewReader(bodyBytes)) + req, err := http.NewRequestWithContext(ctx, http.MethodPost, pod.JobScriptBuilderURL, bytes.NewReader(bodyBytes)) if err != nil { return http.StatusInternalServerError, err } @@ -157,7 +165,7 @@ func (h *InterLinkHandler) podGroupJobScript( // ReqWithError writes the builder's status code to its ResponseWriter, which must // not be the caller's: the caller is answered once for the whole group. var builderStatus statusRecorder - script, err := ReqWithError(h.Ctx, req, &builderStatus, start, span, false, true, sessionContext, http.DefaultClient) + script, err := ReqWithError(ctx, req, &builderStatus, start, span, false, true, sessionContext, http.DefaultClient) if err != nil { statusCode := builderStatus.code if statusCode == 0 || statusCode == http.StatusOK { @@ -180,10 +188,11 @@ func (h *InterLinkHandler) podGroupJobScript( } // writePodGroupError answers the caller with statusCode and the error text, escaped as -// ReqWithError escapes the errors of the sidecar. -func writePodGroupError(ctx context.Context, w http.ResponseWriter, statusCode int, err error) { +// ReqWithError escapes the errors of the sidecar, and marks the span as failed with them. +func writePodGroupError(ctx context.Context, w http.ResponseWriter, span trace.Span, statusCode int, err error) { log.G(ctx).Error(err) w.WriteHeader(statusCode) + types.SetSpanError(span, statusCode, err) if _, werr := w.Write([]byte(html.EscapeString(err.Error()))); werr != nil { log.G(ctx).Error(werr) } diff --git a/pkg/interlink/api/createpodgroup_test.go b/pkg/interlink/api/createpodgroup_test.go index 91b8c450..1fd3a5c0 100644 --- a/pkg/interlink/api/createpodgroup_test.go +++ b/pkg/interlink/api/createpodgroup_test.go @@ -13,6 +13,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "go.opentelemetry.io/otel/codes" v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" k8stypes "k8s.io/apimachinery/pkg/types" @@ -196,6 +197,69 @@ func TestCreatePodGroupHandler_RelaysSidecarErrors(t *testing.T) { } } +// The group call records its outcome on the span as the other handlers do, and the +// caller's identity only with detailed tracing. +func TestCreatePodGroupHandler_Span(t *testing.T) { + const okAnswer = `[{"PodUID":"uid-launcher-0","PodJID":"42"},{"PodUID":"uid-worker-0","PodJID":"42"},{"PodUID":"uid-worker-1","PodJID":"42"}]` + withCaller := func(r *http.Request) *http.Request { + r.Header.Set("X-Forwarded-Email", "someone@example.org") + return r + } + + t.Run("ok when the sidecar takes the group", func(t *testing.T) { + exporter := newSpanRecorder(t) + handler, _ := newPodGroupSidecar(t, http.StatusOK, okAnswer) + + handler.CreatePodGroupHandler(httptest.NewRecorder(), withCaller(podGroupRequest(t, nil))) + + span := exportedSpan(t, exporter, "CreatePodGroupAPI") + assert.Equal(t, codes.Ok, span.Status.Code) + _, ok := spanAttrString(span, "X-Forwarded-Email") + assert.False(t, ok, "the caller email must not be on the span unless tracing is detailed") + }) + + t.Run("error when the sidecar refuses it", func(t *testing.T) { + exporter := newSpanRecorder(t) + handler, _ := newPodGroupSidecar(t, http.StatusServiceUnavailable, "busy") + + handler.CreatePodGroupHandler(httptest.NewRecorder(), podGroupRequest(t, nil)) + + span := exportedSpan(t, exporter, "CreatePodGroupAPI") + assert.Equal(t, codes.Error, span.Status.Code) + code, ok := spanAttrInt(span, "exit.code") + assert.True(t, ok) + assert.Equal(t, int64(http.StatusServiceUnavailable), code) + }) + + t.Run("error when the request has no pods", func(t *testing.T) { + exporter := newSpanRecorder(t) + handler, calls := newPodGroupSidecar(t, http.StatusOK, okAnswer) + body, err := json.Marshal(types.PodGroupCreateRequests{GroupUID: testGroupUID}) + require.NoError(t, err) + + handler.CreatePodGroupHandler(httptest.NewRecorder(), httptest.NewRequest(http.MethodPost, testCreatePodGroupPath, bytes.NewReader(body))) + + span := exportedSpan(t, exporter, "CreatePodGroupAPI") + assert.Equal(t, codes.Error, span.Status.Code) + code, ok := spanAttrInt(span, "exit.code") + assert.True(t, ok) + assert.Equal(t, int64(http.StatusBadRequest), code) + assert.Empty(t, calls) + }) + + t.Run("the caller is recorded with detailed tracing", func(t *testing.T) { + exporter := newSpanRecorder(t) + handler, _ := newPodGroupSidecar(t, http.StatusOK, okAnswer) + handler.Config.Tracing.Detailed = true + + handler.CreatePodGroupHandler(httptest.NewRecorder(), withCaller(podGroupRequest(t, nil))) + + email, ok := spanAttrString(exportedSpan(t, exporter, "CreatePodGroupAPI"), "X-Forwarded-Email") + require.True(t, ok) + assert.Equal(t, "someone@example.org", email) + }) +} + func TestCreatePodGroupHandler_RefusesBadRequests(t *testing.T) { _, cleanup := setupTestTracer() defer cleanup()