Get next free port when forwarding ports on podman (#6377)

* Refacto get first free port

* Use next free port for Podman

* Save used ports to reuse them

* Fix unit tests

* Do not rely on system for ports during tests
This commit is contained in:
Philippe Martin
2022-12-09 12:41:29 +01:00
committed by GitHub
parent eeaff57dda
commit 6c99ca1024
6 changed files with 64 additions and 25 deletions

View File

@@ -14,6 +14,7 @@ import (
"github.com/redhat-developer/odo/pkg/util"
corev1 "k8s.io/api/core/v1"
"k8s.io/klog"
)
func createPodFromComponent(
@@ -23,6 +24,7 @@ func createPodFromComponent(
buildCommand string,
runCommand string,
debugCommand string,
usedPorts []int,
) (*corev1.Pod, []api.ForwardedPort, error) {
containers, err := generator.GetContainers(devfileObj, common.DevfileOptions{})
if err != nil {
@@ -39,7 +41,7 @@ func createPodFromComponent(
utils.AddOdoProjectVolume(&containers)
utils.AddOdoMandatoryVolume(&containers)
fwPorts := addHostPorts(containers)
fwPorts := addHostPorts(containers, usedPorts)
volumes := []corev1.Volume{
{
@@ -105,19 +107,25 @@ func getVolumeName(volume string, componentName string, appName string) string {
return volume + "-" + componentName + "-" + appName
}
func addHostPorts(containers []corev1.Container) []api.ForwardedPort {
func addHostPorts(containers []corev1.Container, usedPorts []int) []api.ForwardedPort {
result := []api.ForwardedPort{}
hostPort := int32(39001)
startPort := 40001
endPort := startPort + 10000
for i := range containers {
for j := range containers[i].Ports {
freePort, err := util.NextFreePort(startPort, endPort, usedPorts)
if err != nil {
klog.Infof("%s", err)
continue
}
result = append(result, api.ForwardedPort{
ContainerName: containers[i].Name,
LocalAddress: "127.0.0.1",
LocalPort: int(hostPort),
LocalPort: freePort,
ContainerPort: int(containers[i].Ports[j].ContainerPort),
})
containers[i].Ports[j].HostPort = hostPort
hostPort++
containers[i].Ports[j].HostPort = int32(freePort)
startPort = freePort + 1
}
}
return result
@@ -135,3 +143,11 @@ func addVolumeMountToContainer(containers []corev1.Container, devfileVolume stor
}
return fmt.Errorf("container %q not found", devfileVolume.Container)
}
func getUsedPorts(ports []api.ForwardedPort) []int {
res := make([]int, 0, len(ports))
for _, port := range ports {
res = append(res, port.LocalPort)
}
return res
}

View File

@@ -222,7 +222,7 @@ func Test_createPodFromComponent(t *testing.T) {
Name: "http",
ContainerPort: 8080,
Protocol: "TCP",
HostPort: 39001,
HostPort: 40001,
})
return pod
},
@@ -230,7 +230,7 @@ func Test_createPodFromComponent(t *testing.T) {
{
ContainerName: "mycomponent",
LocalAddress: "127.0.0.1",
LocalPort: 39001,
LocalPort: 40001,
ContainerPort: 8080,
},
},
@@ -264,13 +264,13 @@ func Test_createPodFromComponent(t *testing.T) {
Name: "http",
ContainerPort: 8080,
Protocol: "TCP",
HostPort: 39001,
HostPort: 40001,
})
pod.Spec.Containers[0].Ports = append(pod.Spec.Containers[0].Ports, corev1.ContainerPort{
Name: "debug",
ContainerPort: 5858,
Protocol: "TCP",
HostPort: 39002,
HostPort: 40002,
})
return pod
},
@@ -278,13 +278,13 @@ func Test_createPodFromComponent(t *testing.T) {
{
ContainerName: "mycomponent",
LocalAddress: "127.0.0.1",
LocalPort: 39001,
LocalPort: 40001,
ContainerPort: 8080,
},
{
ContainerName: "mycomponent",
LocalAddress: "127.0.0.1",
LocalPort: 39002,
LocalPort: 40002,
ContainerPort: 5858,
},
},
@@ -332,7 +332,7 @@ func Test_createPodFromComponent(t *testing.T) {
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, gotFwPorts, err := createPodFromComponent(tt.args.devfileObj(), tt.args.componentName, tt.args.appName, tt.args.buildCommand, tt.args.runCommand, tt.args.debugCommand)
got, gotFwPorts, err := createPodFromComponent(tt.args.devfileObj(), tt.args.componentName, tt.args.appName, tt.args.buildCommand, tt.args.runCommand, tt.args.debugCommand, []int{40001, 40002})
if (err != nil) != tt.wantErr {
t.Errorf("createPodFromComponent() error = %v, wantErr %v", err, tt.wantErr)
return

View File

@@ -35,6 +35,7 @@ type DevClient struct {
watchClient watch.Client
deployedPod *corev1.Pod
usedPorts []int
}
var _ dev.Client = (*DevClient)(nil)

View File

@@ -136,10 +136,12 @@ func (o *DevClient) deployPod(ctx context.Context, options dev.StartOptions) (*c
options.BuildCommand,
options.RunCommand,
"",
o.usedPorts,
)
if err != nil {
return nil, nil, err
}
o.usedPorts = getUsedPorts(fwPorts)
if equality.Semantic.DeepEqual(o.deployedPod, pod) {
klog.V(4).Info("pod is already deployed as required")

View File

@@ -11,6 +11,7 @@ import (
"github.com/devfile/library/pkg/devfile/parser"
parsercommon "github.com/devfile/library/pkg/devfile/parser/data/v2/common"
"k8s.io/apimachinery/pkg/util/runtime"
"k8s.io/klog"
"github.com/redhat-developer/odo/pkg/kclient"
"github.com/redhat-developer/odo/pkg/libdevfile"
@@ -192,23 +193,21 @@ func randomPortPairsFromContainerEndpoints(ceMap map[string][]int) map[string][]
// portPairsFromContainerEndpoints assigns a port on localhost to each port in the provided containerEndpoints map
// it returns a map of the format "<container-name>":{"<local-port-1>:<remote-port-1>", "<local-port-2>:<remote-port-2>"}
// "container1": {"400001:3000", "400002:3001"}
// "container1": {"40001:3000", "40002:3001"}
func portPairsFromContainerEndpoints(ceMap map[string][]int) map[string][]string {
portPairs := make(map[string][]string)
port := 40000
startPort := 40001
endPort := startPort + 10000
for name, ports := range ceMap {
for _, p := range ports {
port++
for {
isPortFree := util.IsPortFree(port)
if isPortFree {
pair := fmt.Sprintf("%d:%d", port, p)
portPairs[name] = append(portPairs[name], pair)
break
}
port++
freePort, err := util.NextFreePort(startPort, endPort, nil)
if err != nil {
klog.Infof("%s", err)
continue
}
pair := fmt.Sprintf("%d:%d", freePort, p)
portPairs[name] = append(portPairs[name], pair)
startPort = freePort + 1
}
}
return portPairs

View File

@@ -784,6 +784,27 @@ func IsPortFree(port int) bool {
return err == nil
}
// NextFreePort returns the next free port on system, starting at start
// end finishing at end.
// If no port is found in the range [start, end], 0 is returned
func NextFreePort(start, end int, usedPorts []int) (int, error) {
port := start
for {
for _, usedPort := range usedPorts {
if usedPort == port {
return port, nil
}
}
if IsPortFree(port) {
return port, nil
}
port++
if port > end {
return 0, fmt.Errorf("no free port in range [%d-%d]", start, end)
}
}
}
// WriteToJSONFile writes a struct to json file
func WriteToJSONFile(c interface{}, filename string) error {
data, err := json.Marshal(c)