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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions api/v2/common_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
package v2

import (
"math"
"strings"

metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
Expand Down Expand Up @@ -100,6 +101,34 @@ func ValidateNamespacedResourceMetadata(obj metav1.Object) field.ErrorList {
return errorList
}

func validateRetainedResourceMonitor(
retainRuntimeResource bool,
monitorPID *int64,
monitorTimestamp metav1.MicroTime,
monitorPath *field.Path,
) field.ErrorList {
errorList := field.ErrorList{}
monitorTimestampSet := !monitorTimestamp.IsZero()

if monitorPID != nil && (*monitorPID <= 0 || *monitorPID > math.MaxUint32) {
errorList = append(errorList, field.Invalid(monitorPath.Child("monitorPID"), *monitorPID, "monitorPID must be between 1 and 4294967295"))
}
if !retainRuntimeResource && monitorPID != nil {
errorList = append(errorList, field.Forbidden(monitorPath.Child("monitorPID"), "monitorPID can only be set for retained runtime resources"))
}
if !retainRuntimeResource && monitorTimestampSet {
errorList = append(errorList, field.Forbidden(monitorPath.Child("monitorTimestamp"), "monitorTimestamp can only be set for retained runtime resources"))
}
if monitorPID != nil && !monitorTimestampSet {
errorList = append(errorList, field.Required(monitorPath.Child("monitorTimestamp"), "monitorTimestamp must be set when monitorPID is set"))
}
if monitorPID == nil && monitorTimestampSet {
errorList = append(errorList, field.Required(monitorPath.Child("monitorPID"), "monitorPID must be set when monitorTimestamp is set"))
}

return errorList
}

func validateSameNamespaceResourceReference(
reference string,
namespace string,
Expand Down
17 changes: 17 additions & 0 deletions api/v2/physical_container_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,14 @@ type PhysicalContainerConfig struct {
// RetainRuntimeContainer keeps a runtime container created by this resource in place when the resource is deleted.
RetainRuntimeContainer bool `json:"retainRuntimeContainer,omitempty"`

// MonitorPID optionally scopes a retained runtime container to another process lifetime.
// When set, monitorTimestamp must also be set and retainRuntimeContainer must be true.
// The container is stopped but not removed when the monitored process exits.
MonitorPID *int64 `json:"monitorPID,omitempty"`

// MonitorTimestamp identifies the process in monitorPID and guards against PID reuse.
MonitorTimestamp metav1.MicroTime `json:"monitorTimestamp,omitempty"`

// ImageRef identifies a PhysicalContainerImage in the same namespace using <name> or <namespace>/<name>.
// Cross-namespace references are not supported.
ImageRef string `json:"imageRef,omitempty"`
Expand Down Expand Up @@ -363,6 +371,15 @@ func (pc *PhysicalContainer) Validate(ctx context.Context) field.ErrorList {

container := pc.Spec.Container
containerPath := specPath.Child("container")
errorList = append(
errorList,
validateRetainedResourceMonitor(
container.RetainRuntimeContainer,
container.MonitorPID,
container.MonitorTimestamp,
containerPath,
)...,
)
errorList = append(errorList, validateSameNamespaceResourceReference(container.ImageRef, pc.Namespace, containerPath.Child("imageRef"))...)
if container.ContainerName != "" && !validContainerNameRegexp.MatchString(container.ContainerName) {
errorList = append(errorList, field.Invalid(containerPath.Child("containerName"), container.ContainerName, fmt.Sprintf("containerName must match regex '%s'", validContainerName)))
Expand Down
65 changes: 65 additions & 0 deletions api/v2/physical_container_types_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ package v2
import (
"context"
"testing"
"time"

"github.com/stretchr/testify/require"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
Expand All @@ -16,6 +17,9 @@ import (
)

func TestPhysicalContainerValidate(t *testing.T) {
monitorPID := int64(42)
invalidMonitorPID := int64(0)
monitorTimestamp := metav1.NewMicroTime(time.Now().UTC())
testCases := []struct {
name string
container PhysicalContainer
Expand All @@ -31,6 +35,18 @@ func TestPhysicalContainerValidate(t *testing.T) {
Spec: PhysicalContainerSpec{Container: &PhysicalContainerConfig{ImageRef: "test-image"}},
},
},
{
name: "valid retained container monitor",
container: PhysicalContainer{
ObjectMeta: metav1.ObjectMeta{Name: "test-container", Namespace: "test-namespace"},
Spec: PhysicalContainerSpec{Container: &PhysicalContainerConfig{
ImageRef: "test-image",
RetainRuntimeContainer: true,
MonitorPID: &monitorPID,
MonitorTimestamp: monitorTimestamp,
}},
},
},
{
name: "valid existing container",
container: PhysicalContainer{
Expand Down Expand Up @@ -214,6 +230,55 @@ func TestPhysicalContainerValidate(t *testing.T) {
},
expectedError: "spec.container.containerName",
},
{
name: "monitor requires retained container",
container: PhysicalContainer{
ObjectMeta: metav1.ObjectMeta{Name: "test-container", Namespace: "test-namespace"},
Spec: PhysicalContainerSpec{Container: &PhysicalContainerConfig{
ImageRef: "test-image",
MonitorPID: &monitorPID,
MonitorTimestamp: monitorTimestamp,
}},
},
expectedError: "spec.container.monitorPID",
},
{
name: "monitor pid requires timestamp",
container: PhysicalContainer{
ObjectMeta: metav1.ObjectMeta{Name: "test-container", Namespace: "test-namespace"},
Spec: PhysicalContainerSpec{Container: &PhysicalContainerConfig{
ImageRef: "test-image",
RetainRuntimeContainer: true,
MonitorPID: &monitorPID,
}},
},
expectedError: "spec.container.monitorTimestamp",
},
{
name: "monitor timestamp requires pid",
container: PhysicalContainer{
ObjectMeta: metav1.ObjectMeta{Name: "test-container", Namespace: "test-namespace"},
Spec: PhysicalContainerSpec{Container: &PhysicalContainerConfig{
ImageRef: "test-image",
RetainRuntimeContainer: true,
MonitorTimestamp: monitorTimestamp,
}},
},
expectedError: "spec.container.monitorPID",
},
{
name: "monitor pid must be valid",
container: PhysicalContainer{
ObjectMeta: metav1.ObjectMeta{Name: "test-container", Namespace: "test-namespace"},
Spec: PhysicalContainerSpec{Container: &PhysicalContainerConfig{
ImageRef: "test-image",
RetainRuntimeContainer: true,
MonitorPID: &invalidMonitorPID,
MonitorTimestamp: monitorTimestamp,
}},
},
expectedError: "spec.container.monitorPID",
},
{
name: "invalid container port range size",
container: PhysicalContainer{
Expand Down
5 changes: 3 additions & 2 deletions api/v2/physical_container_volume_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -98,8 +98,9 @@ type PhysicalContainerVolumeConfig struct {
// VolumeName is the runtime name to use when creating a new volume.
VolumeName string `json:"volumeName,omitempty"`

// RetainRuntimeVolume keeps the created runtime volume in place when this resource is deleted.
RetainRuntimeVolume bool `json:"retainRuntimeVolume,omitempty"`
// RemoveRuntimeVolumeOnDelete removes the created runtime volume when this resource is deleted.
// Created runtime volumes are retained by default.
RemoveRuntimeVolumeOnDelete bool `json:"removeRuntimeVolumeOnDelete,omitempty"`

// ReplaceExisting removes an existing runtime volume with volumeName before creating a new one.
// Replacement retries non-forced removal while the existing volume is in use and never removes attached containers.
Expand Down
2 changes: 1 addition & 1 deletion api/v2/physical_container_volume_types_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ func TestPhysicalContainerVolumeValidateUpdateRejectsSpecChanges(t *testing.T) {
},
}
newVolume := oldVolume.DeepCopy()
newVolume.Spec.Volume.RetainRuntimeVolume = true
newVolume.Spec.Volume.RemoveRuntimeVolumeOnDelete = true

errorList := newVolume.ValidateUpdate(context.Background(), oldVolume)

Expand Down
17 changes: 17 additions & 0 deletions api/v2/physical_process_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,14 @@ type PhysicalProcessConfig struct {
// RetainRuntimeProcess keeps a process launched by this resource running when the resource is deleted.
RetainRuntimeProcess bool `json:"retainRuntimeProcess,omitempty"`

// MonitorPID optionally scopes a retained runtime process to another process lifetime.
// When set, monitorTimestamp must also be set and retainRuntimeProcess must be true.
// The retained process is stopped when the monitored process exits.
MonitorPID *int64 `json:"monitorPID,omitempty"`

// MonitorTimestamp identifies the process in monitorPID and guards against PID reuse.
MonitorTimestamp metav1.MicroTime `json:"monitorTimestamp,omitempty"`

// ExecutablePath is the executable path or name to launch.
ExecutablePath string `json:"executablePath"`

Expand Down Expand Up @@ -230,6 +238,15 @@ func (pp *PhysicalProcess) Validate(ctx context.Context) field.ErrorList {

processConfig := pp.Spec.Process
processPath := specPath.Child("process")
errorList = append(
errorList,
validateRetainedResourceMonitor(
processConfig.RetainRuntimeProcess,
processConfig.MonitorPID,
processConfig.MonitorTimestamp,
processPath,
)...,
)
if strings.TrimSpace(processConfig.ExecutablePath) == "" {
errorList = append(errorList, field.Required(processPath.Child("executablePath"), "executablePath must be set"))
}
Expand Down
64 changes: 64 additions & 0 deletions api/v2/physical_process_types_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"context"
"math"
"testing"
"time"

"github.com/stretchr/testify/require"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
Expand All @@ -18,6 +19,8 @@ func TestPhysicalProcessValidate(t *testing.T) {
validPID := int64(42)
zeroPID := int64(0)
largePID := int64(math.MaxUint32) + 1
monitorPID := int64(43)
monitorTimestamp := metav1.NewMicroTime(time.Now().UTC())
testCases := []struct {
name string
process PhysicalProcess
Expand All @@ -43,6 +46,18 @@ func TestPhysicalProcessValidate(t *testing.T) {
Spec: PhysicalProcessSpec{PID: &validPID},
},
},
{
name: "valid retained process monitor",
process: PhysicalProcess{
ObjectMeta: metav1.ObjectMeta{Name: "test-process", Namespace: "test-namespace"},
Spec: PhysicalProcessSpec{Process: &PhysicalProcessConfig{
ExecutablePath: "test-command",
RetainRuntimeProcess: true,
MonitorPID: &monitorPID,
MonitorTimestamp: monitorTimestamp,
}},
},
},
{
name: "missing namespace",
process: PhysicalProcess{
Expand Down Expand Up @@ -101,6 +116,55 @@ func TestPhysicalProcessValidate(t *testing.T) {
},
expectedError: "spec.process.executablePath",
},
{
name: "monitor requires retained process",
process: PhysicalProcess{
ObjectMeta: metav1.ObjectMeta{Name: "test-process", Namespace: "test-namespace"},
Spec: PhysicalProcessSpec{Process: &PhysicalProcessConfig{
ExecutablePath: "test-command",
MonitorPID: &monitorPID,
MonitorTimestamp: monitorTimestamp,
}},
},
expectedError: "spec.process.monitorPID",
},
{
name: "monitor pid requires timestamp",
process: PhysicalProcess{
ObjectMeta: metav1.ObjectMeta{Name: "test-process", Namespace: "test-namespace"},
Spec: PhysicalProcessSpec{Process: &PhysicalProcessConfig{
ExecutablePath: "test-command",
RetainRuntimeProcess: true,
MonitorPID: &monitorPID,
}},
},
expectedError: "spec.process.monitorTimestamp",
},
{
name: "monitor timestamp requires pid",
process: PhysicalProcess{
ObjectMeta: metav1.ObjectMeta{Name: "test-process", Namespace: "test-namespace"},
Spec: PhysicalProcessSpec{Process: &PhysicalProcessConfig{
ExecutablePath: "test-command",
RetainRuntimeProcess: true,
MonitorTimestamp: monitorTimestamp,
}},
},
expectedError: "spec.process.monitorPID",
},
{
name: "monitor pid must be valid",
process: PhysicalProcess{
ObjectMeta: metav1.ObjectMeta{Name: "test-process", Namespace: "test-namespace"},
Spec: PhysicalProcessSpec{Process: &PhysicalProcessConfig{
ExecutablePath: "test-command",
RetainRuntimeProcess: true,
MonitorPID: &zeroPID,
MonitorTimestamp: monitorTimestamp,
}},
},
expectedError: "spec.process.monitorPID",
},
}

for _, testCase := range testCases {
Expand Down
12 changes: 12 additions & 0 deletions api/v2/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading