summaryrefslogtreecommitdiff
path: root/libpod
diff options
context:
space:
mode:
Diffstat (limited to 'libpod')
-rw-r--r--libpod/container.go19
-rw-r--r--libpod/container_config.go5
-rw-r--r--libpod/container_inspect.go3
-rw-r--r--libpod/container_internal.go24
-rw-r--r--libpod/container_internal_linux.go12
-rw-r--r--libpod/define/container_inspect.go3
-rw-r--r--libpod/image/docker_registry_options.go1
-rw-r--r--libpod/kube.go40
-rw-r--r--libpod/network/config.go141
-rw-r--r--libpod/network/create.go195
-rw-r--r--libpod/network/devices.go63
-rw-r--r--libpod/network/files.go174
-rw-r--r--libpod/network/ip.go19
-rw-r--r--libpod/network/lock.go26
-rw-r--r--libpod/network/netconflist.go155
-rw-r--r--libpod/network/netconflist_test.go38
-rw-r--r--libpod/network/network.go225
-rw-r--r--libpod/network/network_test.go35
-rw-r--r--libpod/network/subnet.go78
-rw-r--r--libpod/network/subnet_test.go35
-rw-r--r--libpod/oci_conmon_linux.go12
-rw-r--r--libpod/pod_api.go241
-rw-r--r--libpod/runtime_ctr.go1
23 files changed, 1383 insertions, 162 deletions
diff --git a/libpod/container.go b/libpod/container.go
index 9b4ccbd5f..01419500e 100644
--- a/libpod/container.go
+++ b/libpod/container.go
@@ -888,9 +888,22 @@ func (c *Container) NamespacePath(linuxNS LinuxNS) (string, error) { //nolint:in
return fmt.Sprintf("/proc/%d/ns/%s", c.state.PID, linuxNS.String()), nil
}
+// CgroupManager returns the cgroup manager used by the given container.
+func (c *Container) CgroupManager() string {
+ cgroupManager := c.config.CgroupManager
+ if cgroupManager == "" {
+ cgroupManager = c.runtime.config.Engine.CgroupManager
+ }
+ return cgroupManager
+}
+
// CGroupPath returns a cgroups "path" for a given container.
func (c *Container) CGroupPath() (string, error) {
+ cgroupManager := c.CgroupManager()
+
switch {
+ case c.config.NoCgroups || c.config.CgroupsMode == "disabled":
+ return "", errors.Wrapf(define.ErrNoCgroups, "this container is not creating cgroups")
case c.config.CgroupsMode == cgroupSplit:
if c.config.CgroupParent != "" {
return "", errors.Errorf("cannot specify cgroup-parent with cgroup-mode %q", cgroupSplit)
@@ -906,9 +919,9 @@ func (c *Container) CGroupPath() (string, error) {
return "", errors.Errorf("invalid cgroup for conmon %q", cg)
}
return strings.TrimSuffix(cg, "/supervisor") + "/container", nil
- case c.runtime.config.Engine.CgroupManager == config.CgroupfsCgroupsManager:
+ case cgroupManager == config.CgroupfsCgroupsManager:
return filepath.Join(c.config.CgroupParent, fmt.Sprintf("libpod-%s", c.ID())), nil
- case c.runtime.config.Engine.CgroupManager == config.SystemdCgroupsManager:
+ case cgroupManager == config.SystemdCgroupsManager:
if rootless.IsRootless() {
uid := rootless.GetRootlessUID()
parts := strings.SplitN(c.config.CgroupParent, "/", 2)
@@ -922,7 +935,7 @@ func (c *Container) CGroupPath() (string, error) {
}
return filepath.Join(c.config.CgroupParent, createUnitName("libpod", c.ID())), nil
default:
- return "", errors.Wrapf(define.ErrInvalidArg, "unsupported CGroup manager %s in use", c.runtime.config.Engine.CgroupManager)
+ return "", errors.Wrapf(define.ErrInvalidArg, "unsupported CGroup manager %s in use", cgroupManager)
}
}
diff --git a/libpod/container_config.go b/libpod/container_config.go
index fc93140dd..e264da4da 100644
--- a/libpod/container_config.go
+++ b/libpod/container_config.go
@@ -275,13 +275,16 @@ type ContainerMiscConfig struct {
StopTimeout uint `json:"stopTimeout,omitempty"`
// Time container was created
CreatedTime time.Time `json:"createdTime"`
+ // CgroupManager is the cgroup manager used to create this container.
+ // If empty, the runtime default will be used.
+ CgroupManager string `json:"cgroupManager,omitempty"`
// NoCgroups indicates that the container will not create CGroups. It is
// incompatible with CgroupParent. Deprecated in favor of CgroupsMode.
NoCgroups bool `json:"noCgroups,omitempty"`
// CgroupsMode indicates how the container will create cgroups
// (disabled, no-conmon, enabled). It supersedes NoCgroups.
CgroupsMode string `json:"cgroupsMode,omitempty"`
- // Cgroup parent of the container
+ // Cgroup parent of the container.
CgroupParent string `json:"cgroupParent"`
// LogPath log location
LogPath string `json:"logPath"`
diff --git a/libpod/container_inspect.go b/libpod/container_inspect.go
index 835dccd71..b8bce1272 100644
--- a/libpod/container_inspect.go
+++ b/libpod/container_inspect.go
@@ -729,7 +729,7 @@ func (c *Container) generateInspectContainerHostConfig(ctrSpec *spec.Spec, named
// CGroup parent
// Need to check if it's the default, and not print if so.
defaultCgroupParent := ""
- switch c.runtime.config.Engine.CgroupManager {
+ switch c.CgroupManager() {
case config.CgroupfsCgroupsManager:
defaultCgroupParent = CgroupfsDefaultCgroupParent
case config.SystemdCgroupsManager:
@@ -738,6 +738,7 @@ func (c *Container) generateInspectContainerHostConfig(ctrSpec *spec.Spec, named
if c.config.CgroupParent != defaultCgroupParent {
hostConfig.CgroupParent = c.config.CgroupParent
}
+ hostConfig.CgroupManager = c.CgroupManager()
// PID namespace mode
pidMode := ""
diff --git a/libpod/container_internal.go b/libpod/container_internal.go
index d64d3ab87..4ae571de6 100644
--- a/libpod/container_internal.go
+++ b/libpod/container_internal.go
@@ -976,6 +976,21 @@ func (c *Container) completeNetworkSetup() error {
}
}
}
+ // check if we have a bindmount for /etc/hosts
+ if hostsBindMount, ok := state.BindMounts["/etc/hosts"]; ok && len(c.cniHosts()) > 0 {
+ ctrHostPath := filepath.Join(c.state.RunDir, "hosts")
+ if hostsBindMount == ctrHostPath {
+ // read the existing hosts
+ b, err := ioutil.ReadFile(hostsBindMount)
+ if err != nil {
+ return err
+ }
+ if err := ioutil.WriteFile(hostsBindMount, append(b, []byte(c.cniHosts())...), 0644); err != nil {
+ return err
+ }
+ }
+ }
+
// check if we have a bindmount for resolv.conf
resolvBindMount := state.BindMounts["/etc/resolv.conf"]
if len(outResolvConf) < 1 || resolvBindMount == "" || len(c.config.NetNsCtr) > 0 {
@@ -997,6 +1012,15 @@ func (c *Container) completeNetworkSetup() error {
return ioutil.WriteFile(resolvBindMount, []byte(strings.Join(outResolvConf, "\n")), 0644)
}
+func (c *Container) cniHosts() string {
+ var hosts string
+ if len(c.state.NetworkStatus) > 0 && len(c.state.NetworkStatus[0].IPs) > 0 {
+ ipAddress := strings.Split(c.state.NetworkStatus[0].IPs[0].Address.String(), "/")[0]
+ hosts += fmt.Sprintf("%s\t%s %s\n", ipAddress, c.Hostname(), c.Config().Name)
+ }
+ return hosts
+}
+
// Initialize a container, creating it in the runtime
func (c *Container) init(ctx context.Context, retainRetries bool) error {
span, _ := opentracing.StartSpanFromContext(ctx, "init")
diff --git a/libpod/container_internal_linux.go b/libpod/container_internal_linux.go
index 894982973..3a71c6601 100644
--- a/libpod/container_internal_linux.go
+++ b/libpod/container_internal_linux.go
@@ -1543,10 +1543,7 @@ func (c *Container) getHosts() string {
// When using slirp4netns, the interface gets a static IP
hosts += fmt.Sprintf("# used by slirp4netns\n%s\t%s %s\n", "10.0.2.100", c.Hostname(), c.Config().Name)
}
- if len(c.state.NetworkStatus) > 0 && len(c.state.NetworkStatus[0].IPs) > 0 {
- ipAddress := strings.Split(c.state.NetworkStatus[0].IPs[0].Address.String(), "/")[0]
- hosts += fmt.Sprintf("%s\t%s %s\n", ipAddress, c.Hostname(), c.Config().Name)
- }
+ hosts += c.cniHosts()
return hosts
}
@@ -1968,6 +1965,7 @@ func (c *Container) getOCICgroupPath() (string, error) {
if err != nil {
return "", err
}
+ cgroupManager := c.CgroupManager()
switch {
case (rootless.IsRootless() && !unified) || c.config.NoCgroups:
return "", nil
@@ -1980,14 +1978,14 @@ func (c *Container) getOCICgroupPath() (string, error) {
return "", err
}
return filepath.Join(selfCgroup, "container"), nil
- case c.runtime.config.Engine.CgroupManager == config.SystemdCgroupsManager:
+ case cgroupManager == config.SystemdCgroupsManager:
// When the OCI runtime is set to use Systemd as a cgroup manager, it
// expects cgroups to be passed as follows:
// slice:prefix:name
systemdCgroups := fmt.Sprintf("%s:libpod:%s", path.Base(c.config.CgroupParent), c.ID())
logrus.Debugf("Setting CGroups for container %s to %s", c.ID(), systemdCgroups)
return systemdCgroups, nil
- case c.runtime.config.Engine.CgroupManager == config.CgroupfsCgroupsManager:
+ case cgroupManager == config.CgroupfsCgroupsManager:
cgroupPath, err := c.CGroupPath()
if err != nil {
return "", err
@@ -1995,7 +1993,7 @@ func (c *Container) getOCICgroupPath() (string, error) {
logrus.Debugf("Setting CGroup path for container %s to %s", c.ID(), cgroupPath)
return cgroupPath, nil
default:
- return "", errors.Wrapf(define.ErrInvalidArg, "invalid cgroup manager %s requested", c.runtime.config.Engine.CgroupManager)
+ return "", errors.Wrapf(define.ErrInvalidArg, "invalid cgroup manager %s requested", cgroupManager)
}
}
diff --git a/libpod/define/container_inspect.go b/libpod/define/container_inspect.go
index 44c3d515b..38b3a6686 100644
--- a/libpod/define/container_inspect.go
+++ b/libpod/define/container_inspect.go
@@ -236,6 +236,9 @@ type InspectContainerHostConfig struct {
// include a Mounts field in inspect.
// Format: <src>:<destination>[:<comma-separated options>]
Binds []string `json:"Binds"`
+ // CgroupManager is the cgroup manager used by the container.
+ // At present, allowed values are either "cgroupfs" or "systemd".
+ CgroupManager string `json:"CgroupManager,omitempty"`
// CgroupMode is the configuration of the container's cgroup namespace.
// Populated as follows:
// private - a cgroup namespace has been created
diff --git a/libpod/image/docker_registry_options.go b/libpod/image/docker_registry_options.go
index 257b7ae8d..835473a1f 100644
--- a/libpod/image/docker_registry_options.go
+++ b/libpod/image/docker_registry_options.go
@@ -55,6 +55,7 @@ func (o DockerRegistryOptions) GetSystemContext(parent *types.SystemContext, add
sc.DockerRegistryUserAgent = parent.DockerRegistryUserAgent
sc.OSChoice = parent.OSChoice
sc.ArchitectureChoice = parent.ArchitectureChoice
+ sc.BlobInfoCacheDir = parent.BlobInfoCacheDir
}
return sc
}
diff --git a/libpod/kube.go b/libpod/kube.go
index 6df79e394..cd5064c84 100644
--- a/libpod/kube.go
+++ b/libpod/kube.go
@@ -307,18 +307,40 @@ func containerToV1Container(c *Container) (v1.Container, []v1.Volume, error) {
kubeContainer.StdinOnce = false
kubeContainer.TTY = c.config.Spec.Process.Terminal
- // TODO add CPU limit support.
if c.config.Spec.Linux != nil &&
- c.config.Spec.Linux.Resources != nil &&
- c.config.Spec.Linux.Resources.Memory != nil &&
- c.config.Spec.Linux.Resources.Memory.Limit != nil {
- if kubeContainer.Resources.Limits == nil {
- kubeContainer.Resources.Limits = v1.ResourceList{}
+ c.config.Spec.Linux.Resources != nil {
+ if c.config.Spec.Linux.Resources.Memory != nil &&
+ c.config.Spec.Linux.Resources.Memory.Limit != nil {
+ if kubeContainer.Resources.Limits == nil {
+ kubeContainer.Resources.Limits = v1.ResourceList{}
+ }
+
+ qty := kubeContainer.Resources.Limits.Memory()
+ qty.Set(*c.config.Spec.Linux.Resources.Memory.Limit)
+ kubeContainer.Resources.Limits[v1.ResourceMemory] = *qty
}
- qty := kubeContainer.Resources.Limits.Memory()
- qty.Set(*c.config.Spec.Linux.Resources.Memory.Limit)
- kubeContainer.Resources.Limits[v1.ResourceMemory] = *qty
+ if c.config.Spec.Linux.Resources.CPU != nil &&
+ c.config.Spec.Linux.Resources.CPU.Quota != nil &&
+ c.config.Spec.Linux.Resources.CPU.Period != nil {
+ quota := *c.config.Spec.Linux.Resources.CPU.Quota
+ period := *c.config.Spec.Linux.Resources.CPU.Period
+
+ if quota > 0 && period > 0 {
+ cpuLimitMilli := int64(1000 * float64(quota) / float64(period))
+
+ // Kubernetes: precision finer than 1m is not allowed
+ if cpuLimitMilli >= 1 {
+ if kubeContainer.Resources.Limits == nil {
+ kubeContainer.Resources.Limits = v1.ResourceList{}
+ }
+
+ qty := kubeContainer.Resources.Limits.Cpu()
+ qty.SetMilli(cpuLimitMilli)
+ kubeContainer.Resources.Limits[v1.ResourceCPU] = *qty
+ }
+ }
+ }
}
return kubeContainer, kubeVolumes, nil
diff --git a/libpod/network/config.go b/libpod/network/config.go
new file mode 100644
index 000000000..a08e684d8
--- /dev/null
+++ b/libpod/network/config.go
@@ -0,0 +1,141 @@
+package network
+
+import (
+ "encoding/json"
+ "net"
+
+ "github.com/containers/storage/pkg/lockfile"
+)
+
+// TODO once the containers.conf file stuff is worked out, this should be modified
+// to honor defines in the containers.conf as well as overrides?
+
+const (
+ // CNIConfigDir is the path where CNI config files exist
+ CNIConfigDir = "/etc/cni/net.d"
+ // CNIDeviceName is the default network device name and in
+ // reality should have an int appended to it (cni-podman4)
+ CNIDeviceName = "cni-podman"
+ // DefaultPodmanDomainName is used for the dnsname plugin to define
+ // a localized domain name for a created network
+ DefaultPodmanDomainName = "dns.podman"
+ // LockFileName is used for obtaining a lock and is appended
+ // to libpod's tmpdir in practice
+ LockFileName = "cni.lock"
+)
+
+// CNILock is for preventing name collision and
+// unpredictable results when doing some CNI operations.
+type CNILock struct {
+ lockfile.Locker
+}
+
+// GetDefaultPodmanNetwork outputs the default network for podman
+func GetDefaultPodmanNetwork() (*net.IPNet, error) {
+ _, n, err := net.ParseCIDR("10.88.1.0/24")
+ return n, err
+}
+
+// CNIPlugins is a way of marshalling a CNI network configuration to disk
+type CNIPlugins interface {
+ Bytes() ([]byte, error)
+}
+
+// HostLocalBridge describes a configuration for a bridge plugin
+// https://github.com/containernetworking/plugins/tree/master/plugins/main/bridge#network-configuration-reference
+type HostLocalBridge struct {
+ PluginType string `json:"type"`
+ BrName string `json:"bridge,omitempty"`
+ IsGW bool `json:"isGateway"`
+ IsDefaultGW bool `json:"isDefaultGateway,omitempty"`
+ ForceAddress bool `json:"forceAddress,omitempty"`
+ IPMasq bool `json:"ipMasq,omitempty"`
+ MTU int `json:"mtu,omitempty"`
+ HairpinMode bool `json:"hairpinMode,omitempty"`
+ PromiscMode bool `json:"promiscMode,omitempty"`
+ Vlan int `json:"vlan,omitempty"`
+ IPAM IPAMHostLocalConf `json:"ipam"`
+}
+
+// Bytes outputs []byte
+func (h *HostLocalBridge) Bytes() ([]byte, error) {
+ return json.MarshalIndent(h, "", "\t")
+}
+
+// IPAMHostLocalConf describes an IPAM configuration
+// https://github.com/containernetworking/plugins/tree/master/plugins/ipam/host-local#network-configuration-reference
+type IPAMHostLocalConf struct {
+ PluginType string `json:"type"`
+ Routes []IPAMRoute `json:"routes,omitempty"`
+ ResolveConf string `json:"resolveConf,omitempty"`
+ DataDir string `json:"dataDir,omitempty"`
+ Ranges [][]IPAMLocalHostRangeConf `json:"ranges,omitempty"`
+}
+
+// IPAMLocalHostRangeConf describes the new style IPAM ranges
+type IPAMLocalHostRangeConf struct {
+ Subnet string `json:"subnet"`
+ RangeStart string `json:"rangeStart,omitempty"`
+ RangeEnd string `json:"rangeEnd,omitempty"`
+ Gateway string `json:"gateway,omitempty"`
+}
+
+// Bytes outputs the configuration as []byte
+func (i IPAMHostLocalConf) Bytes() ([]byte, error) {
+ return json.MarshalIndent(i, "", "\t")
+}
+
+// IPAMRoute describes a route in an ipam config
+type IPAMRoute struct {
+ Dest string `json:"dst"`
+}
+
+// PortMapConfig describes the default portmapping config
+type PortMapConfig struct {
+ PluginType string `json:"type"`
+ Capabilities map[string]bool `json:"capabilities"`
+}
+
+// Bytes outputs the configuration as []byte
+func (p PortMapConfig) Bytes() ([]byte, error) {
+ return json.MarshalIndent(p, "", "\t")
+}
+
+// IPAMDHCP describes the ipamdhcp config
+type IPAMDHCP struct {
+ DHCP string `json:"type"`
+}
+
+// MacVLANConfig describes the macvlan config
+type MacVLANConfig struct {
+ PluginType string `json:"type"`
+ Master string `json:"master"`
+ IPAM IPAMDHCP `json:"ipam"`
+}
+
+// Bytes outputs the configuration as []byte
+func (p MacVLANConfig) Bytes() ([]byte, error) {
+ return json.MarshalIndent(p, "", "\t")
+}
+
+// FirewallConfig describes the firewall plugin
+type FirewallConfig struct {
+ PluginType string `json:"type"`
+ Backend string `json:"backend"`
+}
+
+// Bytes outputs the configuration as []byte
+func (f FirewallConfig) Bytes() ([]byte, error) {
+ return json.MarshalIndent(f, "", "\t")
+}
+
+// DNSNameConfig describes the dns container name resolution plugin config
+type DNSNameConfig struct {
+ PluginType string `json:"type"`
+ DomainName string `json:"domainName"`
+}
+
+// Bytes outputs the configuration as []byte
+func (d DNSNameConfig) Bytes() ([]byte, error) {
+ return json.MarshalIndent(d, "", "\t")
+}
diff --git a/libpod/network/create.go b/libpod/network/create.go
new file mode 100644
index 000000000..a9ed4c4ef
--- /dev/null
+++ b/libpod/network/create.go
@@ -0,0 +1,195 @@
+package network
+
+import (
+ "encoding/json"
+ "fmt"
+ "io/ioutil"
+ "os"
+ "path/filepath"
+
+ "github.com/containernetworking/cni/pkg/version"
+ "github.com/containers/podman/v2/libpod"
+ "github.com/containers/podman/v2/pkg/domain/entities"
+ "github.com/containers/podman/v2/pkg/util"
+ "github.com/pkg/errors"
+)
+
+func Create(name string, options entities.NetworkCreateOptions, r *libpod.Runtime) (*entities.NetworkCreateReport, error) {
+ var fileName string
+ if err := isSupportedDriver(options.Driver); err != nil {
+ return nil, err
+ }
+ config, err := r.GetConfig()
+ if err != nil {
+ return nil, err
+ }
+ // Acquire a lock for CNI
+ l, err := acquireCNILock(filepath.Join(config.Engine.TmpDir, LockFileName))
+ if err != nil {
+ return nil, err
+ }
+ defer l.releaseCNILock()
+ if len(options.MacVLAN) > 0 {
+ fileName, err = createMacVLAN(r, name, options)
+ } else {
+ fileName, err = createBridge(r, name, options)
+ }
+ if err != nil {
+ return nil, err
+ }
+ return &entities.NetworkCreateReport{Filename: fileName}, nil
+}
+
+// createBridge creates a CNI network
+func createBridge(r *libpod.Runtime, name string, options entities.NetworkCreateOptions) (string, error) {
+ isGateway := true
+ ipMasq := true
+ subnet := &options.Subnet
+ ipRange := options.Range
+ runtimeConfig, err := r.GetConfig()
+ if err != nil {
+ return "", err
+ }
+ // if range is provided, make sure it is "in" network
+ if subnet.IP != nil {
+ // if network is provided, does it conflict with existing CNI or live networks
+ err = ValidateUserNetworkIsAvailable(runtimeConfig, subnet)
+ } else {
+ // if no network is provided, figure out network
+ subnet, err = GetFreeNetwork(runtimeConfig)
+ }
+ if err != nil {
+ return "", err
+ }
+ gateway := options.Gateway
+ if gateway == nil {
+ // if no gateway is provided, provide it as first ip of network
+ gateway = CalcGatewayIP(subnet)
+ }
+ // if network is provided and if gateway is provided, make sure it is "in" network
+ if options.Subnet.IP != nil && options.Gateway != nil {
+ if !subnet.Contains(gateway) {
+ return "", errors.Errorf("gateway %s is not in valid for subnet %s", gateway.String(), subnet.String())
+ }
+ }
+ if options.Internal {
+ isGateway = false
+ ipMasq = false
+ }
+
+ // if a range is given, we need to ensure it is "in" the network range.
+ if options.Range.IP != nil {
+ if options.Subnet.IP == nil {
+ return "", errors.New("you must define a subnet range to define an ip-range")
+ }
+ firstIP, err := FirstIPInSubnet(&options.Range)
+ if err != nil {
+ return "", err
+ }
+ lastIP, err := LastIPInSubnet(&options.Range)
+ if err != nil {
+ return "", err
+ }
+ if !subnet.Contains(firstIP) || !subnet.Contains(lastIP) {
+ return "", errors.Errorf("the ip range %s does not fall within the subnet range %s", options.Range.String(), subnet.String())
+ }
+ }
+ bridgeDeviceName, err := GetFreeDeviceName(runtimeConfig)
+ if err != nil {
+ return "", err
+ }
+
+ if len(name) > 0 {
+ netNames, err := GetNetworkNamesFromFileSystem(runtimeConfig)
+ if err != nil {
+ return "", err
+ }
+ if util.StringInSlice(name, netNames) {
+ return "", errors.Errorf("the network name %s is already used", name)
+ }
+ } else {
+ // If no name is given, we give the name of the bridge device
+ name = bridgeDeviceName
+ }
+
+ ncList := NewNcList(name, version.Current())
+ var plugins []CNIPlugins
+ var routes []IPAMRoute
+
+ defaultRoute, err := NewIPAMDefaultRoute(IsIPv6(subnet.IP))
+ if err != nil {
+ return "", err
+ }
+ routes = append(routes, defaultRoute)
+ ipamConfig, err := NewIPAMHostLocalConf(subnet, routes, ipRange, gateway)
+ if err != nil {
+ return "", err
+ }
+
+ // TODO need to iron out the role of isDefaultGW and IPMasq
+ bridge := NewHostLocalBridge(bridgeDeviceName, isGateway, false, ipMasq, ipamConfig)
+ plugins = append(plugins, bridge)
+ plugins = append(plugins, NewPortMapPlugin())
+ plugins = append(plugins, NewFirewallPlugin())
+ // if we find the dnsname plugin, we add configuration for it
+ if HasDNSNamePlugin(runtimeConfig.Network.CNIPluginDirs) && !options.DisableDNS {
+ // Note: in the future we might like to allow for dynamic domain names
+ plugins = append(plugins, NewDNSNamePlugin(DefaultPodmanDomainName))
+ }
+ ncList["plugins"] = plugins
+ b, err := json.MarshalIndent(ncList, "", " ")
+ if err != nil {
+ return "", err
+ }
+ if err := os.MkdirAll(GetCNIConfDir(runtimeConfig), 0755); err != nil {
+ return "", err
+ }
+ cniPathName := filepath.Join(GetCNIConfDir(runtimeConfig), fmt.Sprintf("%s.conflist", name))
+ err = ioutil.WriteFile(cniPathName, b, 0644)
+ return cniPathName, err
+}
+
+func createMacVLAN(r *libpod.Runtime, name string, options entities.NetworkCreateOptions) (string, error) {
+ var (
+ plugins []CNIPlugins
+ )
+ liveNetNames, err := GetLiveNetworkNames()
+ if err != nil {
+ return "", err
+ }
+
+ config, err := r.GetConfig()
+ if err != nil {
+ 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)
+ }
+ if len(name) > 0 {
+ netNames, err := GetNetworkNamesFromFileSystem(config)
+ if err != nil {
+ return "", err
+ }
+ if util.StringInSlice(name, netNames) {
+ return "", errors.Errorf("the network name %s is already used", name)
+ }
+ } else {
+ name, err = GetFreeDeviceName(config)
+ if err != nil {
+ return "", err
+ }
+ }
+ ncList := NewNcList(name, version.Current())
+ macvlan := NewMacVLANPlugin(options.MacVLAN)
+ plugins = append(plugins, macvlan)
+ ncList["plugins"] = plugins
+ b, err := json.MarshalIndent(ncList, "", " ")
+ if err != nil {
+ return "", err
+ }
+ cniPathName := filepath.Join(GetCNIConfDir(config), fmt.Sprintf("%s.conflist", name))
+ err = ioutil.WriteFile(cniPathName, b, 0644)
+ return cniPathName, err
+}
diff --git a/libpod/network/devices.go b/libpod/network/devices.go
new file mode 100644
index 000000000..a5d23fae4
--- /dev/null
+++ b/libpod/network/devices.go
@@ -0,0 +1,63 @@
+package network
+
+import (
+ "fmt"
+ "os/exec"
+
+ "github.com/containers/common/pkg/config"
+ "github.com/containers/podman/v2/pkg/util"
+ "github.com/containers/podman/v2/utils"
+ "github.com/sirupsen/logrus"
+)
+
+// GetFreeDeviceName returns a device name that is unused; used when no network
+// name is provided by user
+func GetFreeDeviceName(config *config.Config) (string, error) {
+ var (
+ deviceNum uint
+ deviceName string
+ )
+ networkNames, err := GetNetworkNamesFromFileSystem(config)
+ if err != nil {
+ return "", err
+ }
+ liveNetworksNames, err := GetLiveNetworkNames()
+ if err != nil {
+ return "", err
+ }
+ bridgeNames, err := GetBridgeNamesFromFileSystem(config)
+ if err != nil {
+ return "", err
+ }
+ for {
+ deviceName = fmt.Sprintf("%s%d", CNIDeviceName, deviceNum)
+ logrus.Debugf("checking if device name %q exists in other cni networks", deviceName)
+ if util.StringInSlice(deviceName, networkNames) {
+ deviceNum++
+ continue
+ }
+ logrus.Debugf("checking if device name %q exists in live networks", deviceName)
+ if util.StringInSlice(deviceName, liveNetworksNames) {
+ deviceNum++
+ continue
+ }
+ logrus.Debugf("checking if device name %q already exists as a bridge name ", deviceName)
+ if !util.StringInSlice(deviceName, bridgeNames) {
+ break
+ }
+ deviceNum++
+ }
+ return deviceName, nil
+}
+
+// RemoveInterface removes an interface by the given name
+func RemoveInterface(interfaceName string) error {
+ // Make sure we have the ip command on the system
+ ipPath, err := exec.LookPath("ip")
+ if err != nil {
+ return err
+ }
+ // Delete the network interface
+ _, err = utils.ExecCmd(ipPath, []string{"link", "del", interfaceName}...)
+ return err
+}
diff --git a/libpod/network/files.go b/libpod/network/files.go
new file mode 100644
index 000000000..a2090491f
--- /dev/null
+++ b/libpod/network/files.go
@@ -0,0 +1,174 @@
+package network
+
+import (
+ "encoding/json"
+ "fmt"
+ "io/ioutil"
+ "sort"
+ "strings"
+
+ "github.com/containernetworking/cni/libcni"
+ "github.com/containernetworking/plugins/plugins/ipam/host-local/backend/allocator"
+ "github.com/containers/common/pkg/config"
+ "github.com/containers/podman/v2/libpod/define"
+ "github.com/pkg/errors"
+)
+
+func GetCNIConfDir(configArg *config.Config) string {
+ if len(configArg.Network.NetworkConfigDir) < 1 {
+ dc, err := config.DefaultConfig()
+ if err != nil {
+ // Fallback to hard-coded dir
+ return CNIConfigDir
+ }
+ return dc.Network.NetworkConfigDir
+ }
+ return configArg.Network.NetworkConfigDir
+}
+
+// LoadCNIConfsFromDir loads all the CNI configurations from a dir
+func LoadCNIConfsFromDir(dir string) ([]*libcni.NetworkConfigList, error) {
+ files, err := libcni.ConfFiles(dir, []string{".conflist"})
+ if err != nil {
+ return nil, err
+ }
+ sort.Strings(files)
+
+ configs := make([]*libcni.NetworkConfigList, 0, len(files))
+ for _, confFile := range files {
+ conf, err := libcni.ConfListFromFile(confFile)
+ if err != nil {
+ return nil, errors.Wrapf(err, "in %s", confFile)
+ }
+ configs = append(configs, conf)
+ }
+ return configs, nil
+}
+
+// GetCNIConfigPathByName finds a CNI network by name and
+// returns its configuration file path
+func GetCNIConfigPathByName(config *config.Config, name string) (string, error) {
+ files, err := libcni.ConfFiles(GetCNIConfDir(config), []string{".conflist"})
+ if err != nil {
+ return "", err
+ }
+ for _, confFile := range files {
+ conf, err := libcni.ConfListFromFile(confFile)
+ if err != nil {
+ return "", errors.Wrapf(err, "in %s", confFile)
+ }
+ if conf.Name == name {
+ return confFile, nil
+ }
+ }
+ return "", errors.Wrap(define.ErrNoSuchNetwork, fmt.Sprintf("unable to find network configuration for %s", name))
+}
+
+// ReadRawCNIConfByName reads the raw CNI configuration for a CNI
+// network by name
+func ReadRawCNIConfByName(config *config.Config, name string) ([]byte, error) {
+ confFile, err := GetCNIConfigPathByName(config, name)
+ if err != nil {
+ return nil, err
+ }
+ b, err := ioutil.ReadFile(confFile)
+ return b, err
+}
+
+// GetCNIPlugins returns a list of plugins that a given network
+// has in the form of a string
+func GetCNIPlugins(list *libcni.NetworkConfigList) string {
+ plugins := make([]string, 0, len(list.Plugins))
+ for _, plug := range list.Plugins {
+ plugins = append(plugins, plug.Network.Type)
+ }
+ return strings.Join(plugins, ",")
+}
+
+// GetNetworksFromFilesystem gets all the networks from the cni configuration
+// files
+func GetNetworksFromFilesystem(config *config.Config) ([]*allocator.Net, error) {
+ var cniNetworks []*allocator.Net
+
+ networks, err := LoadCNIConfsFromDir(GetCNIConfDir(config))
+ if err != nil {
+ return nil, err
+ }
+ for _, n := range networks {
+ for _, cniplugin := range n.Plugins {
+ if cniplugin.Network.Type == "bridge" {
+ ipamConf := allocator.Net{}
+ if err := json.Unmarshal(cniplugin.Bytes, &ipamConf); err != nil {
+ return nil, err
+ }
+ cniNetworks = append(cniNetworks, &ipamConf)
+ break
+ }
+ }
+ }
+ return cniNetworks, nil
+}
+
+// GetNetworkNamesFromFileSystem gets all the names from the cni network
+// configuration files
+func GetNetworkNamesFromFileSystem(config *config.Config) ([]string, error) {
+ networks, err := LoadCNIConfsFromDir(GetCNIConfDir(config))
+ if err != nil {
+ return nil, err
+ }
+ networkNames := []string{}
+ for _, n := range networks {
+ networkNames = append(networkNames, n.Name)
+ }
+ return networkNames, nil
+}
+
+// GetInterfaceNameFromConfig returns the interface name for the bridge plugin
+func GetInterfaceNameFromConfig(path string) (string, error) {
+ var name string
+ conf, err := libcni.ConfListFromFile(path)
+ if err != nil {
+ return "", err
+ }
+ for _, cniplugin := range conf.Plugins {
+ if cniplugin.Network.Type == "bridge" {
+ plugin := make(map[string]interface{})
+ if err := json.Unmarshal(cniplugin.Bytes, &plugin); err != nil {
+ return "", err
+ }
+ name = plugin["bridge"].(string)
+ break
+ }
+ }
+ if len(name) == 0 {
+ return "", errors.New("unable to find interface name for network")
+ }
+ return name, nil
+}
+
+// GetBridgeNamesFromFileSystem is a convenience function to get all the bridge
+// names from the configured networks
+func GetBridgeNamesFromFileSystem(config *config.Config) ([]string, error) {
+ networks, err := LoadCNIConfsFromDir(GetCNIConfDir(config))
+ if err != nil {
+ return nil, err
+ }
+
+ bridgeNames := []string{}
+ for _, n := range networks {
+ var name string
+ // iterate network conflists
+ for _, cniplugin := range n.Plugins {
+ // iterate plugins
+ if cniplugin.Network.Type == "bridge" {
+ plugin := make(map[string]interface{})
+ if err := json.Unmarshal(cniplugin.Bytes, &plugin); err != nil {
+ continue
+ }
+ name = plugin["bridge"].(string)
+ }
+ }
+ bridgeNames = append(bridgeNames, name)
+ }
+ return bridgeNames, nil
+}
diff --git a/libpod/network/ip.go b/libpod/network/ip.go
new file mode 100644
index 000000000..ba93a0d05
--- /dev/null
+++ b/libpod/network/ip.go
@@ -0,0 +1,19 @@
+package network
+
+import (
+ "net"
+
+ "github.com/containernetworking/plugins/pkg/ip"
+)
+
+// CalcGatewayIP takes a network and returns the first IP in it.
+func CalcGatewayIP(ipn *net.IPNet) net.IP {
+ // taken from cni bridge plugin as it is not exported
+ nid := ipn.IP.Mask(ipn.Mask)
+ return ip.NextIP(nid)
+}
+
+// IsIPv6 returns if netIP is IPv6.
+func IsIPv6(netIP net.IP) bool {
+ return netIP != nil && netIP.To4() == nil
+}
diff --git a/libpod/network/lock.go b/libpod/network/lock.go
new file mode 100644
index 000000000..0395359eb
--- /dev/null
+++ b/libpod/network/lock.go
@@ -0,0 +1,26 @@
+package network
+
+import (
+ "github.com/containers/storage"
+)
+
+// acquireCNILock gets a lock that should be used in create and
+// delete cases to avoid unwanted collisions in network names.
+// TODO this uses a file lock and should be converted to shared memory
+// when we have a more general shared memory lock in libpod
+func acquireCNILock(lockPath string) (*CNILock, error) {
+ l, err := storage.GetLockfile(lockPath)
+ if err != nil {
+ return nil, err
+ }
+ l.Lock()
+ cnilock := CNILock{
+ Locker: l,
+ }
+ return &cnilock, nil
+}
+
+// ReleaseCNILock unlocks the previously held lock
+func (l *CNILock) releaseCNILock() {
+ l.Unlock()
+}
diff --git a/libpod/network/netconflist.go b/libpod/network/netconflist.go
new file mode 100644
index 000000000..8187fdb39
--- /dev/null
+++ b/libpod/network/netconflist.go
@@ -0,0 +1,155 @@
+package network
+
+import (
+ "net"
+ "os"
+ "path/filepath"
+)
+
+const (
+ defaultIPv4Route = "0.0.0.0/0"
+ defaultIPv6Route = "::/0"
+)
+
+// NcList describes a generic map
+type NcList map[string]interface{}
+
+// NewNcList creates a generic map of values with string
+// keys and adds in version and network name
+func NewNcList(name, version string) NcList {
+ n := NcList{}
+ n["cniVersion"] = version
+ n["name"] = name
+ return n
+}
+
+// NewHostLocalBridge creates a new LocalBridge for host-local
+func NewHostLocalBridge(name string, isGateWay, isDefaultGW, ipMasq bool, ipamConf IPAMHostLocalConf) *HostLocalBridge {
+ hostLocalBridge := HostLocalBridge{
+ PluginType: "bridge",
+ BrName: name,
+ IPMasq: ipMasq,
+ HairpinMode: true,
+ IPAM: ipamConf,
+ }
+ if isGateWay {
+ hostLocalBridge.IsGW = true
+ }
+ if isDefaultGW {
+ hostLocalBridge.IsDefaultGW = true
+ }
+ return &hostLocalBridge
+}
+
+// NewIPAMHostLocalConf creates a new IPAMHostLocal configfuration
+func NewIPAMHostLocalConf(subnet *net.IPNet, routes []IPAMRoute, ipRange net.IPNet, gw net.IP) (IPAMHostLocalConf, error) {
+ var ipamRanges [][]IPAMLocalHostRangeConf
+ ipamConf := IPAMHostLocalConf{
+ PluginType: "host-local",
+ Routes: routes,
+ // Possible future support ? Leaving for clues
+ //ResolveConf: "",
+ //DataDir: ""
+ }
+ IPAMRange, err := newIPAMLocalHostRange(subnet, &ipRange, &gw)
+ if err != nil {
+ return ipamConf, err
+ }
+ ipamRanges = append(ipamRanges, IPAMRange)
+ ipamConf.Ranges = ipamRanges
+ return ipamConf, nil
+}
+
+func newIPAMLocalHostRange(subnet *net.IPNet, ipRange *net.IPNet, gw *net.IP) ([]IPAMLocalHostRangeConf, error) { //nolint:interfacer
+ var ranges []IPAMLocalHostRangeConf
+ hostRange := IPAMLocalHostRangeConf{
+ Subnet: subnet.String(),
+ }
+ // an user provided a range, we add it here
+ if ipRange.IP != nil {
+ first, err := FirstIPInSubnet(ipRange)
+ if err != nil {
+ return nil, err
+ }
+ last, err := LastIPInSubnet(ipRange)
+ if err != nil {
+ return nil, err
+ }
+ hostRange.RangeStart = first.String()
+ hostRange.RangeEnd = last.String()
+ }
+ if gw != nil {
+ hostRange.Gateway = gw.String()
+ }
+ ranges = append(ranges, hostRange)
+ return ranges, nil
+}
+
+// NewIPAMRoute creates a new IPAM route configuration
+func NewIPAMRoute(r *net.IPNet) IPAMRoute { //nolint:interfacer
+ return IPAMRoute{Dest: r.String()}
+}
+
+// NewIPAMDefaultRoute creates a new IPAMDefault route of
+// 0.0.0.0/0 for IPv4 or ::/0 for IPv6
+func NewIPAMDefaultRoute(isIPv6 bool) (IPAMRoute, error) {
+ route := defaultIPv4Route
+ if isIPv6 {
+ route = defaultIPv6Route
+ }
+ _, n, err := net.ParseCIDR(route)
+ if err != nil {
+ return IPAMRoute{}, err
+ }
+ return NewIPAMRoute(n), nil
+}
+
+// NewPortMapPlugin creates a predefined, default portmapping
+// configuration
+func NewPortMapPlugin() PortMapConfig {
+ caps := make(map[string]bool)
+ caps["portMappings"] = true
+ p := PortMapConfig{
+ PluginType: "portmap",
+ Capabilities: caps,
+ }
+ return p
+}
+
+// NewFirewallPlugin creates a generic firewall plugin
+func NewFirewallPlugin() FirewallConfig {
+ return FirewallConfig{
+ PluginType: "firewall",
+ }
+}
+
+// NewDNSNamePlugin creates the dnsname config with a given
+// domainname
+func NewDNSNamePlugin(domainName string) DNSNameConfig {
+ return DNSNameConfig{
+ PluginType: "dnsname",
+ DomainName: domainName,
+ }
+}
+
+// HasDNSNamePlugin looks to see if the dnsname cni plugin is present
+func HasDNSNamePlugin(paths []string) bool {
+ for _, p := range paths {
+ if _, err := os.Stat(filepath.Join(p, "dnsname")); err == nil {
+ return true
+ }
+ }
+ return false
+}
+
+// NewMacVLANPlugin creates a macvlanconfig with a given device name
+func NewMacVLANPlugin(device string) MacVLANConfig {
+ i := IPAMDHCP{DHCP: "dhcp"}
+
+ m := MacVLANConfig{
+ PluginType: "macvlan",
+ Master: device,
+ IPAM: i,
+ }
+ return m
+}
diff --git a/libpod/network/netconflist_test.go b/libpod/network/netconflist_test.go
new file mode 100644
index 000000000..5893bf985
--- /dev/null
+++ b/libpod/network/netconflist_test.go
@@ -0,0 +1,38 @@
+package network
+
+import (
+ "reflect"
+ "testing"
+)
+
+func TestNewIPAMDefaultRoute(t *testing.T) {
+
+ tests := []struct {
+ name string
+ isIPv6 bool
+ want IPAMRoute
+ }{
+ {
+ name: "IPv4 default route",
+ isIPv6: false,
+ want: IPAMRoute{defaultIPv4Route},
+ },
+ {
+ name: "IPv6 default route",
+ isIPv6: true,
+ want: IPAMRoute{defaultIPv6Route},
+ },
+ }
+ for _, tt := range tests {
+ tt := tt
+ t.Run(tt.name, func(t *testing.T) {
+ got, err := NewIPAMDefaultRoute(tt.isIPv6)
+ if err != nil {
+ t.Errorf("no error expected: %v", err)
+ }
+ if !reflect.DeepEqual(got, tt.want) {
+ t.Errorf("NewIPAMDefaultRoute() = %v, want %v", got, tt.want)
+ }
+ })
+ }
+}
diff --git a/libpod/network/network.go b/libpod/network/network.go
new file mode 100644
index 000000000..7327a1a7d
--- /dev/null
+++ b/libpod/network/network.go
@@ -0,0 +1,225 @@
+package network
+
+import (
+ "encoding/json"
+ "net"
+ "os"
+ "path/filepath"
+
+ "github.com/containernetworking/cni/pkg/types"
+ "github.com/containernetworking/plugins/plugins/ipam/host-local/backend/allocator"
+ "github.com/containers/common/pkg/config"
+ "github.com/containers/podman/v2/libpod/define"
+ "github.com/containers/podman/v2/pkg/util"
+ "github.com/pkg/errors"
+ "github.com/sirupsen/logrus"
+)
+
+// DefaultNetworkDriver is the default network type used
+var DefaultNetworkDriver = "bridge"
+
+// SupportedNetworkDrivers describes the list of supported drivers
+var SupportedNetworkDrivers = []string{DefaultNetworkDriver}
+
+// isSupportedDriver checks if the user provided driver is supported
+func isSupportedDriver(driver string) error {
+ if util.StringInSlice(driver, SupportedNetworkDrivers) {
+ return nil
+ }
+ return errors.Errorf("driver '%s' is not supported", driver)
+}
+
+// GetLiveNetworks returns a slice of networks representing what the system
+// has defined as network interfaces
+func GetLiveNetworks() ([]*net.IPNet, error) {
+ addrs, err := net.InterfaceAddrs()
+ if err != nil {
+ return nil, err
+ }
+ nets := make([]*net.IPNet, 0, len(addrs))
+ for _, address := range addrs {
+ _, n, err := net.ParseCIDR(address.String())
+ if err != nil {
+ return nil, err
+ }
+ nets = append(nets, n)
+ }
+ return nets, nil
+}
+
+// GetLiveNetworkNames returns a list of network interfaces on the system
+func GetLiveNetworkNames() ([]string, error) {
+ liveInterfaces, err := net.Interfaces()
+ if err != nil {
+ return nil, err
+ }
+ interfaceNames := make([]string, 0, len(liveInterfaces))
+ for _, i := range liveInterfaces {
+ interfaceNames = append(interfaceNames, i.Name)
+ }
+ return interfaceNames, nil
+}
+
+// GetFreeNetwork looks for a free network according to existing cni configuration
+// files and network interfaces.
+func GetFreeNetwork(config *config.Config) (*net.IPNet, error) {
+ networks, err := GetNetworksFromFilesystem(config)
+ if err != nil {
+ return nil, err
+ }
+ liveNetworks, err := GetLiveNetworks()
+ if err != nil {
+ return nil, err
+ }
+ nextNetwork, err := GetDefaultPodmanNetwork()
+ if err != nil {
+ return nil, err
+ }
+ logrus.Debugf("default network is %s", nextNetwork.String())
+ for {
+ newNetwork, err := NextSubnet(nextNetwork)
+ if err != nil {
+ return nil, err
+ }
+ logrus.Debugf("checking if network %s intersects with other cni networks", nextNetwork.String())
+ if intersectsConfig, _ := networkIntersectsWithNetworks(newNetwork, allocatorToIPNets(networks)); intersectsConfig {
+ logrus.Debugf("network %s is already being used by a cni configuration", nextNetwork.String())
+ nextNetwork = newNetwork
+ continue
+ }
+ logrus.Debugf("checking if network %s intersects with any network interfaces", nextNetwork.String())
+ if intersectsLive, _ := networkIntersectsWithNetworks(newNetwork, liveNetworks); !intersectsLive {
+ break
+ }
+ logrus.Debugf("network %s is being used by a network interface", nextNetwork.String())
+ nextNetwork = newNetwork
+ }
+ return nextNetwork, nil
+}
+
+func allocatorToIPNets(networks []*allocator.Net) []*net.IPNet {
+ var nets []*net.IPNet
+ for _, network := range networks {
+ if len(network.IPAM.Ranges) > 0 {
+ // this is the new IPAM range style
+ // append each subnet from ipam the rangeset
+ for _, r := range network.IPAM.Ranges[0] {
+ nets = append(nets, newIPNetFromSubnet(r.Subnet))
+ }
+ } else {
+ // looks like the old, deprecated style
+ nets = append(nets, newIPNetFromSubnet(network.IPAM.Subnet))
+ }
+ }
+ return nets
+}
+
+func newIPNetFromSubnet(subnet types.IPNet) *net.IPNet {
+ n := net.IPNet{
+ IP: subnet.IP,
+ Mask: subnet.Mask,
+ }
+ return &n
+}
+
+func networkIntersectsWithNetworks(n *net.IPNet, networklist []*net.IPNet) (bool, *net.IPNet) {
+ for _, nw := range networklist {
+ if networkIntersect(n, nw) {
+ return true, nw
+ }
+ }
+ return false, nil
+}
+
+func networkIntersect(n1, n2 *net.IPNet) bool {
+ return n2.Contains(n1.IP) || n1.Contains(n2.IP)
+}
+
+// ValidateUserNetworkIsAvailable returns via an error if a network is available
+// to be used
+func ValidateUserNetworkIsAvailable(config *config.Config, userNet *net.IPNet) error {
+ if len(userNet.IP) == 0 || len(userNet.Mask) == 0 {
+ return errors.Errorf("network %s's ip or mask cannot be empty", userNet.String())
+ }
+
+ ones, bit := userNet.Mask.Size()
+ if ones == 0 || bit == 0 {
+ return errors.Errorf("network %s's mask is invalid", userNet.String())
+ }
+
+ networks, err := GetNetworksFromFilesystem(config)
+ if err != nil {
+ return err
+ }
+ liveNetworks, err := GetLiveNetworks()
+ if err != nil {
+ return err
+ }
+ logrus.Debugf("checking if network %s exists in cni networks", userNet.String())
+ if intersectsConfig, _ := networkIntersectsWithNetworks(userNet, allocatorToIPNets(networks)); intersectsConfig {
+ return errors.Errorf("network %s is already being used by a cni configuration", userNet.String())
+ }
+ logrus.Debugf("checking if network %s exists in any network interfaces", userNet.String())
+ if intersectsLive, _ := networkIntersectsWithNetworks(userNet, liveNetworks); intersectsLive {
+ return errors.Errorf("network %s is being used by a network interface", userNet.String())
+ }
+ return nil
+}
+
+// RemoveNetwork removes a given network by name. If the network has container associated with it, that
+// must be handled outside the context of this.
+func RemoveNetwork(config *config.Config, name string) error {
+ l, err := acquireCNILock(filepath.Join(config.Engine.TmpDir, LockFileName))
+ if err != nil {
+ return err
+ }
+ defer l.releaseCNILock()
+ cniPath, err := GetCNIConfigPathByName(config, name)
+ if err != nil {
+ return err
+ }
+ // Before we delete the configuration file, we need to make sure we can read and parse
+ // it to get the network interface name so we can remove that too
+ interfaceName, err := GetInterfaceNameFromConfig(cniPath)
+ if err != nil {
+ return errors.Wrapf(err, "failed to find network interface name in %q", cniPath)
+ }
+ liveNetworkNames, err := GetLiveNetworkNames()
+ if err != nil {
+ return errors.Wrapf(err, "failed to get live network names")
+ }
+ if util.StringInSlice(interfaceName, liveNetworkNames) {
+ if err := RemoveInterface(interfaceName); err != nil {
+ return errors.Wrapf(err, "failed to delete the network interface %q", interfaceName)
+ }
+ }
+ // Remove the configuration file
+ if err := os.Remove(cniPath); err != nil {
+ return errors.Wrapf(err, "failed to remove network configuration file %q", cniPath)
+ }
+ return nil
+}
+
+// InspectNetwork reads a CNI config and returns its configuration
+func InspectNetwork(config *config.Config, name string) (map[string]interface{}, error) {
+ b, err := ReadRawCNIConfByName(config, name)
+ if err != nil {
+ return nil, err
+ }
+ rawList := make(map[string]interface{})
+ err = json.Unmarshal(b, &rawList)
+ return rawList, err
+}
+
+// Exists says whether a given network exists or not; it meant
+// specifically for restful responses so 404s can be used
+func Exists(config *config.Config, name string) (bool, error) {
+ _, err := ReadRawCNIConfByName(config, name)
+ if err != nil {
+ if errors.Cause(err) == define.ErrNoSuchNetwork {
+ return false, nil
+ }
+ return false, err
+ }
+ return true, nil
+}
diff --git a/libpod/network/network_test.go b/libpod/network/network_test.go
new file mode 100644
index 000000000..1969e792c
--- /dev/null
+++ b/libpod/network/network_test.go
@@ -0,0 +1,35 @@
+package network
+
+import (
+ "net"
+ "testing"
+)
+
+func parseCIDR(n string) *net.IPNet {
+ _, parsedNet, _ := net.ParseCIDR(n)
+ return parsedNet
+}
+
+func Test_networkIntersect(t *testing.T) {
+ type args struct {
+ n1 *net.IPNet
+ n2 *net.IPNet
+ }
+ tests := []struct {
+ name string
+ args args
+ want bool
+ }{
+ {"16 and 24 intersects", args{n1: parseCIDR("192.168.0.0/16"), n2: parseCIDR("192.168.1.0/24")}, true},
+ {"24 and 25 intersects", args{n1: parseCIDR("192.168.1.0/24"), n2: parseCIDR("192.168.1.0/25")}, true},
+ {"Two 24s", args{n1: parseCIDR("192.168.1.0/24"), n2: parseCIDR("192.168.2.0/24")}, false},
+ }
+ for _, tt := range tests {
+ test := tt
+ t.Run(tt.name, func(t *testing.T) {
+ if got := networkIntersect(test.args.n1, test.args.n2); got != test.want {
+ t.Errorf("networkIntersect() = %v, want %v", got, test.want)
+ }
+ })
+ }
+}
diff --git a/libpod/network/subnet.go b/libpod/network/subnet.go
new file mode 100644
index 000000000..90f0cdfce
--- /dev/null
+++ b/libpod/network/subnet.go
@@ -0,0 +1,78 @@
+package network
+
+/*
+ The code in this was kindly contributed by Dan Williams(dcbw@redhat.com). Many thanks
+ for his contributions.
+*/
+
+import (
+ "fmt"
+ "net"
+)
+
+func incByte(subnet *net.IPNet, idx int, shift uint) error {
+ if idx < 0 {
+ return fmt.Errorf("no more subnets left")
+ }
+ if subnet.IP[idx] == 255 {
+ subnet.IP[idx] = 0
+ return incByte(subnet, idx-1, 0)
+ }
+ subnet.IP[idx] += 1 << shift
+ return nil
+}
+
+// NextSubnet returns subnet incremented by 1
+func NextSubnet(subnet *net.IPNet) (*net.IPNet, error) {
+ newSubnet := &net.IPNet{
+ IP: subnet.IP,
+ Mask: subnet.Mask,
+ }
+ ones, bits := newSubnet.Mask.Size()
+ if ones == 0 {
+ return nil, fmt.Errorf("%s has only one subnet", subnet.String())
+ }
+ zeroes := uint(bits - ones)
+ shift := zeroes % 8
+ idx := ones/8 - 1
+ if idx < 0 {
+ idx = 0
+ }
+ if err := incByte(newSubnet, idx, shift); err != nil {
+ return nil, err
+ }
+ return newSubnet, nil
+}
+
+// LastIPInSubnet gets the last IP in a subnet
+func LastIPInSubnet(addr *net.IPNet) (net.IP, error) { //nolint:interfacer
+ // re-parse to ensure clean network address
+ _, cidr, err := net.ParseCIDR(addr.String())
+ if err != nil {
+ return nil, err
+ }
+
+ ones, bits := cidr.Mask.Size()
+ if ones == bits {
+ return FirstIPInSubnet(cidr)
+ }
+ hostStart := ones / 8
+ // Handle the first host byte
+ cidr.IP[hostStart] |= 0xff & cidr.Mask[hostStart]
+ // Fill the rest with ones
+ for i := hostStart; i < len(cidr.IP); i++ {
+ cidr.IP[i] = 0xff
+ }
+ return cidr.IP, nil
+}
+
+// FirstIPInSubnet gets the first IP in a subnet
+func FirstIPInSubnet(addr *net.IPNet) (net.IP, error) { //nolint:interfacer
+ // re-parse to ensure clean network address
+ _, cidr, err := net.ParseCIDR(addr.String())
+ if err != nil {
+ return nil, err
+ }
+ cidr.IP[len(cidr.IP)-1]++
+ return cidr.IP, nil
+}
diff --git a/libpod/network/subnet_test.go b/libpod/network/subnet_test.go
new file mode 100644
index 000000000..917c3be88
--- /dev/null
+++ b/libpod/network/subnet_test.go
@@ -0,0 +1,35 @@
+package network
+
+import (
+ "net"
+ "reflect"
+ "testing"
+)
+
+func TestNextSubnet(t *testing.T) {
+ type args struct {
+ subnet *net.IPNet
+ }
+ tests := []struct {
+ name string
+ args args
+ want *net.IPNet
+ wantErr bool
+ }{
+ {"class b", args{subnet: parseCIDR("192.168.0.0/16")}, parseCIDR("192.169.0.0/16"), false},
+ {"class c", args{subnet: parseCIDR("192.168.1.0/24")}, parseCIDR("192.168.2.0/24"), false},
+ }
+ for _, tt := range tests {
+ test := tt
+ t.Run(test.name, func(t *testing.T) {
+ got, err := NextSubnet(test.args.subnet)
+ if (err != nil) != test.wantErr {
+ t.Errorf("NextSubnet() error = %v, wantErr %v", err, test.wantErr)
+ return
+ }
+ if !reflect.DeepEqual(got, test.want) {
+ t.Errorf("NextSubnet() got = %v, want %v", got, test.want)
+ }
+ })
+ }
+}
diff --git a/libpod/oci_conmon_linux.go b/libpod/oci_conmon_linux.go
index 7fb374e0d..94630e57b 100644
--- a/libpod/oci_conmon_linux.go
+++ b/libpod/oci_conmon_linux.go
@@ -57,7 +57,6 @@ type ConmonOCIRuntime struct {
path string
conmonPath string
conmonEnv []string
- cgroupManager string
tmpDir string
exitsDir string
socketsDir string
@@ -102,7 +101,6 @@ func newConmonOCIRuntime(name string, paths []string, conmonPath string, runtime
runtime.runtimeFlags = runtimeFlags
runtime.conmonEnv = runtimeCfg.Engine.ConmonEnvVars
- runtime.cgroupManager = runtimeCfg.Engine.CgroupManager
runtime.tmpDir = runtimeCfg.Engine.TmpDir
runtime.logSizeMax = runtimeCfg.Containers.LogSizeMax
runtime.noPivot = runtimeCfg.Engine.NoPivotRoot
@@ -149,10 +147,6 @@ func newConmonOCIRuntime(name string, paths []string, conmonPath string, runtime
runtime.exitsDir = filepath.Join(runtime.tmpDir, "exits")
runtime.socketsDir = filepath.Join(runtime.tmpDir, "socket")
- if runtime.cgroupManager != config.CgroupfsCgroupsManager && runtime.cgroupManager != config.SystemdCgroupsManager {
- return nil, errors.Wrapf(define.ErrInvalidArg, "invalid cgroup manager specified: %s", runtime.cgroupManager)
- }
-
// Create the exit files and attach sockets directories
if err := os.MkdirAll(runtime.exitsDir, 0750); err != nil {
// The directory is allowed to exist
@@ -1325,7 +1319,7 @@ func (r *ConmonOCIRuntime) sharedConmonArgs(ctr *Container, cuuid, bundlePath, p
args = append(args, rFlags...)
}
- if r.cgroupManager == config.SystemdCgroupsManager && !ctr.config.NoCgroups && ctr.config.CgroupsMode != cgroupSplit {
+ if ctr.CgroupManager() == config.SystemdCgroupsManager && !ctr.config.NoCgroups && ctr.config.CgroupsMode != cgroupSplit {
args = append(args, "-s")
}
@@ -1442,8 +1436,10 @@ func (r *ConmonOCIRuntime) moveConmonToCgroupAndSignal(ctr *Container, cmd *exec
}
if mustCreateCgroup {
+ // TODO: This should be a switch - we are not guaranteed that
+ // there are only 2 valid cgroup managers
cgroupParent := ctr.CgroupParent()
- if r.cgroupManager == config.SystemdCgroupsManager {
+ if ctr.CgroupManager() == config.SystemdCgroupsManager {
unitName := createUnitName("libpod-conmon", ctr.ID())
realCgroupParent := cgroupParent
diff --git a/libpod/pod_api.go b/libpod/pod_api.go
index 0ae180356..f2ddba9c9 100644
--- a/libpod/pod_api.go
+++ b/libpod/pod_api.go
@@ -6,6 +6,7 @@ import (
"github.com/containers/podman/v2/libpod/define"
"github.com/containers/podman/v2/libpod/events"
"github.com/containers/podman/v2/pkg/cgroups"
+ "github.com/containers/podman/v2/pkg/parallel"
"github.com/containers/podman/v2/pkg/rootless"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
@@ -99,47 +100,52 @@ func (p *Pod) StopWithTimeout(ctx context.Context, cleanup bool, timeout int) (m
return nil, err
}
- ctrErrors := make(map[string]error)
-
// TODO: There may be cases where it makes sense to order stops based on
// dependencies. Should we bother with this?
- // Stop to all containers
- for _, ctr := range allCtrs {
- ctr.lock.Lock()
+ ctrErrChan := make(map[string]<-chan error)
- if err := ctr.syncContainer(); err != nil {
- ctr.lock.Unlock()
- ctrErrors[ctr.ID()] = err
- continue
- }
-
- // Ignore containers that are not running
- if ctr.state.State != define.ContainerStateRunning {
- ctr.lock.Unlock()
- continue
- }
- stopTimeout := ctr.config.StopTimeout
- if timeout > -1 {
- stopTimeout = uint(timeout)
- }
- if err := ctr.stop(stopTimeout); err != nil {
- ctr.lock.Unlock()
- ctrErrors[ctr.ID()] = err
- continue
- }
+ // Enqueue a function for each container with the parallel executor.
+ for _, ctr := range allCtrs {
+ c := ctr
+ logrus.Debugf("Adding parallel job to stop container %s", c.ID())
+ retChan := parallel.Enqueue(ctx, func() error {
+ // TODO: Might be better to batch stop and cleanup
+ // together?
+ if timeout > -1 {
+ if err := c.StopWithTimeout(uint(timeout)); err != nil {
+ return err
+ }
+ } else {
+ if err := c.Stop(); err != nil {
+ return err
+ }
+ }
- if cleanup {
- if err := ctr.cleanup(ctx); err != nil {
- ctrErrors[ctr.ID()] = err
+ if cleanup {
+ return c.Cleanup(ctx)
}
- }
- ctr.lock.Unlock()
+ return nil
+ })
+
+ ctrErrChan[c.ID()] = retChan
}
p.newPodEvent(events.Stop)
+ ctrErrors := make(map[string]error)
+
+ // Get returned error for every container we worked on
+ for id, channel := range ctrErrChan {
+ if err := <-channel; err != nil {
+ if errors.Cause(err) == define.ErrCtrStateInvalid || errors.Cause(err) == define.ErrCtrStopped {
+ continue
+ }
+ ctrErrors[id] = err
+ }
+ }
+
if len(ctrErrors) > 0 {
return ctrErrors, errors.Wrapf(define.ErrPodPartialFail, "error stopping some containers")
}
@@ -169,45 +175,29 @@ func (p *Pod) Cleanup(ctx context.Context) (map[string]error, error) {
return nil, err
}
- ctrErrors := make(map[string]error)
+ ctrErrChan := make(map[string]<-chan error)
- // Clean up all containers
+ // Enqueue a function for each container with the parallel executor.
for _, ctr := range allCtrs {
- ctr.lock.Lock()
-
- if err := ctr.syncContainer(); err != nil {
- ctr.lock.Unlock()
- ctrErrors[ctr.ID()] = err
- continue
- }
-
- // Ignore containers that are running/paused
- if !ctr.ensureState(define.ContainerStateConfigured, define.ContainerStateCreated, define.ContainerStateStopped, define.ContainerStateExited) {
- ctr.lock.Unlock()
- continue
- }
-
- // Check for running exec sessions, ignore containers with them.
- sessions, err := ctr.getActiveExecSessions()
- if err != nil {
- ctr.lock.Unlock()
- ctrErrors[ctr.ID()] = err
- continue
- }
- if len(sessions) > 0 {
- ctr.lock.Unlock()
- continue
- }
+ c := ctr
+ logrus.Debugf("Adding parallel job to clean up container %s", c.ID())
+ retChan := parallel.Enqueue(ctx, func() error {
+ return c.Cleanup(ctx)
+ })
- // TODO: Should we handle restart policy here?
+ ctrErrChan[c.ID()] = retChan
+ }
- ctr.newContainerEvent(events.Cleanup)
+ ctrErrors := make(map[string]error)
- if err := ctr.cleanup(ctx); err != nil {
- ctrErrors[ctr.ID()] = err
+ // Get returned error for every container we worked on
+ for id, channel := range ctrErrChan {
+ if err := <-channel; err != nil {
+ if errors.Cause(err) == define.ErrCtrStateInvalid || errors.Cause(err) == define.ErrCtrStopped {
+ continue
+ }
+ ctrErrors[id] = err
}
-
- ctr.lock.Unlock()
}
if len(ctrErrors) > 0 {
@@ -229,7 +219,7 @@ func (p *Pod) Cleanup(ctx context.Context) (map[string]error, error) {
// containers. The container ID is mapped to the error encountered. The error is
// set to ErrPodPartialFail.
// If both error and the map are nil, all containers were paused without error
-func (p *Pod) Pause() (map[string]error, error) {
+func (p *Pod) Pause(ctx context.Context) (map[string]error, error) {
p.lock.Lock()
defer p.lock.Unlock()
@@ -252,37 +242,34 @@ func (p *Pod) Pause() (map[string]error, error) {
return nil, err
}
- ctrErrors := make(map[string]error)
+ ctrErrChan := make(map[string]<-chan error)
- // Pause to all containers
+ // Enqueue a function for each container with the parallel executor.
for _, ctr := range allCtrs {
- ctr.lock.Lock()
+ c := ctr
+ logrus.Debugf("Adding parallel job to pause container %s", c.ID())
+ retChan := parallel.Enqueue(ctx, c.Pause)
- if err := ctr.syncContainer(); err != nil {
- ctr.lock.Unlock()
- ctrErrors[ctr.ID()] = err
- continue
- }
+ ctrErrChan[c.ID()] = retChan
+ }
- // Ignore containers that are not running
- if ctr.state.State != define.ContainerStateRunning {
- ctr.lock.Unlock()
- continue
- }
+ p.newPodEvent(events.Pause)
- if err := ctr.pause(); err != nil {
- ctr.lock.Unlock()
- ctrErrors[ctr.ID()] = err
- continue
- }
+ ctrErrors := make(map[string]error)
- ctr.lock.Unlock()
+ // Get returned error for every container we worked on
+ for id, channel := range ctrErrChan {
+ if err := <-channel; err != nil {
+ if errors.Cause(err) == define.ErrCtrStateInvalid || errors.Cause(err) == define.ErrCtrStopped {
+ continue
+ }
+ ctrErrors[id] = err
+ }
}
if len(ctrErrors) > 0 {
return ctrErrors, errors.Wrapf(define.ErrPodPartialFail, "error pausing some containers")
}
- defer p.newPodEvent(events.Pause)
return nil, nil
}
@@ -298,7 +285,7 @@ func (p *Pod) Pause() (map[string]error, error) {
// containers. The container ID is mapped to the error encountered. The error is
// set to ErrPodPartialFail.
// If both error and the map are nil, all containers were unpaused without error.
-func (p *Pod) Unpause() (map[string]error, error) {
+func (p *Pod) Unpause(ctx context.Context) (map[string]error, error) {
p.lock.Lock()
defer p.lock.Unlock()
@@ -311,38 +298,34 @@ func (p *Pod) Unpause() (map[string]error, error) {
return nil, err
}
- ctrErrors := make(map[string]error)
+ ctrErrChan := make(map[string]<-chan error)
- // Pause to all containers
+ // Enqueue a function for each container with the parallel executor.
for _, ctr := range allCtrs {
- ctr.lock.Lock()
+ c := ctr
+ logrus.Debugf("Adding parallel job to unpause container %s", c.ID())
+ retChan := parallel.Enqueue(ctx, c.Unpause)
- if err := ctr.syncContainer(); err != nil {
- ctr.lock.Unlock()
- ctrErrors[ctr.ID()] = err
- continue
- }
+ ctrErrChan[c.ID()] = retChan
+ }
- // Ignore containers that are not paused
- if ctr.state.State != define.ContainerStatePaused {
- ctr.lock.Unlock()
- continue
- }
+ p.newPodEvent(events.Unpause)
- if err := ctr.unpause(); err != nil {
- ctr.lock.Unlock()
- ctrErrors[ctr.ID()] = err
- continue
- }
+ ctrErrors := make(map[string]error)
- ctr.lock.Unlock()
+ // Get returned error for every container we worked on
+ for id, channel := range ctrErrChan {
+ if err := <-channel; err != nil {
+ if errors.Cause(err) == define.ErrCtrStateInvalid || errors.Cause(err) == define.ErrCtrStopped {
+ continue
+ }
+ ctrErrors[id] = err
+ }
}
if len(ctrErrors) > 0 {
return ctrErrors, errors.Wrapf(define.ErrPodPartialFail, "error unpausing some containers")
}
-
- defer p.newPodEvent(events.Unpause)
return nil, nil
}
@@ -411,7 +394,7 @@ func (p *Pod) Restart(ctx context.Context) (map[string]error, error) {
// containers. The container ID is mapped to the error encountered. The error is
// set to ErrPodPartialFail.
// If both error and the map are nil, all containers were signalled successfully.
-func (p *Pod) Kill(signal uint) (map[string]error, error) {
+func (p *Pod) Kill(ctx context.Context, signal uint) (map[string]error, error) {
p.lock.Lock()
defer p.lock.Unlock()
@@ -424,44 +407,36 @@ func (p *Pod) Kill(signal uint) (map[string]error, error) {
return nil, err
}
- ctrErrors := make(map[string]error)
+ ctrErrChan := make(map[string]<-chan error)
- // Send a signal to all containers
+ // Enqueue a function for each container with the parallel executor.
for _, ctr := range allCtrs {
- ctr.lock.Lock()
-
- if err := ctr.syncContainer(); err != nil {
- ctr.lock.Unlock()
- ctrErrors[ctr.ID()] = err
- continue
- }
+ c := ctr
+ logrus.Debugf("Adding parallel job to kill container %s", c.ID())
+ retChan := parallel.Enqueue(ctx, func() error {
+ return c.Kill(signal)
+ })
- // Ignore containers that are not running
- if ctr.state.State != define.ContainerStateRunning {
- ctr.lock.Unlock()
- continue
- }
+ ctrErrChan[c.ID()] = retChan
+ }
- if err := ctr.ociRuntime.KillContainer(ctr, signal, false); err != nil {
- ctr.lock.Unlock()
- ctrErrors[ctr.ID()] = err
- continue
- }
+ p.newPodEvent(events.Kill)
- logrus.Debugf("Killed container %s with signal %d", ctr.ID(), signal)
+ ctrErrors := make(map[string]error)
- ctr.state.StoppedByUser = true
- if err := ctr.save(); err != nil {
- ctrErrors[ctr.ID()] = err
+ // Get returned error for every container we worked on
+ for id, channel := range ctrErrChan {
+ if err := <-channel; err != nil {
+ if errors.Cause(err) == define.ErrCtrStateInvalid || errors.Cause(err) == define.ErrCtrStopped {
+ continue
+ }
+ ctrErrors[id] = err
}
-
- ctr.lock.Unlock()
}
if len(ctrErrors) > 0 {
return ctrErrors, errors.Wrapf(define.ErrPodPartialFail, "error killing some containers")
}
- defer p.newPodEvent(events.Kill)
return nil, nil
}
diff --git a/libpod/runtime_ctr.go b/libpod/runtime_ctr.go
index abb97293f..51b4c5f03 100644
--- a/libpod/runtime_ctr.go
+++ b/libpod/runtime_ctr.go
@@ -208,6 +208,7 @@ func (r *Runtime) setupContainer(ctx context.Context, ctr *Container) (_ *Contai
// Check CGroup parent sanity, and set it if it was not set.
// Only if we're actually configuring CGroups.
if !ctr.config.NoCgroups {
+ ctr.config.CgroupManager = r.config.Engine.CgroupManager
switch r.config.Engine.CgroupManager {
case config.CgroupfsCgroupsManager:
if ctr.config.CgroupParent == "" {