mirror of
https://github.com/redhat-developer/odo.git
synced 2025-10-19 03:06:19 +03:00
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:
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -35,6 +35,7 @@ type DevClient struct {
|
||||
watchClient watch.Client
|
||||
|
||||
deployedPod *corev1.Pod
|
||||
usedPorts []int
|
||||
}
|
||||
|
||||
var _ dev.Client = (*DevClient)(nil)
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user