mirror of https://github.com/containers/podman.git
Merge pull request #11890 from Luap99/ports
libpod: deduplicate ports in db
This commit is contained in:
commit
abbd6c167e
|
@ -8,7 +8,6 @@ import (
|
|||
"github.com/containers/podman/v3/cmd/podman/common"
|
||||
"github.com/containers/podman/v3/cmd/podman/registry"
|
||||
"github.com/containers/podman/v3/cmd/podman/validate"
|
||||
"github.com/containers/podman/v3/libpod/network/types"
|
||||
"github.com/containers/podman/v3/pkg/domain/entities"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/cobra"
|
||||
|
@ -73,7 +72,8 @@ func port(_ *cobra.Command, args []string) error {
|
|||
var (
|
||||
container string
|
||||
err error
|
||||
userPort types.OCICNIPortMapping
|
||||
userPort uint16
|
||||
userProto string
|
||||
)
|
||||
|
||||
if len(args) == 0 && !portOpts.Latest && !portOpts.All {
|
||||
|
@ -101,16 +101,12 @@ func port(_ *cobra.Command, args []string) error {
|
|||
fields = append(fields, "tcp")
|
||||
}
|
||||
|
||||
portNum, err := strconv.Atoi(fields[0])
|
||||
portNum, err := strconv.ParseUint(fields[0], 10, 16)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
userPort = types.OCICNIPortMapping{
|
||||
HostPort: 0,
|
||||
ContainerPort: int32(portNum),
|
||||
Protocol: fields[1],
|
||||
HostIP: "",
|
||||
}
|
||||
userPort = uint16(portNum)
|
||||
userProto = fields[1]
|
||||
}
|
||||
|
||||
reports, err := registry.ContainerEngine().ContainerPort(registry.GetContext(), container, portOpts)
|
||||
|
@ -120,24 +116,36 @@ func port(_ *cobra.Command, args []string) error {
|
|||
var found bool
|
||||
// Iterate mappings
|
||||
for _, report := range reports {
|
||||
allPrefix := ""
|
||||
if portOpts.All {
|
||||
allPrefix = report.Id[:12] + "\t"
|
||||
}
|
||||
for _, v := range report.Ports {
|
||||
hostIP := v.HostIP
|
||||
// Set host IP to 0.0.0.0 if blank
|
||||
if hostIP == "" {
|
||||
hostIP = "0.0.0.0"
|
||||
}
|
||||
if portOpts.All {
|
||||
fmt.Printf("%s\t", report.Id[:12])
|
||||
}
|
||||
// If not searching by port or port/proto, then dump what we see
|
||||
if port == "" {
|
||||
fmt.Printf("%d/%s -> %s:%d\n", v.ContainerPort, v.Protocol, hostIP, v.HostPort)
|
||||
continue
|
||||
}
|
||||
if v.ContainerPort == userPort.ContainerPort {
|
||||
fmt.Printf("%s:%d\n", hostIP, v.HostPort)
|
||||
found = true
|
||||
break
|
||||
protocols := strings.Split(v.Protocol, ",")
|
||||
for _, protocol := range protocols {
|
||||
// If not searching by port or port/proto, then dump what we see
|
||||
if port == "" {
|
||||
for i := uint16(0); i < v.Range; i++ {
|
||||
fmt.Printf("%s%d/%s -> %s:%d\n", allPrefix, v.ContainerPort+i, protocol, hostIP, v.HostPort+i)
|
||||
}
|
||||
continue
|
||||
}
|
||||
// check if the proto matches and if the port is in the range
|
||||
// this is faster than looping over the range for no reason
|
||||
if v.Protocol == userProto &&
|
||||
v.ContainerPort <= userPort &&
|
||||
v.ContainerPort+v.Range > userPort {
|
||||
// we have to add the current range to the host port
|
||||
hostPort := v.HostPort + userPort - v.ContainerPort
|
||||
fmt.Printf("%s%s:%d\n", allPrefix, hostIP, hostPort)
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if !found && port != "" {
|
||||
|
|
|
@ -3,8 +3,6 @@ package containers
|
|||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
|
@ -477,174 +475,31 @@ func (l psReporter) UTS() string {
|
|||
|
||||
// portsToString converts the ports used to a string of the from "port1, port2"
|
||||
// and also groups a continuous list of ports into a readable format.
|
||||
func portsToString(ports []types.OCICNIPortMapping) string {
|
||||
// The format is IP:HostPort(-Range)->ContainerPort(-Range)/Proto
|
||||
func portsToString(ports []types.PortMapping) string {
|
||||
if len(ports) == 0 {
|
||||
return ""
|
||||
}
|
||||
// Sort the ports, so grouping continuous ports become easy.
|
||||
sort.Slice(ports, func(i, j int) bool {
|
||||
return comparePorts(ports[i], ports[j])
|
||||
})
|
||||
|
||||
portGroups := [][]types.OCICNIPortMapping{}
|
||||
currentGroup := []types.OCICNIPortMapping{}
|
||||
for i, v := range ports {
|
||||
var prevPort, nextPort *int32
|
||||
if i > 0 {
|
||||
prevPort = &ports[i-1].ContainerPort
|
||||
}
|
||||
if i+1 < len(ports) {
|
||||
nextPort = &ports[i+1].ContainerPort
|
||||
}
|
||||
|
||||
port := v.ContainerPort
|
||||
|
||||
// Helper functions
|
||||
addToCurrentGroup := func(x types.OCICNIPortMapping) {
|
||||
currentGroup = append(currentGroup, x)
|
||||
}
|
||||
|
||||
addToPortGroup := func(x types.OCICNIPortMapping) {
|
||||
portGroups = append(portGroups, []types.OCICNIPortMapping{x})
|
||||
}
|
||||
|
||||
finishCurrentGroup := func() {
|
||||
portGroups = append(portGroups, currentGroup)
|
||||
currentGroup = []types.OCICNIPortMapping{}
|
||||
}
|
||||
|
||||
// Single entry slice
|
||||
if prevPort == nil && nextPort == nil {
|
||||
addToPortGroup(v)
|
||||
}
|
||||
|
||||
// Start of the slice with len > 0
|
||||
if prevPort == nil && nextPort != nil {
|
||||
isGroup := *nextPort-1 == port
|
||||
|
||||
if isGroup {
|
||||
// Start with a group
|
||||
addToCurrentGroup(v)
|
||||
} else {
|
||||
// Start with single item
|
||||
addToPortGroup(v)
|
||||
}
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
// Middle of the slice with len > 0
|
||||
if prevPort != nil && nextPort != nil {
|
||||
currentIsGroup := *prevPort+1 == port
|
||||
nextIsGroup := *nextPort-1 == port
|
||||
|
||||
if currentIsGroup {
|
||||
// Maybe in the middle of a group
|
||||
addToCurrentGroup(v)
|
||||
|
||||
if !nextIsGroup {
|
||||
// End of a group
|
||||
finishCurrentGroup()
|
||||
}
|
||||
} else if nextIsGroup {
|
||||
// Start of a new group
|
||||
addToCurrentGroup(v)
|
||||
} else {
|
||||
// No group at all
|
||||
addToPortGroup(v)
|
||||
}
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
// End of the slice with len > 0
|
||||
if prevPort != nil && nextPort == nil {
|
||||
isGroup := *prevPort+1 == port
|
||||
|
||||
if isGroup {
|
||||
// End group
|
||||
addToCurrentGroup(v)
|
||||
finishCurrentGroup()
|
||||
} else {
|
||||
// End single item
|
||||
addToPortGroup(v)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
portDisplay := []string{}
|
||||
for _, group := range portGroups {
|
||||
if len(group) == 0 {
|
||||
// Usually should not happen, but better do not crash.
|
||||
continue
|
||||
}
|
||||
|
||||
first := group[0]
|
||||
|
||||
hostIP := first.HostIP
|
||||
sb := &strings.Builder{}
|
||||
for _, port := range ports {
|
||||
hostIP := port.HostIP
|
||||
if hostIP == "" {
|
||||
hostIP = "0.0.0.0"
|
||||
}
|
||||
|
||||
// Single mappings
|
||||
if len(group) == 1 {
|
||||
portDisplay = append(portDisplay,
|
||||
fmt.Sprintf(
|
||||
"%s:%d->%d/%s",
|
||||
hostIP, first.HostPort, first.ContainerPort, first.Protocol,
|
||||
),
|
||||
)
|
||||
continue
|
||||
protocols := strings.Split(port.Protocol, ",")
|
||||
for _, protocol := range protocols {
|
||||
if port.Range > 1 {
|
||||
fmt.Fprintf(sb, "%s:%d-%d->%d-%d/%s, ",
|
||||
hostIP, port.HostPort, port.HostPort+port.Range-1,
|
||||
port.ContainerPort, port.ContainerPort+port.Range-1, protocol)
|
||||
} else {
|
||||
fmt.Fprintf(sb, "%s:%d->%d/%s, ",
|
||||
hostIP, port.HostPort,
|
||||
port.ContainerPort, protocol)
|
||||
}
|
||||
}
|
||||
|
||||
// Group mappings
|
||||
last := group[len(group)-1]
|
||||
portDisplay = append(portDisplay, formatGroup(
|
||||
fmt.Sprintf("%s/%s", hostIP, first.Protocol),
|
||||
first.HostPort, last.HostPort,
|
||||
first.ContainerPort, last.ContainerPort,
|
||||
))
|
||||
}
|
||||
return strings.Join(portDisplay, ", ")
|
||||
}
|
||||
|
||||
func comparePorts(i, j types.OCICNIPortMapping) bool {
|
||||
if i.ContainerPort != j.ContainerPort {
|
||||
return i.ContainerPort < j.ContainerPort
|
||||
}
|
||||
|
||||
if i.HostIP != j.HostIP {
|
||||
return i.HostIP < j.HostIP
|
||||
}
|
||||
|
||||
if i.HostPort != j.HostPort {
|
||||
return i.HostPort < j.HostPort
|
||||
}
|
||||
|
||||
return i.Protocol < j.Protocol
|
||||
}
|
||||
|
||||
// formatGroup returns the group in the format:
|
||||
// <IP:firstHost:lastHost->firstCtr:lastCtr/Proto>
|
||||
// e.g 0.0.0.0:1000-1006->2000-2006/tcp.
|
||||
func formatGroup(key string, firstHost, lastHost, firstCtr, lastCtr int32) string {
|
||||
parts := strings.Split(key, "/")
|
||||
groupType := parts[0]
|
||||
var ip string
|
||||
if len(parts) > 1 {
|
||||
ip = parts[0]
|
||||
groupType = parts[1]
|
||||
}
|
||||
|
||||
group := func(first, last int32) string {
|
||||
group := strconv.Itoa(int(first))
|
||||
if first != last {
|
||||
group = fmt.Sprintf("%s-%d", group, last)
|
||||
}
|
||||
return group
|
||||
}
|
||||
hostGroup := group(firstHost, lastHost)
|
||||
ctrGroup := group(firstCtr, lastCtr)
|
||||
|
||||
return fmt.Sprintf("%s:%s->%s/%s", ip, hostGroup, ctrGroup, groupType)
|
||||
display := sb.String()
|
||||
// make sure to trim the last ", " of the string
|
||||
return display[:len(display)-2]
|
||||
}
|
||||
|
|
|
@ -10,6 +10,7 @@ import (
|
|||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/containernetworking/plugins/pkg/ns"
|
||||
"github.com/containers/podman/v3/libpod/network/types"
|
||||
|
@ -289,25 +290,30 @@ func handler(ctx context.Context, conn io.Reader, pm rkport.Manager) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func exposePorts(pm rkport.Manager, portMappings []types.OCICNIPortMapping, childIP string) error {
|
||||
func exposePorts(pm rkport.Manager, portMappings []types.PortMapping, childIP string) error {
|
||||
ctx := context.TODO()
|
||||
for _, i := range portMappings {
|
||||
hostIP := i.HostIP
|
||||
if hostIP == "" {
|
||||
hostIP = "0.0.0.0"
|
||||
}
|
||||
spec := rkport.Spec{
|
||||
Proto: i.Protocol,
|
||||
ParentIP: hostIP,
|
||||
ParentPort: int(i.HostPort),
|
||||
ChildPort: int(i.ContainerPort),
|
||||
ChildIP: childIP,
|
||||
}
|
||||
if err := rkportutil.ValidatePortSpec(spec, nil); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := pm.AddPort(ctx, spec); err != nil {
|
||||
return err
|
||||
for _, port := range portMappings {
|
||||
protocols := strings.Split(port.Protocol, ",")
|
||||
for _, protocol := range protocols {
|
||||
hostIP := port.HostIP
|
||||
if hostIP == "" {
|
||||
hostIP = "0.0.0.0"
|
||||
}
|
||||
for i := uint16(0); i < port.Range; i++ {
|
||||
spec := rkport.Spec{
|
||||
Proto: protocol,
|
||||
ParentIP: hostIP,
|
||||
ParentPort: int(port.HostPort + i),
|
||||
ChildPort: int(port.ContainerPort + i),
|
||||
ChildIP: childIP,
|
||||
}
|
||||
if err := rkportutil.ValidatePortSpec(spec, nil); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := pm.AddPort(ctx, spec); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
|
|
@ -384,6 +384,15 @@ func (s *BoltState) getContainerConfigFromDB(id []byte, config *ContainerConfig,
|
|||
return errors.Wrapf(err, "error unmarshalling container %s config", string(id))
|
||||
}
|
||||
|
||||
// convert ports to the new format if needed
|
||||
if len(config.ContainerNetworkConfig.OldPortMappings) > 0 && len(config.ContainerNetworkConfig.PortMappings) == 0 {
|
||||
config.ContainerNetworkConfig.PortMappings = ocicniPortsToNetTypesPorts(config.ContainerNetworkConfig.OldPortMappings)
|
||||
// keep the OldPortMappings in case an user has to downgrade podman
|
||||
|
||||
// indicate the the config was modified and should be written back to the db when possible
|
||||
config.rewrite = true
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
|
@ -41,18 +41,20 @@ func getTestContainer(id, name string, manager lock.Manager) (*Container, error)
|
|||
ContainerNetworkConfig: ContainerNetworkConfig{
|
||||
DNSServer: []net.IP{net.ParseIP("192.168.1.1"), net.ParseIP("192.168.2.2")},
|
||||
DNSSearch: []string{"example.com", "example.example.com"},
|
||||
PortMappings: []types.OCICNIPortMapping{
|
||||
PortMappings: []types.PortMapping{
|
||||
{
|
||||
HostPort: 80,
|
||||
ContainerPort: 90,
|
||||
Protocol: "tcp",
|
||||
HostIP: "192.168.3.3",
|
||||
Range: 1,
|
||||
},
|
||||
{
|
||||
HostPort: 100,
|
||||
ContainerPort: 110,
|
||||
Protocol: "udp",
|
||||
HostIP: "192.168.4.4",
|
||||
Range: 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
|
@ -465,7 +465,7 @@ func (c *Container) NewNetNS() bool {
|
|||
// PortMappings returns the ports that will be mapped into a container if
|
||||
// a new network namespace is created
|
||||
// If NewNetNS() is false, this value is unused
|
||||
func (c *Container) PortMappings() ([]types.OCICNIPortMapping, error) {
|
||||
func (c *Container) PortMappings() ([]types.PortMapping, error) {
|
||||
// First check if the container belongs to a network namespace (like a pod)
|
||||
if len(c.config.NetNsCtr) > 0 {
|
||||
netNsCtr, err := c.runtime.GetContainer(c.config.NetNsCtr)
|
||||
|
|
|
@ -78,6 +78,11 @@ type ContainerConfig struct {
|
|||
// These containers must be started before this container is started.
|
||||
Dependencies []string
|
||||
|
||||
// rewrite is an internal bool to indicate that the config was modified after
|
||||
// a read from the db, e.g. to migrate config fields after an upgrade.
|
||||
// This field should never be written to the db, the json tag ensures this.
|
||||
rewrite bool `json:"-"`
|
||||
|
||||
// embedded sub-configs
|
||||
ContainerRootFSConfig
|
||||
ContainerSecurityConfig
|
||||
|
@ -232,7 +237,12 @@ type ContainerNetworkConfig struct {
|
|||
// PortMappings are the ports forwarded to the container's network
|
||||
// namespace
|
||||
// These are not used unless CreateNetNS is true
|
||||
PortMappings []types.OCICNIPortMapping `json:"portMappings,omitempty"`
|
||||
PortMappings []types.PortMapping `json:"newPortMappings,omitempty"`
|
||||
// OldPortMappings are the ports forwarded to the container's network
|
||||
// namespace. As of podman 4.0 this field is deprecated, use PortMappings
|
||||
// instead. The db will convert the old ports to the new structure for you.
|
||||
// These are not used unless CreateNetNS is true
|
||||
OldPortMappings []types.OCICNIPortMapping `json:"portMappings,omitempty"`
|
||||
// ExposedPorts are the ports which are exposed but not forwarded
|
||||
// into the container.
|
||||
// The map key is the port and the string slice contains the protocols,
|
||||
|
|
|
@ -667,6 +667,19 @@ func (c *Container) refresh() error {
|
|||
c.state.NetworkStatus = nil
|
||||
c.state.NetworkStatusOld = nil
|
||||
|
||||
// Rewrite the config if necessary.
|
||||
// Podman 4.0 uses a new port format in the config.
|
||||
// getContainerConfigFromDB() already converted the old ports to the new one
|
||||
// but it did not write the config to the db back for performance reasons.
|
||||
// If a rewrite must happen the config.rewrite field is set to true.
|
||||
if c.config.rewrite {
|
||||
// SafeRewriteContainerConfig must be used with care. Make sure to not change config fields by accident.
|
||||
if err := c.runtime.state.SafeRewriteContainerConfig(c, "", "", c.config); err != nil {
|
||||
return errors.Wrapf(err, "failed to rewrite the config for container %s", c.config.ID)
|
||||
}
|
||||
c.config.rewrite = false
|
||||
}
|
||||
|
||||
if err := c.save(); err != nil {
|
||||
return errors.Wrapf(err, "error refreshing state for container %s", c.ID())
|
||||
}
|
||||
|
|
|
@ -75,7 +75,7 @@ func (p *Pod) GenerateForKube(ctx context.Context) (*v1.Pod, []v1.ServicePort, e
|
|||
Hostnames: []string{hostSli[0]},
|
||||
})
|
||||
}
|
||||
ports, err = ocicniPortMappingToContainerPort(infraContainer.config.PortMappings)
|
||||
ports, err = portMappingToContainerPort(infraContainer.config.PortMappings)
|
||||
if err != nil {
|
||||
return nil, servicePorts, err
|
||||
}
|
||||
|
@ -452,7 +452,7 @@ func containerToV1Container(ctx context.Context, c *Container) (v1.Container, []
|
|||
if err != nil {
|
||||
return kubeContainer, kubeVolumes, nil, annotations, err
|
||||
}
|
||||
ports, err := ocicniPortMappingToContainerPort(portmappings)
|
||||
ports, err := portMappingToContainerPort(portmappings)
|
||||
if err != nil {
|
||||
return kubeContainer, kubeVolumes, nil, annotations, err
|
||||
}
|
||||
|
@ -588,29 +588,36 @@ func containerToV1Container(ctx context.Context, c *Container) (v1.Container, []
|
|||
return kubeContainer, kubeVolumes, &dns, annotations, nil
|
||||
}
|
||||
|
||||
// ocicniPortMappingToContainerPort takes an ocicni portmapping and converts
|
||||
// portMappingToContainerPort takes an portmapping and converts
|
||||
// it to a v1.ContainerPort format for kube output
|
||||
func ocicniPortMappingToContainerPort(portMappings []types.OCICNIPortMapping) ([]v1.ContainerPort, error) {
|
||||
func portMappingToContainerPort(portMappings []types.PortMapping) ([]v1.ContainerPort, error) {
|
||||
containerPorts := make([]v1.ContainerPort, 0, len(portMappings))
|
||||
for _, p := range portMappings {
|
||||
var protocol v1.Protocol
|
||||
switch strings.ToUpper(p.Protocol) {
|
||||
case "TCP":
|
||||
// do nothing as it is the default protocol in k8s, there is no need to explicitly
|
||||
// add it to the generated yaml
|
||||
case "UDP":
|
||||
protocol = v1.ProtocolUDP
|
||||
default:
|
||||
return containerPorts, errors.Errorf("unknown network protocol %s", p.Protocol)
|
||||
protocols := strings.Split(p.Protocol, ",")
|
||||
for _, proto := range protocols {
|
||||
var protocol v1.Protocol
|
||||
switch strings.ToUpper(proto) {
|
||||
case "TCP":
|
||||
// do nothing as it is the default protocol in k8s, there is no need to explicitly
|
||||
// add it to the generated yaml
|
||||
case "UDP":
|
||||
protocol = v1.ProtocolUDP
|
||||
case "SCTP":
|
||||
protocol = v1.ProtocolSCTP
|
||||
default:
|
||||
return containerPorts, errors.Errorf("unknown network protocol %s", p.Protocol)
|
||||
}
|
||||
for i := uint16(0); i < p.Range; i++ {
|
||||
cp := v1.ContainerPort{
|
||||
// Name will not be supported
|
||||
HostPort: int32(p.HostPort + i),
|
||||
HostIP: p.HostIP,
|
||||
ContainerPort: int32(p.ContainerPort + i),
|
||||
Protocol: protocol,
|
||||
}
|
||||
containerPorts = append(containerPorts, cp)
|
||||
}
|
||||
}
|
||||
cp := v1.ContainerPort{
|
||||
// Name will not be supported
|
||||
HostPort: p.HostPort,
|
||||
HostIP: p.HostIP,
|
||||
ContainerPort: p.ContainerPort,
|
||||
Protocol: protocol,
|
||||
}
|
||||
containerPorts = append(containerPorts, cp)
|
||||
}
|
||||
return containerPorts, nil
|
||||
}
|
||||
|
|
|
@ -11,6 +11,7 @@ import (
|
|||
"os/exec"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"syscall"
|
||||
|
@ -91,10 +92,7 @@ func (c *Container) getNetworkOptions() (types.NetworkOptions, error) {
|
|||
ContainerID: c.config.ID,
|
||||
ContainerName: getCNIPodName(c),
|
||||
}
|
||||
// TODO remove ocicni PortMappings from container config and store as types PortMappings
|
||||
if len(c.config.PortMappings) > 0 {
|
||||
opts.PortMappings = ocicniPortsToNetTypesPorts(c.config.PortMappings)
|
||||
}
|
||||
opts.PortMappings = c.config.PortMappings
|
||||
networks, _, err := c.networks()
|
||||
if err != nil {
|
||||
return opts, err
|
||||
|
@ -1209,9 +1207,7 @@ func (c *Container) NetworkDisconnect(nameOrID, netName string, force bool) erro
|
|||
ContainerID: c.config.ID,
|
||||
ContainerName: getCNIPodName(c),
|
||||
}
|
||||
if len(c.config.PortMappings) > 0 {
|
||||
opts.PortMappings = ocicniPortsToNetTypesPorts(c.config.PortMappings)
|
||||
}
|
||||
opts.PortMappings = c.config.PortMappings
|
||||
eth, exists := c.state.NetInterfaceDescriptions.getInterfaceByName(netName)
|
||||
if !exists {
|
||||
return errors.Errorf("no network interface name for container %s on network %s", c.config.ID, netName)
|
||||
|
@ -1303,9 +1299,7 @@ func (c *Container) NetworkConnect(nameOrID, netName string, aliases []string) e
|
|||
ContainerID: c.config.ID,
|
||||
ContainerName: getCNIPodName(c),
|
||||
}
|
||||
if len(c.config.PortMappings) > 0 {
|
||||
opts.PortMappings = ocicniPortsToNetTypesPorts(c.config.PortMappings)
|
||||
}
|
||||
opts.PortMappings = c.config.PortMappings
|
||||
eth, exists := c.state.NetInterfaceDescriptions.getInterfaceByName(netName)
|
||||
if !exists {
|
||||
return errors.Errorf("no network interface name for container %s on network %s", c.config.ID, netName)
|
||||
|
@ -1373,16 +1367,67 @@ func (r *Runtime) normalizeNetworkName(nameOrID string) (string, error) {
|
|||
return net.Name, nil
|
||||
}
|
||||
|
||||
// ocicniPortsToNetTypesPorts convert the old port format to the new one
|
||||
// while deduplicating ports into ranges
|
||||
func ocicniPortsToNetTypesPorts(ports []types.OCICNIPortMapping) []types.PortMapping {
|
||||
newPorts := make([]types.PortMapping, 0, len(ports))
|
||||
for _, port := range ports {
|
||||
newPorts = append(newPorts, types.PortMapping{
|
||||
HostIP: port.HostIP,
|
||||
HostPort: uint16(port.HostPort),
|
||||
ContainerPort: uint16(port.ContainerPort),
|
||||
Protocol: port.Protocol,
|
||||
Range: 1,
|
||||
})
|
||||
if len(ports) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
newPorts := make([]types.PortMapping, 0, len(ports))
|
||||
|
||||
// first sort the ports
|
||||
sort.Slice(ports, func(i, j int) bool {
|
||||
return compareOCICNIPorts(ports[i], ports[j])
|
||||
})
|
||||
|
||||
// we already check if the slice is empty so we can use the first element
|
||||
currentPort := types.PortMapping{
|
||||
HostIP: ports[0].HostIP,
|
||||
HostPort: uint16(ports[0].HostPort),
|
||||
ContainerPort: uint16(ports[0].ContainerPort),
|
||||
Protocol: ports[0].Protocol,
|
||||
Range: 1,
|
||||
}
|
||||
|
||||
for i := 1; i < len(ports); i++ {
|
||||
if ports[i].HostIP == currentPort.HostIP &&
|
||||
ports[i].Protocol == currentPort.Protocol &&
|
||||
ports[i].HostPort-int32(currentPort.Range) == int32(currentPort.HostPort) &&
|
||||
ports[i].ContainerPort-int32(currentPort.Range) == int32(currentPort.ContainerPort) {
|
||||
currentPort.Range = currentPort.Range + 1
|
||||
} else {
|
||||
newPorts = append(newPorts, currentPort)
|
||||
currentPort = types.PortMapping{
|
||||
HostIP: ports[i].HostIP,
|
||||
HostPort: uint16(ports[i].HostPort),
|
||||
ContainerPort: uint16(ports[i].ContainerPort),
|
||||
Protocol: ports[i].Protocol,
|
||||
Range: 1,
|
||||
}
|
||||
}
|
||||
}
|
||||
newPorts = append(newPorts, currentPort)
|
||||
return newPorts
|
||||
}
|
||||
|
||||
// compareOCICNIPorts will sort the ocicni ports by
|
||||
// 1) host ip
|
||||
// 2) protocol
|
||||
// 3) hostPort
|
||||
// 4) container port
|
||||
func compareOCICNIPorts(i, j types.OCICNIPortMapping) bool {
|
||||
if i.HostIP != j.HostIP {
|
||||
return i.HostIP < j.HostIP
|
||||
}
|
||||
|
||||
if i.Protocol != j.Protocol {
|
||||
return i.Protocol < j.Protocol
|
||||
}
|
||||
|
||||
if i.HostPort != j.HostPort {
|
||||
return i.HostPort < j.HostPort
|
||||
}
|
||||
|
||||
return i.ContainerPort < j.ContainerPort
|
||||
}
|
||||
|
|
|
@ -0,0 +1,323 @@
|
|||
package libpod
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/containers/podman/v3/libpod/network/types"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func Test_ocicniPortsToNetTypesPorts(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
arg []types.OCICNIPortMapping
|
||||
want []types.PortMapping
|
||||
}{
|
||||
{
|
||||
name: "no ports",
|
||||
arg: nil,
|
||||
want: nil,
|
||||
},
|
||||
{
|
||||
name: "empty ports",
|
||||
arg: []types.OCICNIPortMapping{},
|
||||
want: nil,
|
||||
},
|
||||
{
|
||||
name: "single port",
|
||||
arg: []types.OCICNIPortMapping{
|
||||
{
|
||||
HostPort: 8080,
|
||||
ContainerPort: 80,
|
||||
Protocol: "tcp",
|
||||
},
|
||||
},
|
||||
want: []types.PortMapping{
|
||||
{
|
||||
HostPort: 8080,
|
||||
ContainerPort: 80,
|
||||
Protocol: "tcp",
|
||||
Range: 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "two separate ports",
|
||||
arg: []types.OCICNIPortMapping{
|
||||
{
|
||||
HostPort: 8080,
|
||||
ContainerPort: 80,
|
||||
Protocol: "tcp",
|
||||
},
|
||||
{
|
||||
HostPort: 9000,
|
||||
ContainerPort: 90,
|
||||
Protocol: "tcp",
|
||||
},
|
||||
},
|
||||
want: []types.PortMapping{
|
||||
{
|
||||
HostPort: 8080,
|
||||
ContainerPort: 80,
|
||||
Protocol: "tcp",
|
||||
Range: 1,
|
||||
},
|
||||
{
|
||||
HostPort: 9000,
|
||||
ContainerPort: 90,
|
||||
Protocol: "tcp",
|
||||
Range: 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "two ports joined",
|
||||
arg: []types.OCICNIPortMapping{
|
||||
{
|
||||
HostPort: 8080,
|
||||
ContainerPort: 80,
|
||||
Protocol: "tcp",
|
||||
},
|
||||
{
|
||||
HostPort: 8081,
|
||||
ContainerPort: 81,
|
||||
Protocol: "tcp",
|
||||
},
|
||||
},
|
||||
want: []types.PortMapping{
|
||||
{
|
||||
HostPort: 8080,
|
||||
ContainerPort: 80,
|
||||
Protocol: "tcp",
|
||||
Range: 2,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "three ports with different container port are not joined",
|
||||
arg: []types.OCICNIPortMapping{
|
||||
{
|
||||
HostPort: 8080,
|
||||
ContainerPort: 80,
|
||||
Protocol: "tcp",
|
||||
},
|
||||
{
|
||||
HostPort: 8081,
|
||||
ContainerPort: 79,
|
||||
Protocol: "tcp",
|
||||
},
|
||||
{
|
||||
HostPort: 8082,
|
||||
ContainerPort: 82,
|
||||
Protocol: "tcp",
|
||||
},
|
||||
},
|
||||
want: []types.PortMapping{
|
||||
{
|
||||
HostPort: 8080,
|
||||
ContainerPort: 80,
|
||||
Protocol: "tcp",
|
||||
Range: 1,
|
||||
},
|
||||
{
|
||||
HostPort: 8081,
|
||||
ContainerPort: 79,
|
||||
Protocol: "tcp",
|
||||
Range: 1,
|
||||
},
|
||||
{
|
||||
HostPort: 8082,
|
||||
ContainerPort: 82,
|
||||
Protocol: "tcp",
|
||||
Range: 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "three ports joined (not sorted)",
|
||||
arg: []types.OCICNIPortMapping{
|
||||
{
|
||||
HostPort: 8081,
|
||||
ContainerPort: 81,
|
||||
Protocol: "tcp",
|
||||
},
|
||||
{
|
||||
HostPort: 8080,
|
||||
ContainerPort: 80,
|
||||
Protocol: "tcp",
|
||||
},
|
||||
{
|
||||
HostPort: 8082,
|
||||
ContainerPort: 82,
|
||||
Protocol: "tcp",
|
||||
},
|
||||
},
|
||||
want: []types.PortMapping{
|
||||
{
|
||||
HostPort: 8080,
|
||||
ContainerPort: 80,
|
||||
Protocol: "tcp",
|
||||
Range: 3,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "different protocols ports are not joined",
|
||||
arg: []types.OCICNIPortMapping{
|
||||
{
|
||||
HostPort: 8080,
|
||||
ContainerPort: 80,
|
||||
Protocol: "tcp",
|
||||
},
|
||||
{
|
||||
HostPort: 8081,
|
||||
ContainerPort: 81,
|
||||
Protocol: "udp",
|
||||
},
|
||||
},
|
||||
want: []types.PortMapping{
|
||||
{
|
||||
HostPort: 8080,
|
||||
ContainerPort: 80,
|
||||
Protocol: "tcp",
|
||||
Range: 1,
|
||||
},
|
||||
{
|
||||
HostPort: 8081,
|
||||
ContainerPort: 81,
|
||||
Protocol: "udp",
|
||||
Range: 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "different host ip ports are not joined",
|
||||
arg: []types.OCICNIPortMapping{
|
||||
{
|
||||
HostPort: 8080,
|
||||
ContainerPort: 80,
|
||||
Protocol: "tcp",
|
||||
HostIP: "192.168.1.1",
|
||||
},
|
||||
{
|
||||
HostPort: 8081,
|
||||
ContainerPort: 81,
|
||||
Protocol: "tcp",
|
||||
HostIP: "192.168.1.2",
|
||||
},
|
||||
},
|
||||
want: []types.PortMapping{
|
||||
{
|
||||
HostPort: 8080,
|
||||
ContainerPort: 80,
|
||||
Protocol: "tcp",
|
||||
Range: 1,
|
||||
HostIP: "192.168.1.1",
|
||||
},
|
||||
{
|
||||
HostPort: 8081,
|
||||
ContainerPort: 81,
|
||||
Protocol: "tcp",
|
||||
Range: 1,
|
||||
HostIP: "192.168.1.2",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
tt := tt
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result := ocicniPortsToNetTypesPorts(tt.arg)
|
||||
assert.Equal(t, tt.want, result, "ports do not match")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func benchmarkOCICNIPortsToNetTypesPorts(b *testing.B, ports []types.OCICNIPortMapping) {
|
||||
for n := 0; n < b.N; n++ {
|
||||
ocicniPortsToNetTypesPorts(ports)
|
||||
}
|
||||
}
|
||||
|
||||
func Benchmark_ocicniPortsToNetTypesPortsNoPorts(b *testing.B) {
|
||||
benchmarkOCICNIPortsToNetTypesPorts(b, nil)
|
||||
}
|
||||
|
||||
func Benchmark_ocicniPortsToNetTypesPorts1(b *testing.B) {
|
||||
benchmarkOCICNIPortsToNetTypesPorts(b, []types.OCICNIPortMapping{
|
||||
{
|
||||
HostPort: 8080,
|
||||
ContainerPort: 80,
|
||||
Protocol: "tcp",
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func Benchmark_ocicniPortsToNetTypesPorts10(b *testing.B) {
|
||||
ports := make([]types.OCICNIPortMapping, 0, 10)
|
||||
for i := int32(8080); i < 8090; i++ {
|
||||
ports = append(ports, types.OCICNIPortMapping{
|
||||
HostPort: i,
|
||||
ContainerPort: i,
|
||||
Protocol: "tcp",
|
||||
})
|
||||
}
|
||||
b.ResetTimer()
|
||||
benchmarkOCICNIPortsToNetTypesPorts(b, ports)
|
||||
}
|
||||
|
||||
func Benchmark_ocicniPortsToNetTypesPorts100(b *testing.B) {
|
||||
ports := make([]types.OCICNIPortMapping, 0, 100)
|
||||
for i := int32(8080); i < 8180; i++ {
|
||||
ports = append(ports, types.OCICNIPortMapping{
|
||||
HostPort: i,
|
||||
ContainerPort: i,
|
||||
Protocol: "tcp",
|
||||
})
|
||||
}
|
||||
b.ResetTimer()
|
||||
benchmarkOCICNIPortsToNetTypesPorts(b, ports)
|
||||
}
|
||||
|
||||
func Benchmark_ocicniPortsToNetTypesPorts1k(b *testing.B) {
|
||||
ports := make([]types.OCICNIPortMapping, 0, 1000)
|
||||
for i := int32(8080); i < 9080; i++ {
|
||||
ports = append(ports, types.OCICNIPortMapping{
|
||||
HostPort: i,
|
||||
ContainerPort: i,
|
||||
Protocol: "tcp",
|
||||
})
|
||||
}
|
||||
b.ResetTimer()
|
||||
benchmarkOCICNIPortsToNetTypesPorts(b, ports)
|
||||
}
|
||||
|
||||
func Benchmark_ocicniPortsToNetTypesPorts10k(b *testing.B) {
|
||||
ports := make([]types.OCICNIPortMapping, 0, 30000)
|
||||
for i := int32(8080); i < 18080; i++ {
|
||||
ports = append(ports, types.OCICNIPortMapping{
|
||||
HostPort: i,
|
||||
ContainerPort: i,
|
||||
Protocol: "tcp",
|
||||
})
|
||||
}
|
||||
b.ResetTimer()
|
||||
benchmarkOCICNIPortsToNetTypesPorts(b, ports)
|
||||
}
|
||||
|
||||
func Benchmark_ocicniPortsToNetTypesPorts1m(b *testing.B) {
|
||||
ports := make([]types.OCICNIPortMapping, 0, 1000000)
|
||||
for j := 0; j < 20; j++ {
|
||||
for i := int32(1); i <= 50000; i++ {
|
||||
ports = append(ports, types.OCICNIPortMapping{
|
||||
HostPort: i,
|
||||
ContainerPort: i,
|
||||
Protocol: "tcp",
|
||||
HostIP: fmt.Sprintf("192.168.1.%d", j),
|
||||
})
|
||||
}
|
||||
}
|
||||
b.ResetTimer()
|
||||
benchmarkOCICNIPortsToNetTypesPorts(b, ports)
|
||||
}
|
|
@ -38,9 +38,9 @@ type slirpFeatures struct {
|
|||
type slirp4netnsCmdArg struct {
|
||||
Proto string `json:"proto,omitempty"`
|
||||
HostAddr string `json:"host_addr"`
|
||||
HostPort int32 `json:"host_port"`
|
||||
HostPort uint16 `json:"host_port"`
|
||||
GuestAddr string `json:"guest_addr"`
|
||||
GuestPort int32 `json:"guest_port"`
|
||||
GuestPort uint16 `json:"guest_port"`
|
||||
}
|
||||
|
||||
type slirp4netnsCmd struct {
|
||||
|
|
|
@ -32,95 +32,110 @@ func createUnitName(prefix string, name string) string {
|
|||
}
|
||||
|
||||
// Bind ports to keep them closed on the host
|
||||
func bindPorts(ports []types.OCICNIPortMapping) ([]*os.File, error) {
|
||||
func bindPorts(ports []types.PortMapping) ([]*os.File, error) {
|
||||
var files []*os.File
|
||||
notifySCTP := false
|
||||
for _, i := range ports {
|
||||
isV6 := net.ParseIP(i.HostIP).To4() == nil
|
||||
if i.HostIP == "" {
|
||||
sctpWarning := true
|
||||
for _, port := range ports {
|
||||
isV6 := net.ParseIP(port.HostIP).To4() == nil
|
||||
if port.HostIP == "" {
|
||||
isV6 = false
|
||||
}
|
||||
switch i.Protocol {
|
||||
case "udp":
|
||||
var (
|
||||
addr *net.UDPAddr
|
||||
err error
|
||||
)
|
||||
if isV6 {
|
||||
addr, err = net.ResolveUDPAddr("udp6", fmt.Sprintf("[%s]:%d", i.HostIP, i.HostPort))
|
||||
} else {
|
||||
addr, err = net.ResolveUDPAddr("udp4", fmt.Sprintf("%s:%d", i.HostIP, i.HostPort))
|
||||
protocols := strings.Split(port.Protocol, ",")
|
||||
for _, protocol := range protocols {
|
||||
for i := uint16(0); i < port.Range; i++ {
|
||||
f, err := bindPort(protocol, port.HostIP, port.HostPort+i, isV6, &sctpWarning)
|
||||
if err != nil {
|
||||
return files, err
|
||||
}
|
||||
if f != nil {
|
||||
files = append(files, f)
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "cannot resolve the UDP address")
|
||||
}
|
||||
|
||||
proto := "udp4"
|
||||
if isV6 {
|
||||
proto = "udp6"
|
||||
}
|
||||
server, err := net.ListenUDP(proto, addr)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "cannot listen on the UDP port")
|
||||
}
|
||||
f, err := server.File()
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "cannot get file for UDP socket")
|
||||
}
|
||||
files = append(files, f)
|
||||
// close the listener
|
||||
// note that this does not affect the fd, see the godoc for server.File()
|
||||
err = server.Close()
|
||||
if err != nil {
|
||||
logrus.Warnf("Failed to close connection: %v", err)
|
||||
}
|
||||
|
||||
case "tcp":
|
||||
var (
|
||||
addr *net.TCPAddr
|
||||
err error
|
||||
)
|
||||
if isV6 {
|
||||
addr, err = net.ResolveTCPAddr("tcp6", fmt.Sprintf("[%s]:%d", i.HostIP, i.HostPort))
|
||||
} else {
|
||||
addr, err = net.ResolveTCPAddr("tcp4", fmt.Sprintf("%s:%d", i.HostIP, i.HostPort))
|
||||
}
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "cannot resolve the TCP address")
|
||||
}
|
||||
|
||||
proto := "tcp4"
|
||||
if isV6 {
|
||||
proto = "tcp6"
|
||||
}
|
||||
server, err := net.ListenTCP(proto, addr)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "cannot listen on the TCP port")
|
||||
}
|
||||
f, err := server.File()
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "cannot get file for TCP socket")
|
||||
}
|
||||
files = append(files, f)
|
||||
// close the listener
|
||||
// note that this does not affect the fd, see the godoc for server.File()
|
||||
err = server.Close()
|
||||
if err != nil {
|
||||
logrus.Warnf("Failed to close connection: %v", err)
|
||||
}
|
||||
|
||||
case "sctp":
|
||||
if !notifySCTP {
|
||||
notifySCTP = true
|
||||
logrus.Warnf("Port reservation for SCTP is not supported")
|
||||
}
|
||||
default:
|
||||
return nil, fmt.Errorf("unknown protocol %s", i.Protocol)
|
||||
}
|
||||
}
|
||||
return files, nil
|
||||
}
|
||||
|
||||
func bindPort(protocol, hostIP string, port uint16, isV6 bool, sctpWarning *bool) (*os.File, error) {
|
||||
var file *os.File
|
||||
switch protocol {
|
||||
case "udp":
|
||||
var (
|
||||
addr *net.UDPAddr
|
||||
err error
|
||||
)
|
||||
if isV6 {
|
||||
addr, err = net.ResolveUDPAddr("udp6", fmt.Sprintf("[%s]:%d", hostIP, port))
|
||||
} else {
|
||||
addr, err = net.ResolveUDPAddr("udp4", fmt.Sprintf("%s:%d", hostIP, port))
|
||||
}
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "cannot resolve the UDP address")
|
||||
}
|
||||
|
||||
proto := "udp4"
|
||||
if isV6 {
|
||||
proto = "udp6"
|
||||
}
|
||||
server, err := net.ListenUDP(proto, addr)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "cannot listen on the UDP port")
|
||||
}
|
||||
file, err = server.File()
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "cannot get file for UDP socket")
|
||||
}
|
||||
// close the listener
|
||||
// note that this does not affect the fd, see the godoc for server.File()
|
||||
err = server.Close()
|
||||
if err != nil {
|
||||
logrus.Warnf("Failed to close connection: %v", err)
|
||||
}
|
||||
|
||||
case "tcp":
|
||||
var (
|
||||
addr *net.TCPAddr
|
||||
err error
|
||||
)
|
||||
if isV6 {
|
||||
addr, err = net.ResolveTCPAddr("tcp6", fmt.Sprintf("[%s]:%d", hostIP, port))
|
||||
} else {
|
||||
addr, err = net.ResolveTCPAddr("tcp4", fmt.Sprintf("%s:%d", hostIP, port))
|
||||
}
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "cannot resolve the TCP address")
|
||||
}
|
||||
|
||||
proto := "tcp4"
|
||||
if isV6 {
|
||||
proto = "tcp6"
|
||||
}
|
||||
server, err := net.ListenTCP(proto, addr)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "cannot listen on the TCP port")
|
||||
}
|
||||
file, err = server.File()
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "cannot get file for TCP socket")
|
||||
}
|
||||
// close the listener
|
||||
// note that this does not affect the fd, see the godoc for server.File()
|
||||
err = server.Close()
|
||||
if err != nil {
|
||||
logrus.Warnf("Failed to close connection: %v", err)
|
||||
}
|
||||
|
||||
case "sctp":
|
||||
if *sctpWarning {
|
||||
logrus.Info("Port reservation for SCTP is not supported")
|
||||
*sctpWarning = false
|
||||
}
|
||||
default:
|
||||
return nil, fmt.Errorf("unknown protocol %s", protocol)
|
||||
}
|
||||
return file, nil
|
||||
}
|
||||
|
||||
func getOCIRuntimeError(runtimeMsg string) error {
|
||||
includeFullOutput := logrus.GetLevel() == logrus.DebugLevel
|
||||
|
||||
|
|
|
@ -1051,7 +1051,7 @@ func WithDependencyCtrs(ctrs []*Container) CtrCreateOption {
|
|||
// namespace with a minimal configuration.
|
||||
// An optional array of port mappings can be provided.
|
||||
// Conflicts with WithNetNSFrom().
|
||||
func WithNetNS(portMappings []nettypes.OCICNIPortMapping, exposedPorts map[uint16][]string, postConfigureNetNS bool, netmode string, networks []string) CtrCreateOption {
|
||||
func WithNetNS(portMappings []nettypes.PortMapping, exposedPorts map[uint16][]string, postConfigureNetNS bool, netmode string, networks []string) CtrCreateOption {
|
||||
return func(ctr *Container) error {
|
||||
if ctr.valid {
|
||||
return define.ErrCtrFinalized
|
||||
|
@ -2084,21 +2084,6 @@ func WithInfraContainer() PodCreateOption {
|
|||
}
|
||||
}
|
||||
|
||||
// WithInfraContainerPorts tells the pod to add port bindings to the pause container
|
||||
func WithInfraContainerPorts(bindings []nettypes.OCICNIPortMapping, infraSpec *specgen.SpecGenerator) []nettypes.PortMapping {
|
||||
bindingSpec := []nettypes.PortMapping{}
|
||||
for _, bind := range bindings {
|
||||
currBind := nettypes.PortMapping{}
|
||||
currBind.ContainerPort = uint16(bind.ContainerPort)
|
||||
currBind.HostIP = bind.HostIP
|
||||
currBind.HostPort = uint16(bind.HostPort)
|
||||
currBind.Protocol = bind.Protocol
|
||||
bindingSpec = append(bindingSpec, currBind)
|
||||
}
|
||||
infraSpec.PortMappings = bindingSpec
|
||||
return infraSpec.PortMappings
|
||||
}
|
||||
|
||||
// WithVolatile sets the volatile flag for the container storage.
|
||||
// The option can potentially cause data loss when used on a container that must survive a machine reboot.
|
||||
func WithVolatile() CtrCreateOption {
|
||||
|
|
|
@ -11,6 +11,7 @@ import (
|
|||
"github.com/containers/common/pkg/config"
|
||||
"github.com/containers/podman/v3/libpod/define"
|
||||
"github.com/containers/podman/v3/libpod/lock"
|
||||
"github.com/containers/podman/v3/libpod/network/types"
|
||||
"github.com/containers/storage"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
@ -3705,3 +3706,42 @@ func TestGetContainerConfigNonExistentIDFails(t *testing.T) {
|
|||
assert.Error(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
// Test that the state will convert the ports to the new format
|
||||
func TestConvertPortMapping(t *testing.T) {
|
||||
runForAllStates(t, func(t *testing.T, state State, manager lock.Manager) {
|
||||
testCtr, err := getTestCtr1(manager)
|
||||
assert.NoError(t, err)
|
||||
|
||||
ports := testCtr.config.PortMappings
|
||||
|
||||
oldPorts := []types.OCICNIPortMapping{
|
||||
{
|
||||
HostPort: 80,
|
||||
ContainerPort: 90,
|
||||
Protocol: "tcp",
|
||||
HostIP: "192.168.3.3",
|
||||
},
|
||||
{
|
||||
HostPort: 100,
|
||||
ContainerPort: 110,
|
||||
Protocol: "udp",
|
||||
HostIP: "192.168.4.4",
|
||||
},
|
||||
}
|
||||
|
||||
testCtr.config.OldPortMappings = oldPorts
|
||||
testCtr.config.PortMappings = nil
|
||||
|
||||
err = state.AddContainer(testCtr)
|
||||
assert.NoError(t, err)
|
||||
|
||||
retrievedCtr, err := state.Container(testCtr.ID())
|
||||
assert.NoError(t, err)
|
||||
|
||||
// set values to expected ones
|
||||
testCtr.config.PortMappings = ports
|
||||
|
||||
testContainersEqual(t, retrievedCtr, testCtr, true)
|
||||
})
|
||||
}
|
||||
|
|
|
@ -295,19 +295,21 @@ func writeHijackHeader(r *http.Request, conn io.Writer) {
|
|||
}
|
||||
|
||||
// Convert OCICNI port bindings into Inspect-formatted port bindings.
|
||||
func makeInspectPortBindings(bindings []types.OCICNIPortMapping, expose map[uint16][]string) map[string][]define.InspectHostPort {
|
||||
func makeInspectPortBindings(bindings []types.PortMapping, expose map[uint16][]string) map[string][]define.InspectHostPort {
|
||||
portBindings := make(map[string][]define.InspectHostPort)
|
||||
for _, port := range bindings {
|
||||
key := fmt.Sprintf("%d/%s", port.ContainerPort, port.Protocol)
|
||||
hostPorts := portBindings[key]
|
||||
if hostPorts == nil {
|
||||
hostPorts = []define.InspectHostPort{}
|
||||
protocols := strings.Split(port.Protocol, ",")
|
||||
for _, protocol := range protocols {
|
||||
for i := uint16(0); i < port.Range; i++ {
|
||||
key := fmt.Sprintf("%d/%s", port.ContainerPort+i, protocol)
|
||||
hostPorts := portBindings[key]
|
||||
hostPorts = append(hostPorts, define.InspectHostPort{
|
||||
HostIP: port.HostIP,
|
||||
HostPort: fmt.Sprintf("%d", port.HostPort+i),
|
||||
})
|
||||
portBindings[key] = hostPorts
|
||||
}
|
||||
}
|
||||
hostPorts = append(hostPorts, define.InspectHostPort{
|
||||
HostIP: port.HostIP,
|
||||
HostPort: fmt.Sprintf("%d", port.HostPort),
|
||||
})
|
||||
portBindings[key] = hostPorts
|
||||
}
|
||||
// add exposed ports without host port information to match docker
|
||||
for port, protocols := range expose {
|
||||
|
|
|
@ -193,7 +193,7 @@ func CRImportCheckpoint(ctx context.Context, runtime *libpod.Runtime, restoreOpt
|
|||
}
|
||||
|
||||
if len(restoreOptions.PublishPorts) > 0 {
|
||||
ports, _, _, err := generate.ParsePortMapping(restoreOptions.PublishPorts)
|
||||
ports, err := generate.ParsePortMapping(restoreOptions.PublishPorts, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
@ -54,7 +54,7 @@ type ListContainer struct {
|
|||
// boolean to be set
|
||||
PodName string
|
||||
// Port mappings
|
||||
Ports []types.OCICNIPortMapping
|
||||
Ports []types.PortMapping
|
||||
// Size of the container rootfs. Requires the size boolean to be true
|
||||
Size *define.ContainerSize
|
||||
// Time when container started
|
||||
|
|
|
@ -422,7 +422,7 @@ type ContainerPortOptions struct {
|
|||
// the CLI to output ports
|
||||
type ContainerPortReport struct {
|
||||
Id string //nolint
|
||||
Ports []nettypes.OCICNIPortMapping
|
||||
Ports []nettypes.PortMapping
|
||||
}
|
||||
|
||||
// ContainerCpOptions describes input options for cp.
|
||||
|
|
|
@ -23,7 +23,7 @@ const (
|
|||
// Config needs to be provided to the process via stdin as a JSON string.
|
||||
// stdin needs to be closed after the message has been written.
|
||||
type Config struct {
|
||||
Mappings []types.OCICNIPortMapping
|
||||
Mappings []types.PortMapping
|
||||
NetNSPath string
|
||||
ExitFD int
|
||||
ReadyFD int
|
||||
|
|
|
@ -204,11 +204,11 @@ func createPodOptions(p *specgen.PodSpecGenerator, rt *libpod.Runtime, infraSpec
|
|||
// replacing necessary values with those specified in pod creation
|
||||
func MapSpec(p *specgen.PodSpecGenerator) (*specgen.SpecGenerator, error) {
|
||||
if len(p.PortMappings) > 0 {
|
||||
ports, _, _, err := ParsePortMapping(p.PortMappings)
|
||||
ports, err := ParsePortMapping(p.PortMappings, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
p.InfraContainerSpec.PortMappings = libpod.WithInfraContainerPorts(ports, p.InfraContainerSpec)
|
||||
p.InfraContainerSpec.PortMappings = ports
|
||||
}
|
||||
switch p.NetNS.NSMode {
|
||||
case specgen.Default, "":
|
||||
|
|
|
@ -2,7 +2,9 @@ package generate
|
|||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
|
@ -11,6 +13,7 @@ import (
|
|||
"github.com/containers/podman/v3/utils"
|
||||
|
||||
"github.com/containers/podman/v3/pkg/specgen"
|
||||
"github.com/containers/podman/v3/pkg/util"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
@ -21,252 +24,323 @@ const (
|
|||
protoSCTP = "sctp"
|
||||
)
|
||||
|
||||
// Parse port maps to OCICNI port mappings.
|
||||
// Returns a set of OCICNI port mappings, and maps of utilized container and
|
||||
// joinTwoPortsToRangePortIfPossible will expect two ports the previous port one must have a lower or equal hostPort than the current port.
|
||||
func joinTwoPortsToRangePortIfPossible(ports *[]types.PortMapping, allHostPorts, allContainerPorts, currentHostPorts *[65536]bool,
|
||||
previousPort *types.PortMapping, port types.PortMapping) (*types.PortMapping, error) {
|
||||
// no previous port just return the current one
|
||||
if previousPort == nil {
|
||||
return &port, nil
|
||||
}
|
||||
if previousPort.HostPort+previousPort.Range >= port.HostPort {
|
||||
// check if the port range matches the host and container ports
|
||||
portDiff := port.HostPort - previousPort.HostPort
|
||||
if portDiff == port.ContainerPort-previousPort.ContainerPort {
|
||||
// calc the new range use the old range and add the difference between the ports
|
||||
newRange := port.Range + portDiff
|
||||
// if the newRange is greater than the old range use it
|
||||
// this is important otherwise we would could lower the range
|
||||
if newRange > previousPort.Range {
|
||||
previousPort.Range = newRange
|
||||
}
|
||||
return previousPort, nil
|
||||
}
|
||||
// if both host port ranges overlap and the container port range did not match
|
||||
// we have to error because we cannot assign the same host port to more than one container port
|
||||
if previousPort.HostPort+previousPort.Range-1 > port.HostPort {
|
||||
return nil, errors.Errorf("conflicting port mappings for host port %d (protocol %s)", port.HostPort, port.Protocol)
|
||||
}
|
||||
}
|
||||
// we could not join the ports so we append the old one to the list
|
||||
// and return the current port as previous port
|
||||
addPortToUsedPorts(ports, allHostPorts, allContainerPorts, currentHostPorts, previousPort)
|
||||
return &port, nil
|
||||
}
|
||||
|
||||
// joinTwoContainerPortsToRangePortIfPossible will expect two ports with both no host port set,
|
||||
// the previous port one must have a lower or equal containerPort than the current port.
|
||||
func joinTwoContainerPortsToRangePortIfPossible(ports *[]types.PortMapping, allHostPorts, allContainerPorts, currentHostPorts *[65536]bool,
|
||||
previousPort *types.PortMapping, port types.PortMapping) (*types.PortMapping, error) {
|
||||
// no previous port just return the current one
|
||||
if previousPort == nil {
|
||||
return &port, nil
|
||||
}
|
||||
if previousPort.ContainerPort+previousPort.Range > port.ContainerPort {
|
||||
// calc the new range use the old range and add the difference between the ports
|
||||
newRange := port.ContainerPort - previousPort.ContainerPort + port.Range
|
||||
// if the newRange is greater than the old range use it
|
||||
// this is important otherwise we would could lower the range
|
||||
if newRange > previousPort.Range {
|
||||
previousPort.Range = newRange
|
||||
}
|
||||
return previousPort, nil
|
||||
}
|
||||
// we could not join the ports so we append the old one to the list
|
||||
// and return the current port as previous port
|
||||
newPort, err := getRandomHostPort(currentHostPorts, *previousPort)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
addPortToUsedPorts(ports, allHostPorts, allContainerPorts, currentHostPorts, &newPort)
|
||||
return &port, nil
|
||||
}
|
||||
|
||||
func addPortToUsedPorts(ports *[]types.PortMapping, allHostPorts, allContainerPorts, currentHostPorts *[65536]bool, port *types.PortMapping) {
|
||||
for i := uint16(0); i < port.Range; i++ {
|
||||
h := port.HostPort + i
|
||||
allHostPorts[h] = true
|
||||
currentHostPorts[h] = true
|
||||
c := port.ContainerPort + i
|
||||
allContainerPorts[c] = true
|
||||
}
|
||||
*ports = append(*ports, *port)
|
||||
}
|
||||
|
||||
// getRandomHostPort get a random host port mapping for the given port
|
||||
// the caller has to supply a array with he already used ports
|
||||
func getRandomHostPort(hostPorts *[65536]bool, port types.PortMapping) (types.PortMapping, error) {
|
||||
outer:
|
||||
for i := 0; i < 15; i++ {
|
||||
ranPort, err := utils.GetRandomPort()
|
||||
if err != nil {
|
||||
return port, err
|
||||
}
|
||||
|
||||
// if port range is exceeds max port we cannot use it
|
||||
if ranPort+int(port.Range) > 65535 {
|
||||
continue
|
||||
}
|
||||
|
||||
// check if there is a port in the range which is used
|
||||
for j := 0; j < int(port.Range); j++ {
|
||||
// port already used
|
||||
if hostPorts[ranPort+j] {
|
||||
continue outer
|
||||
}
|
||||
}
|
||||
|
||||
port.HostPort = uint16(ranPort)
|
||||
return port, nil
|
||||
}
|
||||
|
||||
// add range to error message if needed
|
||||
rangePort := ""
|
||||
if port.Range > 1 {
|
||||
rangePort = fmt.Sprintf("with range %d ", port.Range)
|
||||
}
|
||||
|
||||
return port, errors.Errorf("failed to find an open port to expose container port %d %son the host", port.ContainerPort, rangePort)
|
||||
}
|
||||
|
||||
// Parse port maps to port mappings.
|
||||
// Returns a set of port mappings, and maps of utilized container and
|
||||
// host ports.
|
||||
func ParsePortMapping(portMappings []types.PortMapping) ([]types.OCICNIPortMapping, map[string]map[string]map[uint16]uint16, map[string]map[string]map[uint16]uint16, error) {
|
||||
// First, we need to validate the ports passed in the specgen, and then
|
||||
// convert them into CNI port mappings.
|
||||
func ParsePortMapping(portMappings []types.PortMapping, exposePorts map[uint16][]string) ([]types.PortMapping, error) {
|
||||
if len(portMappings) == 0 && len(exposePorts) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// tempMapping stores the ports without ip and protocol
|
||||
type tempMapping struct {
|
||||
mapping types.OCICNIPortMapping
|
||||
startOfRange bool
|
||||
isInRange bool
|
||||
}
|
||||
tempMappings := []tempMapping{}
|
||||
|
||||
// To validate, we need two maps: one for host ports, one for container
|
||||
// ports.
|
||||
// Each is a map of protocol to map of IP address to map of port to
|
||||
// port (for hostPortValidate, it's host port to container port;
|
||||
// for containerPortValidate, container port to host port.
|
||||
// These will ensure no collisions.
|
||||
hostPortValidate := make(map[string]map[string]map[uint16]uint16)
|
||||
containerPortValidate := make(map[string]map[string]map[uint16]uint16)
|
||||
|
||||
// Initialize the first level of maps (we can't really guess keys for
|
||||
// the rest).
|
||||
for _, proto := range []string{protoTCP, protoUDP, protoSCTP} {
|
||||
hostPortValidate[proto] = make(map[string]map[uint16]uint16)
|
||||
containerPortValidate[proto] = make(map[string]map[uint16]uint16)
|
||||
hostPort uint16
|
||||
containerPort uint16
|
||||
rangePort uint16
|
||||
}
|
||||
|
||||
postAssignHostPort := false
|
||||
// portMap is a temporary structure to sort all ports
|
||||
// the map is hostIp -> protocol -> array of mappings
|
||||
portMap := make(map[string]map[string][]tempMapping)
|
||||
|
||||
// Iterate through all port mappings, generating OCICNI PortMapping
|
||||
// structs and validating there is no overlap.
|
||||
// allUsedContainerPorts stores all used ports for each protocol
|
||||
// the key is the protocol and the array is 65536 elements long for each port.
|
||||
allUsedContainerPortsMap := make(map[string][65536]bool)
|
||||
allUsedHostPortsMap := make(map[string][65536]bool)
|
||||
|
||||
// First, we need to validate the ports passed in the specgen
|
||||
for _, port := range portMappings {
|
||||
// First, check proto
|
||||
protocols, err := checkProtocol(port.Protocol, true)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Validate host IP
|
||||
hostIP := port.HostIP
|
||||
if hostIP == "" {
|
||||
hostIP = "0.0.0.0"
|
||||
}
|
||||
if ip := net.ParseIP(hostIP); ip == nil {
|
||||
return nil, nil, nil, errors.Errorf("invalid IP address %s in port mapping", port.HostIP)
|
||||
if port.HostIP != "" {
|
||||
if ip := net.ParseIP(port.HostIP); ip == nil {
|
||||
return nil, errors.Errorf("invalid IP address %q in port mapping", port.HostIP)
|
||||
}
|
||||
}
|
||||
|
||||
// Validate port numbers and range.
|
||||
len := port.Range
|
||||
if len == 0 {
|
||||
len = 1
|
||||
portRange := port.Range
|
||||
if portRange == 0 {
|
||||
portRange = 1
|
||||
}
|
||||
containerPort := port.ContainerPort
|
||||
if containerPort == 0 {
|
||||
return nil, nil, nil, errors.Errorf("container port number must be non-0")
|
||||
return nil, errors.Errorf("container port number must be non-0")
|
||||
}
|
||||
hostPort := port.HostPort
|
||||
if uint32(len-1)+uint32(containerPort) > 65535 {
|
||||
return nil, nil, nil, errors.Errorf("container port range exceeds maximum allowable port number")
|
||||
if uint32(portRange-1)+uint32(containerPort) > 65535 {
|
||||
return nil, errors.Errorf("container port range exceeds maximum allowable port number")
|
||||
}
|
||||
if uint32(len-1)+uint32(hostPort) > 65536 {
|
||||
return nil, nil, nil, errors.Errorf("host port range exceeds maximum allowable port number")
|
||||
if uint32(portRange-1)+uint32(hostPort) > 65535 {
|
||||
return nil, errors.Errorf("host port range exceeds maximum allowable port number")
|
||||
}
|
||||
|
||||
// Iterate through ports, populating maps to check for conflicts
|
||||
// and generating CNI port mappings.
|
||||
for _, p := range protocols {
|
||||
hostIPMap := hostPortValidate[p]
|
||||
ctrIPMap := containerPortValidate[p]
|
||||
|
||||
hostPortMap, ok := hostIPMap[hostIP]
|
||||
if !ok {
|
||||
hostPortMap = make(map[uint16]uint16)
|
||||
hostIPMap[hostIP] = hostPortMap
|
||||
}
|
||||
ctrPortMap, ok := ctrIPMap[hostIP]
|
||||
if !ok {
|
||||
ctrPortMap = make(map[uint16]uint16)
|
||||
ctrIPMap[hostIP] = ctrPortMap
|
||||
hostProtoMap, ok := portMap[port.HostIP]
|
||||
if !ok {
|
||||
hostProtoMap = make(map[string][]tempMapping)
|
||||
for _, proto := range []string{protoTCP, protoUDP, protoSCTP} {
|
||||
hostProtoMap[proto] = make([]tempMapping, 0)
|
||||
}
|
||||
portMap[port.HostIP] = hostProtoMap
|
||||
}
|
||||
|
||||
// Iterate through all port numbers in the requested
|
||||
// range.
|
||||
var index uint16
|
||||
for index = 0; index < len; index++ {
|
||||
cPort := containerPort + index
|
||||
hPort := hostPort
|
||||
// Only increment host port if it's not 0.
|
||||
if hostPort != 0 {
|
||||
hPort += index
|
||||
}
|
||||
p := tempMapping{
|
||||
hostPort: port.HostPort,
|
||||
containerPort: port.ContainerPort,
|
||||
rangePort: portRange,
|
||||
}
|
||||
|
||||
if cPort == 0 {
|
||||
return nil, nil, nil, errors.Errorf("container port cannot be 0")
|
||||
}
|
||||
|
||||
// Host port is allowed to be 0. If it is, we
|
||||
// select a random port on the host.
|
||||
// This will happen *after* all other ports are
|
||||
// placed, to ensure we don't accidentally
|
||||
// select a port that a later mapping wanted.
|
||||
if hPort == 0 {
|
||||
// If we already have a host port
|
||||
// assigned to their container port -
|
||||
// just use that.
|
||||
if ctrPortMap[cPort] != 0 {
|
||||
hPort = ctrPortMap[cPort]
|
||||
} else {
|
||||
postAssignHostPort = true
|
||||
}
|
||||
} else {
|
||||
testHPort := hostPortMap[hPort]
|
||||
if testHPort != 0 && testHPort != cPort {
|
||||
return nil, nil, nil, errors.Errorf("conflicting port mappings for host port %d (protocol %s)", hPort, p)
|
||||
}
|
||||
hostPortMap[hPort] = cPort
|
||||
|
||||
// Mapping a container port to multiple
|
||||
// host ports is allowed.
|
||||
// We only store the latest of these in
|
||||
// the container port map - we don't
|
||||
// need to know all of them, just one.
|
||||
testCPort := ctrPortMap[cPort]
|
||||
ctrPortMap[cPort] = hPort
|
||||
|
||||
// If we have an exact duplicate, just continue
|
||||
if testCPort == hPort && testHPort == cPort {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// We appear to be clear. Make an OCICNI port
|
||||
// struct.
|
||||
// Don't use hostIP - we want to preserve the
|
||||
// empty string hostIP by default for compat.
|
||||
cniPort := types.OCICNIPortMapping{
|
||||
HostPort: int32(hPort),
|
||||
ContainerPort: int32(cPort),
|
||||
Protocol: p,
|
||||
HostIP: port.HostIP,
|
||||
}
|
||||
tempMappings = append(
|
||||
tempMappings,
|
||||
tempMapping{
|
||||
mapping: cniPort,
|
||||
startOfRange: port.Range > 1 && index == 0,
|
||||
isInRange: port.Range > 1,
|
||||
},
|
||||
)
|
||||
}
|
||||
for _, proto := range protocols {
|
||||
hostProtoMap[proto] = append(hostProtoMap[proto], p)
|
||||
}
|
||||
}
|
||||
|
||||
// Handle any 0 host ports now by setting random container ports.
|
||||
if postAssignHostPort {
|
||||
remadeMappings := make([]types.OCICNIPortMapping, 0, len(tempMappings))
|
||||
// we do no longer need the original port mappings
|
||||
// set it to 0 length so we can resuse it to populate
|
||||
// the slice again while keeping the underlying capacity
|
||||
portMappings = portMappings[:0]
|
||||
|
||||
var (
|
||||
candidate int
|
||||
err error
|
||||
)
|
||||
|
||||
// Iterate over all
|
||||
for _, tmp := range tempMappings {
|
||||
p := tmp.mapping
|
||||
|
||||
if p.HostPort != 0 {
|
||||
remadeMappings = append(remadeMappings, p)
|
||||
for hostIP, protoMap := range portMap {
|
||||
for protocol, ports := range protoMap {
|
||||
ports := ports
|
||||
if len(ports) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
hostIPMap := hostPortValidate[p.Protocol]
|
||||
ctrIPMap := containerPortValidate[p.Protocol]
|
||||
|
||||
hostPortMap, ok := hostIPMap[p.HostIP]
|
||||
if !ok {
|
||||
hostPortMap = make(map[uint16]uint16)
|
||||
hostIPMap[p.HostIP] = hostPortMap
|
||||
}
|
||||
ctrPortMap, ok := ctrIPMap[p.HostIP]
|
||||
if !ok {
|
||||
ctrPortMap = make(map[uint16]uint16)
|
||||
ctrIPMap[p.HostIP] = ctrPortMap
|
||||
}
|
||||
|
||||
// See if container port has been used elsewhere
|
||||
if ctrPortMap[uint16(p.ContainerPort)] != 0 {
|
||||
// Duplicate definition. Let's not bother
|
||||
// including it.
|
||||
continue
|
||||
}
|
||||
|
||||
// Max retries to ensure we don't loop forever.
|
||||
for i := 0; i < 15; i++ {
|
||||
// Only get a random candidate for single entries or the start
|
||||
// of a range. Otherwise we just increment the candidate.
|
||||
if !tmp.isInRange || tmp.startOfRange {
|
||||
candidate, err = utils.GetRandomPort()
|
||||
if err != nil {
|
||||
return nil, nil, nil, errors.Wrapf(err, "error getting candidate host port for container port %d", p.ContainerPort)
|
||||
}
|
||||
} else {
|
||||
candidate++
|
||||
// 1. sort the ports by host port
|
||||
// use a small hack to make sure ports with host port 0 are sorted last
|
||||
sort.Slice(ports, func(i, j int) bool {
|
||||
if ports[i].hostPort == ports[j].hostPort {
|
||||
return ports[i].containerPort < ports[j].containerPort
|
||||
}
|
||||
if ports[i].hostPort == 0 {
|
||||
return false
|
||||
}
|
||||
if ports[j].hostPort == 0 {
|
||||
return true
|
||||
}
|
||||
return ports[i].hostPort < ports[j].hostPort
|
||||
})
|
||||
|
||||
if hostPortMap[uint16(candidate)] == 0 {
|
||||
logrus.Debugf("Successfully assigned container port %d to host port %d (IP %s Protocol %s)", p.ContainerPort, candidate, p.HostIP, p.Protocol)
|
||||
hostPortMap[uint16(candidate)] = uint16(p.ContainerPort)
|
||||
ctrPortMap[uint16(p.ContainerPort)] = uint16(candidate)
|
||||
p.HostPort = int32(candidate)
|
||||
allUsedContainerPorts := allUsedContainerPortsMap[protocol]
|
||||
allUsedHostPorts := allUsedHostPortsMap[protocol]
|
||||
var usedHostPorts [65536]bool
|
||||
|
||||
var previousPort *types.PortMapping
|
||||
var i int
|
||||
for i = 0; i < len(ports); i++ {
|
||||
if ports[i].hostPort == 0 {
|
||||
// because the ports are sorted and host port 0 is last
|
||||
// we can break when we hit 0
|
||||
// we will fit them in afterwards
|
||||
break
|
||||
}
|
||||
p := types.PortMapping{
|
||||
HostIP: hostIP,
|
||||
Protocol: protocol,
|
||||
HostPort: ports[i].hostPort,
|
||||
ContainerPort: ports[i].containerPort,
|
||||
Range: ports[i].rangePort,
|
||||
}
|
||||
var err error
|
||||
previousPort, err = joinTwoPortsToRangePortIfPossible(&portMappings, &allUsedHostPorts,
|
||||
&allUsedContainerPorts, &usedHostPorts, previousPort, p)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if p.HostPort == 0 {
|
||||
return nil, nil, nil, errors.Errorf("could not find open host port to map container port %d to", p.ContainerPort)
|
||||
if previousPort != nil {
|
||||
addPortToUsedPorts(&portMappings, &allUsedHostPorts,
|
||||
&allUsedContainerPorts, &usedHostPorts, previousPort)
|
||||
}
|
||||
remadeMappings = append(remadeMappings, p)
|
||||
|
||||
// now take care of the hostPort = 0 ports
|
||||
previousPort = nil
|
||||
for i < len(ports) {
|
||||
p := types.PortMapping{
|
||||
HostIP: hostIP,
|
||||
Protocol: protocol,
|
||||
ContainerPort: ports[i].containerPort,
|
||||
Range: ports[i].rangePort,
|
||||
}
|
||||
var err error
|
||||
previousPort, err = joinTwoContainerPortsToRangePortIfPossible(&portMappings, &allUsedHostPorts,
|
||||
&allUsedContainerPorts, &usedHostPorts, previousPort, p)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
i++
|
||||
}
|
||||
if previousPort != nil {
|
||||
newPort, err := getRandomHostPort(&usedHostPorts, *previousPort)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
addPortToUsedPorts(&portMappings, &allUsedHostPorts,
|
||||
&allUsedContainerPorts, &usedHostPorts, &newPort)
|
||||
}
|
||||
|
||||
allUsedContainerPortsMap[protocol] = allUsedContainerPorts
|
||||
allUsedHostPortsMap[protocol] = allUsedHostPorts
|
||||
}
|
||||
return remadeMappings, containerPortValidate, hostPortValidate, nil
|
||||
}
|
||||
|
||||
finalMappings := []types.OCICNIPortMapping{}
|
||||
for _, m := range tempMappings {
|
||||
finalMappings = append(finalMappings, m.mapping)
|
||||
}
|
||||
if len(exposePorts) > 0 {
|
||||
logrus.Debugf("Adding exposed ports")
|
||||
|
||||
return finalMappings, containerPortValidate, hostPortValidate, nil
|
||||
for port, protocols := range exposePorts {
|
||||
newProtocols := make([]string, 0, len(protocols))
|
||||
for _, protocol := range protocols {
|
||||
if !allUsedContainerPortsMap[protocol][port] {
|
||||
p := types.PortMapping{
|
||||
ContainerPort: port,
|
||||
Protocol: protocol,
|
||||
Range: 1,
|
||||
}
|
||||
allPorts := allUsedContainerPortsMap[protocol]
|
||||
p, err := getRandomHostPort(&allPorts, p)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
portMappings = append(portMappings, p)
|
||||
} else {
|
||||
newProtocols = append(newProtocols, protocol)
|
||||
}
|
||||
}
|
||||
// make sure to delete the key from the map if there are no protocols left
|
||||
if len(newProtocols) == 0 {
|
||||
delete(exposePorts, port)
|
||||
} else {
|
||||
exposePorts[port] = newProtocols
|
||||
}
|
||||
}
|
||||
}
|
||||
return portMappings, nil
|
||||
}
|
||||
|
||||
func appendProtocolsNoDuplicates(slice []string, protocols []string) []string {
|
||||
for _, proto := range protocols {
|
||||
if util.StringInSlice(proto, slice) {
|
||||
continue
|
||||
}
|
||||
slice = append(slice, proto)
|
||||
}
|
||||
return slice
|
||||
}
|
||||
|
||||
// Make final port mappings for the container
|
||||
func createPortMappings(ctx context.Context, s *specgen.SpecGenerator, imageData *libimage.ImageData) ([]types.OCICNIPortMapping, map[uint16][]string, error) {
|
||||
finalMappings, containerPortValidate, hostPortValidate, err := ParsePortMapping(s.PortMappings)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
// No exposed ports so return the port mappings we've made so far.
|
||||
if len(s.Expose) == 0 && imageData == nil {
|
||||
return finalMappings, nil, nil
|
||||
}
|
||||
|
||||
logrus.Debugf("Adding exposed ports")
|
||||
|
||||
func createPortMappings(ctx context.Context, s *specgen.SpecGenerator, imageData *libimage.ImageData) ([]types.PortMapping, map[uint16][]string, error) {
|
||||
expose := make(map[uint16]string)
|
||||
var err error
|
||||
if imageData != nil {
|
||||
expose, err = GenExposedPorts(imageData.Config.ExposedPorts)
|
||||
if err != nil {
|
||||
|
@ -274,103 +348,30 @@ func createPortMappings(ctx context.Context, s *specgen.SpecGenerator, imageData
|
|||
}
|
||||
}
|
||||
|
||||
// We need to merge s.Expose into image exposed ports
|
||||
for k, v := range s.Expose {
|
||||
expose[k] = v
|
||||
}
|
||||
// There's been a request to expose some ports. Let's do that.
|
||||
// Start by figuring out what needs to be exposed.
|
||||
// This is a map of container port number to protocols to expose.
|
||||
toExpose := make(map[uint16][]string)
|
||||
for port, proto := range expose {
|
||||
// Validate protocol first
|
||||
protocols, err := checkProtocol(proto, false)
|
||||
if err != nil {
|
||||
return nil, nil, errors.Wrapf(err, "error validating protocols for exposed port %d", port)
|
||||
}
|
||||
|
||||
if port == 0 {
|
||||
return nil, nil, errors.Errorf("cannot expose 0 as it is not a valid port number")
|
||||
}
|
||||
|
||||
// Check to see if the port is already present in existing
|
||||
// mappings.
|
||||
for _, p := range protocols {
|
||||
ctrPortMap, ok := containerPortValidate[p]["0.0.0.0"]
|
||||
if !ok {
|
||||
ctrPortMap = make(map[uint16]uint16)
|
||||
containerPortValidate[p]["0.0.0.0"] = ctrPortMap
|
||||
toExpose := make(map[uint16][]string, len(s.Expose)+len(expose))
|
||||
for _, expose := range []map[uint16]string{expose, s.Expose} {
|
||||
for port, proto := range expose {
|
||||
if port == 0 {
|
||||
return nil, nil, errors.Errorf("cannot expose 0 as it is not a valid port number")
|
||||
}
|
||||
|
||||
if portNum := ctrPortMap[port]; portNum == 0 {
|
||||
// We want to expose this port for this protocol
|
||||
exposeProto, ok := toExpose[port]
|
||||
if !ok {
|
||||
exposeProto = []string{}
|
||||
}
|
||||
exposeProto = append(exposeProto, p)
|
||||
toExpose[port] = exposeProto
|
||||
protocols, err := checkProtocol(proto, false)
|
||||
if err != nil {
|
||||
return nil, nil, errors.Wrapf(err, "error validating protocols for exposed port %d", port)
|
||||
}
|
||||
toExpose[port] = appendProtocolsNoDuplicates(toExpose[port], protocols)
|
||||
}
|
||||
}
|
||||
|
||||
// If not publishing exposed ports return mappings and exposed ports.
|
||||
publishPorts := toExpose
|
||||
if !s.PublishExposedPorts {
|
||||
return finalMappings, toExpose, nil
|
||||
publishPorts = nil
|
||||
}
|
||||
|
||||
// We now have a final list of ports that we want exposed.
|
||||
// Let's find empty, unallocated host ports for them.
|
||||
for port, protocols := range toExpose {
|
||||
for _, p := range protocols {
|
||||
// Find an open port on the host.
|
||||
// I see a faint possibility that this will infinite
|
||||
// loop trying to find a valid open port, so I've
|
||||
// included a max-tries counter.
|
||||
hostPort := 0
|
||||
tries := 15
|
||||
for hostPort == 0 && tries > 0 {
|
||||
// We can't select a specific protocol, which is
|
||||
// unfortunate for the UDP case.
|
||||
candidate, err := utils.GetRandomPort()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
// Check if the host port is already bound
|
||||
hostPortMap, ok := hostPortValidate[p]["0.0.0.0"]
|
||||
if !ok {
|
||||
hostPortMap = make(map[uint16]uint16)
|
||||
hostPortValidate[p]["0.0.0.0"] = hostPortMap
|
||||
}
|
||||
|
||||
if checkPort := hostPortMap[uint16(candidate)]; checkPort != 0 {
|
||||
// Host port is already allocated, try again
|
||||
tries--
|
||||
continue
|
||||
}
|
||||
|
||||
hostPortMap[uint16(candidate)] = port
|
||||
hostPort = candidate
|
||||
logrus.Debugf("Mapping exposed port %d/%s to host port %d", port, p, hostPort)
|
||||
|
||||
// Make a CNI port mapping
|
||||
cniPort := types.OCICNIPortMapping{
|
||||
HostPort: int32(candidate),
|
||||
ContainerPort: int32(port),
|
||||
Protocol: p,
|
||||
HostIP: "",
|
||||
}
|
||||
finalMappings = append(finalMappings, cniPort)
|
||||
}
|
||||
if tries == 0 && hostPort == 0 {
|
||||
// We failed to find an open port.
|
||||
return nil, nil, errors.Errorf("failed to find an open port to expose container port %d on the host", port)
|
||||
}
|
||||
}
|
||||
finalMappings, err := ParsePortMapping(s.PortMappings, publishPorts)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return finalMappings, nil, nil
|
||||
return finalMappings, toExpose, nil
|
||||
}
|
||||
|
||||
// Check a string to ensure it is a comma-separated set of valid protocols
|
||||
|
@ -409,7 +410,7 @@ func checkProtocol(protocol string, allowSCTP bool) ([]string, error) {
|
|||
}
|
||||
|
||||
func GenExposedPorts(exposedPorts map[string]struct{}) (map[uint16]string, error) {
|
||||
expose := make(map[uint16]string)
|
||||
expose := make(map[uint16]string, len(exposedPorts))
|
||||
for imgExpose := range exposedPorts {
|
||||
// Expose format is portNumber[/protocol]
|
||||
splitExpose := strings.SplitN(imgExpose, "/", 2)
|
||||
|
@ -420,12 +421,20 @@ func GenExposedPorts(exposedPorts map[string]struct{}) (map[uint16]string, error
|
|||
if num > 65535 || num < 1 {
|
||||
return nil, errors.Errorf("%d from image EXPOSE statement %q is not a valid port number", num, imgExpose)
|
||||
}
|
||||
// No need to validate protocol, we'll do it below.
|
||||
if len(splitExpose) == 1 {
|
||||
expose[uint16(num)] = "tcp"
|
||||
} else {
|
||||
expose[uint16(num)] = splitExpose[1]
|
||||
|
||||
// No need to validate protocol, we'll do it later.
|
||||
newProto := "tcp"
|
||||
if len(splitExpose) == 2 {
|
||||
newProto = splitExpose[1]
|
||||
}
|
||||
|
||||
proto := expose[uint16(num)]
|
||||
if len(proto) > 1 {
|
||||
proto = proto + "," + newProto
|
||||
} else {
|
||||
proto = newProto
|
||||
}
|
||||
expose[uint16(num)] = proto
|
||||
}
|
||||
return expose, nil
|
||||
}
|
||||
|
|
|
@ -0,0 +1,197 @@
|
|||
package generate
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/containers/podman/v3/libpod/network/types"
|
||||
)
|
||||
|
||||
func benchmarkParsePortMapping(b *testing.B, ports []types.PortMapping) {
|
||||
for n := 0; n < b.N; n++ {
|
||||
ParsePortMapping(ports, nil)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkParsePortMappingNoPorts(b *testing.B) {
|
||||
benchmarkParsePortMapping(b, nil)
|
||||
}
|
||||
|
||||
func BenchmarkParsePortMapping1(b *testing.B) {
|
||||
benchmarkParsePortMapping(b, []types.PortMapping{
|
||||
{
|
||||
HostPort: 8080,
|
||||
ContainerPort: 80,
|
||||
Protocol: "tcp",
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func BenchmarkParsePortMapping100(b *testing.B) {
|
||||
ports := make([]types.PortMapping, 0, 100)
|
||||
for i := uint16(8080); i < 8180; i++ {
|
||||
ports = append(ports, types.PortMapping{
|
||||
HostPort: i,
|
||||
ContainerPort: i,
|
||||
Protocol: "tcp",
|
||||
})
|
||||
}
|
||||
b.ResetTimer()
|
||||
benchmarkParsePortMapping(b, ports)
|
||||
}
|
||||
|
||||
func BenchmarkParsePortMapping1k(b *testing.B) {
|
||||
ports := make([]types.PortMapping, 0, 1000)
|
||||
for i := uint16(8080); i < 9080; i++ {
|
||||
ports = append(ports, types.PortMapping{
|
||||
HostPort: i,
|
||||
ContainerPort: i,
|
||||
Protocol: "tcp",
|
||||
})
|
||||
}
|
||||
b.ResetTimer()
|
||||
benchmarkParsePortMapping(b, ports)
|
||||
}
|
||||
|
||||
func BenchmarkParsePortMapping10k(b *testing.B) {
|
||||
ports := make([]types.PortMapping, 0, 30000)
|
||||
for i := uint16(8080); i < 18080; i++ {
|
||||
ports = append(ports, types.PortMapping{
|
||||
HostPort: i,
|
||||
ContainerPort: i,
|
||||
Protocol: "tcp",
|
||||
})
|
||||
}
|
||||
b.ResetTimer()
|
||||
benchmarkParsePortMapping(b, ports)
|
||||
}
|
||||
|
||||
func BenchmarkParsePortMapping1m(b *testing.B) {
|
||||
ports := make([]types.PortMapping, 0, 1000000)
|
||||
for j := 0; j < 20; j++ {
|
||||
for i := uint16(1); i <= 50000; i++ {
|
||||
ports = append(ports, types.PortMapping{
|
||||
HostPort: i,
|
||||
ContainerPort: i,
|
||||
Protocol: "tcp",
|
||||
HostIP: fmt.Sprintf("192.168.1.%d", j),
|
||||
})
|
||||
}
|
||||
}
|
||||
b.ResetTimer()
|
||||
benchmarkParsePortMapping(b, ports)
|
||||
}
|
||||
|
||||
func BenchmarkParsePortMappingReverse100(b *testing.B) {
|
||||
ports := make([]types.PortMapping, 0, 100)
|
||||
for i := uint16(8180); i > 8080; i-- {
|
||||
ports = append(ports, types.PortMapping{
|
||||
HostPort: i,
|
||||
ContainerPort: i,
|
||||
Protocol: "tcp",
|
||||
})
|
||||
}
|
||||
b.ResetTimer()
|
||||
benchmarkParsePortMapping(b, ports)
|
||||
}
|
||||
|
||||
func BenchmarkParsePortMappingReverse1k(b *testing.B) {
|
||||
ports := make([]types.PortMapping, 0, 1000)
|
||||
for i := uint16(9080); i > 8080; i-- {
|
||||
ports = append(ports, types.PortMapping{
|
||||
HostPort: i,
|
||||
ContainerPort: i,
|
||||
Protocol: "tcp",
|
||||
})
|
||||
}
|
||||
b.ResetTimer()
|
||||
benchmarkParsePortMapping(b, ports)
|
||||
}
|
||||
|
||||
func BenchmarkParsePortMappingReverse10k(b *testing.B) {
|
||||
ports := make([]types.PortMapping, 0, 30000)
|
||||
for i := uint16(18080); i > 8080; i-- {
|
||||
ports = append(ports, types.PortMapping{
|
||||
HostPort: i,
|
||||
ContainerPort: i,
|
||||
Protocol: "tcp",
|
||||
})
|
||||
}
|
||||
b.ResetTimer()
|
||||
benchmarkParsePortMapping(b, ports)
|
||||
}
|
||||
|
||||
func BenchmarkParsePortMappingReverse1m(b *testing.B) {
|
||||
ports := make([]types.PortMapping, 0, 1000000)
|
||||
for j := 0; j < 20; j++ {
|
||||
for i := uint16(50000); i > 0; i-- {
|
||||
ports = append(ports, types.PortMapping{
|
||||
HostPort: i,
|
||||
ContainerPort: i,
|
||||
Protocol: "tcp",
|
||||
HostIP: fmt.Sprintf("192.168.1.%d", j),
|
||||
})
|
||||
}
|
||||
}
|
||||
b.ResetTimer()
|
||||
benchmarkParsePortMapping(b, ports)
|
||||
}
|
||||
|
||||
func BenchmarkParsePortMappingRange1(b *testing.B) {
|
||||
benchmarkParsePortMapping(b, []types.PortMapping{
|
||||
{
|
||||
HostPort: 8080,
|
||||
ContainerPort: 80,
|
||||
Protocol: "tcp",
|
||||
Range: 1,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func BenchmarkParsePortMappingRange100(b *testing.B) {
|
||||
benchmarkParsePortMapping(b, []types.PortMapping{
|
||||
{
|
||||
HostPort: 8080,
|
||||
ContainerPort: 80,
|
||||
Protocol: "tcp",
|
||||
Range: 100,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func BenchmarkParsePortMappingRange1k(b *testing.B) {
|
||||
benchmarkParsePortMapping(b, []types.PortMapping{
|
||||
{
|
||||
HostPort: 8080,
|
||||
ContainerPort: 80,
|
||||
Protocol: "tcp",
|
||||
Range: 1000,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func BenchmarkParsePortMappingRange10k(b *testing.B) {
|
||||
benchmarkParsePortMapping(b, []types.PortMapping{
|
||||
{
|
||||
HostPort: 8080,
|
||||
ContainerPort: 80,
|
||||
Protocol: "tcp",
|
||||
Range: 10000,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func BenchmarkParsePortMappingRange1m(b *testing.B) {
|
||||
ports := make([]types.PortMapping, 0, 1000000)
|
||||
for j := 0; j < 20; j++ {
|
||||
ports = append(ports, types.PortMapping{
|
||||
HostPort: 1,
|
||||
ContainerPort: 1,
|
||||
Protocol: "tcp",
|
||||
Range: 50000,
|
||||
HostIP: fmt.Sprintf("192.168.1.%d", j),
|
||||
})
|
||||
}
|
||||
b.ResetTimer()
|
||||
benchmarkParsePortMapping(b, ports)
|
||||
}
|
|
@ -0,0 +1,989 @@
|
|||
package generate
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/containers/podman/v3/libpod/network/types"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestParsePortMappingWithHostPort(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
arg []types.PortMapping
|
||||
arg2 map[uint16][]string
|
||||
want []types.PortMapping
|
||||
}{
|
||||
{
|
||||
name: "no ports",
|
||||
arg: nil,
|
||||
want: nil,
|
||||
},
|
||||
{
|
||||
name: "one tcp port",
|
||||
arg: []types.PortMapping{
|
||||
{
|
||||
HostPort: 8080,
|
||||
ContainerPort: 80,
|
||||
Protocol: "tcp",
|
||||
},
|
||||
},
|
||||
want: []types.PortMapping{
|
||||
{
|
||||
HostPort: 8080,
|
||||
ContainerPort: 80,
|
||||
Protocol: "tcp",
|
||||
Range: 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "one tcp port no proto",
|
||||
arg: []types.PortMapping{
|
||||
{
|
||||
HostPort: 8080,
|
||||
ContainerPort: 80,
|
||||
},
|
||||
},
|
||||
want: []types.PortMapping{
|
||||
{
|
||||
HostPort: 8080,
|
||||
ContainerPort: 80,
|
||||
Protocol: "tcp",
|
||||
Range: 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "one udp port",
|
||||
arg: []types.PortMapping{
|
||||
{
|
||||
HostPort: 8080,
|
||||
ContainerPort: 80,
|
||||
Protocol: "udp",
|
||||
},
|
||||
},
|
||||
want: []types.PortMapping{
|
||||
{
|
||||
HostPort: 8080,
|
||||
ContainerPort: 80,
|
||||
Protocol: "udp",
|
||||
Range: 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "one sctp port",
|
||||
arg: []types.PortMapping{
|
||||
{
|
||||
HostPort: 8080,
|
||||
ContainerPort: 80,
|
||||
Protocol: "sctp",
|
||||
},
|
||||
},
|
||||
want: []types.PortMapping{
|
||||
{
|
||||
HostPort: 8080,
|
||||
ContainerPort: 80,
|
||||
Protocol: "sctp",
|
||||
Range: 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "one port two protocols",
|
||||
arg: []types.PortMapping{
|
||||
{
|
||||
HostPort: 8080,
|
||||
ContainerPort: 80,
|
||||
Protocol: "tcp,udp",
|
||||
},
|
||||
},
|
||||
want: []types.PortMapping{
|
||||
{
|
||||
HostPort: 8080,
|
||||
ContainerPort: 80,
|
||||
Protocol: "tcp",
|
||||
Range: 1,
|
||||
},
|
||||
{
|
||||
HostPort: 8080,
|
||||
ContainerPort: 80,
|
||||
Protocol: "udp",
|
||||
Range: 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "one port three protocols",
|
||||
arg: []types.PortMapping{
|
||||
{
|
||||
HostPort: 8080,
|
||||
ContainerPort: 80,
|
||||
Protocol: "tcp,udp,sctp",
|
||||
},
|
||||
},
|
||||
want: []types.PortMapping{
|
||||
{
|
||||
HostPort: 8080,
|
||||
ContainerPort: 80,
|
||||
Protocol: "tcp",
|
||||
Range: 1,
|
||||
},
|
||||
{
|
||||
HostPort: 8080,
|
||||
ContainerPort: 80,
|
||||
Protocol: "udp",
|
||||
Range: 1,
|
||||
},
|
||||
{
|
||||
HostPort: 8080,
|
||||
ContainerPort: 80,
|
||||
Protocol: "sctp",
|
||||
Range: 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "one port with range 1",
|
||||
arg: []types.PortMapping{
|
||||
{
|
||||
HostPort: 8080,
|
||||
ContainerPort: 80,
|
||||
Protocol: "tcp",
|
||||
Range: 1,
|
||||
},
|
||||
},
|
||||
want: []types.PortMapping{
|
||||
{
|
||||
HostPort: 8080,
|
||||
ContainerPort: 80,
|
||||
Protocol: "tcp",
|
||||
Range: 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "one port with range 5",
|
||||
arg: []types.PortMapping{
|
||||
{
|
||||
HostPort: 8080,
|
||||
ContainerPort: 80,
|
||||
Protocol: "tcp",
|
||||
Range: 5,
|
||||
},
|
||||
},
|
||||
want: []types.PortMapping{
|
||||
{
|
||||
HostPort: 8080,
|
||||
ContainerPort: 80,
|
||||
Protocol: "tcp",
|
||||
Range: 5,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "two ports joined",
|
||||
arg: []types.PortMapping{
|
||||
{
|
||||
HostPort: 8080,
|
||||
ContainerPort: 80,
|
||||
Protocol: "tcp",
|
||||
},
|
||||
{
|
||||
HostPort: 8081,
|
||||
ContainerPort: 81,
|
||||
Protocol: "tcp",
|
||||
},
|
||||
},
|
||||
want: []types.PortMapping{
|
||||
{
|
||||
HostPort: 8080,
|
||||
ContainerPort: 80,
|
||||
Protocol: "tcp",
|
||||
Range: 2,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "two ports joined with range",
|
||||
arg: []types.PortMapping{
|
||||
{
|
||||
HostPort: 8080,
|
||||
ContainerPort: 80,
|
||||
Protocol: "tcp",
|
||||
Range: 2,
|
||||
},
|
||||
{
|
||||
HostPort: 8081,
|
||||
ContainerPort: 81,
|
||||
Protocol: "tcp",
|
||||
},
|
||||
},
|
||||
want: []types.PortMapping{
|
||||
{
|
||||
HostPort: 8080,
|
||||
ContainerPort: 80,
|
||||
Protocol: "tcp",
|
||||
Range: 2,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "two ports with no overlapping range",
|
||||
arg: []types.PortMapping{
|
||||
{
|
||||
HostPort: 8080,
|
||||
ContainerPort: 80,
|
||||
Protocol: "tcp",
|
||||
Range: 10,
|
||||
},
|
||||
{
|
||||
HostPort: 9090,
|
||||
ContainerPort: 9090,
|
||||
Protocol: "tcp",
|
||||
},
|
||||
},
|
||||
want: []types.PortMapping{
|
||||
{
|
||||
HostPort: 9090,
|
||||
ContainerPort: 9090,
|
||||
Protocol: "tcp",
|
||||
Range: 1,
|
||||
},
|
||||
{
|
||||
HostPort: 8080,
|
||||
ContainerPort: 80,
|
||||
Protocol: "tcp",
|
||||
Range: 10,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "four ports with two overlapping ranges",
|
||||
arg: []types.PortMapping{
|
||||
{
|
||||
HostPort: 8080,
|
||||
ContainerPort: 80,
|
||||
Protocol: "tcp",
|
||||
Range: 10,
|
||||
},
|
||||
{
|
||||
HostPort: 8085,
|
||||
ContainerPort: 85,
|
||||
Protocol: "tcp",
|
||||
Range: 10,
|
||||
},
|
||||
{
|
||||
HostPort: 100,
|
||||
ContainerPort: 5,
|
||||
Protocol: "tcp",
|
||||
},
|
||||
{
|
||||
HostPort: 101,
|
||||
ContainerPort: 6,
|
||||
Protocol: "tcp",
|
||||
},
|
||||
},
|
||||
want: []types.PortMapping{
|
||||
{
|
||||
HostPort: 8080,
|
||||
ContainerPort: 80,
|
||||
Protocol: "tcp",
|
||||
Range: 15,
|
||||
},
|
||||
{
|
||||
HostPort: 100,
|
||||
ContainerPort: 5,
|
||||
Protocol: "tcp",
|
||||
Range: 2,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "two overlapping ranges",
|
||||
arg: []types.PortMapping{
|
||||
{
|
||||
HostPort: 8080,
|
||||
ContainerPort: 80,
|
||||
Protocol: "tcp",
|
||||
Range: 10,
|
||||
},
|
||||
{
|
||||
HostPort: 8085,
|
||||
ContainerPort: 85,
|
||||
Protocol: "tcp",
|
||||
Range: 2,
|
||||
},
|
||||
},
|
||||
want: []types.PortMapping{
|
||||
{
|
||||
HostPort: 8080,
|
||||
ContainerPort: 80,
|
||||
Protocol: "tcp",
|
||||
Range: 10,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "four overlapping ranges",
|
||||
arg: []types.PortMapping{
|
||||
{
|
||||
HostPort: 8080,
|
||||
ContainerPort: 80,
|
||||
Protocol: "tcp",
|
||||
Range: 10,
|
||||
},
|
||||
{
|
||||
HostPort: 8085,
|
||||
ContainerPort: 85,
|
||||
Protocol: "tcp",
|
||||
Range: 2,
|
||||
},
|
||||
{
|
||||
HostPort: 8090,
|
||||
ContainerPort: 90,
|
||||
Protocol: "tcp",
|
||||
Range: 7,
|
||||
},
|
||||
{
|
||||
HostPort: 8095,
|
||||
ContainerPort: 95,
|
||||
Protocol: "tcp",
|
||||
},
|
||||
},
|
||||
want: []types.PortMapping{
|
||||
{
|
||||
HostPort: 8080,
|
||||
ContainerPort: 80,
|
||||
Protocol: "tcp",
|
||||
Range: 17,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "one port range overlaps 5 ports",
|
||||
arg: []types.PortMapping{
|
||||
{
|
||||
HostPort: 8080,
|
||||
ContainerPort: 80,
|
||||
Range: 20,
|
||||
},
|
||||
{
|
||||
HostPort: 8085,
|
||||
ContainerPort: 85,
|
||||
Range: 2,
|
||||
},
|
||||
{
|
||||
HostPort: 8090,
|
||||
ContainerPort: 90,
|
||||
},
|
||||
{
|
||||
HostPort: 8095,
|
||||
ContainerPort: 95,
|
||||
},
|
||||
{
|
||||
HostPort: 8096,
|
||||
ContainerPort: 96,
|
||||
},
|
||||
},
|
||||
want: []types.PortMapping{
|
||||
{
|
||||
HostPort: 8080,
|
||||
ContainerPort: 80,
|
||||
Protocol: "tcp",
|
||||
Range: 20,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "different host ip same port",
|
||||
arg: []types.PortMapping{
|
||||
{
|
||||
HostPort: 8080,
|
||||
ContainerPort: 80,
|
||||
Protocol: "tcp",
|
||||
HostIP: "192.168.1.1",
|
||||
},
|
||||
{
|
||||
HostPort: 8080,
|
||||
ContainerPort: 80,
|
||||
Protocol: "tcp",
|
||||
HostIP: "192.168.2.1",
|
||||
},
|
||||
},
|
||||
want: []types.PortMapping{
|
||||
{
|
||||
HostPort: 8080,
|
||||
ContainerPort: 80,
|
||||
Protocol: "tcp",
|
||||
HostIP: "192.168.1.1",
|
||||
Range: 1,
|
||||
},
|
||||
{
|
||||
HostPort: 8080,
|
||||
ContainerPort: 80,
|
||||
Protocol: "tcp",
|
||||
HostIP: "192.168.2.1",
|
||||
Range: 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
tt := tt
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := ParsePortMapping(tt.arg, tt.arg2)
|
||||
assert.NoError(t, err, "error is not nil")
|
||||
// use ElementsMatch instead of Equal because the order is not consistent
|
||||
assert.ElementsMatch(t, tt.want, got, "got unexpected port mapping")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestParsePortMappingWithoutHostPort(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
arg []types.PortMapping
|
||||
arg2 map[uint16][]string
|
||||
want []types.PortMapping
|
||||
}{
|
||||
{
|
||||
name: "one tcp port",
|
||||
arg: []types.PortMapping{
|
||||
{
|
||||
HostPort: 0,
|
||||
ContainerPort: 80,
|
||||
Protocol: "tcp",
|
||||
},
|
||||
},
|
||||
want: []types.PortMapping{
|
||||
{
|
||||
HostPort: 0,
|
||||
ContainerPort: 80,
|
||||
Protocol: "tcp",
|
||||
Range: 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "one port with two protocols",
|
||||
arg: []types.PortMapping{
|
||||
{
|
||||
HostPort: 0,
|
||||
ContainerPort: 80,
|
||||
Protocol: "tcp,udp",
|
||||
},
|
||||
},
|
||||
want: []types.PortMapping{
|
||||
{
|
||||
HostPort: 0,
|
||||
ContainerPort: 80,
|
||||
Protocol: "tcp",
|
||||
Range: 1,
|
||||
},
|
||||
{
|
||||
HostPort: 0,
|
||||
ContainerPort: 80,
|
||||
Protocol: "udp",
|
||||
Range: 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "same port twice",
|
||||
arg: []types.PortMapping{
|
||||
{
|
||||
HostPort: 0,
|
||||
ContainerPort: 80,
|
||||
Protocol: "tcp",
|
||||
},
|
||||
{
|
||||
HostPort: 0,
|
||||
ContainerPort: 80,
|
||||
Protocol: "tcp",
|
||||
},
|
||||
},
|
||||
want: []types.PortMapping{
|
||||
{
|
||||
HostPort: 0,
|
||||
ContainerPort: 80,
|
||||
Protocol: "tcp",
|
||||
Range: 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "neighbor ports are not joined",
|
||||
arg: []types.PortMapping{
|
||||
{
|
||||
HostPort: 0,
|
||||
ContainerPort: 80,
|
||||
Protocol: "tcp",
|
||||
},
|
||||
{
|
||||
HostPort: 0,
|
||||
ContainerPort: 81,
|
||||
Protocol: "tcp",
|
||||
},
|
||||
},
|
||||
want: []types.PortMapping{
|
||||
{
|
||||
HostPort: 0,
|
||||
ContainerPort: 80,
|
||||
Protocol: "tcp",
|
||||
Range: 1,
|
||||
},
|
||||
{
|
||||
HostPort: 0,
|
||||
ContainerPort: 81,
|
||||
Protocol: "tcp",
|
||||
Range: 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "overlapping range ports are joined",
|
||||
arg: []types.PortMapping{
|
||||
{
|
||||
HostPort: 0,
|
||||
ContainerPort: 80,
|
||||
Protocol: "tcp",
|
||||
Range: 2,
|
||||
},
|
||||
{
|
||||
HostPort: 0,
|
||||
ContainerPort: 81,
|
||||
Protocol: "tcp",
|
||||
},
|
||||
},
|
||||
want: []types.PortMapping{
|
||||
{
|
||||
HostPort: 0,
|
||||
ContainerPort: 80,
|
||||
Protocol: "tcp",
|
||||
Range: 2,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "four overlapping range ports are joined",
|
||||
arg: []types.PortMapping{
|
||||
{
|
||||
HostPort: 0,
|
||||
ContainerPort: 80,
|
||||
Protocol: "tcp",
|
||||
Range: 3,
|
||||
},
|
||||
{
|
||||
HostPort: 0,
|
||||
ContainerPort: 81,
|
||||
Protocol: "tcp",
|
||||
},
|
||||
{
|
||||
HostPort: 0,
|
||||
ContainerPort: 82,
|
||||
Protocol: "tcp",
|
||||
Range: 10,
|
||||
},
|
||||
{
|
||||
HostPort: 0,
|
||||
ContainerPort: 90,
|
||||
Protocol: "tcp",
|
||||
Range: 5,
|
||||
},
|
||||
},
|
||||
want: []types.PortMapping{
|
||||
{
|
||||
HostPort: 0,
|
||||
ContainerPort: 80,
|
||||
Protocol: "tcp",
|
||||
Range: 15,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "expose one tcp port",
|
||||
arg2: map[uint16][]string{
|
||||
8080: {"tcp"},
|
||||
},
|
||||
want: []types.PortMapping{
|
||||
{
|
||||
HostPort: 0,
|
||||
ContainerPort: 8080,
|
||||
Protocol: "tcp",
|
||||
Range: 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "expose already defined port",
|
||||
arg: []types.PortMapping{
|
||||
{
|
||||
HostPort: 0,
|
||||
ContainerPort: 8080,
|
||||
Protocol: "tcp",
|
||||
},
|
||||
},
|
||||
arg2: map[uint16][]string{
|
||||
8080: {"tcp"},
|
||||
},
|
||||
want: []types.PortMapping{
|
||||
{
|
||||
HostPort: 0,
|
||||
ContainerPort: 8080,
|
||||
Protocol: "tcp",
|
||||
Range: 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "expose different proto",
|
||||
arg: []types.PortMapping{
|
||||
{
|
||||
HostPort: 0,
|
||||
ContainerPort: 8080,
|
||||
Protocol: "tcp",
|
||||
},
|
||||
},
|
||||
arg2: map[uint16][]string{
|
||||
8080: {"udp"},
|
||||
},
|
||||
want: []types.PortMapping{
|
||||
{
|
||||
HostPort: 0,
|
||||
ContainerPort: 8080,
|
||||
Protocol: "tcp",
|
||||
Range: 1,
|
||||
},
|
||||
{
|
||||
HostPort: 0,
|
||||
ContainerPort: 8080,
|
||||
Protocol: "udp",
|
||||
Range: 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
tt := tt
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := ParsePortMapping(tt.arg, tt.arg2)
|
||||
assert.NoError(t, err, "error is not nil")
|
||||
|
||||
// because we always get random host ports when it is set to 0 we cannot check that exactly
|
||||
// check if it is not 0 and set to to 0 afterwards
|
||||
for i := range got {
|
||||
assert.Greater(t, got[i].HostPort, uint16(0), "host port is zero")
|
||||
got[i].HostPort = 0
|
||||
}
|
||||
|
||||
// use ElementsMatch instead of Equal because the order is not consistent
|
||||
assert.ElementsMatch(t, tt.want, got, "got unexpected port mapping")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestParsePortMappingMixedHostPort(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
arg []types.PortMapping
|
||||
want []types.PortMapping
|
||||
resetHostPorts []int
|
||||
}{
|
||||
{
|
||||
name: "two ports one without a hostport set",
|
||||
arg: []types.PortMapping{
|
||||
{
|
||||
HostPort: 0,
|
||||
ContainerPort: 80,
|
||||
},
|
||||
{
|
||||
HostPort: 8080,
|
||||
ContainerPort: 8080,
|
||||
},
|
||||
},
|
||||
want: []types.PortMapping{
|
||||
{
|
||||
HostPort: 8080,
|
||||
ContainerPort: 8080,
|
||||
Protocol: "tcp",
|
||||
Range: 1,
|
||||
},
|
||||
{
|
||||
HostPort: 0,
|
||||
ContainerPort: 80,
|
||||
Protocol: "tcp",
|
||||
Range: 1,
|
||||
},
|
||||
},
|
||||
resetHostPorts: []int{1},
|
||||
},
|
||||
{
|
||||
name: "two ports one without a hostport set, inverted order",
|
||||
arg: []types.PortMapping{
|
||||
{
|
||||
HostPort: 8080,
|
||||
ContainerPort: 8080,
|
||||
},
|
||||
{
|
||||
HostPort: 0,
|
||||
ContainerPort: 80,
|
||||
},
|
||||
},
|
||||
want: []types.PortMapping{
|
||||
{
|
||||
HostPort: 8080,
|
||||
ContainerPort: 8080,
|
||||
Protocol: "tcp",
|
||||
Range: 1,
|
||||
},
|
||||
{
|
||||
HostPort: 0,
|
||||
ContainerPort: 80,
|
||||
Protocol: "tcp",
|
||||
Range: 1,
|
||||
},
|
||||
},
|
||||
resetHostPorts: []int{1},
|
||||
},
|
||||
{
|
||||
name: "three ports without host ports, one with a hostport set, , inverted order",
|
||||
arg: []types.PortMapping{
|
||||
{
|
||||
HostPort: 0,
|
||||
ContainerPort: 80,
|
||||
},
|
||||
{
|
||||
HostPort: 0,
|
||||
ContainerPort: 85,
|
||||
},
|
||||
{
|
||||
HostPort: 0,
|
||||
ContainerPort: 90,
|
||||
},
|
||||
{
|
||||
HostPort: 8080,
|
||||
ContainerPort: 8080,
|
||||
},
|
||||
},
|
||||
want: []types.PortMapping{
|
||||
{
|
||||
HostPort: 8080,
|
||||
ContainerPort: 8080,
|
||||
Protocol: "tcp",
|
||||
Range: 1,
|
||||
},
|
||||
{
|
||||
HostPort: 0,
|
||||
ContainerPort: 80,
|
||||
Protocol: "tcp",
|
||||
Range: 1,
|
||||
},
|
||||
{
|
||||
HostPort: 0,
|
||||
ContainerPort: 85,
|
||||
Protocol: "tcp",
|
||||
Range: 1,
|
||||
},
|
||||
{
|
||||
HostPort: 0,
|
||||
ContainerPort: 90,
|
||||
Protocol: "tcp",
|
||||
Range: 1,
|
||||
},
|
||||
},
|
||||
resetHostPorts: []int{1, 2, 3},
|
||||
},
|
||||
{
|
||||
name: "three ports without host ports, one with a hostport set",
|
||||
arg: []types.PortMapping{
|
||||
{
|
||||
HostPort: 8080,
|
||||
ContainerPort: 8080,
|
||||
},
|
||||
{
|
||||
HostPort: 0,
|
||||
ContainerPort: 90,
|
||||
},
|
||||
{
|
||||
HostPort: 0,
|
||||
ContainerPort: 85,
|
||||
},
|
||||
{
|
||||
HostPort: 0,
|
||||
ContainerPort: 80,
|
||||
},
|
||||
},
|
||||
want: []types.PortMapping{
|
||||
{
|
||||
HostPort: 8080,
|
||||
ContainerPort: 8080,
|
||||
Protocol: "tcp",
|
||||
Range: 1,
|
||||
},
|
||||
{
|
||||
HostPort: 0,
|
||||
ContainerPort: 80,
|
||||
Protocol: "tcp",
|
||||
Range: 1,
|
||||
},
|
||||
{
|
||||
HostPort: 0,
|
||||
ContainerPort: 85,
|
||||
Protocol: "tcp",
|
||||
Range: 1,
|
||||
},
|
||||
{
|
||||
HostPort: 0,
|
||||
ContainerPort: 90,
|
||||
Protocol: "tcp",
|
||||
Range: 1,
|
||||
},
|
||||
},
|
||||
resetHostPorts: []int{1, 2, 3},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
tt := tt
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := ParsePortMapping(tt.arg, nil)
|
||||
assert.NoError(t, err, "error is not nil")
|
||||
|
||||
// because we always get random host ports when it is set to 0 we cannot check that exactly
|
||||
// use resetHostPorts to know which port element is 0
|
||||
for _, num := range tt.resetHostPorts {
|
||||
assert.Greater(t, got[num].HostPort, uint16(0), "host port is zero")
|
||||
got[num].HostPort = 0
|
||||
}
|
||||
|
||||
assert.Equal(t, tt.want, got, "got unexpected port mapping")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestParsePortMappingError(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
arg []types.PortMapping
|
||||
err string
|
||||
}{
|
||||
{
|
||||
name: "container port is 0",
|
||||
arg: []types.PortMapping{
|
||||
{
|
||||
HostPort: 8080,
|
||||
ContainerPort: 0,
|
||||
Protocol: "tcp",
|
||||
},
|
||||
},
|
||||
err: "container port number must be non-0",
|
||||
},
|
||||
{
|
||||
name: "container port range exceeds max",
|
||||
arg: []types.PortMapping{
|
||||
{
|
||||
HostPort: 8080,
|
||||
ContainerPort: 65000,
|
||||
Protocol: "tcp",
|
||||
Range: 10000,
|
||||
},
|
||||
},
|
||||
err: "container port range exceeds maximum allowable port number",
|
||||
},
|
||||
{
|
||||
name: "host port range exceeds max",
|
||||
arg: []types.PortMapping{
|
||||
{
|
||||
HostPort: 60000,
|
||||
ContainerPort: 1,
|
||||
Protocol: "tcp",
|
||||
Range: 10000,
|
||||
},
|
||||
},
|
||||
err: "host port range exceeds maximum allowable port number",
|
||||
},
|
||||
{
|
||||
name: "invalid protocol",
|
||||
arg: []types.PortMapping{
|
||||
{
|
||||
HostPort: 8080,
|
||||
ContainerPort: 80,
|
||||
Protocol: "1",
|
||||
},
|
||||
},
|
||||
err: "unrecognized protocol \"1\" in port mapping",
|
||||
},
|
||||
{
|
||||
name: "invalid protocol 2",
|
||||
arg: []types.PortMapping{
|
||||
{
|
||||
HostPort: 8080,
|
||||
ContainerPort: 80,
|
||||
Protocol: "udp,u",
|
||||
},
|
||||
},
|
||||
err: "unrecognized protocol \"u\" in port mapping",
|
||||
},
|
||||
{
|
||||
name: "invalid ip address",
|
||||
arg: []types.PortMapping{
|
||||
{
|
||||
HostPort: 8080,
|
||||
ContainerPort: 80,
|
||||
HostIP: "blah",
|
||||
},
|
||||
},
|
||||
err: "invalid IP address \"blah\" in port mapping",
|
||||
},
|
||||
{
|
||||
name: "invalid overalpping range",
|
||||
arg: []types.PortMapping{
|
||||
{
|
||||
HostPort: 8080,
|
||||
ContainerPort: 80,
|
||||
Range: 5,
|
||||
},
|
||||
{
|
||||
HostPort: 8081,
|
||||
ContainerPort: 60,
|
||||
},
|
||||
},
|
||||
err: "conflicting port mappings for host port 8081 (protocol tcp)",
|
||||
},
|
||||
{
|
||||
name: "big port range with host port zero does not fit",
|
||||
arg: []types.PortMapping{
|
||||
{
|
||||
HostPort: 0,
|
||||
ContainerPort: 1,
|
||||
Range: 65535,
|
||||
},
|
||||
},
|
||||
err: "failed to find an open port to expose container port 1 with range 65535 on the host",
|
||||
},
|
||||
{
|
||||
name: "big port range with host port zero does not fit",
|
||||
arg: []types.PortMapping{
|
||||
{
|
||||
HostPort: 0,
|
||||
ContainerPort: 80,
|
||||
Range: 1,
|
||||
},
|
||||
{
|
||||
HostPort: 0,
|
||||
ContainerPort: 1000,
|
||||
Range: 64535,
|
||||
},
|
||||
},
|
||||
err: "failed to find an open port to expose container port 1000 with range 64535 on the host",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
tt := tt
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
_, err := ParsePortMapping(tt.arg, nil)
|
||||
assert.EqualError(t, err, tt.err, "error does not match")
|
||||
})
|
||||
}
|
||||
}
|
|
@ -494,6 +494,23 @@ var _ = Describe("Podman run networking", func() {
|
|||
Expect(containerConfig[0].NetworkSettings.Ports["80/tcp"][0].HostPort).ToNot(Equal(80))
|
||||
})
|
||||
|
||||
It("podman run forward sctp protocol", func() {
|
||||
SkipIfRootless("sctp protocol only works as root")
|
||||
session := podmanTest.Podman([]string{"--log-level=info", "run", "--name=test", "-p", "80/sctp", "-p", "81/sctp", ALPINE})
|
||||
session.Wait(90)
|
||||
Expect(session).Should(Exit(0))
|
||||
// we can only check logrus on local podman
|
||||
if !IsRemote() {
|
||||
// check that the info message for sctp protocol is only displayed once
|
||||
Expect(strings.Count(session.ErrorToString(), "Port reservation for SCTP is not supported")).To(Equal(1), "`Port reservation for SCTP is not supported` is not displayed exactly one time in the logrus logs")
|
||||
}
|
||||
results := podmanTest.Podman([]string{"inspect", "test"})
|
||||
results.Wait(30)
|
||||
Expect(results).Should(Exit(0))
|
||||
Expect(results.OutputToString()).To(ContainSubstring(`"80/sctp":`))
|
||||
Expect(results.OutputToString()).To(ContainSubstring(`"81/sctp":`))
|
||||
})
|
||||
|
||||
It("podman run hostname test", func() {
|
||||
session := podmanTest.Podman([]string{"run", "--rm", ALPINE, "printenv", "HOSTNAME"})
|
||||
session.WaitWithDefaultTimeout()
|
||||
|
|
|
@ -97,6 +97,7 @@ podman \$opts run --name myfailedcontainer --label mylabel=$LABEL_FAILED \
|
|||
podman \$opts run -d --name myrunningcontainer --label mylabel=$LABEL_RUNNING \
|
||||
--network bridge \
|
||||
-p $HOST_PORT:80 \
|
||||
-p 127.0.0.1:8080-8082:8080-8082 \
|
||||
-v $pmroot/var/www:/var/www \
|
||||
-w /var/www \
|
||||
--mac-address aa:bb:cc:dd:ee:ff \
|
||||
|
@ -186,7 +187,7 @@ EOF
|
|||
is "${lines[1]}" "mycreatedcontainer--Created----$LABEL_CREATED" "created"
|
||||
is "${lines[2]}" "mydonecontainer--Exited (0).*----<no value>" "done"
|
||||
is "${lines[3]}" "myfailedcontainer--Exited (17) .*----$LABEL_FAILED" "fail"
|
||||
is "${lines[4]}" "myrunningcontainer--Up .*--0.0.0.0:$HOST_PORT->80/tcp--$LABEL_RUNNING" "running"
|
||||
is "${lines[4]}" "myrunningcontainer--Up .*--0\.0\.0\.0:$HOST_PORT->80\/tcp, 127\.0\.0\.1\:8080-8082->8080-8082\/tcp--$LABEL_RUNNING" "running"
|
||||
|
||||
# For debugging: dump containers and IDs
|
||||
if [[ -n "$PODMAN_UPGRADE_TEST_DEBUG" ]]; then
|
||||
|
|
Loading…
Reference in New Issue