diff options
Diffstat (limited to 'libpod')
-rw-r--r-- | libpod/container.go | 12 | ||||
-rw-r--r-- | libpod/container_api.go | 109 | ||||
-rw-r--r-- | libpod/container_internal.go | 6 | ||||
-rw-r--r-- | libpod/container_internal_linux.go | 1 | ||||
-rw-r--r-- | libpod/define/errors.go | 4 | ||||
-rw-r--r-- | libpod/kube.go | 43 | ||||
-rw-r--r-- | libpod/network/config.go | 5 | ||||
-rw-r--r-- | libpod/network/create.go | 31 | ||||
-rw-r--r-- | libpod/network/netconflist.go | 22 | ||||
-rw-r--r-- | libpod/network/network.go | 12 | ||||
-rw-r--r-- | libpod/networking_linux.go | 16 | ||||
-rw-r--r-- | libpod/options.go | 29 | ||||
-rw-r--r-- | libpod/pod.go | 1 | ||||
-rw-r--r-- | libpod/runtime_pod_infra_linux.go | 14 |
14 files changed, 243 insertions, 62 deletions
diff --git a/libpod/container.go b/libpod/container.go index 58bf95470..ed7535bc8 100644 --- a/libpod/container.go +++ b/libpod/container.go @@ -1073,6 +1073,18 @@ func networkDisabled(c *Container) (bool, error) { return false, nil } +func (c *Container) HostNetwork() bool { + if c.config.CreateNetNS || c.config.NetNsCtr != "" { + return false + } + for _, ns := range c.config.Spec.Linux.Namespaces { + if ns.Type == spec.NetworkNamespace { + return false + } + } + return true +} + // ContainerState returns containerstate struct func (c *Container) ContainerState() (*ContainerState, error) { if !c.batched { diff --git a/libpod/container_api.go b/libpod/container_api.go index 951227a4f..2473acec0 100644 --- a/libpod/container_api.go +++ b/libpod/container_api.go @@ -5,6 +5,7 @@ import ( "io/ioutil" "net/http" "os" + "sync" "time" "github.com/containers/podman/v2/libpod/define" @@ -478,13 +479,13 @@ func (c *Container) RemoveArtifact(name string) error { } // Wait blocks until the container exits and returns its exit code. -func (c *Container) Wait() (int32, error) { - return c.WaitWithInterval(DefaultWaitInterval) +func (c *Container) Wait(ctx context.Context) (int32, error) { + return c.WaitWithInterval(ctx, DefaultWaitInterval) } // WaitWithInterval blocks until the container to exit and returns its exit // code. The argument is the interval at which checks the container's status. -func (c *Container) WaitWithInterval(waitTimeout time.Duration) (int32, error) { +func (c *Container) WaitWithInterval(ctx context.Context, waitTimeout time.Duration) (int32, error) { if !c.valid { return -1, define.ErrCtrRemoved } @@ -495,41 +496,111 @@ func (c *Container) WaitWithInterval(waitTimeout time.Duration) (int32, error) { } chWait := make(chan error, 1) - defer close(chWait) + go func() { + <-ctx.Done() + chWait <- define.ErrCanceled + }() for { - // ignore errors here, it is only used to avoid waiting + // ignore errors here (with exception of cancellation), it is only used to avoid waiting // too long. - _, _ = WaitForFile(exitFile, chWait, waitTimeout) + _, e := WaitForFile(exitFile, chWait, waitTimeout) + if e == define.ErrCanceled { + return -1, define.ErrCanceled + } - stopped, err := c.isStopped() + stopped, code, err := c.isStopped() if err != nil { return -1, err } if stopped { - return c.state.ExitCode, nil + return code, nil } } } -func (c *Container) WaitForConditionWithInterval(waitTimeout time.Duration, condition define.ContainerStatus) (int32, error) { +type waitResult struct { + code int32 + err error +} + +func (c *Container) WaitForConditionWithInterval(ctx context.Context, waitTimeout time.Duration, conditions ...define.ContainerStatus) (int32, error) { if !c.valid { return -1, define.ErrCtrRemoved } - if condition == define.ContainerStateStopped || condition == define.ContainerStateExited { - return c.WaitWithInterval(waitTimeout) + + if len(conditions) == 0 { + panic("at least one condition should be passed") } - for { - state, err := c.State() - if err != nil { - return -1, err + + ctx, cancelFn := context.WithCancel(ctx) + defer cancelFn() + + resultChan := make(chan waitResult) + waitForExit := false + wantedStates := make(map[define.ContainerStatus]bool, len(conditions)) + + for _, condition := range conditions { + if condition == define.ContainerStateStopped || condition == define.ContainerStateExited { + waitForExit = true + continue } - if state == condition { - break + wantedStates[condition] = true + } + + trySend := func(code int32, err error) { + select { + case resultChan <- waitResult{code, err}: + case <-ctx.Done(): } - time.Sleep(waitTimeout) } - return -1, nil + + var wg sync.WaitGroup + + if waitForExit { + wg.Add(1) + go func() { + defer wg.Done() + + code, err := c.WaitWithInterval(ctx, waitTimeout) + trySend(code, err) + }() + } + + if len(wantedStates) > 0 { + wg.Add(1) + go func() { + defer wg.Done() + + for { + state, err := c.State() + if err != nil { + trySend(-1, err) + return + } + if _, found := wantedStates[state]; found { + trySend(-1, nil) + return + } + select { + case <-ctx.Done(): + return + case <-time.After(waitTimeout): + continue + } + } + }() + } + + var result waitResult + select { + case result = <-resultChan: + cancelFn() + case <-ctx.Done(): + result = waitResult{-1, define.ErrCanceled} + } + wg.Wait() + return result.code, result.err } // Cleanup unmounts all mount points in container and cleans up container storage diff --git a/libpod/container_internal.go b/libpod/container_internal.go index b9ea50783..5a61f7fe6 100644 --- a/libpod/container_internal.go +++ b/libpod/container_internal.go @@ -754,17 +754,17 @@ func (c *Container) getArtifactPath(name string) string { } // Used with Wait() to determine if a container has exited -func (c *Container) isStopped() (bool, error) { +func (c *Container) isStopped() (bool, int32, error) { if !c.batched { c.lock.Lock() defer c.lock.Unlock() } err := c.syncContainer() if err != nil { - return true, err + return true, -1, err } - return !c.ensureState(define.ContainerStateRunning, define.ContainerStatePaused, define.ContainerStateStopping), nil + return !c.ensureState(define.ContainerStateRunning, define.ContainerStatePaused, define.ContainerStateStopping), c.state.ExitCode, nil } // save container state to the database diff --git a/libpod/container_internal_linux.go b/libpod/container_internal_linux.go index 6c9489a08..ba85a1f47 100644 --- a/libpod/container_internal_linux.go +++ b/libpod/container_internal_linux.go @@ -213,6 +213,7 @@ func (c *Container) resolveWorkDir() error { // we need to return the full error. return errors.Wrapf(err, "error detecting workdir %q on container %s", workdir, c.ID()) } + return nil } // Ensure container entrypoint is created (if required). diff --git a/libpod/define/errors.go b/libpod/define/errors.go index d37bc397e..2e85454b2 100644 --- a/libpod/define/errors.go +++ b/libpod/define/errors.go @@ -198,4 +198,8 @@ var ( // ErrSecurityAttribute indicates that an error processing security attributes // for the container ErrSecurityAttribute = fmt.Errorf("%w: unable to process security attribute", ErrOCIRuntime) + + // ErrCanceled indicates that an operation has been cancelled by a user. + // Useful for potentially long running tasks. + ErrCanceled = errors.New("cancelled by user") ) diff --git a/libpod/kube.go b/libpod/kube.go index b5197293e..f9ead027d 100644 --- a/libpod/kube.go +++ b/libpod/kube.go @@ -49,6 +49,7 @@ func (p *Pod) GenerateForKube() (*v1.Pod, []v1.ServicePort, error) { } extraHost := make([]v1.HostAlias, 0) + hostNetwork := false if p.HasInfraContainer() { infraContainer, err := p.getInfraContainer() if err != nil { @@ -69,9 +70,9 @@ func (p *Pod) GenerateForKube() (*v1.Pod, []v1.ServicePort, error) { return nil, servicePorts, err } servicePorts = containerPortsToServicePorts(ports) - + hostNetwork = p.config.InfraContainer.HostNetwork } - pod, err := p.podWithContainers(allContainers, ports) + pod, err := p.podWithContainers(allContainers, ports, hostNetwork) if err != nil { return nil, servicePorts, err } @@ -167,7 +168,7 @@ func containersToServicePorts(containers []v1.Container) []v1.ServicePort { return sps } -func (p *Pod) podWithContainers(containers []*Container, ports []v1.ContainerPort) (*v1.Pod, error) { +func (p *Pod) podWithContainers(containers []*Container, ports []v1.ContainerPort, hostNetwork bool) (*v1.Pod, error) { deDupPodVolumes := make(map[string]*v1.Volume) first := true podContainers := make([]v1.Container, 0, len(containers)) @@ -220,10 +221,10 @@ func (p *Pod) podWithContainers(containers []*Container, ports []v1.ContainerPor podVolumes = append(podVolumes, *vol) } - return addContainersAndVolumesToPodObject(podContainers, podVolumes, p.Name(), &dnsInfo), nil + return addContainersAndVolumesToPodObject(podContainers, podVolumes, p.Name(), &dnsInfo, hostNetwork), nil } -func addContainersAndVolumesToPodObject(containers []v1.Container, volumes []v1.Volume, podName string, dnsOptions *v1.PodDNSConfig) *v1.Pod { +func addContainersAndVolumesToPodObject(containers []v1.Container, volumes []v1.Volume, podName string, dnsOptions *v1.PodDNSConfig, hostNetwork bool) *v1.Pod { tm := v12.TypeMeta{ Kind: "Pod", APIVersion: "v1", @@ -242,8 +243,9 @@ func addContainersAndVolumesToPodObject(containers []v1.Container, volumes []v1. CreationTimestamp: v12.Now(), } ps := v1.PodSpec{ - Containers: containers, - Volumes: volumes, + Containers: containers, + Volumes: volumes, + HostNetwork: hostNetwork, } if dnsOptions != nil { ps.DNSConfig = dnsOptions @@ -261,8 +263,12 @@ func addContainersAndVolumesToPodObject(containers []v1.Container, volumes []v1. func simplePodWithV1Containers(ctrs []*Container) (*v1.Pod, error) { kubeCtrs := make([]v1.Container, 0, len(ctrs)) kubeVolumes := make([]v1.Volume, 0) + hostNetwork := true podDNS := v1.PodDNSConfig{} for _, ctr := range ctrs { + if !ctr.HostNetwork() { + hostNetwork = false + } kubeCtr, kubeVols, ctrDNS, err := containerToV1Container(ctr) if err != nil { return nil, err @@ -303,7 +309,7 @@ func simplePodWithV1Containers(ctrs []*Container) (*v1.Pod, error) { } } // end if ctrDNS } - return addContainersAndVolumesToPodObject(kubeCtrs, kubeVolumes, strings.ReplaceAll(ctrs[0].Name(), "_", ""), &podDNS), nil + return addContainersAndVolumesToPodObject(kubeCtrs, kubeVolumes, strings.ReplaceAll(ctrs[0].Name(), "_", ""), &podDNS, hostNetwork), nil } // containerToV1Container converts information we know about a libpod container @@ -347,22 +353,21 @@ func containerToV1Container(c *Container) (v1.Container, []v1.Volume, *v1.PodDNS return kubeContainer, kubeVolumes, nil, err } - containerCommands := c.Command() - kubeContainer.Name = removeUnderscores(c.Name()) + // Handle command and arguments. + if ep := c.Entrypoint(); len(ep) > 0 { + // If we have an entrypoint, set the container's command as + // arguments. + kubeContainer.Command = ep + kubeContainer.Args = c.Command() + } else { + kubeContainer.Command = c.Command() + } + kubeContainer.Name = removeUnderscores(c.Name()) _, image := c.Image() kubeContainer.Image = image kubeContainer.Stdin = c.Stdin() - // prepend the entrypoint of the container to command - if ep := c.Entrypoint(); len(c.Entrypoint()) > 0 { - ep = append(ep, containerCommands...) - containerCommands = ep - } - kubeContainer.Command = containerCommands - // TODO need to figure out how we handle command vs entry point. Kube appears to prefer entrypoint. - // right now we just take the container's command - //container.Args = args kubeContainer.WorkingDir = c.WorkingDir() kubeContainer.Ports = ports // This should not be applicable diff --git a/libpod/network/config.go b/libpod/network/config.go index ce351129e..294e23509 100644 --- a/libpod/network/config.go +++ b/libpod/network/config.go @@ -103,7 +103,9 @@ func (p PortMapConfig) Bytes() ([]byte, error) { // IPAMDHCP describes the ipamdhcp config type IPAMDHCP struct { - DHCP string `json:"type"` + DHCP string `json:"type"` + Routes []IPAMRoute `json:"routes,omitempty"` + Ranges [][]IPAMLocalHostRangeConf `json:"ranges,omitempty"` } // MacVLANConfig describes the macvlan config @@ -111,6 +113,7 @@ type MacVLANConfig struct { PluginType string `json:"type"` Master string `json:"master"` IPAM IPAMDHCP `json:"ipam"` + MTU int `json:"mtu,omitempty"` } // Bytes outputs the configuration as []byte diff --git a/libpod/network/create.go b/libpod/network/create.go index a8f985af9..deacf487a 100644 --- a/libpod/network/create.go +++ b/libpod/network/create.go @@ -29,7 +29,7 @@ func Create(name string, options entities.NetworkCreateOptions, runtimeConfig *c return nil, err } defer l.releaseCNILock() - if len(options.MacVLAN) > 0 { + if len(options.MacVLAN) > 0 || options.Driver == MacVLANNetworkDriver { fileName, err = createMacVLAN(name, options, runtimeConfig) } else { fileName, err = createBridge(name, options, runtimeConfig) @@ -249,6 +249,7 @@ func createBridge(name string, options entities.NetworkCreateOptions, runtimeCon func createMacVLAN(name string, options entities.NetworkCreateOptions, runtimeConfig *config.Config) (string, error) { var ( + mtu int plugins []CNIPlugins ) liveNetNames, err := GetLiveNetworkNames() @@ -256,9 +257,17 @@ func createMacVLAN(name string, options entities.NetworkCreateOptions, runtimeCo return "", err } - // Make sure the host-device exists - if !util.StringInSlice(options.MacVLAN, liveNetNames) { - return "", errors.Errorf("failed to find network interface %q", options.MacVLAN) + // The parent can be defined with --macvlan or as an option (-o parent:device) + parentNetworkDevice := options.MacVLAN + if len(parentNetworkDevice) < 1 { + if parent, ok := options.Options["parent"]; ok { + parentNetworkDevice = parent + } + } + + // Make sure the host-device exists if provided + if len(parentNetworkDevice) > 0 && !util.StringInSlice(parentNetworkDevice, liveNetNames) { + return "", errors.Errorf("failed to find network interface %q", parentNetworkDevice) } if len(name) > 0 { netNames, err := GetNetworkNamesFromFileSystem(runtimeConfig) @@ -275,7 +284,19 @@ func createMacVLAN(name string, options entities.NetworkCreateOptions, runtimeCo } } ncList := NewNcList(name, version.Current(), options.Labels) - macvlan := NewMacVLANPlugin(options.MacVLAN) + if val, ok := options.Options["mtu"]; ok { + intVal, err := strconv.Atoi(val) + if err != nil { + return "", err + } + if intVal > 0 { + mtu = intVal + } + } + macvlan, err := NewMacVLANPlugin(parentNetworkDevice, options.Gateway, &options.Range, &options.Subnet, mtu) + if err != nil { + return "", err + } plugins = append(plugins, macvlan) ncList["plugins"] = plugins b, err := json.MarshalIndent(ncList, "", " ") diff --git a/libpod/network/netconflist.go b/libpod/network/netconflist.go index 165a9067b..9be98e78f 100644 --- a/libpod/network/netconflist.go +++ b/libpod/network/netconflist.go @@ -172,15 +172,31 @@ func HasDNSNamePlugin(paths []string) bool { } // NewMacVLANPlugin creates a macvlanconfig with a given device name -func NewMacVLANPlugin(device string) MacVLANConfig { +func NewMacVLANPlugin(device string, gateway net.IP, ipRange *net.IPNet, subnet *net.IPNet, mtu int) (MacVLANConfig, error) { i := IPAMDHCP{DHCP: "dhcp"} + if gateway != nil || ipRange != nil || subnet != nil { + ipam, err := NewIPAMLocalHostRange(subnet, ipRange, gateway) + if err != nil { + return MacVLANConfig{}, err + } + ranges := make([][]IPAMLocalHostRangeConf, 0) + ranges = append(ranges, ipam) + i.Ranges = ranges + } m := MacVLANConfig{ PluginType: "macvlan", - Master: device, IPAM: i, } - return m + if mtu > 0 { + m.MTU = mtu + } + // CNI is supposed to use the default route if a + // parent device is not provided + if len(device) > 0 { + m.Master = device + } + return m, nil } // IfPassesFilter filters NetworkListReport and returns true if the filter match the given config diff --git a/libpod/network/network.go b/libpod/network/network.go index 0fb878b18..0ff14c1f7 100644 --- a/libpod/network/network.go +++ b/libpod/network/network.go @@ -17,11 +17,17 @@ import ( "github.com/sirupsen/logrus" ) -// DefaultNetworkDriver is the default network type used -var DefaultNetworkDriver = "bridge" +var ( + // BridgeNetworkDriver defines the bridge cni driver + BridgeNetworkDriver = "bridge" + // DefaultNetworkDriver is the default network type used + DefaultNetworkDriver = BridgeNetworkDriver + // MacVLANNetworkDriver defines the macvlan cni driver + MacVLANNetworkDriver = "macvlan" +) // SupportedNetworkDrivers describes the list of supported drivers -var SupportedNetworkDrivers = []string{DefaultNetworkDriver} +var SupportedNetworkDrivers = []string{BridgeNetworkDriver, MacVLANNetworkDriver} // isSupportedDriver checks if the user provided driver is supported func isSupportedDriver(driver string) error { diff --git a/libpod/networking_linux.go b/libpod/networking_linux.go index 737dbf935..55d338e7d 100644 --- a/libpod/networking_linux.go +++ b/libpod/networking_linux.go @@ -15,6 +15,7 @@ import ( "path/filepath" "regexp" "sort" + "strconv" "strings" "syscall" "time" @@ -42,6 +43,9 @@ const ( // slirp4netnsDNS is the IP for the built-in DNS server in the slirp network slirp4netnsDNS = "10.0.2.3" + + // slirp4netnsMTU the default MTU override + slirp4netnsMTU = 65520 ) // Get an OCICNI network config @@ -282,6 +286,7 @@ func (r *Runtime) setupSlirp4netns(ctr *Container) error { enableIPv6 := false outboundAddr := "" outboundAddr6 := "" + mtu := slirp4netnsMTU if ctr.config.NetworkOptions != nil { slirpOptions = append(slirpOptions, ctr.config.NetworkOptions["slirp4netns"]...) @@ -345,6 +350,11 @@ func (r *Runtime) setupSlirp4netns(ctr *Container) error { } } outboundAddr6 = value + case "mtu": + mtu, err = strconv.Atoi(value) + if mtu < 68 || err != nil { + return errors.Errorf("invalid mtu %q", value) + } default: return errors.Errorf("unknown option for slirp4netns: %q", o) } @@ -358,8 +368,8 @@ func (r *Runtime) setupSlirp4netns(ctr *Container) error { if disableHostLoopback && slirpFeatures.HasDisableHostLoopback { cmdArgs = append(cmdArgs, "--disable-host-loopback") } - if slirpFeatures.HasMTU { - cmdArgs = append(cmdArgs, "--mtu", "65520") + if mtu > -1 && slirpFeatures.HasMTU { + cmdArgs = append(cmdArgs, fmt.Sprintf("--mtu=%d", mtu)) } if !noPivotRoot && slirpFeatures.HasEnableSandbox { cmdArgs = append(cmdArgs, "--enable-sandbox") @@ -1170,7 +1180,7 @@ func (c *Container) NetworkDisconnect(nameOrID, netName string, force bool) erro // update network status if container is not running networkStatus := c.state.NetworkStatus // clip out the index of the network - tmpNetworkStatus := make([]*cnitypes.Result, len(networkStatus)-1) + tmpNetworkStatus := make([]*cnitypes.Result, 0, len(networkStatus)-1) for k, v := range networkStatus { if index != k { tmpNetworkStatus = append(tmpNetworkStatus, v) diff --git a/libpod/options.go b/libpod/options.go index c7bac7e1f..20f62ee37 100644 --- a/libpod/options.go +++ b/libpod/options.go @@ -2190,13 +2190,37 @@ func WithPodNetworks(networks []string) PodCreateOption { } } +// WithPodNoNetwork tells the pod to disable external networking. +func WithPodNoNetwork() PodCreateOption { + return func(pod *Pod) error { + if pod.valid { + return define.ErrPodFinalized + } + + if !pod.config.InfraContainer.HasInfraContainer { + return errors.Wrapf(define.ErrInvalidArg, "cannot disable pod networking as no infra container is being created") + } + + if len(pod.config.InfraContainer.PortBindings) > 0 || + pod.config.InfraContainer.StaticIP != nil || + pod.config.InfraContainer.StaticMAC != nil || + len(pod.config.InfraContainer.Networks) > 0 || + pod.config.InfraContainer.HostNetwork { + return errors.Wrapf(define.ErrInvalidArg, "cannot disable pod network if network-related configuration is specified") + } + + pod.config.InfraContainer.NoNetwork = true + + return nil + } +} + // WithPodHostNetwork tells the pod to use the host's network namespace. func WithPodHostNetwork() PodCreateOption { return func(pod *Pod) error { if pod.valid { return define.ErrPodFinalized } - if !pod.config.InfraContainer.HasInfraContainer { return errors.Wrapf(define.ErrInvalidArg, "cannot configure pod host networking as no infra container is being created") } @@ -2204,7 +2228,8 @@ func WithPodHostNetwork() PodCreateOption { if len(pod.config.InfraContainer.PortBindings) > 0 || pod.config.InfraContainer.StaticIP != nil || pod.config.InfraContainer.StaticMAC != nil || - len(pod.config.InfraContainer.Networks) > 0 { + len(pod.config.InfraContainer.Networks) > 0 || + pod.config.InfraContainer.NoNetwork { return errors.Wrapf(define.ErrInvalidArg, "cannot set host network if network-related configuration is specified") } diff --git a/libpod/pod.go b/libpod/pod.go index c8f62ca18..784c2cf5e 100644 --- a/libpod/pod.go +++ b/libpod/pod.go @@ -93,6 +93,7 @@ type podState struct { type InfraContainerConfig struct { ConmonPidFile string `json:"conmonPidFile"` HasInfraContainer bool `json:"makeInfraContainer"` + NoNetwork bool `json:"noNetwork,omitempty"` HostNetwork bool `json:"infraHostNetwork,omitempty"` PortBindings []ocicni.PortMapping `json:"infraPortBindings"` StaticIP net.IP `json:"staticIP,omitempty"` diff --git a/libpod/runtime_pod_infra_linux.go b/libpod/runtime_pod_infra_linux.go index dd957527d..564851f4e 100644 --- a/libpod/runtime_pod_infra_linux.go +++ b/libpod/runtime_pod_infra_linux.go @@ -94,8 +94,16 @@ func (r *Runtime) makeInfraContainer(ctx context.Context, p *Pod, imgName, rawIm } } - // Since user namespace sharing is not implemented, we only need to check if it's rootless - if !p.config.InfraContainer.HostNetwork { + switch { + case p.config.InfraContainer.HostNetwork: + if err := g.RemoveLinuxNamespace(string(spec.NetworkNamespace)); err != nil { + return nil, errors.Wrapf(err, "error removing network namespace from pod %s infra container", p.ID()) + } + case p.config.InfraContainer.NoNetwork: + // Do nothing - we have a network namespace by default, + // but should not configure slirp. + default: + // Since user namespace sharing is not implemented, we only need to check if it's rootless netmode := "bridge" if isRootless || p.config.InfraContainer.Slirp4netns { netmode = "slirp4netns" @@ -106,8 +114,6 @@ func (r *Runtime) makeInfraContainer(ctx context.Context, p *Pod, imgName, rawIm // PostConfigureNetNS should not be set since user namespace sharing is not implemented // and rootless networking no longer supports post configuration setup options = append(options, WithNetNS(p.config.InfraContainer.PortBindings, false, netmode, p.config.InfraContainer.Networks)) - } else if err := g.RemoveLinuxNamespace(string(spec.NetworkNamespace)); err != nil { - return nil, errors.Wrapf(err, "error removing network namespace from pod %s infra container", p.ID()) } // For each option in InfraContainerConfig - if set, pass into |