mirror of
https://github.com/gitpod-io/gitpod.git
synced 2025-12-08 17:36:30 +00:00
993 lines
34 KiB
Go
993 lines
34 KiB
Go
// Copyright (c) 2020 Gitpod GmbH. All rights reserved.
|
|
// Licensed under the GNU Affero General Public License (AGPL).
|
|
// See License.AGPL.txt in the project root for license information.
|
|
|
|
package ports
|
|
|
|
import (
|
|
"context"
|
|
"io"
|
|
"net"
|
|
"sync"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/gitpod-io/gitpod/common-go/log"
|
|
gitpod "github.com/gitpod-io/gitpod/gitpod-protocol"
|
|
"github.com/gitpod-io/gitpod/supervisor/api"
|
|
"github.com/google/go-cmp/cmp"
|
|
"github.com/google/go-cmp/cmp/cmpopts"
|
|
"github.com/sirupsen/logrus"
|
|
"golang.org/x/sync/errgroup"
|
|
)
|
|
|
|
func TestPortsUpdateState(t *testing.T) {
|
|
type ExposureExpectation []ExposedPort
|
|
type UpdateExpectation [][]*api.PortsStatus
|
|
type ConfigChange struct {
|
|
instance []*gitpod.PortsItems
|
|
}
|
|
type Change struct {
|
|
Config *ConfigChange
|
|
Served []ServedPort
|
|
Exposed []ExposedPort
|
|
Tunneled []PortTunnelState
|
|
ConfigErr error
|
|
ServedErr error
|
|
ExposedErr error
|
|
TunneledErr error
|
|
}
|
|
tests := []struct {
|
|
Desc string
|
|
InternalPorts []uint32
|
|
Changes []Change
|
|
ExpectedExposure ExposureExpectation
|
|
ExpectedUpdates UpdateExpectation
|
|
}{
|
|
{
|
|
Desc: "basic locally served",
|
|
Changes: []Change{
|
|
{Served: []ServedPort{{net.IPv4(127, 0, 0, 1), 8080, true}}},
|
|
{Exposed: []ExposedPort{{LocalPort: 8080, URL: "foobar"}}},
|
|
{Served: []ServedPort{{net.IPv4(127, 0, 0, 1), 8080, true}, {net.IPv4zero, 60000, false}}},
|
|
{Served: []ServedPort{{net.IPv4zero, 60000, false}}},
|
|
{Served: []ServedPort{}},
|
|
},
|
|
ExpectedExposure: []ExposedPort{
|
|
{LocalPort: 8080},
|
|
{LocalPort: 60000},
|
|
},
|
|
ExpectedUpdates: UpdateExpectation{
|
|
{},
|
|
[]*api.PortsStatus{{LocalPort: 8080, Served: true, OnOpen: api.PortsStatus_notify_private}},
|
|
[]*api.PortsStatus{{LocalPort: 8080, Served: true, OnOpen: api.PortsStatus_notify_private, Exposed: &api.ExposedPortInfo{OnExposed: api.OnPortExposedAction_notify_private, Visibility: api.PortVisibility_private, Url: "foobar"}}},
|
|
[]*api.PortsStatus{{LocalPort: 8080, Served: true, OnOpen: api.PortsStatus_notify_private, Exposed: &api.ExposedPortInfo{OnExposed: api.OnPortExposedAction_notify_private, Visibility: api.PortVisibility_private, Url: "foobar"}}, {LocalPort: 60000, Served: true}},
|
|
[]*api.PortsStatus{{LocalPort: 8080, Served: false, OnOpen: api.PortsStatus_notify_private, Exposed: &api.ExposedPortInfo{OnExposed: api.OnPortExposedAction_notify_private, Visibility: api.PortVisibility_private, Url: "foobar"}}, {LocalPort: 60000, Served: true}},
|
|
[]*api.PortsStatus{{LocalPort: 8080, Served: false, OnOpen: api.PortsStatus_notify_private, Exposed: &api.ExposedPortInfo{OnExposed: api.OnPortExposedAction_notify_private, Visibility: api.PortVisibility_private, Url: "foobar"}}},
|
|
},
|
|
},
|
|
{
|
|
Desc: "basic globally served",
|
|
Changes: []Change{
|
|
{Served: []ServedPort{{net.IPv4zero, 8080, false}}},
|
|
{Served: []ServedPort{}},
|
|
},
|
|
ExpectedExposure: []ExposedPort{
|
|
{LocalPort: 8080},
|
|
},
|
|
ExpectedUpdates: UpdateExpectation{
|
|
{},
|
|
[]*api.PortsStatus{{LocalPort: 8080, Served: true, OnOpen: api.PortsStatus_notify_private}},
|
|
{},
|
|
},
|
|
},
|
|
{
|
|
Desc: "basic port publically exposed",
|
|
Changes: []Change{
|
|
{Served: []ServedPort{{Port: 8080}}},
|
|
{Exposed: []ExposedPort{{LocalPort: 8080, Public: true, URL: "foobar"}}},
|
|
{Exposed: []ExposedPort{{LocalPort: 8080, Public: false, URL: "foobar"}}},
|
|
},
|
|
ExpectedExposure: ExposureExpectation{
|
|
{LocalPort: 8080},
|
|
},
|
|
ExpectedUpdates: UpdateExpectation{
|
|
{},
|
|
[]*api.PortsStatus{{LocalPort: 8080, Served: true, OnOpen: api.PortsStatus_notify_private}},
|
|
[]*api.PortsStatus{{LocalPort: 8080, Served: true, OnOpen: api.PortsStatus_notify_private, Exposed: &api.ExposedPortInfo{Visibility: api.PortVisibility_public, Url: "foobar", OnExposed: api.OnPortExposedAction_notify_private}}},
|
|
[]*api.PortsStatus{{LocalPort: 8080, Served: true, OnOpen: api.PortsStatus_notify_private, Exposed: &api.ExposedPortInfo{Visibility: api.PortVisibility_private, Url: "foobar", OnExposed: api.OnPortExposedAction_notify_private}}},
|
|
},
|
|
},
|
|
{
|
|
Desc: "internal ports served",
|
|
InternalPorts: []uint32{8080},
|
|
Changes: []Change{
|
|
{Served: []ServedPort{}},
|
|
{Served: []ServedPort{{net.IPv4zero, 8080, false}}},
|
|
},
|
|
ExpectedExposure: ExposureExpectation(nil),
|
|
ExpectedUpdates: UpdateExpectation{{}},
|
|
},
|
|
{
|
|
Desc: "serving port from the configured port range",
|
|
Changes: []Change{
|
|
{Config: &ConfigChange{
|
|
instance: []*gitpod.PortsItems{{
|
|
OnOpen: "open-browser",
|
|
Port: "4000-5000",
|
|
}},
|
|
}},
|
|
{Served: []ServedPort{{net.IPv4(127, 0, 0, 1), 4040, true}}},
|
|
{Exposed: []ExposedPort{{LocalPort: 4040, Public: true, URL: "4040-foobar"}}},
|
|
{Served: []ServedPort{{net.IPv4(127, 0, 0, 1), 4040, true}, {net.IPv4zero, 60000, false}}},
|
|
},
|
|
ExpectedExposure: []ExposedPort{
|
|
{LocalPort: 4040},
|
|
{LocalPort: 60000},
|
|
},
|
|
ExpectedUpdates: UpdateExpectation{
|
|
{},
|
|
{},
|
|
[]*api.PortsStatus{{LocalPort: 4040, Served: true, OnOpen: api.PortsStatus_open_browser}},
|
|
[]*api.PortsStatus{{LocalPort: 4040, Served: true, OnOpen: api.PortsStatus_open_browser, Exposed: &api.ExposedPortInfo{Visibility: api.PortVisibility_public, Url: "4040-foobar", OnExposed: api.OnPortExposedAction_open_browser}}},
|
|
[]*api.PortsStatus{
|
|
{LocalPort: 4040, Served: true, OnOpen: api.PortsStatus_open_browser, Exposed: &api.ExposedPortInfo{Visibility: api.PortVisibility_public, Url: "4040-foobar", OnExposed: api.OnPortExposedAction_open_browser}},
|
|
{LocalPort: 60000, Served: true},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Desc: "auto expose configured ports",
|
|
Changes: []Change{
|
|
{
|
|
Config: &ConfigChange{instance: []*gitpod.PortsItems{
|
|
{Port: 8080, Visibility: "private"},
|
|
}},
|
|
},
|
|
{
|
|
Exposed: []ExposedPort{{LocalPort: 8080, Public: false, URL: "foobar"}},
|
|
},
|
|
{
|
|
Exposed: []ExposedPort{{LocalPort: 8080, Public: true, URL: "foobar"}},
|
|
},
|
|
{
|
|
Served: []ServedPort{{net.IPv4(127, 0, 0, 1), 8080, true}},
|
|
},
|
|
{
|
|
Exposed: []ExposedPort{{LocalPort: 8080, Public: true, URL: "foobar"}},
|
|
},
|
|
{
|
|
Served: []ServedPort{{net.IPv4(127, 0, 0, 1), 8080, true}},
|
|
},
|
|
{
|
|
Served: []ServedPort{},
|
|
},
|
|
{
|
|
Served: []ServedPort{{net.IPv4(127, 0, 0, 1), 8080, false}},
|
|
},
|
|
},
|
|
ExpectedExposure: []ExposedPort{
|
|
{LocalPort: 8080, Public: false},
|
|
},
|
|
ExpectedUpdates: UpdateExpectation{
|
|
{},
|
|
[]*api.PortsStatus{{LocalPort: 8080, OnOpen: api.PortsStatus_notify}},
|
|
[]*api.PortsStatus{{LocalPort: 8080, OnOpen: api.PortsStatus_notify, Exposed: &api.ExposedPortInfo{Visibility: api.PortVisibility_private, OnExposed: api.OnPortExposedAction_notify, Url: "foobar"}}},
|
|
[]*api.PortsStatus{{LocalPort: 8080, OnOpen: api.PortsStatus_notify, Exposed: &api.ExposedPortInfo{Visibility: api.PortVisibility_public, OnExposed: api.OnPortExposedAction_notify, Url: "foobar"}}},
|
|
[]*api.PortsStatus{{LocalPort: 8080, Served: true, OnOpen: api.PortsStatus_notify, Exposed: &api.ExposedPortInfo{Visibility: api.PortVisibility_public, OnExposed: api.OnPortExposedAction_notify, Url: "foobar"}}},
|
|
[]*api.PortsStatus{{LocalPort: 8080, OnOpen: api.PortsStatus_notify, Exposed: &api.ExposedPortInfo{Visibility: api.PortVisibility_public, OnExposed: api.OnPortExposedAction_notify, Url: "foobar"}}},
|
|
[]*api.PortsStatus{{LocalPort: 8080, Served: true, OnOpen: api.PortsStatus_notify, Exposed: &api.ExposedPortInfo{Visibility: api.PortVisibility_public, OnExposed: api.OnPortExposedAction_notify, Url: "foobar"}}},
|
|
},
|
|
},
|
|
{
|
|
Desc: "starting multiple proxies for the same served event",
|
|
Changes: []Change{
|
|
{
|
|
Served: []ServedPort{{net.IPv4(127, 0, 0, 1), 8080, true}, {net.IPv4zero, 3000, true}},
|
|
},
|
|
},
|
|
ExpectedExposure: []ExposedPort{
|
|
{LocalPort: 8080},
|
|
{LocalPort: 3000},
|
|
},
|
|
ExpectedUpdates: UpdateExpectation{
|
|
{},
|
|
{
|
|
{LocalPort: 3000, Served: true, OnOpen: api.PortsStatus_notify_private},
|
|
{LocalPort: 8080, Served: true, OnOpen: api.PortsStatus_notify_private},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Desc: "served between auto exposing configured and exposed update",
|
|
Changes: []Change{
|
|
{
|
|
Config: &ConfigChange{instance: []*gitpod.PortsItems{
|
|
{Port: 8080, Visibility: "private"},
|
|
}},
|
|
},
|
|
{
|
|
Served: []ServedPort{{net.IPv4zero, 8080, false}},
|
|
},
|
|
{
|
|
Exposed: []ExposedPort{{LocalPort: 8080, Public: false, URL: "foobar"}},
|
|
},
|
|
},
|
|
ExpectedExposure: []ExposedPort{
|
|
{LocalPort: 8080},
|
|
},
|
|
ExpectedUpdates: UpdateExpectation{
|
|
{},
|
|
{{LocalPort: 8080, OnOpen: api.PortsStatus_notify}},
|
|
{{LocalPort: 8080, Served: true, OnOpen: api.PortsStatus_notify}},
|
|
{{LocalPort: 8080, Served: true, OnOpen: api.PortsStatus_notify, Exposed: &api.ExposedPortInfo{Visibility: api.PortVisibility_private, OnExposed: api.OnPortExposedAction_notify, Url: "foobar"}}},
|
|
},
|
|
},
|
|
{
|
|
Desc: "the same port served locally and then globally too, prefer globally (exposed in between)",
|
|
Changes: []Change{
|
|
{
|
|
Served: []ServedPort{{net.IPv4(127, 0, 0, 1), 5900, true}},
|
|
},
|
|
{
|
|
Exposed: []ExposedPort{{LocalPort: 5900, URL: "foobar"}},
|
|
},
|
|
{
|
|
Served: []ServedPort{{net.IPv4(127, 0, 0, 1), 5900, true}, {net.IPv4zero, 5900, false}},
|
|
},
|
|
{
|
|
Exposed: []ExposedPort{{LocalPort: 5900, URL: "foobar"}},
|
|
},
|
|
},
|
|
ExpectedExposure: []ExposedPort{
|
|
{LocalPort: 5900},
|
|
},
|
|
ExpectedUpdates: UpdateExpectation{
|
|
{},
|
|
{{LocalPort: 5900, Served: true, OnOpen: api.PortsStatus_notify_private}},
|
|
{{LocalPort: 5900, Served: true, OnOpen: api.PortsStatus_notify_private, Exposed: &api.ExposedPortInfo{Visibility: api.PortVisibility_private, OnExposed: api.OnPortExposedAction_notify_private, Url: "foobar"}}},
|
|
},
|
|
},
|
|
{
|
|
Desc: "the same port served locally and then globally too, prefer globally (exposed after)",
|
|
Changes: []Change{
|
|
{
|
|
Served: []ServedPort{{net.IPv4(127, 0, 0, 1), 5900, true}},
|
|
},
|
|
{
|
|
Served: []ServedPort{{net.IPv4(127, 0, 0, 1), 5900, true}, {net.IPv4zero, 5900, false}},
|
|
},
|
|
{
|
|
Exposed: []ExposedPort{{LocalPort: 5900, URL: "foobar"}},
|
|
},
|
|
{
|
|
Exposed: []ExposedPort{{LocalPort: 5900, URL: "foobar"}},
|
|
},
|
|
},
|
|
ExpectedExposure: []ExposedPort{
|
|
{LocalPort: 5900},
|
|
},
|
|
ExpectedUpdates: UpdateExpectation{
|
|
{},
|
|
{{LocalPort: 5900, Served: true, OnOpen: api.PortsStatus_notify_private}},
|
|
{{LocalPort: 5900, Served: true, OnOpen: api.PortsStatus_notify_private, Exposed: &api.ExposedPortInfo{Visibility: api.PortVisibility_private, OnExposed: api.OnPortExposedAction_notify_private, Url: "foobar"}}},
|
|
},
|
|
},
|
|
{
|
|
Desc: "the same port served globally and then locally too, prefer globally (exposed in between)",
|
|
Changes: []Change{
|
|
{
|
|
Served: []ServedPort{{net.IPv4zero, 5900, false}},
|
|
},
|
|
{
|
|
Exposed: []ExposedPort{{LocalPort: 5900, URL: "foobar"}},
|
|
},
|
|
{
|
|
Served: []ServedPort{{net.IPv4zero, 5900, false}, {net.IPv4(127, 0, 0, 1), 5900, true}},
|
|
},
|
|
},
|
|
ExpectedExposure: []ExposedPort{
|
|
{LocalPort: 5900},
|
|
},
|
|
ExpectedUpdates: UpdateExpectation{
|
|
{},
|
|
{{LocalPort: 5900, Served: true, OnOpen: api.PortsStatus_notify_private}},
|
|
{{LocalPort: 5900, Served: true, OnOpen: api.PortsStatus_notify_private, Exposed: &api.ExposedPortInfo{Visibility: api.PortVisibility_private, OnExposed: api.OnPortExposedAction_notify_private, Url: "foobar"}}},
|
|
},
|
|
},
|
|
{
|
|
Desc: "the same port served globally and then locally too, prefer globally (exposed after)",
|
|
Changes: []Change{
|
|
{
|
|
Served: []ServedPort{{net.IPv4zero, 5900, false}},
|
|
},
|
|
{
|
|
Served: []ServedPort{{net.IPv4zero, 5900, false}, {net.IPv4(127, 0, 0, 1), 5900, true}},
|
|
},
|
|
{
|
|
Exposed: []ExposedPort{{LocalPort: 5900, URL: "foobar"}},
|
|
},
|
|
},
|
|
ExpectedExposure: []ExposedPort{
|
|
{LocalPort: 5900},
|
|
},
|
|
ExpectedUpdates: UpdateExpectation{
|
|
{},
|
|
{{LocalPort: 5900, Served: true, OnOpen: api.PortsStatus_notify_private}},
|
|
{{LocalPort: 5900, Served: true, OnOpen: api.PortsStatus_notify_private, Exposed: &api.ExposedPortInfo{Visibility: api.PortVisibility_private, OnExposed: api.OnPortExposedAction_notify_private, Url: "foobar"}}},
|
|
},
|
|
},
|
|
{
|
|
Desc: "the same port served locally on ip4 and then locally on ip6 too, prefer first (exposed in between)",
|
|
Changes: []Change{
|
|
{
|
|
Served: []ServedPort{{net.IPv4(127, 0, 0, 1), 5900, true}},
|
|
},
|
|
{
|
|
Exposed: []ExposedPort{{LocalPort: 5900, URL: "foobar"}},
|
|
},
|
|
{
|
|
Served: []ServedPort{{net.IPv4(127, 0, 0, 1), 5900, true}, {net.IPv6zero, 5900, true}},
|
|
},
|
|
},
|
|
ExpectedExposure: []ExposedPort{
|
|
{LocalPort: 5900},
|
|
},
|
|
ExpectedUpdates: UpdateExpectation{
|
|
{},
|
|
{{LocalPort: 5900, Served: true, OnOpen: api.PortsStatus_notify_private}},
|
|
{{LocalPort: 5900, Served: true, OnOpen: api.PortsStatus_notify_private, Exposed: &api.ExposedPortInfo{Visibility: api.PortVisibility_private, OnExposed: api.OnPortExposedAction_notify_private, Url: "foobar"}}},
|
|
},
|
|
},
|
|
{
|
|
Desc: "the same port served locally on ip4 and then locally on ip6 too, prefer first (exposed after)",
|
|
Changes: []Change{
|
|
{
|
|
Served: []ServedPort{{net.IPv4(127, 0, 0, 1), 5900, true}},
|
|
},
|
|
{
|
|
Served: []ServedPort{{net.IPv4(127, 0, 0, 1), 5900, true}, {net.IPv6zero, 5900, true}},
|
|
},
|
|
{
|
|
Exposed: []ExposedPort{{LocalPort: 5900, URL: "foobar"}},
|
|
},
|
|
},
|
|
ExpectedExposure: []ExposedPort{
|
|
{LocalPort: 5900},
|
|
},
|
|
ExpectedUpdates: UpdateExpectation{
|
|
{},
|
|
{{LocalPort: 5900, Served: true, OnOpen: api.PortsStatus_notify_private}},
|
|
{{LocalPort: 5900, Served: true, OnOpen: api.PortsStatus_notify_private, Exposed: &api.ExposedPortInfo{Visibility: api.PortVisibility_private, OnExposed: api.OnPortExposedAction_notify_private, Url: "foobar"}}},
|
|
},
|
|
},
|
|
{
|
|
Desc: "the same port served locally on ip4 and then globally on ip6 too, prefer first (exposed in between)",
|
|
Changes: []Change{
|
|
{
|
|
Served: []ServedPort{{net.IPv4zero, 5900, false}},
|
|
},
|
|
{
|
|
Exposed: []ExposedPort{{LocalPort: 5900, URL: "foobar"}},
|
|
},
|
|
{
|
|
Served: []ServedPort{{net.IPv4zero, 5900, false}, {net.IPv6zero, 5900, false}},
|
|
},
|
|
},
|
|
ExpectedExposure: []ExposedPort{
|
|
{LocalPort: 5900},
|
|
},
|
|
ExpectedUpdates: UpdateExpectation{
|
|
{},
|
|
{{LocalPort: 5900, Served: true, OnOpen: api.PortsStatus_notify_private}},
|
|
{{LocalPort: 5900, Served: true, OnOpen: api.PortsStatus_notify_private, Exposed: &api.ExposedPortInfo{Visibility: api.PortVisibility_private, OnExposed: api.OnPortExposedAction_notify_private, Url: "foobar"}}},
|
|
},
|
|
},
|
|
{
|
|
Desc: "the same port served locally on ip4 and then globally on ip6 too, prefer first (exposed after)",
|
|
Changes: []Change{
|
|
{
|
|
Served: []ServedPort{{net.IPv4zero, 5900, false}},
|
|
},
|
|
{
|
|
Served: []ServedPort{{net.IPv4zero, 5900, false}, {net.IPv6zero, 5900, false}},
|
|
},
|
|
{
|
|
Exposed: []ExposedPort{{LocalPort: 5900, URL: "foobar"}},
|
|
},
|
|
},
|
|
ExpectedExposure: []ExposedPort{
|
|
{LocalPort: 5900},
|
|
},
|
|
ExpectedUpdates: UpdateExpectation{
|
|
{},
|
|
{{LocalPort: 5900, Served: true, OnOpen: api.PortsStatus_notify_private}},
|
|
{{LocalPort: 5900, Served: true, OnOpen: api.PortsStatus_notify_private, Exposed: &api.ExposedPortInfo{Visibility: api.PortVisibility_private, OnExposed: api.OnPortExposedAction_notify_private, Url: "foobar"}}},
|
|
},
|
|
},
|
|
{
|
|
Desc: "port status has description set as soon as the port gets exposed, if there was a description configured",
|
|
Changes: []Change{
|
|
{
|
|
Config: &ConfigChange{instance: []*gitpod.PortsItems{
|
|
{Port: 8080, Visibility: "private", Description: "Development server"},
|
|
}},
|
|
},
|
|
{
|
|
Served: []ServedPort{{net.IPv4zero, 8080, false}},
|
|
},
|
|
{
|
|
Exposed: []ExposedPort{{LocalPort: 8080, Public: false, URL: "foobar"}},
|
|
},
|
|
},
|
|
ExpectedExposure: []ExposedPort{
|
|
{LocalPort: 8080},
|
|
},
|
|
ExpectedUpdates: UpdateExpectation{
|
|
{},
|
|
{{LocalPort: 8080, Description: "Development server", OnOpen: api.PortsStatus_notify}},
|
|
{{LocalPort: 8080, Description: "Development server", Served: true, OnOpen: api.PortsStatus_notify}},
|
|
{{LocalPort: 8080, Description: "Development server", Served: true, OnOpen: api.PortsStatus_notify, Exposed: &api.ExposedPortInfo{Visibility: api.PortVisibility_private, OnExposed: api.OnPortExposedAction_notify, Url: "foobar"}}},
|
|
},
|
|
},
|
|
{
|
|
Desc: "port status has the name attribute set as soon as the port gets exposed, if there was a name configured in Gitpod's Workspace",
|
|
Changes: []Change{
|
|
{
|
|
Config: &ConfigChange{instance: []*gitpod.PortsItems{
|
|
{Port: 3000, Visibility: "private", Name: "react"},
|
|
}},
|
|
},
|
|
{
|
|
Served: []ServedPort{{net.IPv4zero, 3000, false}},
|
|
},
|
|
{
|
|
Exposed: []ExposedPort{{LocalPort: 3000, Public: false, URL: "foobar"}},
|
|
},
|
|
},
|
|
ExpectedExposure: []ExposedPort{
|
|
{LocalPort: 3000},
|
|
},
|
|
ExpectedUpdates: UpdateExpectation{
|
|
{},
|
|
{{LocalPort: 3000, Name: "react", OnOpen: api.PortsStatus_notify}},
|
|
{{LocalPort: 3000, Name: "react", Served: true, OnOpen: api.PortsStatus_notify}},
|
|
{{LocalPort: 3000, Name: "react", Served: true, OnOpen: api.PortsStatus_notify, Exposed: &api.ExposedPortInfo{Visibility: api.PortVisibility_private, OnExposed: api.OnPortExposedAction_notify, Url: "foobar"}}},
|
|
},
|
|
},
|
|
{
|
|
Desc: "change configed ports order",
|
|
Changes: []Change{
|
|
{
|
|
Config: &ConfigChange{instance: []*gitpod.PortsItems{
|
|
{Port: 3001, Visibility: "private", Name: "react"},
|
|
{Port: 3000, Visibility: "private", Name: "react"},
|
|
}},
|
|
},
|
|
{
|
|
Config: &ConfigChange{instance: []*gitpod.PortsItems{
|
|
{Port: "5000-5999", Visibility: "private", Name: "react"},
|
|
{Port: 3001, Visibility: "private", Name: "react"},
|
|
{Port: 3000, Visibility: "private", Name: "react"},
|
|
}},
|
|
},
|
|
{
|
|
Served: []ServedPort{{net.IPv4zero, 5002, false}},
|
|
},
|
|
{
|
|
Served: []ServedPort{{net.IPv4zero, 5002, false}, {net.IPv4zero, 5001, false}},
|
|
},
|
|
{
|
|
Config: &ConfigChange{instance: []*gitpod.PortsItems{
|
|
{Port: 3000, Visibility: "private", Name: "react"},
|
|
{Port: 3001, Visibility: "private", Name: "react"},
|
|
}},
|
|
},
|
|
{
|
|
Served: []ServedPort{{net.IPv4zero, 5001, false}, {net.IPv4zero, 3000, false}},
|
|
},
|
|
{
|
|
Exposed: []ExposedPort{{LocalPort: 3000, Public: false, URL: "foobar"}},
|
|
},
|
|
},
|
|
ExpectedExposure: []ExposedPort{
|
|
{LocalPort: 5002},
|
|
{LocalPort: 5001},
|
|
{LocalPort: 3000},
|
|
{LocalPort: 3001},
|
|
},
|
|
ExpectedUpdates: UpdateExpectation{
|
|
{},
|
|
{
|
|
{LocalPort: 3001, Name: "react", OnOpen: api.PortsStatus_notify},
|
|
{LocalPort: 3000, Name: "react", OnOpen: api.PortsStatus_notify},
|
|
},
|
|
{
|
|
{LocalPort: 3001, Name: "react", OnOpen: api.PortsStatus_notify},
|
|
{LocalPort: 3000, Name: "react", OnOpen: api.PortsStatus_notify},
|
|
},
|
|
{
|
|
{LocalPort: 5002, Name: "react", Served: true, OnOpen: api.PortsStatus_notify},
|
|
{LocalPort: 3001, Name: "react", OnOpen: api.PortsStatus_notify},
|
|
{LocalPort: 3000, Name: "react", OnOpen: api.PortsStatus_notify},
|
|
},
|
|
{
|
|
{LocalPort: 5001, Name: "react", Served: true, OnOpen: api.PortsStatus_notify},
|
|
{LocalPort: 5002, Name: "react", Served: true, OnOpen: api.PortsStatus_notify},
|
|
{LocalPort: 3001, Name: "react", OnOpen: api.PortsStatus_notify},
|
|
{LocalPort: 3000, Name: "react", OnOpen: api.PortsStatus_notify},
|
|
},
|
|
{
|
|
{LocalPort: 3000, Name: "react", OnOpen: api.PortsStatus_notify},
|
|
{LocalPort: 3001, Name: "react", OnOpen: api.PortsStatus_notify},
|
|
{LocalPort: 5001, Served: true, OnOpen: api.PortsStatus_notify_private},
|
|
{LocalPort: 5002, Served: true, OnOpen: api.PortsStatus_notify_private},
|
|
},
|
|
{
|
|
{LocalPort: 3000, Name: "react", Served: true, OnOpen: api.PortsStatus_notify},
|
|
{LocalPort: 3001, Name: "react", OnOpen: api.PortsStatus_notify},
|
|
{LocalPort: 5001, Served: true, OnOpen: api.PortsStatus_notify_private},
|
|
},
|
|
{
|
|
{LocalPort: 3000, Name: "react", Served: true, OnOpen: api.PortsStatus_notify, Exposed: &api.ExposedPortInfo{Visibility: api.PortVisibility_private, OnExposed: api.OnPortExposedAction_notify, Url: "foobar"}},
|
|
{LocalPort: 3001, Name: "react", OnOpen: api.PortsStatus_notify},
|
|
{LocalPort: 5001, Served: true, OnOpen: api.PortsStatus_notify_private},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Desc: "change configed ports order with ranged covered not ranged",
|
|
Changes: []Change{
|
|
{
|
|
Config: &ConfigChange{
|
|
instance: []*gitpod.PortsItems{
|
|
{Port: 3001, Visibility: "private", Name: "react"},
|
|
{Port: 3000, Visibility: "private", Name: "react"},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Config: &ConfigChange{
|
|
instance: []*gitpod.PortsItems{
|
|
{Port: 3003, Visibility: "private", Name: "react"},
|
|
{Port: 3001, Visibility: "private", Name: "react"},
|
|
{Port: "3001-3005", Visibility: "private", Name: "react"},
|
|
{Port: 3000, Visibility: "private", Name: "react"},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Served: []ServedPort{{net.IPv4zero, 3000, false}},
|
|
},
|
|
{
|
|
Served: []ServedPort{{net.IPv4zero, 3000, false}, {net.IPv4zero, 3001, false}, {net.IPv4zero, 3002, false}},
|
|
},
|
|
{
|
|
Config: &ConfigChange{
|
|
instance: []*gitpod.PortsItems{
|
|
{Port: 3003, Visibility: "private", Name: "react"},
|
|
{Port: 3000, Visibility: "private", Name: "react"},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Config: &ConfigChange{
|
|
instance: []*gitpod.PortsItems{
|
|
{Port: "3001-3005", Visibility: "private", Name: "react"},
|
|
{Port: 3003, Visibility: "private", Name: "react"},
|
|
{Port: 3000, Visibility: "private", Name: "react"},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
ExpectedExposure: []ExposedPort{
|
|
{LocalPort: 3000},
|
|
{LocalPort: 3001},
|
|
{LocalPort: 3002},
|
|
{LocalPort: 3003},
|
|
},
|
|
ExpectedUpdates: UpdateExpectation{
|
|
{},
|
|
{
|
|
{LocalPort: 3001, Name: "react", OnOpen: api.PortsStatus_notify},
|
|
{LocalPort: 3000, Name: "react", OnOpen: api.PortsStatus_notify},
|
|
},
|
|
{
|
|
{LocalPort: 3003, Name: "react", OnOpen: api.PortsStatus_notify},
|
|
{LocalPort: 3001, Name: "react", OnOpen: api.PortsStatus_notify},
|
|
{LocalPort: 3000, Name: "react", OnOpen: api.PortsStatus_notify},
|
|
},
|
|
{
|
|
{LocalPort: 3003, Name: "react", OnOpen: api.PortsStatus_notify},
|
|
{LocalPort: 3001, Name: "react", OnOpen: api.PortsStatus_notify},
|
|
{LocalPort: 3000, Served: true, Name: "react", OnOpen: api.PortsStatus_notify},
|
|
},
|
|
{
|
|
{LocalPort: 3003, Name: "react", OnOpen: api.PortsStatus_notify},
|
|
{LocalPort: 3001, Served: true, Name: "react", OnOpen: api.PortsStatus_notify},
|
|
{LocalPort: 3002, Served: true, Name: "react", OnOpen: api.PortsStatus_notify},
|
|
{LocalPort: 3000, Served: true, Name: "react", OnOpen: api.PortsStatus_notify},
|
|
},
|
|
{
|
|
{LocalPort: 3003, Name: "react", OnOpen: api.PortsStatus_notify},
|
|
{LocalPort: 3000, Served: true, Name: "react", OnOpen: api.PortsStatus_notify},
|
|
{LocalPort: 3001, Served: true, OnOpen: api.PortsStatus_notify_private},
|
|
{LocalPort: 3002, Served: true, OnOpen: api.PortsStatus_notify_private},
|
|
},
|
|
{
|
|
{LocalPort: 3001, Name: "react", Served: true, OnOpen: api.PortsStatus_notify},
|
|
{LocalPort: 3002, Name: "react", Served: true, OnOpen: api.PortsStatus_notify},
|
|
{LocalPort: 3003, Name: "react", OnOpen: api.PortsStatus_notify},
|
|
{LocalPort: 3000, Served: true, Name: "react", OnOpen: api.PortsStatus_notify},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
// Please make sure this test pass for code browser resolveExternalPort
|
|
// see also https://github.com/gitpod-io/openvscode-server/blob/5ab7644a8bbf37d28e23212bc6f1529cafd8bf7b/extensions/gitpod-web/src/extension.ts#L310-L339
|
|
Desc: "expose port without served, port should be responded for use case of openvscode-server",
|
|
Changes: []Change{
|
|
{
|
|
Exposed: []ExposedPort{{LocalPort: 3000, Public: false, URL: "foobar"}},
|
|
},
|
|
},
|
|
// this will not exposed because test manager didn't implement it properly
|
|
// ExpectedExposure: []ExposedPort{
|
|
// {LocalPort: 3000},
|
|
// },
|
|
ExpectedUpdates: UpdateExpectation{
|
|
{},
|
|
{
|
|
{LocalPort: 3000, OnOpen: api.PortsStatus_notify_private, Exposed: &api.ExposedPortInfo{Visibility: api.PortVisibility_private, OnExposed: api.OnPortExposedAction_notify_private, Url: "foobar"}},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
log.Log.Logger.SetLevel(logrus.FatalLevel)
|
|
|
|
for _, test := range tests {
|
|
t.Run(test.Desc, func(t *testing.T) {
|
|
var (
|
|
exposed = &testExposedPorts{
|
|
Changes: make(chan []ExposedPort),
|
|
Error: make(chan error, 1),
|
|
}
|
|
served = &testServedPorts{
|
|
Changes: make(chan []ServedPort),
|
|
Error: make(chan error, 1),
|
|
}
|
|
config = &testConfigService{
|
|
Changes: make(chan *Configs),
|
|
Error: make(chan error, 1),
|
|
}
|
|
tunneled = &testTunneledPorts{
|
|
Changes: make(chan []PortTunnelState),
|
|
Error: make(chan error, 1),
|
|
}
|
|
|
|
pm = NewManager(exposed, served, config, tunneled, test.InternalPorts...)
|
|
updts [][]*api.PortsStatus
|
|
)
|
|
pm.proxyStarter = func(port uint32) (io.Closer, error) {
|
|
return io.NopCloser(nil), nil
|
|
}
|
|
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
defer cancel()
|
|
var wg sync.WaitGroup
|
|
wg.Add(3)
|
|
go pm.Run(ctx, &wg)
|
|
sub, err := pm.Subscribe()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
go func() {
|
|
defer wg.Done()
|
|
defer sub.Close()
|
|
|
|
for up := range sub.Updates() {
|
|
updts = append(updts, up)
|
|
}
|
|
}()
|
|
go func() {
|
|
defer wg.Done()
|
|
defer close(config.Error)
|
|
defer close(config.Changes)
|
|
defer close(served.Error)
|
|
defer close(served.Changes)
|
|
defer close(exposed.Error)
|
|
defer close(exposed.Changes)
|
|
defer close(tunneled.Error)
|
|
defer close(tunneled.Changes)
|
|
|
|
for _, c := range test.Changes {
|
|
if c.Config != nil {
|
|
change := &Configs{}
|
|
portConfigs, rangeConfigs := parseInstanceConfigs(c.Config.instance)
|
|
change.instancePortConfigs = portConfigs
|
|
change.instanceRangeConfigs = rangeConfigs
|
|
config.Changes <- change
|
|
} else if c.ConfigErr != nil {
|
|
config.Error <- c.ConfigErr
|
|
} else if c.Served != nil {
|
|
served.Changes <- c.Served
|
|
} else if c.ServedErr != nil {
|
|
served.Error <- c.ServedErr
|
|
} else if c.Exposed != nil {
|
|
exposed.Changes <- c.Exposed
|
|
} else if c.ExposedErr != nil {
|
|
exposed.Error <- c.ExposedErr
|
|
} else if c.Tunneled != nil {
|
|
tunneled.Changes <- c.Tunneled
|
|
} else if c.TunneledErr != nil {
|
|
tunneled.Error <- c.TunneledErr
|
|
}
|
|
}
|
|
}()
|
|
|
|
wg.Wait()
|
|
|
|
var (
|
|
sortExposed = cmpopts.SortSlices(func(x, y ExposedPort) bool { return x.LocalPort < y.LocalPort })
|
|
ignoreUnexported = cmpopts.IgnoreUnexported(
|
|
api.PortsStatus{},
|
|
api.ExposedPortInfo{},
|
|
)
|
|
)
|
|
if diff := cmp.Diff(test.ExpectedExposure, ExposureExpectation(exposed.Exposures), sortExposed, ignoreUnexported); diff != "" {
|
|
t.Errorf("unexpected exposures (-want +got):\n%s", diff)
|
|
}
|
|
|
|
if diff := cmp.Diff(test.ExpectedUpdates, UpdateExpectation(updts), ignoreUnexported); diff != "" {
|
|
t.Errorf("unexpected updates (-want +got):\n%s", diff)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
type testTunneledPorts struct {
|
|
Changes chan []PortTunnelState
|
|
Error chan error
|
|
}
|
|
|
|
func (tep *testTunneledPorts) Observe(ctx context.Context) (<-chan []PortTunnelState, <-chan error) {
|
|
return tep.Changes, tep.Error
|
|
}
|
|
func (tep *testTunneledPorts) Tunnel(ctx context.Context, options *TunnelOptions, descs ...*PortTunnelDescription) ([]uint32, error) {
|
|
return nil, nil
|
|
}
|
|
func (tep *testTunneledPorts) CloseTunnel(ctx context.Context, localPorts ...uint32) ([]uint32, error) {
|
|
return nil, nil
|
|
}
|
|
func (tep *testTunneledPorts) EstablishTunnel(ctx context.Context, clientID string, localPort uint32, targetPort uint32) (net.Conn, error) {
|
|
return nil, nil
|
|
}
|
|
|
|
type testConfigService struct {
|
|
Changes chan *Configs
|
|
Error chan error
|
|
}
|
|
|
|
func (tep *testConfigService) Observe(ctx context.Context) (<-chan *Configs, <-chan error) {
|
|
return tep.Changes, tep.Error
|
|
}
|
|
|
|
type testExposedPorts struct {
|
|
Changes chan []ExposedPort
|
|
Error chan error
|
|
|
|
Exposures []ExposedPort
|
|
mu sync.Mutex
|
|
}
|
|
|
|
func (tep *testExposedPorts) Observe(ctx context.Context) (<-chan []ExposedPort, <-chan error) {
|
|
return tep.Changes, tep.Error
|
|
}
|
|
|
|
func (tep *testExposedPorts) Run(ctx context.Context) {
|
|
}
|
|
|
|
func (tep *testExposedPorts) Expose(ctx context.Context, local uint32, public bool) <-chan error {
|
|
tep.mu.Lock()
|
|
defer tep.mu.Unlock()
|
|
|
|
tep.Exposures = append(tep.Exposures, ExposedPort{
|
|
LocalPort: local,
|
|
Public: public,
|
|
})
|
|
return nil
|
|
}
|
|
|
|
type testServedPorts struct {
|
|
Changes chan []ServedPort
|
|
Error chan error
|
|
}
|
|
|
|
func (tps *testServedPorts) Observe(ctx context.Context) (<-chan []ServedPort, <-chan error) {
|
|
return tps.Changes, tps.Error
|
|
}
|
|
|
|
// testing for deadlocks between subscribing and processing events
|
|
func TestPortsConcurrentSubscribe(t *testing.T) {
|
|
var (
|
|
subscribes = 100
|
|
subscribing = make(chan struct{})
|
|
exposed = &testExposedPorts{
|
|
Changes: make(chan []ExposedPort),
|
|
Error: make(chan error, 1),
|
|
}
|
|
served = &testServedPorts{
|
|
Changes: make(chan []ServedPort),
|
|
Error: make(chan error, 1),
|
|
}
|
|
config = &testConfigService{
|
|
Changes: make(chan *Configs),
|
|
Error: make(chan error, 1),
|
|
}
|
|
tunneled = &testTunneledPorts{
|
|
Changes: make(chan []PortTunnelState),
|
|
Error: make(chan error, 1),
|
|
}
|
|
pm = NewManager(exposed, served, config, tunneled)
|
|
)
|
|
pm.proxyStarter = func(local uint32) (io.Closer, error) {
|
|
return io.NopCloser(nil), nil
|
|
}
|
|
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
defer cancel()
|
|
var wg sync.WaitGroup
|
|
wg.Add(2)
|
|
go pm.Run(ctx, &wg)
|
|
go func() {
|
|
defer wg.Done()
|
|
defer close(config.Error)
|
|
defer close(config.Changes)
|
|
defer close(served.Error)
|
|
defer close(served.Changes)
|
|
defer close(exposed.Error)
|
|
defer close(exposed.Changes)
|
|
defer close(tunneled.Error)
|
|
defer close(tunneled.Changes)
|
|
|
|
var j uint32
|
|
for {
|
|
|
|
select {
|
|
case <-time.After(50 * time.Millisecond):
|
|
served.Changes <- []ServedPort{{Port: j}}
|
|
j++
|
|
case <-subscribing:
|
|
return
|
|
}
|
|
}
|
|
}()
|
|
|
|
eg, _ := errgroup.WithContext(context.Background())
|
|
for i := 0; i < maxSubscriptions; i++ {
|
|
eg.Go(func() error {
|
|
for j := 0; j < subscribes; j++ {
|
|
sub, err := pm.Subscribe()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
// status
|
|
select {
|
|
case <-sub.Updates():
|
|
// update
|
|
case <-sub.Updates():
|
|
}
|
|
sub.Close()
|
|
}
|
|
return nil
|
|
})
|
|
}
|
|
err := eg.Wait()
|
|
close(subscribing)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
wg.Wait()
|
|
}
|
|
|
|
func TestManager_getStatus(t *testing.T) {
|
|
type portState struct {
|
|
port uint32
|
|
notServed bool
|
|
}
|
|
type fields struct {
|
|
orderInYaml []any
|
|
state []portState
|
|
}
|
|
tests := []struct {
|
|
name string
|
|
fields fields
|
|
want []uint32
|
|
}{
|
|
{
|
|
name: "happy path",
|
|
fields: fields{
|
|
// The port number (e.g. 1337) or range (e.g. 3000-3999) to expose.
|
|
orderInYaml: []any{1002, 1000, "3000-3999", 1001},
|
|
state: []portState{{port: 1000}, {port: 1001}, {port: 1002}, {port: 3003}, {port: 3001}, {port: 3002}, {port: 4002}, {port: 4000}, {port: 5000}, {port: 5005}},
|
|
},
|
|
want: []uint32{1002, 1000, 3001, 3002, 3003, 1001, 4000, 4002, 5000, 5005},
|
|
},
|
|
{
|
|
name: "order for ranged ports and inside ranged order by number ASC",
|
|
fields: fields{
|
|
orderInYaml: []any{1002, "3000-3999", 1009, "4000-4999"},
|
|
state: []portState{{port: 5000}, {port: 1000}, {port: 1009}, {port: 4000}, {port: 4001}, {port: 3000}, {port: 3009}},
|
|
},
|
|
want: []uint32{3000, 3009, 1009, 4000, 4001, 1000, 5000},
|
|
},
|
|
{
|
|
name: "served ports order by number ASC",
|
|
fields: fields{
|
|
orderInYaml: []any{},
|
|
state: []portState{{port: 4000}, {port: 4003}, {port: 4007}, {port: 4001}, {port: 4006}},
|
|
},
|
|
want: []uint32{4000, 4001, 4003, 4006, 4007},
|
|
},
|
|
{
|
|
// Please make sure this test pass for code browser resolveExternalPort
|
|
// see also https://github.com/gitpod-io/openvscode-server/blob/5ab7644a8bbf37d28e23212bc6f1529cafd8bf7b/extensions/gitpod-web/src/extension.ts#L310-L339
|
|
name: "expose not served ports should respond their status",
|
|
fields: fields{
|
|
orderInYaml: []any{},
|
|
state: []portState{{port: 4000, notServed: true}},
|
|
},
|
|
want: []uint32{4000},
|
|
},
|
|
// It will not works because we do not `Run` ports Manger
|
|
// As ports Manger will autoExpose those ports (but not ranged port) in yaml
|
|
// and they will exists in state
|
|
// {
|
|
// name: "not ignore ports that not served but exists in yaml",
|
|
// fields: fields{
|
|
// orderInYaml: []any{1002, 1000, 1001},
|
|
// state: []uint32{},
|
|
// },
|
|
// want: []uint32{1002, 1000, 1001},
|
|
// },
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
state := make(map[uint32]*managedPort)
|
|
for _, s := range tt.fields.state {
|
|
state[s.port] = &managedPort{
|
|
Served: !s.notServed,
|
|
LocalhostPort: s.port,
|
|
TunneledTargetPort: s.port,
|
|
TunneledClients: map[string]uint32{},
|
|
}
|
|
}
|
|
portsItems := []*gitpod.PortsItems{}
|
|
for _, port := range tt.fields.orderInYaml {
|
|
portsItems = append(portsItems, &gitpod.PortsItems{Port: port})
|
|
}
|
|
portsConfig, rangeConfig := parseInstanceConfigs(portsItems)
|
|
pm := &Manager{
|
|
configs: &Configs{
|
|
instancePortConfigs: portsConfig,
|
|
instanceRangeConfigs: rangeConfig,
|
|
},
|
|
state: state,
|
|
}
|
|
got := pm.getStatus()
|
|
if len(got) != len(tt.want) {
|
|
t.Errorf("Manager.getStatus() length = %v, want %v", len(got), len(tt.want))
|
|
}
|
|
gotPorts := []uint32{}
|
|
for _, g := range got {
|
|
gotPorts = append(gotPorts, g.LocalPort)
|
|
}
|
|
if diff := cmp.Diff(gotPorts, tt.want); diff != "" {
|
|
t.Errorf("unexpected exposures (-want +got):\n%s", diff)
|
|
}
|
|
})
|
|
}
|
|
}
|