From 0aeb2e552c997ee8b9f934efeaa83348f28d7992 Mon Sep 17 00:00:00 2001 From: Navid Yaghoobi Date: Fri, 7 Mar 2025 16:25:33 +1100 Subject: [PATCH] New feature - container create resource settings category Signed-off-by: Navid Yaghoobi --- pdcs/containers/create.go | 109 +++++- test/001-image.bats | 26 +- test/002-volume.bats | 12 +- test/003-network.bats | 18 +- test/004-pod.bats | 22 +- test/005-container.bats | 115 +++++-- test/006-system.bats | 12 +- test/helpers_tui.bash | 24 +- ui/containers/cntdialogs/create.go | 422 ++++++++++++++++++++++-- ui/containers/cntdialogs/create_test.go | 68 ++++ 10 files changed, 729 insertions(+), 99 deletions(-) diff --git a/pdcs/containers/create.go b/pdcs/containers/create.go index 2bb7a8571..a416fb53b 100644 --- a/pdcs/containers/create.go +++ b/pdcs/containers/create.go @@ -76,10 +76,24 @@ type CreateOptions struct { HealthStartupRetries string HealthStartupSuccess string HealthStartupTimeout string + Memory string + MemoryReservation string + MemorySwap string + MemorySwappiness string + CPUs string + CPUShares string + CPUPeriod string + CPUQuota string + CPURtPeriod string + CPURtRuntime string + CPUSetCPUs string + CPUSetMems string + SHMSize string + SHMSizeSystemd string } // Create creates a new container. -func Create(opts CreateOptions, run bool) ([]string, string, error) { //nolint:cyclop,gocognit,gocyclo +func Create(opts CreateOptions, run bool) ([]string, string, error) { //nolint:cyclop,gocognit,gocyclo,maintidx var ( warningResponse []string containerID string @@ -232,6 +246,99 @@ func Create(opts CreateOptions, run bool) ([]string, string, error) { //nolint:c return warningResponse, containerID, err } + // add resources + if opts.Memory != "" { + createOptions.Memory = opts.Memory + } + + if opts.MemoryReservation != "" { + createOptions.MemoryReservation = opts.MemoryReservation + } + + if opts.MemorySwap != "" { + createOptions.MemorySwap = opts.MemorySwap + } + + if opts.MemorySwappiness != "" { + val, err := strconv.Atoi(opts.MemorySwappiness) + if err != nil { + return warningResponse, containerID, err + } + + createOptions.MemorySwappiness = int64(val) + } + + if opts.CPUs != "" { + val, err := strconv.ParseFloat(opts.CPUs, 64) + if err != nil { + return warningResponse, containerID, err + } + + createOptions.CPUS = val + } + + if opts.CPUShares != "" { + val, err := strconv.ParseUint(opts.CPUShares, 10, 64) + if err != nil { + return warningResponse, containerID, err + } + + createOptions.CPUShares = val + } + + if opts.CPUPeriod != "" { + val, err := strconv.ParseUint(opts.CPUPeriod, 10, 64) + if err != nil { + return warningResponse, containerID, err + } + + createOptions.CPUPeriod = val + } + + if opts.CPURtPeriod != "" { + val, err := strconv.ParseUint(opts.CPURtPeriod, 10, 64) + if err != nil { + return warningResponse, containerID, err + } + + createOptions.CPURTPeriod = val + } + + if opts.CPUQuota != "" { + val, err := strconv.Atoi(opts.CPUQuota) + if err != nil { + return warningResponse, containerID, err + } + + createOptions.CPUQuota = int64(val) + } + + if opts.CPURtRuntime != "" { + val, err := strconv.Atoi(opts.CPURtRuntime) + if err != nil { + return warningResponse, containerID, err + } + + createOptions.CPURTRuntime = int64(val) + } + + if opts.CPUSetCPUs != "" { + createOptions.CPUSetCPUs = opts.CPUSetCPUs + } + + if opts.CPUSetMems != "" { + createOptions.CPUSetMems = opts.CPUSetMems + } + + if opts.SHMSize != "" { + createOptions.ShmSize = opts.SHMSize + } + + if opts.SHMSizeSystemd != "" { + createOptions.ShmSizeSystemd = opts.SHMSizeSystemd + } + + // generate spec s := specgen.NewSpecGenerator(opts.Name, false) if err := specgenutil.FillOutSpecGen(s, &createOptions, nil); err != nil { return warningResponse, containerID, err diff --git a/test/001-image.bats b/test/001-image.bats index 6757f8e80..0194a8c45 100644 --- a/test/001-image.bats +++ b/test/001-image.bats @@ -19,9 +19,9 @@ load helpers_tui podman_tui_set_view "images" podman_tui_select_image_cmd "pull" podman_tui_send_inputs "busybox" "Enter" - sleep 8 + sleep $TEST_TIMEOUT_HIGH podman_tui_send_inputs "Down" "Enter" - sleep 12 + sleep $TEST_TIMEOUT_HIGH run_helper podman image ls busybox --format "{{ .Repository }}" assert "$output" =~ "docker.io/library/busybox" "expected image" @@ -46,7 +46,7 @@ load helpers_tui podman_tui_send_inputs $TEST_IMAGE_SAVE_PATH "Tab" podman_tui_send_inputs "Space" "Tab" "Tab" "Tab" "Tab" podman_tui_send_inputs "Enter" - sleep 6 + sleep $TEST_TIMEOUT_MEDIUM run_helper ls ${TEST_IMAGE_SAVE_PATH} 2> /dev/null assert "$output" == "$TEST_IMAGE_SAVE_PATH" "expected $TEST_IMAGE_SAVE_PATH exists" @@ -70,7 +70,7 @@ load helpers_tui podman_tui_send_inputs "Tab" podman_tui_send_inputs "localhost/${TEST_NAME}_image_imported:latest" podman_tui_send_inputs "Tab" "Tab" "Enter" - sleep 6 + sleep $TEST_TIMEOUT_MEDIUM run_helper podman image ls ${TEST_NAME}_image_imported --format "{{ .Repository }}:{{ .Tag }}" assert "$output" =~ "localhost/${TEST_NAME}_image_imported" "expected image" @@ -96,7 +96,7 @@ load helpers_tui podman_tui_send_inputs ${TEST_IMAGE_BUILD_REPOSITORY} podman_tui_send_inputs "Tab" "Tab" podman_tui_send_inputs "Enter" - sleep 8 + sleep $TEST_TIMEOUT_MEDIUM podman_tui_send_inputs "Tab" "Enter" run_helper podman image ls ${TEST_IMAGE_BUILD_TAG} --format "{{ .Repository }}:{{ .Tag }}" @@ -113,7 +113,7 @@ load helpers_tui podman_tui_set_view "images" podman_tui_select_item $image_index podman_tui_select_image_cmd "diff" - sleep 2 + sleep $TEST_TIMEOUT_LOW podman_tui_send_inputs "Tab" "Enter" run_helper grep -w 'A /var' $PODMAN_TUI_LOG @@ -130,7 +130,7 @@ load helpers_tui podman_tui_set_view "images" podman_tui_select_item $image_index podman_tui_select_image_cmd "history" - sleep 2 + sleep $TEST_TIMEOUT_LOW podman_tui_send_inputs "Tab" "Enter" run_helper egrep -w "\[\[$image_id.*BusyBox.*" $PODMAN_TUI_LOG @@ -148,7 +148,7 @@ load helpers_tui podman_tui_set_view "images" podman_tui_select_item $image_index podman_tui_select_image_cmd "inspect" - sleep 2 + sleep $TEST_TIMEOUT_LOW podman_tui_send_inputs "Enter" run_helper sed -n '/ "RepoTags": \[/, / \],/p' $PODMAN_TUI_LOG @@ -165,7 +165,7 @@ load helpers_tui podman_tui_select_item $busyboxIndex podman_tui_select_image_cmd "tag" podman_tui_send_inputs "$TEST_IMAGE_TAG_NAME" "Tab" "Tab" "Enter" - sleep 2 + sleep $TEST_TIMEOUT_LOW run_helper podman image ls $TEST_IMAGE_TAG_NAME --format "{{ .Repository }}" assert "$output" =~ "$TEST_IMAGE_TAG_NAME" "expected tagged image $TEST_IMAGE_TAG_NAME" @@ -180,10 +180,10 @@ load helpers_tui # press "Tab" 2 times and "Enter" to untag busybox image podman_tui_set_view "images" podman_tui_select_item $busybox_tagindex - sleep 1 + sleep $TEST_TIMEOUT_LOW podman_tui_select_image_cmd "untag" podman_tui_send_inputs "Tab" "Tab" "Enter" - sleep 2 + sleep $TEST_TIMEOUT_LOW untagged_umage=$(podman image ls --format '{{ .Repository }}') assert "$untagged_umage" !~ "$TEST_IMAGE_TAG_NAME" "expected $TEST_IMAGE_TAG_NAME not to be in the list" @@ -205,7 +205,7 @@ load helpers_tui podman_tui_select_item $untagged_image podman_tui_select_image_cmd "remove" podman_tui_send_inputs "Enter" - sleep 2 + sleep $TEST_TIMEOUT_LOW podman_tui_send_inputs "Tab" "Enter" # check if busybox image has been removed @@ -222,7 +222,7 @@ load helpers_tui podman_tui_set_view "images" podman_tui_select_image_cmd "prune" podman_tui_send_inputs "Enter" - sleep 2 + sleep $TEST_TIMEOUT_LOW # check if busybox image has been removed run_helper podman image ls --format "{{ .Repository }}" --filter "reference=busybox" diff --git a/test/002-volume.bats b/test/002-volume.bats index cb3886ff6..ae855fb91 100644 --- a/test/002-volume.bats +++ b/test/002-volume.bats @@ -16,9 +16,9 @@ load helpers_tui podman_tui_set_view "volumes" podman_tui_select_volume_cmd "create" podman_tui_send_inputs "$TEST_VOLUME_NAME" "Tab" "$TEST_LABEL" "Tab" "Tab" "Tab" "Tab" "Enter" - sleep 1 + sleep $TEST_TIMEOUT_LOW podman_tui_send_inputs "Tab" "Enter" - sleep 2 + sleep $TEST_TIMEOUT_LOW run_helper podman volume ls --format "{{ .Name }}" --filter "name=${TEST_VOLUME_NAME}" assert "$output" == "$TEST_VOLUME_NAME" "expected $TEST_VOLUME_NAME to be in the list" @@ -34,9 +34,9 @@ load helpers_tui podman_tui_set_view "volumes" podman_tui_select_item $vol_index podman_tui_select_volume_cmd "inspect" - sleep 1 + sleep $TEST_TIMEOUT_LOW podman_tui_send_inputs "Enter" - sleep 2 + sleep $TEST_TIMEOUT_LOW run_helper sed -n '/ "Labels": {/, / },/p' ${PODMAN_TUI_LOG} assert "$output" =~ "\"$TEST_LABEL_NAME\": \"$TEST_LABEL_VALUE\"" "expected \"$TEST_LABEL_NAME\": \"$TEST_LABEL_VALUE\" in volume inspect" @@ -52,7 +52,7 @@ load helpers_tui podman_tui_select_item $vol_index podman_tui_select_volume_cmd "remove" podman_tui_send_inputs "Enter" - sleep 2 + sleep $TEST_TIMEOUT_LOW run_helper podman volume ls --format "{{ .Name }}" --filter "name=${TEST_VOLUME_NAME}" assert "$output" == "" "expected $TEST_VOLUME_NAME removed" @@ -68,7 +68,7 @@ load helpers_tui podman_tui_set_view "volumes" podman_tui_select_volume_cmd "prune" podman_tui_send_inputs "Enter" - sleep 2 + sleep $TEST_TIMEOUT_LOW run_helper podman volume ls --format "{{ .Name }}" --filter "name=${TEST_NETWORK_NAME}" assert "$output" =~ "" "expected at least $TEST_VOLUME_NAME image removal" diff --git a/test/003-network.bats b/test/003-network.bats index de0bf9118..653460031 100644 --- a/test/003-network.bats +++ b/test/003-network.bats @@ -18,13 +18,13 @@ load helpers_tui podman_tui_set_view "networks" podman_tui_select_network_cmd "connect" - sleep 2 + sleep $TEST_TIMEOUT_LOW podman_tui_send_inputs "Tab" "Tab" podman_tui_send_inputs $TEST_NETWORK_CONNECT_ALIAS podman_tui_send_inputs "Tab" "Tab" "Tab" "Tab" podman_tui_send_inputs "Tab" "Enter" - sleep 2 + sleep $TEST_TIMEOUT_LOW run_helper podman container inspect $TEST_CONTAINER_NAME --format "\"{{ .NetworkSettings.Networks.$TEST_NETWORK_CONNECT }}\"" assert "$output" =~ "$TEST_NETWORK_CONNECT_ALIAS" "expected $TEST_NETWORK_CONNECT_ALIAS to be in the list of aliases" @@ -39,7 +39,7 @@ load helpers_tui podman_tui_set_view "networks" podman_tui_select_network_cmd "disconnect" - sleep 2 + sleep $TEST_TIMEOUT_LOW podman_tui_send_inputs "Tab" "Tab" "Tab" "Enter" run_helper podman container inspect $TEST_CONTAINER_NAME --format "{{ .NetworkSettings.Networks.$TEST_NETWORK_CONNECT }}" @@ -60,9 +60,9 @@ load helpers_tui podman_tui_send_inputs "Tab" podman_tui_send_inputs "$TEST_LABEL" podman_tui_send_inputs "Tab" "Tab" "Tab" "Tab" "Tab" "Enter" - sleep 1 + sleep $TEST_TIMEOUT_LOW podman_tui_send_inputs "Tab" "Enter" - sleep 2 + sleep $TEST_TIMEOUT_LOW run_helper podman network ls --format "{{ .Name }}" --filter "name=${TEST_NETWORK_NAME}$" assert "$output" == "$TEST_NETWORK_NAME" "expected $TEST_NETWORK_NAME to be in the list" } @@ -77,9 +77,9 @@ load helpers_tui podman_tui_set_view "networks" podman_tui_select_item $net_index podman_tui_select_network_cmd "inspect" - sleep 1 + sleep $TEST_TIMEOUT_LOW podman_tui_send_inputs "Enter" - sleep 2 + sleep $TEST_TIMEOUT_LOW run_helper sed -n '/ "labels": {/, / }/p' $PODMAN_TUI_LOG assert "$output" =~ "\"$TEST_LABEL_NAME\": \"$TEST_LABEL_VALUE\"" "expected \"$TEST_LABEL_NAME\": \"$TEST_LABEL_VALUE\" in network inspect" @@ -95,7 +95,7 @@ load helpers_tui podman_tui_select_item $net_index podman_tui_select_network_cmd "remove" podman_tui_send_inputs "Enter" - sleep 2 + sleep $TEST_TIMEOUT_LOW run_helper podman network ls --format "{{ .Name }}" --filter "name=${TEST_NETWORK_NAME}$" assert "$output" == "" "expected $TEST_NETWORK_NAME removed" @@ -111,7 +111,7 @@ load helpers_tui podman_tui_set_view "networks" podman_tui_select_network_cmd "prune" podman_tui_send_inputs "Enter" - sleep 2 + sleep $TEST_TIMEOUT_LOW run_helper podman network ls --format "{{ .Name }}" --filter "name=${TEST_NETWORK_NAME}$" assert "$output" == "" "expected at least $TEST_NETWORK_NAME network removal" diff --git a/test/004-pod.bats b/test/004-pod.bats index 3c049c246..cdbfa1501 100644 --- a/test/004-pod.bats +++ b/test/004-pod.bats @@ -35,7 +35,7 @@ load helpers_tui podman_tui_send_inputs "Tab" "Tab" "Tab" "Tab" "Tab" "Space" podman_tui_send_inputs "Tab" "Tab" podman_tui_send_inputs "Enter" - sleep 4 + sleep $TEST_TIMEOUT_LOW run_helper podman pod ls --filter="name=${TEST_POD_NAME}$" --format "{{ .Status}}" assert $output =~ "Created" "expected $TEST_POD_NAME to be created" @@ -54,7 +54,7 @@ load helpers_tui podman_tui_set_view "pods" podman_tui_select_item $pod_index podman_tui_select_pod_cmd "start" - sleep 2 + sleep $TEST_TIMEOUT_LOW run_helper podman pod ls --filter="name=${TEST_POD_NAME}$" --format "{{ .Status}}" assert $output =~ "Running" "expected $TEST_POD_NAME running" @@ -69,7 +69,7 @@ load helpers_tui podman_tui_set_view "pods" podman_tui_select_item $pod_index podman_tui_select_pod_cmd "pause" - sleep 2 + sleep $TEST_TIMEOUT_LOW run_helper podman pod ls --filter="name=${TEST_POD_NAME}$" --format "{{ .Status}}" assert $output =~ "Paused" "expected $TEST_POD_NAME running" @@ -84,7 +84,7 @@ load helpers_tui podman_tui_set_view "pods" podman_tui_select_item $pod_index podman_tui_select_pod_cmd "unpause" - sleep 2 + sleep $TEST_TIMEOUT_LOW run_helper podman pod ls --filter="name=${TEST_POD_NAME}$" --format "{{ .Status}}" assert $output =~ "Running" "expected $TEST_POD_NAME running" @@ -99,7 +99,7 @@ load helpers_tui podman_tui_set_view "pods" podman_tui_select_item $pod_index podman_tui_select_pod_cmd "stop" - sleep 2 + sleep $TEST_TIMEOUT_LOW run_helper podman pod ls --filter="name=${TEST_POD_NAME}$" --format "{{ .Status}}" assert $output =~ "Exited" "expected $TEST_POD_NAME exited" @@ -114,7 +114,7 @@ load helpers_tui podman_tui_set_view "pods" podman_tui_select_item $pod_index podman_tui_select_pod_cmd "restart" - sleep 2 + sleep $TEST_TIMEOUT_LOW run_helper podman pod ls --filter="name=${TEST_POD_NAME}$" --format "{{ .Status}}" assert $output =~ "Running" "expected $TEST_POD_NAME exited" @@ -129,7 +129,7 @@ load helpers_tui podman_tui_set_view "pods" podman_tui_select_item $pod_index podman_tui_select_pod_cmd "kill" - sleep 2 + sleep $TEST_TIMEOUT_LOW run_helper podman pod ls --filter="name=${TEST_POD_NAME}$" --format "{{ .Status}}" assert $output =~ "Exited" "expected $TEST_POD_NAME exited" @@ -145,7 +145,7 @@ load helpers_tui podman_tui_set_view "pods" podman_tui_select_item $pod_index podman_tui_select_pod_cmd "inspect" - sleep 2 + sleep $TEST_TIMEOUT_LOW podman_tui_send_inputs "Enter" run_helper sed -n '/ "Labels": {/, / },/p' $PODMAN_TUI_LOG @@ -164,7 +164,7 @@ load helpers_tui podman_tui_select_pod_cmd "remove" podman_tui_send_inputs "Enter" podman_tui_send_inputs "Enter" - sleep 2 + sleep $TEST_TIMEOUT_LOW run_helper podman pod ls --format "{{ .Name }}" --filter "name=${TEST_POD_NAME}$" assert "$output" == "" "expected $TEST_POD_NAME pod removal" @@ -174,7 +174,7 @@ load helpers_tui podman pod create --name $TEST_POD_NAME --label $TEST_LABEL || echo done podman pod start $TEST_POD_NAME || echo done podman pod stop $TEST_POD_NAME || echo done - sleep 2 + sleep $TEST_TIMEOUT_LOW # switch to pods view # select prune command from pod commands dialog @@ -182,7 +182,7 @@ load helpers_tui podman_tui_set_view "pods" podman_tui_select_pod_cmd "prune" podman_tui_send_inputs "Enter" - sleep 3 + sleep $TEST_TIMEOUT_LOW run_helper podman pod ls --format "{{ .Name }}" --filter "name=${TEST_POD_NAME}$" assert "$output" == "" "expected at least $TEST_POD_NAME pod removal" diff --git a/test/005-container.bats b/test/005-container.bats index 6759a40c2..a9e15e98a 100644 --- a/test/005-container.bats +++ b/test/005-container.bats @@ -31,9 +31,9 @@ load helpers_tui podman_tui_send_inputs "Enter" "Tab" "Tab" "Tab" podman_tui_send_inputs "Space" "Tab" "Space" "Tab" "$TEST_CONTAINER_TIMEOUT" podman_tui_send_inputs "Tab" "Tab" "Tab" - sleep 2 + sleep $TEST_TIMEOUT_LOW podman_tui_send_inputs "Enter" - sleep 3 + sleep $TEST_TIMEOUT_LOW cnt_status=$(podman container inspect $TEST_CONTAINER_NAME --format "{{ json .State.Status }}") cnt_annotations=$(podman container inspect $TEST_CONTAINER_NAME --format "{{ json .Config.Annotations }}") @@ -66,7 +66,7 @@ load helpers_tui podman_tui_send_inputs "Down" podman_tui_select_item $image_index podman_tui_send_inputs "Enter" "Tab" "Tab" "Tab" "Tab" "Tab" "Tab" "Tab" "Tab" "Tab" - sleep 2 + sleep $TEST_TIMEOUT_LOW # switch to environmen page podman_tui_send_inputs "Down" "Tab" @@ -75,9 +75,9 @@ load helpers_tui podman_tui_send_inputs "Tab" "Tab" "Tab" "Tab" "Tab" "Tab" podman_tui_send_inputs "$TEST_CONTAINER_UMASK" podman_tui_send_inputs "Tab" "Tab" - sleep 2 + sleep $TEST_TIMEOUT_LOW podman_tui_send_inputs "Enter" - sleep 3 + sleep $TEST_TIMEOUT_LOW cnt_workdir=$(podman container inspect $TEST_CONTAINER_NAME --format "{{ json .Config.WorkingDir }}") cnt_vars=$(podman container inspect $TEST_CONTAINER_NAME --format "{{ json .Config.Env }}") @@ -89,6 +89,63 @@ load helpers_tui assert "$cnt_vars" =~ "$TEST_CONTAINER_ENV2" "expected container env to match: $TEST_CONTAINER_ENV2" } +@test "container create (resource page)" { + podman container rm -f $TEST_CONTAINER_NAME || echo done + + buysbox_image=$(podman image ls --sort repository --format "{{ .Repository }}" --filter "reference=docker.io/library/busybox") + if [ "${buysbox_image}" == "" ] ; then + podman image pull docker.io/library/busybox + fi + + image_index=$(podman image ls --sort repository --noheading | nl -v 1 | grep 'busybox ' | awk '{print $1}') + + # switch to containers view + # select create command from container commands dialog + podman_tui_set_view "containers" + podman_tui_select_container_cmd "create" + + # fillout name field + # select image from dropdown widget + podman_tui_send_inputs $TEST_CONTAINER_NAME "Tab" "Tab" + podman_tui_send_inputs "Down" + podman_tui_select_item $image_index + podman_tui_send_inputs "Enter" "Tab" "Tab" "Tab" "Tab" "Tab" "Tab" "Tab" "Tab" "Tab" + sleep $TEST_TIMEOUT_LOW + + # switch to environmen page + podman_tui_send_inputs "Down" "Down" "Down" "Down" "Down" "Down" "Down" "Down" "Down" "Tab" + podman_tui_send_inputs "$TEST_CONTAINER_MEMORY" "Tab" "$TEST_CONTAINER_MEMORY_RESERV" "Tab" + podman_tui_send_inputs "$TEST_CONTAINER_MEMORY_SWAP" "Tab" "$TEST_CONTAINER_MEMORY_SWAPPINESS" + podman_tui_send_inputs "Tab" "Tab" "$TEST_CONTAINER_CPU_SHARES" + podman_tui_send_inputs "Tab" "$TEST_CONTAINER_CPU_PERIOD" + podman_tui_send_inputs "Tab" "Tab" "$TEST_CONTAINER_CPU_QUOTA" + podman_tui_send_inputs "Tab" "Tab" "Tab" "Tab" + podman_tui_send_inputs "$TEST_CONTAINER_SHM_SIZE" "Tab" + podman_tui_send_inputs "$TEST_CONTAINER_SHM_SIZE_SYSTYEMD" + sleep $TEST_TIMEOUT_LOW + podman_tui_send_inputs "Tab" "Tab" + podman_tui_send_inputs "Enter" + sleep $TEST_TIMEOUT_LOW + + cnt_memory=$(podman container inspect $TEST_CONTAINER_NAME --format "{{ json .HostConfig.Memory }}") + cnt_memory_reserv=$(podman container inspect $TEST_CONTAINER_NAME --format "{{ json .HostConfig.MemoryReservation }}") + cnt_memory_swap=$(podman container inspect $TEST_CONTAINER_NAME --format "{{ json .HostConfig.MemorySwap }}") + cnt_memory_swappiness=$(podman container inspect $TEST_CONTAINER_NAME --format "{{ json .HostConfig.MemorySwappiness }}") + cnt_cpu_shares=$(podman container inspect $TEST_CONTAINER_NAME --format "{{ json .HostConfig.CpuShares }}") + cnt_cpu_period=$(podman container inspect $TEST_CONTAINER_NAME --format "{{ json .HostConfig.CpuPeriod }}") + cnt_cpu_quota=$(podman container inspect $TEST_CONTAINER_NAME --format "{{ json .HostConfig.CpuQuota }}") + cnt_shm_size=$(podman container inspect $TEST_CONTAINER_NAME --format "{{ json .HostConfig.ShmSize }}") + + assert "$cnt_memory" =~ "$TEST_CONTAINER_MEMORY" "expected container memory to match: $TEST_CONTAINER_MEMORY" + assert "$cnt_memory_reserv" =~ "$TEST_CONTAINER_MEMORY_RESERV" "expected container memory reservation to match: $TEST_CONTAINER_MEMORY_RESERV" + assert "$cnt_memory_swap" =~ "$TEST_CONTAINER_MEMORY_SWAP" "expected container memory swap to match: $TEST_CONTAINER_MEMORY_SWAP" + assert "$cnt_memory_swappiness" =~ "$TEST_CONTAINER_MEMORY_SWAPPINESS" "expected container memory swappiness to match: $TEST_CONTAINER_MEMORY_SWAPPINESS" + assert "$cnt_cpu_shares" =~ "$TEST_CONTAINER_CPU_SHARES" "expected container cpu shares to match: $TEST_CONTAINER_CPU_SHARES" + assert "$cnt_cpu_period" =~ "$TEST_CONTAINER_CPU_PERIOD" "expected container cpu period to match: $TEST_CONTAINER_CPU_PERIOD" + assert "$cnt_cpu_quota" =~ "$TEST_CONTAINER_CPU_QUOTA" "expected container cpu quota to match: $TEST_CONTAINER_CPU_QUOTA" + assert "$cnt_shm_size" =~ "$TEST_CONTAINER_SHM_SIZE" "expected container shm size to match: $TEST_CONTAINER_SHM_SIZE" +} + @test "container create (pod, network, volume, security options, health)" { httpd_image=$(podman image ls --sort repository --format "{{ .Repository }}" --filter "reference=docker.io/library/httpd") if [ "${httpd_image}" == "" ] ; then @@ -106,7 +163,7 @@ load helpers_tui podman network create $TEST_CONTAINER_NETWORK_NAME || echo done podman volume create $TEST_CONTAINER_VOLUME_NAME || echo done podman pod create --name $TEST_CONTAINER_POD_NAME --network $TEST_CONTAINER_NETWORK_NAME --publish $TEST_CONTAINER_PORT || echo done - sleep 2 + sleep $TEST_TIMEOUT_LOW # get required pod, image, network and volume index for number of KeyDown stroke pod_index=$(podman pod ls --sort name --format "{{ .Name }}" | nl -v 1 | grep "$TEST_CONTAINER_POD_NAME" | awk '{print $1}') @@ -131,9 +188,9 @@ load helpers_tui podman_tui_select_item $pod_index podman_tui_send_inputs "Enter" "Tab" podman_tui_send_inputs $TEST_LABEL "Tab" "Tab" "Tab" "Tab" "Tab" "Tab" - sleep 1 + sleep $TEST_TIMEOUT_LOW podman_tui_send_inputs "Tab" - sleep 1 + sleep $TEST_TIMEOUT_LOW # switch to "health check" create view podman_tui_send_inputs "Down" "Down" "Down" "Down" "Tab" @@ -146,9 +203,9 @@ load helpers_tui podman_tui_send_inputs "Tab" "Tab" podman_tui_send_inputs $TEST_CONTAINER_HEALTH_TIMEOUT podman_tui_send_inputs "Tab" "Tab" "Tab" - sleep 1 + sleep $TEST_TIMEOUT_LOW podman_tui_send_inputs "Tab" - sleep 1 + sleep $TEST_TIMEOUT_LOW # switch to "security options" create view podman_tui_send_inputs "Down" "Down" "Down" "Tab" @@ -161,11 +218,11 @@ load helpers_tui podman_tui_send_inputs "${TEST_CONTAINER_VOLUME_NAME}:${TEST_CONTAINER_VOLUME_MOUNT_POINT}:rw" podman_tui_send_inputs "Tab" "Tab" podman_tui_send_inputs "type=bind,src=${TEST_CONTAINER_MOUNT_SOURCE},dst=${TEST_CONTAINER_MOUNT_DEST}" - sleep 1 + sleep $TEST_TIMEOUT_LOW # go to "Create" button and press Enter podman_tui_send_inputs "Tab" "Tab" "Enter" - sleep 2 + sleep $TEST_TIMEOUT_LOW # get created container information container_information=$(podman container ls --all --pod --filter "name=${TEST_CONTAINER_NAME}$" --format \ @@ -222,7 +279,7 @@ load helpers_tui podman_tui_send_inputs Tab Tab Tab Tab podman_tui_send_inputs Tab Tab Tab Tab podman_tui_send_inputs Enter - sleep 10 + sleep $TEST_TIMEOUT_HIGH run_helper podman image ls ${TEST_CONTAINER_COMMIT_IMAGE_NAME} --format "{{ .Repository }}" assert "$output" =~ "localhost/${TEST_CONTAINER_COMMIT_IMAGE_NAME}" "expected image" } @@ -236,7 +293,7 @@ load helpers_tui podman_tui_set_view "containers" podman_tui_select_item $container_index podman_tui_select_container_cmd "start" - sleep 2 + sleep $TEST_TIMEOUT_LOW run_helper podman container ls --all --filter="name=${TEST_CONTAINER_NAME}$" --format "{{ .Status }}" assert "$output" =~ "Up" "expected $TEST_CONTAINER_NAME to be up" @@ -265,7 +322,7 @@ load helpers_tui podman_tui_send_inputs "Tab" "Tab" "Tab" "Tab" podman_tui_send_inputs "Tab" "Tab" "Enter" - sleep 10 + sleep $TEST_TIMEOUT_HIGH run_helper ls ~/${TEST_CONTAINER_CHECKPOINT_NAME}_dump.tar 2>/dev/null || echo -e '\c' assert "$output" == "/root/${TEST_CONTAINER_CHECKPOINT_NAME}_dump.tar" "expected tar file to be created" @@ -288,7 +345,7 @@ load helpers_tui podman_tui_send_inputs "Tab" "Tab" "Tab" "Tab" podman_tui_send_inputs "Tab" "Tab" "Enter" - sleep 8 + sleep $TEST_TIMEOUT_HIGH run_helper podman container ls --all --format "{{ .Names }}" assert "$output" =~ "${TEST_CONTAINER_CHECKPOINT_NAME}_restore" "expected container to be restored" } @@ -312,10 +369,10 @@ load helpers_tui podman_tui_send_inputs "Tab" "Space" "Tab" podman_tui_send_inputs "Tab" "Tab" "Tab" "Tab" "Tab" "Tab" "Tab" "Tab" podman_tui_send_inputs "Enter" - sleep 1 + sleep $TEST_TIMEOUT_LOW podman_tui_send_inputs "echo Space test Space > Space a.txt" "Enter" podman_tui_send_inputs "Tab" "Enter" - sleep 2 + sleep $TEST_TIMEOUT_LOW run_helper podman container exec $TEST_CONTAINER_NAME cat a.txt @@ -331,7 +388,7 @@ load helpers_tui podman_tui_set_view "containers" podman_tui_select_item $container_index podman_tui_select_container_cmd "inspect" - sleep 2 + sleep $TEST_TIMEOUT_LOW run_helper sed -n '/ "Labels": {/, / },/p' $PODMAN_TUI_LOG @@ -347,7 +404,7 @@ load helpers_tui podman_tui_set_view "containers" podman_tui_select_item $container_index podman_tui_select_container_cmd "diff" - sleep 6 + sleep $TEST_TIMEOUT_MEDIUM run_helper grep -w "/etc" $PODMAN_TUI_LOG assert "$output" =~ '/etc' "expected '/etc' in the logs" @@ -362,7 +419,7 @@ load helpers_tui podman_tui_set_view "containers" podman_tui_select_item $container_index podman_tui_select_container_cmd "top" - sleep 2 + sleep $TEST_TIMEOUT_LOW run_helper grep -w "USER PID PPID" $PODMAN_TUI_LOG assert "$output" =~ 'USER PID PPID' "expected 'USER PID PPID' in the logs" @@ -377,7 +434,7 @@ load helpers_tui podman_tui_set_view "containers" podman_tui_select_item $container_index podman_tui_select_container_cmd "port" - sleep 2 + sleep $TEST_TIMEOUT_LOW container_ports=$(podman container ls --all --filter="name=${TEST_CONTAINER_NAME}$" --format "{{ .Ports }}") run_helper grep -w "$container_ports" $PODMAN_TUI_LOG @@ -393,7 +450,7 @@ load helpers_tui podman_tui_set_view "containers" podman_tui_select_item $container_index podman_tui_select_container_cmd "pause" - sleep 2 + sleep $TEST_TIMEOUT_LOW run_helper podman container ls --all --filter="name=${TEST_CONTAINER_NAME}$" --format "{{ .Status }}" assert "$output" =~ "Paused" "expected $TEST_CONTAINER_NAME to be paused" @@ -408,7 +465,7 @@ load helpers_tui podman_tui_set_view "containers" podman_tui_select_item $container_index podman_tui_select_container_cmd "unpause" - sleep 2 + sleep $TEST_TIMEOUT_LOW run_helper podman container ls --all --filter="name=${TEST_CONTAINER_NAME}$" --format "{{ .Status }}" assert "$output" =~ "Up" "expected $TEST_CONTAINER_NAME to be Up" @@ -423,7 +480,7 @@ load helpers_tui podman_tui_set_view "containers" podman_tui_select_item $container_index podman_tui_select_container_cmd "stop" - sleep 2 + sleep $TEST_TIMEOUT_LOW run_helper podman container ls --all --filter="name=${TEST_CONTAINER_NAME}$" --format "{{ .Status }}" assert "$output" =~ "Exited" "expected $TEST_CONTAINER_NAME to be Up" @@ -439,7 +496,7 @@ load helpers_tui podman_tui_set_view "containers" podman_tui_select_item $container_index podman_tui_select_container_cmd "kill" - sleep 2 + sleep $TEST_TIMEOUT_LOW run_helper podman container ls --all --filter="name=${TEST_CONTAINER_NAME}$" --format "{{ .Status }}" assert "$output" =~ "Exited" "expected $TEST_CONTAINER_NAME to be killed" @@ -455,7 +512,7 @@ load helpers_tui podman_tui_select_item $container_index podman_tui_select_container_cmd "remove" podman_tui_send_inputs "Enter" - sleep 2 + sleep $TEST_TIMEOUT_LOW run_helper podman container ls --all --filter "name=${TEST_CONTAINER_NAME}$" --noheading assert "$output" == "" "expected $TEST_CONTAINER_NAME to be removed" @@ -474,7 +531,7 @@ load helpers_tui podman_tui_select_container_cmd "rename" podman_tui_send_inputs ${TEST_CONTAINER_NAME}_renamed podman_tui_send_inputs "Tab" "Tab" "Enter" - sleep 2 + sleep $TEST_TIMEOUT_LOW run_helper podman container ls --all --filter "name=${TEST_CONTAINER_NAME}_renamed$" --format "{{ .Names }}" assert "$output" == "${TEST_CONTAINER_NAME}_renamed" "expected ${TEST_CONTAINER_NAME}_renamed to be in the list" @@ -492,7 +549,7 @@ load helpers_tui podman_tui_select_item $container_index podman_tui_select_container_cmd "prune" podman_tui_send_inputs "Enter" - sleep 10 + sleep $TEST_TIMEOUT_MEDIUM run_helper podman container ls --all --filter "name=${TEST_CONTAINER_NAME}$" --noheading assert "$output" == "" "expected $TEST_CONTAINER_NAME to be removed" diff --git a/test/006-system.bats b/test/006-system.bats index 72e425dea..64cf79c76 100644 --- a/test/006-system.bats +++ b/test/006-system.bats @@ -18,7 +18,7 @@ load helpers_tui podman_tui_send_inputs "Tab" podman_tui_send_inputs $TEST_SYSTEM_CONN_URI podman_tui_send_inputs "Tab" "Tab" "Tab" "Enter" - sleep 1 + sleep $TEST_TIMEOUT_LOW run_helper tail -2 $PODMAN_TUI_CONFIG_FILE assert "$output" =~ "[services.${TEST_SYSTEM_CONN_NAME}]" "expected [services.${TEST_SYSTEM_CONN_NAME}] in ${PODMAN_TUI_CONFIG_FILE}" @@ -32,7 +32,7 @@ load helpers_tui podman_tui_set_view "system" podman_tui_select_item 1 podman_tui_select_system_cmd "default" - sleep 1 + sleep $TEST_TIMEOUT_LOW run_helper tail -3 $PODMAN_TUI_CONFIG_FILE assert "$output" =~ "[services.${TEST_SYSTEM_CONN_NAME}]" "expected [services.${TEST_SYSTEM_CONN_NAME}] in ${PODMAN_TUI_CONFIG_FILE}" @@ -49,7 +49,7 @@ load helpers_tui podman_tui_select_item 1 podman_tui_select_system_cmd "remove" podman_tui_send_inputs "Enter" - sleep 1 + sleep $TEST_TIMEOUT_LOW run_helper tail -3 $PODMAN_TUI_CONFIG_FILE assert "$output" !~ "services.${TEST_SYSTEM_CONN_NAME}" "expected [services.${TEST_SYSTEM_CONN_NAME}] not in ${PODMAN_TUI_CONFIG_FILE}" @@ -60,7 +60,7 @@ load helpers_tui # select "disconnect" command podman_tui_set_view "system" podman_tui_select_system_cmd "disconnect" - sleep 2 + sleep $TEST_TIMEOUT_LOW run_helper tmux capture-pane -pS 0 -E 0 assert "$output" =~ "DISCONNECTED" "expected DISCONNECTED connection status" @@ -74,13 +74,13 @@ load helpers_tui # select "disconnect" command podman_tui_set_view "system" podman_tui_select_system_cmd "disconnect" - sleep 2 + sleep $TEST_TIMEOUT_LOW run_helper tmux capture-pane -pS 0 -E 0 assert "$output" =~ "DISCONNECTED" "expected DISCONNECTED connection status" # select "connect" command podman_tui_select_system_cmd "connect" - sleep 3 + sleep $TEST_TIMEOUT_LOW run_helper tmux capture-pane -pS 0 -E 0 assert "$output" =~ "STATUS_OK" "expected STATUS_OK connection status" diff --git a/test/helpers_tui.bash b/test/helpers_tui.bash index d09af75cc..329587c0d 100644 --- a/test/helpers_tui.bash +++ b/test/helpers_tui.bash @@ -37,6 +37,18 @@ TEST_IMAGE_BUILD_CONTEXT_DIR="$(realpath .)/test/testdata/" TEST_IMAGE_BUILD_TAG="${TEST_NAME}_image:latest" TEST_IMAGE_BUILD_REPOSITORY="localhost" TEST_IMAGE_SAVE_PATH="/tmp/${TEST_NAME}_image_save.tar" +TEST_CONTAINER_MEMORY=100 +TEST_CONTAINER_MEMORY_RESERV=80 +TEST_CONTAINER_MEMORY_SWAP=150 +TEST_CONTAINER_MEMORY_SWAPPINESS=50 +TEST_CONTAINER_CPU_SHARES=10 +TEST_CONTAINER_CPU_PERIOD=20 +TEST_CONTAINER_CPU_QUOTA=10 +TEST_CONTAINER_SHM_SIZE=120 +TEST_CONTAINER_SHM_SIZE_SYSTYEMD=150 +TEST_TIMEOUT_HIGH=15 +TEST_TIMEOUT_MEDIUM=10 +TEST_TIMEOUT_LOW=5 ################ # podman_tui_set_view # switches to different podman-tui views @@ -237,16 +249,18 @@ function podman_tui_select_container_cmd() { menu_index=14;; "remove") menu_index=15;; - "start") + "run") menu_index=16;; - "stat") + "start") menu_index=17;; - "stop") + "stat") menu_index=18;; - "top") + "stop") menu_index=19;; - "unpause") + "top") menu_index=20;; + "unpause") + menu_index=21;; esac podman_tui_select_menu $menu_index diff --git a/ui/containers/cntdialogs/create.go b/ui/containers/cntdialogs/create.go index abee89ef1..2f29358bd 100644 --- a/ui/containers/cntdialogs/create.go +++ b/ui/containers/cntdialogs/create.go @@ -87,18 +87,33 @@ const ( createContainerHealthStartupRetriesFieldFocus createContainerHealthStartPeriodFieldFocus createContainerHealthStartupSuccessFieldFocus + createContainerMemoryFieldFocus + createContainerMemoryReservatoinFieldFocus + createContainerMemorySwapFieldFocus + createcontainerMemorySwappinessFieldFocus + createContainerCPUsFieldFocus + createContainerCPUSharesFieldFocus + createContainerCPUPeriodFieldFocus + createContainerCPURtPeriodFieldFocus + createContainerCPUQuotaFieldFocus + createContainerCPURtRuntimeFeildFocus + createContainerCPUSetCPUsFieldFocus + createContainerCPUSetMemsFieldFocus + createContainerShmSizeFieldFocus + createContainerShmSizeSystemdFieldFocus ) const ( - containerInfoPageIndex = 0 + iota - environmentPageIndex - userGroupsPageIndex - dnsPageIndex - healthPageIndex - networkingPageIndex - portPageIndex - securityOptsPageIndex - volumePageIndex + createContainerInfoPageIndex = 0 + iota + createContainerEnvironmentPageIndex + createContainerUserGroupsPageIndex + createContainerDNSPageIndex + createContainerHealthPageIndex + createContainerNetworkingPageIndex + createContainerPortPageIndex + createContainerSecurityOptsPageIndex + createContainerVolumePageIndex + createContainerResourcePageIndex ) type ContainerCreateDialogMode int @@ -120,6 +135,7 @@ type ContainerCreateDialog struct { dnsPage *tview.Flex volumePage *tview.Flex healthPage *tview.Flex + resourcePage *tview.Flex form *tview.Form display bool activePageIndex int @@ -180,6 +196,20 @@ type ContainerCreateDialog struct { containerVolumeField *tview.InputField containerImageVolumeField *tview.DropDown containerMountField *tview.InputField + containerMemoryField *tview.InputField + containerMemoryReservationField *tview.InputField + containerMemorySwapField *tview.InputField + containerMemorySwappinessField *tview.InputField + containerCPUsField *tview.InputField + containerCPUPeriodField *tview.InputField + containerCPUQuotaField *tview.InputField + containerCPURtPeriodField *tview.InputField + containerCPURtRuntimeField *tview.InputField + containerCPUSharesField *tview.InputField + containerCPUSetCPUsField *tview.InputField + containerCPUSetMemsField *tview.InputField + containerShmSizeField *tview.InputField + containerShmSizeSystemdField *tview.InputField cancelHandler func() enterHandler func() } @@ -201,6 +231,7 @@ func NewContainerCreateDialog(mode ContainerCreateDialogMode) *ContainerCreateDi portPage: tview.NewFlex(), volumePage: tview.NewFlex(), healthPage: tview.NewFlex(), + resourcePage: tview.NewFlex(), form: tview.NewForm(), categoryLabels: []string{ "Container", @@ -212,6 +243,7 @@ func NewContainerCreateDialog(mode ContainerCreateDialogMode) *ContainerCreateDi "Ports Settings", "Security Options", "Volumes Settings", + "Resource Settings", }, activePageIndex: 0, display: false, @@ -269,6 +301,20 @@ func NewContainerCreateDialog(mode ContainerCreateDialogMode) *ContainerCreateDi containerHealthStartupRetriesField: tview.NewInputField(), containerHealthStartupSuccessField: tview.NewInputField(), containerHealthStartupTimeoutField: tview.NewInputField(), + containerMemoryField: tview.NewInputField(), + containerMemoryReservationField: tview.NewInputField(), + containerMemorySwapField: tview.NewInputField(), + containerMemorySwappinessField: tview.NewInputField(), + containerCPUsField: tview.NewInputField(), + containerCPUPeriodField: tview.NewInputField(), + containerCPUQuotaField: tview.NewInputField(), + containerCPURtPeriodField: tview.NewInputField(), + containerCPURtRuntimeField: tview.NewInputField(), + containerCPUSharesField: tview.NewInputField(), + containerCPUSetCPUsField: tview.NewInputField(), + containerCPUSetMemsField: tview.NewInputField(), + containerShmSizeField: tview.NewInputField(), + containerShmSizeSystemdField: tview.NewInputField(), } containerDialog.setupLayout() @@ -298,6 +344,7 @@ func (d *ContainerCreateDialog) setupLayout() { d.setupUserGroupsPageUI() d.setupDNSPageUI() d.setupHealthPageUI() + d.setupResourcePageUI() d.setupNetworkPageUI() d.setupPortsPageUI() d.setupSecurityPageUI() @@ -317,15 +364,16 @@ func (d *ContainerCreateDialog) setupLayout() { d.form.SetButtonBackgroundColor(style.ButtonBgColor) // adding category pages - d.categoryPages.AddPage(d.categoryLabels[containerInfoPageIndex], d.containerInfoPage, true, true) - d.categoryPages.AddPage(d.categoryLabels[environmentPageIndex], d.environmentPage, true, true) - d.categoryPages.AddPage(d.categoryLabels[userGroupsPageIndex], d.userGroupsPage, true, true) - d.categoryPages.AddPage(d.categoryLabels[dnsPageIndex], d.dnsPage, true, true) - d.categoryPages.AddPage(d.categoryLabels[healthPageIndex], d.healthPage, true, true) - d.categoryPages.AddPage(d.categoryLabels[networkingPageIndex], d.networkingPage, true, true) - d.categoryPages.AddPage(d.categoryLabels[portPageIndex], d.portPage, true, true) - d.categoryPages.AddPage(d.categoryLabels[securityOptsPageIndex], d.securityOptsPage, true, true) - d.categoryPages.AddPage(d.categoryLabels[volumePageIndex], d.volumePage, true, true) + d.categoryPages.AddPage(d.categoryLabels[createContainerInfoPageIndex], d.containerInfoPage, true, true) + d.categoryPages.AddPage(d.categoryLabels[createContainerEnvironmentPageIndex], d.environmentPage, true, true) + d.categoryPages.AddPage(d.categoryLabels[createContainerUserGroupsPageIndex], d.userGroupsPage, true, true) + d.categoryPages.AddPage(d.categoryLabels[createContainerDNSPageIndex], d.dnsPage, true, true) + d.categoryPages.AddPage(d.categoryLabels[createContainerHealthPageIndex], d.healthPage, true, true) + d.categoryPages.AddPage(d.categoryLabels[createContainerNetworkingPageIndex], d.networkingPage, true, true) + d.categoryPages.AddPage(d.categoryLabels[createContainerPortPageIndex], d.portPage, true, true) + d.categoryPages.AddPage(d.categoryLabels[createContainerSecurityOptsPageIndex], d.securityOptsPage, true, true) + d.categoryPages.AddPage(d.categoryLabels[createContainerVolumePageIndex], d.volumePage, true, true) + d.categoryPages.AddPage(d.categoryLabels[createContainerResourcePageIndex], d.resourcePage, true, true) // add it to layout. d.layout.SetBackgroundColor(bgColor) @@ -984,6 +1032,189 @@ func (d *ContainerCreateDialog) setupVolumePageUI() { d.volumePage.SetBackgroundColor(bgColor) } +func (d *ContainerCreateDialog) setupResourcePageUI() { + bgColor := style.DialogBgColor + inputFieldBgColor := style.InputFieldBgColor + resourcePageLabelWidth := 13 + inputFieldWidth := 18 + + getSecondColLabel := func(label string) string { + return fmt.Sprintf("%18s:", label) + } + + // memory + d.containerMemoryField.SetLabel("memory:") + d.containerMemoryField.SetLabelWidth(resourcePageLabelWidth) + d.containerMemoryField.SetBackgroundColor(bgColor) + d.containerMemoryField.SetLabelColor(style.DialogFgColor) + d.containerMemoryField.SetFieldWidth(inputFieldWidth) + d.containerMemoryField.SetFieldBackgroundColor(inputFieldBgColor) + + // memory reservation + memResLabel := "memory reservation:" + d.containerMemoryReservationField.SetLabel(memResLabel) + d.containerMemoryReservationField.SetLabelWidth(len(memResLabel) + 1) + d.containerMemoryReservationField.SetBackgroundColor(bgColor) + d.containerMemoryReservationField.SetLabelColor(style.DialogFgColor) + d.containerMemoryReservationField.SetFieldBackgroundColor(inputFieldBgColor) + + // memory swap + d.containerMemorySwapField.SetLabel("memory swap:") + d.containerMemorySwapField.SetLabelWidth(resourcePageLabelWidth) + d.containerMemorySwapField.SetBackgroundColor(bgColor) + d.containerMemorySwapField.SetLabelColor(style.DialogFgColor) + d.containerMemorySwapField.SetFieldWidth(inputFieldWidth) + d.containerMemorySwapField.SetFieldBackgroundColor(inputFieldBgColor) + + // memory swappiness + d.containerMemorySwappinessField.SetLabel(" memory swappiness:") + d.containerMemorySwappinessField.SetLabelWidth(len(memResLabel) + 1) + d.containerMemorySwappinessField.SetBackgroundColor(bgColor) + d.containerMemorySwappinessField.SetLabelColor(style.DialogFgColor) + d.containerMemorySwappinessField.SetFieldBackgroundColor(inputFieldBgColor) + + // memRow1 + memRow1Layout := tview.NewFlex().SetDirection(tview.FlexColumn) + memRow1Layout.AddItem(d.containerMemoryField, 0, 1, true) + memRow1Layout.AddItem(utils.EmptyBoxSpace(bgColor), 1, 0, true) + memRow1Layout.AddItem(d.containerMemoryReservationField, 0, 1, true) + memRow1Layout.SetBackgroundColor(bgColor) + + // memRow2 + memRow2Layout := tview.NewFlex().SetDirection(tview.FlexColumn) + memRow2Layout.AddItem(d.containerMemorySwapField, 0, 1, true) + memRow2Layout.AddItem(utils.EmptyBoxSpace(bgColor), 1, 0, true) + memRow2Layout.AddItem(d.containerMemorySwappinessField, 0, 1, true) + memRow2Layout.SetBackgroundColor(bgColor) + + // cpus + d.containerCPUsField.SetLabel("cpus:") + d.containerCPUsField.SetLabelWidth(resourcePageLabelWidth) + d.containerCPUsField.SetBackgroundColor(bgColor) + d.containerCPUsField.SetLabelColor(style.DialogFgColor) + d.containerCPUsField.SetFieldBackgroundColor(inputFieldBgColor) + d.containerCPUsField.SetFieldWidth(inputFieldWidth) + + // cpu shares + d.containerCPUSharesField.SetLabel(getSecondColLabel("cpu shares")) + d.containerCPUSharesField.SetLabelWidth(len(memResLabel) + 1) + d.containerCPUSharesField.SetBackgroundColor(bgColor) + d.containerCPUSharesField.SetLabelColor(style.DialogFgColor) + d.containerCPUSharesField.SetFieldBackgroundColor(inputFieldBgColor) + + // cpuRow1 + cpuRow1Layout := tview.NewFlex().SetDirection(tview.FlexColumn) + cpuRow1Layout.AddItem(d.containerCPUsField, 0, 1, true) + cpuRow1Layout.AddItem(utils.EmptyBoxSpace(bgColor), 1, 0, true) + cpuRow1Layout.AddItem(d.containerCPUSharesField, 0, 1, true) + cpuRow1Layout.SetBackgroundColor(bgColor) + + // cpus period + d.containerCPUPeriodField.SetLabel("cpu period:") + d.containerCPUPeriodField.SetLabelWidth(resourcePageLabelWidth) + d.containerCPUPeriodField.SetBackgroundColor(bgColor) + d.containerCPUPeriodField.SetLabelColor(style.DialogFgColor) + d.containerCPUPeriodField.SetFieldBackgroundColor(inputFieldBgColor) + d.containerCPUPeriodField.SetFieldWidth(inputFieldWidth) + + // cpu rt period + d.containerCPURtPeriodField.SetLabel(getSecondColLabel("cpu rt period")) + d.containerCPURtPeriodField.SetLabelWidth(len(memResLabel) + 1) + d.containerCPURtPeriodField.SetBackgroundColor(bgColor) + d.containerCPURtPeriodField.SetLabelColor(style.DialogFgColor) + d.containerCPURtPeriodField.SetFieldBackgroundColor(inputFieldBgColor) + + // cpuRow2 + cpuRow2Layout := tview.NewFlex().SetDirection(tview.FlexColumn) + cpuRow2Layout.AddItem(d.containerCPUPeriodField, 0, 1, true) + cpuRow2Layout.AddItem(utils.EmptyBoxSpace(bgColor), 1, 0, true) + cpuRow2Layout.AddItem(d.containerCPURtPeriodField, 0, 1, true) + cpuRow2Layout.SetBackgroundColor(bgColor) + + // cpus quota + d.containerCPUQuotaField.SetLabel("cpu quota:") + d.containerCPUQuotaField.SetLabelWidth(resourcePageLabelWidth) + d.containerCPUQuotaField.SetBackgroundColor(bgColor) + d.containerCPUQuotaField.SetLabelColor(style.DialogFgColor) + d.containerCPUQuotaField.SetFieldBackgroundColor(inputFieldBgColor) + d.containerCPUQuotaField.SetFieldWidth(inputFieldWidth) + + // cpu rt runtime + d.containerCPURtRuntimeField.SetLabel(getSecondColLabel("cpu rt runtime")) + d.containerCPURtRuntimeField.SetLabelWidth(len(memResLabel) + 1) + d.containerCPURtRuntimeField.SetBackgroundColor(bgColor) + d.containerCPURtRuntimeField.SetLabelColor(style.DialogFgColor) + d.containerCPURtRuntimeField.SetFieldBackgroundColor(inputFieldBgColor) + + // cpuRow3 + cpuRow3Layout := tview.NewFlex().SetDirection(tview.FlexColumn) + cpuRow3Layout.AddItem(d.containerCPUQuotaField, 0, 1, true) + cpuRow3Layout.AddItem(utils.EmptyBoxSpace(bgColor), 1, 0, true) + cpuRow3Layout.AddItem(d.containerCPURtRuntimeField, 0, 1, true) + cpuRow3Layout.SetBackgroundColor(bgColor) + + // cpuset cpus + d.containerCPUSetCPUsField.SetLabel("cpuset cpus:") + d.containerCPUSetCPUsField.SetLabelWidth(resourcePageLabelWidth) + d.containerCPUSetCPUsField.SetBackgroundColor(bgColor) + d.containerCPUSetCPUsField.SetLabelColor(style.DialogFgColor) + d.containerCPUSetCPUsField.SetFieldBackgroundColor(inputFieldBgColor) + d.containerCPUSetCPUsField.SetFieldWidth(inputFieldWidth) + + // cpuset mems + d.containerCPUSetMemsField.SetLabel(getSecondColLabel("cpuset mems")) + d.containerCPUSetMemsField.SetLabelWidth(len(memResLabel) + 1) + d.containerCPUSetMemsField.SetBackgroundColor(bgColor) + d.containerCPUSetMemsField.SetLabelColor(style.DialogFgColor) + d.containerCPUSetMemsField.SetFieldBackgroundColor(inputFieldBgColor) + + // cpuRow4 + cpuRow4Layout := tview.NewFlex().SetDirection(tview.FlexColumn) + cpuRow4Layout.AddItem(d.containerCPUSetCPUsField, 0, 1, true) + cpuRow4Layout.AddItem(utils.EmptyBoxSpace(bgColor), 1, 0, true) + cpuRow4Layout.AddItem(d.containerCPUSetMemsField, 0, 1, true) + cpuRow4Layout.SetBackgroundColor(bgColor) + + // shm size + d.containerShmSizeField.SetLabel("shm size:") + d.containerShmSizeField.SetLabelWidth(resourcePageLabelWidth) + d.containerShmSizeField.SetBackgroundColor(bgColor) + d.containerShmSizeField.SetLabelColor(style.DialogFgColor) + d.containerShmSizeField.SetFieldBackgroundColor(inputFieldBgColor) + d.containerShmSizeField.SetFieldWidth(inputFieldWidth) + + // shm size systemd + d.containerShmSizeSystemdField.SetLabel(getSecondColLabel("shm size systemd")) + d.containerShmSizeSystemdField.SetLabelWidth(len(memResLabel) + 1) + d.containerShmSizeSystemdField.SetBackgroundColor(bgColor) + d.containerShmSizeSystemdField.SetLabelColor(style.DialogFgColor) + d.containerShmSizeSystemdField.SetFieldBackgroundColor(inputFieldBgColor) + + // shmRow1 + shmRow1Layout := tview.NewFlex().SetDirection(tview.FlexColumn) + shmRow1Layout.AddItem(d.containerShmSizeField, 0, 1, true) + shmRow1Layout.AddItem(utils.EmptyBoxSpace(bgColor), 1, 0, true) + shmRow1Layout.AddItem(d.containerShmSizeSystemdField, 0, 1, true) + shmRow1Layout.SetBackgroundColor(bgColor) + + // resource settings page + d.resourcePage.SetDirection(tview.FlexRow) + d.resourcePage.AddItem(memRow1Layout, 1, 0, true) + d.resourcePage.AddItem(utils.EmptyBoxSpace(bgColor), 1, 0, true) + d.resourcePage.AddItem(memRow2Layout, 1, 0, true) + d.resourcePage.AddItem(utils.EmptyBoxSpace(bgColor), 1, 0, true) + d.resourcePage.AddItem(cpuRow1Layout, 1, 0, true) + d.resourcePage.AddItem(utils.EmptyBoxSpace(bgColor), 1, 0, true) + d.resourcePage.AddItem(cpuRow2Layout, 1, 0, true) + d.resourcePage.AddItem(utils.EmptyBoxSpace(bgColor), 1, 0, true) + d.resourcePage.AddItem(cpuRow3Layout, 1, 0, true) + d.resourcePage.AddItem(utils.EmptyBoxSpace(bgColor), 1, 0, true) + d.resourcePage.AddItem(cpuRow4Layout, 1, 0, true) + d.resourcePage.AddItem(utils.EmptyBoxSpace(bgColor), 1, 0, true) + d.resourcePage.AddItem(shmRow1Layout, 1, 0, true) + d.resourcePage.SetBackgroundColor(bgColor) +} + // Display displays this primitive. func (d *ContainerCreateDialog) Display() { d.display = true @@ -1190,6 +1421,35 @@ func (d *ContainerCreateDialog) Focus(delegate func(p tview.Primitive)) { //noli delegate(d.containerHealthStartPeriodField) case createContainerHealthStartupSuccessFieldFocus: delegate(d.containerHealthStartupSuccessField) + // resource page + case createContainerMemoryFieldFocus: + delegate(d.containerMemoryField) + case createContainerMemoryReservatoinFieldFocus: + delegate(d.containerMemoryReservationField) + case createContainerMemorySwapFieldFocus: + delegate(d.containerMemorySwapField) + case createcontainerMemorySwappinessFieldFocus: + delegate(d.containerMemorySwappinessField) + case createContainerCPUsFieldFocus: + delegate(d.containerCPUsField) + case createContainerCPUSharesFieldFocus: + delegate(d.containerCPUSharesField) + case createContainerCPUPeriodFieldFocus: + delegate(d.containerCPUPeriodField) + case createContainerCPURtPeriodFieldFocus: + delegate(d.containerCPURtPeriodField) + case createContainerCPUQuotaFieldFocus: + delegate(d.containerCPUQuotaField) + case createContainerCPURtRuntimeFeildFocus: + delegate(d.containerCPURtRuntimeField) + case createContainerCPUSetCPUsFieldFocus: + delegate(d.containerCPUSetCPUsField) + case createContainerCPUSetMemsFieldFocus: + delegate(d.containerCPUSetMemsField) + case createContainerShmSizeFieldFocus: + delegate(d.containerShmSizeField) + case createContainerShmSizeSystemdFieldFocus: + delegate(d.containerShmSizeSystemdField) // category page case createCategoryPagesFocus: delegate(d.categoryPages) @@ -1345,6 +1605,18 @@ func (d *ContainerCreateDialog) InputHandler() func(event *tcell.EventKey, setFo } } + if d.resourcePage.HasFocus() { + if handler := d.resourcePage.InputHandler(); handler != nil { + if event.Key() == tcell.KeyTab { + d.setResourceSettingsPageNextFocus() + } + + handler(event, setFocus) + + return + } + } + if d.categories.HasFocus() { if categroryHandler := d.categories.InputHandler(); categroryHandler != nil { categroryHandler(event, setFocus) @@ -1601,6 +1873,22 @@ func (d *ContainerCreateDialog) initData() { d.containerMountField.SetText("") d.containerImageVolumeField.SetOptions(imageVolumeOptions, nil) d.containerImageVolumeField.SetCurrentOption(0) + + // resource settings category + d.containerMemoryField.SetText("") + d.containerMemoryReservationField.SetText("") + d.containerMemorySwapField.SetText("") + d.containerMemorySwappinessField.SetText("") + d.containerCPUsField.SetText("") + d.containerCPUSharesField.SetText("") + d.containerCPUPeriodField.SetText("") + d.containerCPURtPeriodField.SetText("") + d.containerCPUQuotaField.SetText("") + d.containerCPURtRuntimeField.SetText("") + d.containerCPUSetCPUsField.SetText("") + d.containerCPUSetMemsField.SetText("") + d.containerShmSizeField.SetText("") + d.containerShmSizeSystemdField.SetText("") } func (d *ContainerCreateDialog) setPortPageNextFocus() { @@ -1835,6 +2123,88 @@ func (d *ContainerCreateDialog) setDNSSettingsPageNextFocus() { d.focusElement = createContainerFormFocus } +func (d *ContainerCreateDialog) setResourceSettingsPageNextFocus() { //nolint:cyclop + if d.containerMemoryField.HasFocus() { + d.focusElement = createContainerMemoryReservatoinFieldFocus + + return + } + + if d.containerMemoryReservationField.HasFocus() { + d.focusElement = createContainerMemorySwapFieldFocus + + return + } + + if d.containerMemorySwapField.HasFocus() { + d.focusElement = createcontainerMemorySwappinessFieldFocus + + return + } + + if d.containerMemorySwappinessField.HasFocus() { + d.focusElement = createContainerCPUsFieldFocus + + return + } + + if d.containerCPUsField.HasFocus() { + d.focusElement = createContainerCPUSharesFieldFocus + + return + } + + if d.containerCPUSharesField.HasFocus() { + d.focusElement = createContainerCPUPeriodFieldFocus + + return + } + + if d.containerCPUPeriodField.HasFocus() { + d.focusElement = createContainerCPURtPeriodFieldFocus + + return + } + + if d.containerCPURtPeriodField.HasFocus() { + d.focusElement = createContainerCPUQuotaFieldFocus + + return + } + + if d.containerCPUQuotaField.HasFocus() { + d.focusElement = createContainerCPURtRuntimeFeildFocus + + return + } + + if d.containerCPURtRuntimeField.HasFocus() { + d.focusElement = createContainerCPUSetCPUsFieldFocus + + return + } + + if d.containerCPUSetCPUsField.HasFocus() { + d.focusElement = createContainerCPUSetMemsFieldFocus + + return + } + + if d.containerCPUSetMemsField.HasFocus() { + d.focusElement = createContainerShmSizeFieldFocus + + return + } + + if d.containerShmSizeField.HasFocus() { + d.focusElement = createContainerShmSizeSystemdFieldFocus + + return + } + + d.focusElement = createContainerFormFocus +} + func (d *ContainerCreateDialog) setHealthSettingsPageNextFocus() { //nolint:cyclop if d.containerHealthCmdField.HasFocus() { d.focusElement = createContainerHealthStartupCmdFieldFocus @@ -1916,7 +2286,7 @@ func (d *ContainerCreateDialog) setVolumeSettingsPageNextFocus() { } // ContainerCreateOptions returns new network options. -func (d *ContainerCreateDialog) ContainerCreateOptions() containers.CreateOptions { //nolint:cyclop,gocognit,gocyclo +func (d *ContainerCreateDialog) ContainerCreateOptions() containers.CreateOptions { //nolint:cyclop,gocognit,gocyclo,maintidx,lll var ( labels []string imageID string @@ -2094,6 +2464,20 @@ func (d *ContainerCreateDialog) ContainerCreateOptions() containers.CreateOption HealthStartupRetries: strings.TrimSpace(d.containerHealthStartupRetriesField.GetText()), HealthStartupSuccess: strings.TrimSpace(d.containerHealthStartupSuccessField.GetText()), HealthStartupTimeout: strings.TrimSpace(d.containerHealthStartupTimeoutField.GetText()), + Memory: strings.TrimSpace(d.containerMemoryField.GetText()), + MemoryReservation: strings.TrimSpace(d.containerMemoryReservationField.GetText()), + MemorySwap: strings.TrimSpace(d.containerMemorySwapField.GetText()), + MemorySwappiness: strings.TrimSpace(d.containerMemorySwappinessField.GetText()), + CPUs: strings.TrimSpace(d.containerCPUsField.GetText()), + CPUShares: strings.TrimSpace(d.containerCPUSharesField.GetText()), + CPUPeriod: strings.TrimSpace(d.containerCPUPeriodField.GetText()), + CPURtPeriod: strings.TrimSpace(d.containerCPURtPeriodField.GetText()), + CPUQuota: strings.TrimSpace(d.containerCPUQuotaField.GetText()), + CPURtRuntime: strings.TrimSpace(d.containerCPURtRuntimeField.GetText()), + CPUSetCPUs: strings.TrimSpace(d.containerCPUSetCPUsField.GetText()), + CPUSetMems: strings.TrimSpace(d.containerCPUSetMemsField.GetText()), + SHMSize: strings.TrimSpace(d.containerShmSizeField.GetText()), + SHMSizeSystemd: strings.TrimSpace(d.containerShmSizeSystemdField.GetText()), } return opts diff --git a/ui/containers/cntdialogs/create_test.go b/ui/containers/cntdialogs/create_test.go index 760d70157..deb2ad18b 100644 --- a/ui/containers/cntdialogs/create_test.go +++ b/ui/containers/cntdialogs/create_test.go @@ -299,6 +299,74 @@ var _ = Describe("container create", Ordered, func() { Expect(createDialog.focusElement).To(Equal(createContainerFormFocus)) }) + It("setResourceSettingsPageNextFocus", func() { + createDialog.focusElement = createContainerMemoryFieldFocus + createDialogApp.SetFocus(createDialog) + createDialogApp.Draw() + createDialog.setResourceSettingsPageNextFocus() + Expect(createDialog.focusElement).To(Equal(createContainerMemoryReservatoinFieldFocus)) + + createDialogApp.SetFocus(createDialog) + createDialogApp.Draw() + createDialog.setResourceSettingsPageNextFocus() + Expect(createDialog.focusElement).To(Equal(createContainerMemorySwapFieldFocus)) + + createDialogApp.SetFocus(createDialog) + createDialogApp.Draw() + createDialog.setResourceSettingsPageNextFocus() + Expect(createDialog.focusElement).To(Equal(createcontainerMemorySwappinessFieldFocus)) + + createDialogApp.SetFocus(createDialog) + createDialogApp.Draw() + createDialog.setResourceSettingsPageNextFocus() + Expect(createDialog.focusElement).To(Equal(createContainerCPUsFieldFocus)) + + createDialogApp.SetFocus(createDialog) + createDialogApp.Draw() + createDialog.setResourceSettingsPageNextFocus() + Expect(createDialog.focusElement).To(Equal(createContainerCPUSharesFieldFocus)) + + createDialogApp.SetFocus(createDialog) + createDialogApp.Draw() + createDialog.setResourceSettingsPageNextFocus() + Expect(createDialog.focusElement).To(Equal(createContainerCPUPeriodFieldFocus)) + + createDialogApp.SetFocus(createDialog) + createDialogApp.Draw() + createDialog.setResourceSettingsPageNextFocus() + Expect(createDialog.focusElement).To(Equal(createContainerCPURtPeriodFieldFocus)) + + createDialogApp.SetFocus(createDialog) + createDialogApp.Draw() + createDialog.setResourceSettingsPageNextFocus() + Expect(createDialog.focusElement).To(Equal(createContainerCPUQuotaFieldFocus)) + + createDialogApp.SetFocus(createDialog) + createDialogApp.Draw() + createDialog.setResourceSettingsPageNextFocus() + Expect(createDialog.focusElement).To(Equal(createContainerCPURtRuntimeFeildFocus)) + + createDialogApp.SetFocus(createDialog) + createDialogApp.Draw() + createDialog.setResourceSettingsPageNextFocus() + Expect(createDialog.focusElement).To(Equal(createContainerCPUSetCPUsFieldFocus)) + + createDialogApp.SetFocus(createDialog) + createDialogApp.Draw() + createDialog.setResourceSettingsPageNextFocus() + Expect(createDialog.focusElement).To(Equal(createContainerCPUSetMemsFieldFocus)) + + createDialogApp.SetFocus(createDialog) + createDialogApp.Draw() + createDialog.setResourceSettingsPageNextFocus() + Expect(createDialog.focusElement).To(Equal(createContainerShmSizeFieldFocus)) + + createDialogApp.SetFocus(createDialog) + createDialogApp.Draw() + createDialog.setResourceSettingsPageNextFocus() + Expect(createDialog.focusElement).To(Equal(createContainerShmSizeSystemdFieldFocus)) + }) + It("hide", func() { createDialog.Hide() Expect(createDialog.IsDisplay()).To(Equal(false))