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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 29 additions & 11 deletions integration/e2e/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ import (
)

var (
dockerPortPattern = regexp.MustCompile(`^.*:(\d+)$`)
errMissingMetric = errors.New("metric not found")
dockerIPv4PortPattern = regexp.MustCompile(`^\d+\.\d+\.\d+\.\d+:(\d+)$`)
errMissingMetric = errors.New("metric not found")
)

// ConcreteService represents microservice with optional ports which will be discoverable from docker
Expand Down Expand Up @@ -118,7 +118,6 @@ func (s *ConcreteService) Start(networkName, sharedDir string) (err error) {
// Get the dynamic local ports mapped to the container.
for _, containerPort := range s.networkPorts {
var out []byte
var localPort int

out, err = RunCommandAndGetOutput("docker", "port", s.containerName(), strconv.Itoa(containerPort))
if err != nil {
Expand All @@ -129,18 +128,14 @@ func (s *ConcreteService) Start(networkName, sharedDir string) (err error) {
return errors.Wrapf(err, "unable to get mapping for port %d; service: %s; output: %q", containerPort, s.name, out)
}

stdout := strings.TrimSpace(string(out))
matches := dockerPortPattern.FindStringSubmatch(stdout)
if len(matches) != 2 {
return fmt.Errorf("unable to get mapping for port %d (output: %s); service: %s", containerPort, stdout, s.name)
}

localPort, err = strconv.Atoi(matches[1])
localPort, err := parseDockerIPv4Port(string(out))
if err != nil {
return errors.Wrapf(err, "unable to get mapping for port %d; service: %s", containerPort, s.name)
return errors.Wrapf(err, "unable to get mapping for port %d (output: %s); service: %s", containerPort, string(out), s.name)
}

s.networkPortsContainerToLocal[containerPort] = localPort
}

logger.Log("Ports for container:", s.containerName(), "Mapping:", s.networkPortsContainerToLocal)
return nil
}
Expand Down Expand Up @@ -668,3 +663,26 @@ func (s *HTTPService) WaitRemovedMetric(metricName string, opts ...MetricsOption

return fmt.Errorf("the metric %s is still exported by %s", metricName, s.name)
}

// parseDockerIPv4Port parses the input string which is expected to be the output of "docker port"
// command and returns the first IPv4 port found.
func parseDockerIPv4Port(out string) (int, error) {
// The "docker port" output may be multiple lines if both IPv4 and IPv6 are supported,
// so we need to parse each line.
for _, line := range strings.Split(out, "\n") {
matches := dockerIPv4PortPattern.FindStringSubmatch(strings.TrimSpace(line))
if len(matches) != 2 {
continue
}

port, err := strconv.Atoi(matches[1])
if err != nil {
continue
}

return port, nil
}

// We've not been able to parse the output format.
return 0, errors.New("unknown output format")
}
18 changes: 18 additions & 0 deletions integration/e2e/service_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"testing"
"time"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"

"github.com/cortexproject/cortex/pkg/util"
Expand Down Expand Up @@ -170,3 +171,20 @@ metric_b 1000
})
require.NoError(t, s.WaitSumMetrics(Equals(math.NaN()), "metric_a"))
}

func TestParseDockerPort(t *testing.T) {
_, err := parseDockerIPv4Port("")
assert.Error(t, err)

actual, err := parseDockerIPv4Port("0.0.0.0:36999")
assert.NoError(t, err)
assert.Equal(t, 36999, actual)

actual, err = parseDockerIPv4Port("0.0.0.0:49155\n:::49156")
assert.NoError(t, err)
assert.Equal(t, 49155, actual)

actual, err = parseDockerIPv4Port(":::49156\n0.0.0.0:49155")
assert.NoError(t, err)
assert.Equal(t, 49155, actual)
}