From 1efb9b5e17a076cefaa17b9f388476138d5157ad Mon Sep 17 00:00:00 2001 From: Valentin Rothberg Date: Fri, 20 Nov 2020 11:31:12 +0100 Subject: fix container cgroup lookup When running on cgroups v1, `/proc/{PID}/cgroup` has multiple entries, each pointing potentially to a different cgroup. Some may be empty, some may point to parents. The one we really need is the libpod-specific one, which always is the longest path. So instead of looking at the first entry, look at all and select the longest one. Fixes: #8397 Signed-off-by: Valentin Rothberg --- libpod/container.go | 35 ++++++++++++++++++++++++++++------- 1 file changed, 28 insertions(+), 7 deletions(-) (limited to 'libpod') diff --git a/libpod/container.go b/libpod/container.go index 4b9e6a5ba..31c958959 100644 --- a/libpod/container.go +++ b/libpod/container.go @@ -17,6 +17,7 @@ import ( "github.com/cri-o/ocicni/pkg/ocicni" spec "github.com/opencontainers/runtime-spec/specs-go" "github.com/pkg/errors" + "github.com/sirupsen/logrus" ) // CgroupfsDefaultCgroupParent is the cgroup parent for CGroupFS in libpod @@ -920,19 +921,39 @@ func (c *Container) CGroupPath() (string, error) { return "", errors.Wrapf(define.ErrNoCgroups, "this container is not creating cgroups") } - // Read /proc/[PID]/cgroup and look at the first line. cgroups(7) - // nails it down to three fields with the 3rd pointing to the cgroup's - // path which works both on v1 and v2. + // Read /proc/[PID]/cgroup and find the *longest* cgroup entry. That's + // needed to account for hacks in cgroups v1, where each line in the + // file could potentially point to a cgroup. The longest one, however, + // is the libpod-specific one we're looking for. + // + // See #8397 on the need for the longest-path look up. procPath := fmt.Sprintf("/proc/%d/cgroup", c.state.PID) lines, err := ioutil.ReadFile(procPath) if err != nil { return "", err } - fields := bytes.Split(bytes.Split(lines, []byte("\n"))[0], []byte(":")) - if len(fields) != 3 { - return "", errors.Errorf("expected 3 fields but got %d: %s", len(fields), procPath) + + var cgroupPath string + for _, line := range bytes.Split(lines, []byte("\n")) { + // cgroups(7) nails it down to three fields with the 3rd + // pointing to the cgroup's path which works both on v1 and v2. + fields := bytes.Split(line, []byte(":")) + if len(fields) != 3 { + logrus.Debugf("Error parsing cgroup: expected 3 fields but got %d: %s", len(fields), procPath) + continue + } + path := string(fields[2]) + if len(path) > len(cgroupPath) { + cgroupPath = path + } + } - return string(fields[2]), nil + + if len(cgroupPath) == 0 { + return "", errors.Errorf("could not find any cgroup in %q", procPath) + } + + return cgroupPath, nil } // RootFsSize returns the root FS size of the container -- cgit v1.2.3-54-g00ecf From dc8996ec84ffd6f272361edbf7c19e91c52519d9 Mon Sep 17 00:00:00 2001 From: Daniel J Walsh Date: Fri, 6 Nov 2020 09:55:40 -0500 Subject: Allow containers to --restart on-failure with --rm Signed-off-by: Daniel J Walsh --- cmd/podman/common/createparse.go | 2 +- libpod/container_api.go | 14 ++++++++++++++ libpod/container_internal.go | 28 +++++++++++++++------------- pkg/api/handlers/libpod/containers.go | 24 ++++++++++++++++++++++++ pkg/bindings/containers/containers.go | 12 ++++++++++++ pkg/domain/infra/abi/containers.go | 15 +++++++++++++-- pkg/domain/infra/tunnel/containers.go | 25 +++++++++++++++++++------ test/e2e/run_test.go | 4 +--- 8 files changed, 99 insertions(+), 25 deletions(-) (limited to 'libpod') diff --git a/cmd/podman/common/createparse.go b/cmd/podman/common/createparse.go index 09ee5aa0c..3a69f11b6 100644 --- a/cmd/podman/common/createparse.go +++ b/cmd/podman/common/createparse.go @@ -9,7 +9,7 @@ import ( // by validate must not need any state information on the flag (i.e. changed) func (c *ContainerCLIOpts) validate() error { var () - if c.Rm && c.Restart != "" && c.Restart != "no" { + if c.Rm && (c.Restart != "" && c.Restart != "no" && c.Restart != "on-failure") { return errors.Errorf(`the --rm option conflicts with --restart, when the restartPolicy is not "" and "no"`) } diff --git a/libpod/container_api.go b/libpod/container_api.go index a9808a30e..6a7ddc421 100644 --- a/libpod/container_api.go +++ b/libpod/container_api.go @@ -714,3 +714,17 @@ func (c *Container) Restore(ctx context.Context, options ContainerCheckpointOpti defer c.newContainerEvent(events.Restore) return c.restore(ctx, options) } + +// Indicate whether or not the container should restart +func (c *Container) ShouldRestart(ctx context.Context) bool { + logrus.Debugf("Checking if container %s should restart", c.ID()) + if !c.batched { + c.lock.Lock() + defer c.lock.Unlock() + + if err := c.syncContainer(); err != nil { + return false + } + } + return c.shouldRestart() +} diff --git a/libpod/container_internal.go b/libpod/container_internal.go index 108954bad..ffd0dc92f 100644 --- a/libpod/container_internal.go +++ b/libpod/container_internal.go @@ -206,37 +206,39 @@ func (c *Container) handleExitFile(exitFile string, fi os.FileInfo) error { return nil } -// Handle container restart policy. -// This is called when a container has exited, and was not explicitly stopped by -// an API call to stop the container or pod it is in. -func (c *Container) handleRestartPolicy(ctx context.Context) (_ bool, retErr error) { - // If we did not get a restart policy match, exit immediately. +func (c *Container) shouldRestart() bool { + // If we did not get a restart policy match, return false // Do the same if we're not a policy that restarts. if !c.state.RestartPolicyMatch || c.config.RestartPolicy == RestartPolicyNo || c.config.RestartPolicy == RestartPolicyNone { - return false, nil + return false } // If we're RestartPolicyOnFailure, we need to check retries and exit // code. if c.config.RestartPolicy == RestartPolicyOnFailure { if c.state.ExitCode == 0 { - return false, nil + return false } // If we don't have a max retries set, continue if c.config.RestartRetries > 0 { - if c.state.RestartCount < c.config.RestartRetries { - logrus.Debugf("Container %s restart policy trigger: on retry %d (of %d)", - c.ID(), c.state.RestartCount, c.config.RestartRetries) - } else { - logrus.Debugf("Container %s restart policy trigger: retries exhausted", c.ID()) - return false, nil + if c.state.RestartCount >= c.config.RestartRetries { + return false } } } + return true +} +// Handle container restart policy. +// This is called when a container has exited, and was not explicitly stopped by +// an API call to stop the container or pod it is in. +func (c *Container) handleRestartPolicy(ctx context.Context) (_ bool, retErr error) { + if !c.shouldRestart() { + return false, nil + } logrus.Debugf("Restarting container %s due to restart policy %s", c.ID(), c.config.RestartPolicy) // Need to check if dependencies are alive. diff --git a/pkg/api/handlers/libpod/containers.go b/pkg/api/handlers/libpod/containers.go index 7e6481321..14eb44831 100644 --- a/pkg/api/handlers/libpod/containers.go +++ b/pkg/api/handlers/libpod/containers.go @@ -344,3 +344,27 @@ func InitContainer(w http.ResponseWriter, r *http.Request) { } utils.WriteResponse(w, http.StatusNoContent, "") } + +func ShouldRestart(w http.ResponseWriter, r *http.Request) { + runtime := r.Context().Value("runtime").(*libpod.Runtime) + // Now use the ABI implementation to prevent us from having duplicate + // code. + containerEngine := abi.ContainerEngine{Libpod: runtime} + + name := utils.GetName(r) + report, err := containerEngine.ShouldRestart(r.Context(), name) + if err != nil { + if errors.Cause(err) == define.ErrNoSuchCtr { + utils.ContainerNotFound(w, name, err) + return + } + utils.InternalServerError(w, err) + return + + } + if report.Value { + utils.WriteResponse(w, http.StatusNoContent, "") + } else { + utils.ContainerNotFound(w, name, define.ErrNoSuchCtr) + } +} diff --git a/pkg/bindings/containers/containers.go b/pkg/bindings/containers/containers.go index b5cd2128b..4331ae6c2 100644 --- a/pkg/bindings/containers/containers.go +++ b/pkg/bindings/containers/containers.go @@ -390,3 +390,15 @@ func ContainerInit(ctx context.Context, nameOrID string) error { } return response.Process(nil) } + +func ShouldRestart(ctx context.Context, nameOrID string) (bool, error) { + conn, err := bindings.GetClient(ctx) + if err != nil { + return false, err + } + response, err := conn.DoRequest(nil, http.MethodPost, "/containers/%s/shouldrestart", nil, nil, nameOrID) + if err != nil { + return false, err + } + return response.IsSuccess(), nil +} diff --git a/pkg/domain/infra/abi/containers.go b/pkg/domain/infra/abi/containers.go index 4b69ac74e..ff4277a2e 100644 --- a/pkg/domain/infra/abi/containers.go +++ b/pkg/domain/infra/abi/containers.go @@ -911,7 +911,7 @@ func (ic *ContainerEngine) ContainerRun(ctx context.Context, opts entities.Conta } else { report.ExitCode = int(ecode) } - if opts.Rm { + if opts.Rm && !ctr.ShouldRestart(ctx) { if err := ic.Libpod.RemoveContainer(ctx, ctr, false, true); err != nil { if errors.Cause(err) == define.ErrNoSuchCtr || errors.Cause(err) == define.ErrCtrRemoved { @@ -992,7 +992,7 @@ func (ic *ContainerEngine) ContainerCleanup(ctx context.Context, namesOrIds []st return []*entities.ContainerCleanupReport{}, nil } - if options.Remove { + if options.Remove && !ctr.ShouldRestart(ctx) { err = ic.Libpod.RemoveContainer(ctx, ctr, false, true) if err != nil { report.RmErr = errors.Wrapf(err, "failed to cleanup and remove container %v", ctr.ID()) @@ -1015,6 +1015,7 @@ func (ic *ContainerEngine) ContainerCleanup(ctx context.Context, namesOrIds []st _, err = ic.Libpod.RemoveImage(ctx, ctrImage, false) report.RmiErr = err } + reports = append(reports, &report) } return reports, nil @@ -1314,3 +1315,13 @@ func (ic *ContainerEngine) ContainerStats(ctx context.Context, namesOrIds []stri return statsChan, nil } + +// ShouldRestart returns whether the container should be restarted +func (ic *ContainerEngine) ShouldRestart(ctx context.Context, nameOrID string) (*entities.BoolReport, error) { + ctr, err := ic.Libpod.LookupContainer(nameOrID) + if err != nil { + return nil, err + } + + return &entities.BoolReport{Value: ctr.ShouldRestart(ctx)}, nil +} diff --git a/pkg/domain/infra/tunnel/containers.go b/pkg/domain/infra/tunnel/containers.go index 8066e1c00..1aa5afbe7 100644 --- a/pkg/domain/infra/tunnel/containers.go +++ b/pkg/domain/infra/tunnel/containers.go @@ -595,12 +595,20 @@ func (ic *ContainerEngine) ContainerRun(ctx context.Context, opts entities.Conta // Defer the removal, so we can return early if needed and // de-spaghetti the code. defer func() { - if err := containers.Remove(ic.ClientCxt, con.ID, bindings.PFalse, bindings.PTrue); err != nil { - if errorhandling.Contains(err, define.ErrNoSuchCtr) || - errorhandling.Contains(err, define.ErrCtrRemoved) { - logrus.Warnf("Container %s does not exist: %v", con.ID, err) - } else { - logrus.Errorf("Error removing container %s: %v", con.ID, err) + shouldRestart, err := containers.ShouldRestart(ic.ClientCxt, con.ID) + if err != nil { + logrus.Errorf("Failed to check if %s should restart: %v", con.ID, err) + return + } + + if !shouldRestart { + if err := containers.Remove(ic.ClientCxt, con.ID, bindings.PFalse, bindings.PTrue); err != nil { + if errorhandling.Contains(err, define.ErrNoSuchCtr) || + errorhandling.Contains(err, define.ErrCtrRemoved) { + logrus.Warnf("Container %s does not exist: %v", con.ID, err) + } else { + logrus.Errorf("Error removing container %s: %v", con.ID, err) + } } } }() @@ -737,3 +745,8 @@ func (ic *ContainerEngine) ContainerStats(ctx context.Context, namesOrIds []stri } return containers.Stats(ic.ClientCxt, namesOrIds, &options.Stream) } + +// ShouldRestart reports back whether the containre will restart +func (ic *ContainerEngine) ShouldRestart(_ context.Context, id string) (bool, error) { + return containers.ShouldRestart(ic.ClientCxt, id) +} diff --git a/test/e2e/run_test.go b/test/e2e/run_test.go index 5ee85efb9..0d65a3e59 100644 --- a/test/e2e/run_test.go +++ b/test/e2e/run_test.go @@ -75,11 +75,9 @@ var _ = Describe("Podman run", func() { session.WaitWithDefaultTimeout() Expect(session.ExitCode()).To(Equal(0)) - // the --rm option conflicts with --restart, when the restartPolicy is not "" and "no" - // so the exitCode should not equal 0 session = podmanTest.Podman([]string{"run", "--rm", "--restart", "on-failure", ALPINE}) session.WaitWithDefaultTimeout() - Expect(session.ExitCode()).To(Not(Equal(0))) + Expect(session.ExitCode()).To(Equal(0)) session = podmanTest.Podman([]string{"run", "--rm", "--restart", "always", ALPINE}) session.WaitWithDefaultTimeout() -- cgit v1.2.3-54-g00ecf From ce775248ada62c117dd5303989052b268e6594bd Mon Sep 17 00:00:00 2001 From: Matthew Heon Date: Fri, 20 Nov 2020 13:49:40 -0500 Subject: Make c.networks() list include the default network This makes things a lot more clear - if we are actually joining a CNI network, we are guaranteed to get a non-zero length list of networks. We do, however, need to know if the network we are joining is the default network for inspecting containers as it determines how we populate the response struct. To handle this, add a bool to indicate that the network listed was the default network, and only the default network. Signed-off-by: Matthew Heon --- libpod/container.go | 20 ++++++++++++++------ libpod/container_internal.go | 9 ++------- libpod/networking_linux.go | 21 +++++++++++++-------- libpod/rootless_cni_linux.go | 4 ++-- 4 files changed, 31 insertions(+), 23 deletions(-) (limited to 'libpod') diff --git a/libpod/container.go b/libpod/container.go index 31c958959..e954d84eb 100644 --- a/libpod/container.go +++ b/libpod/container.go @@ -13,6 +13,7 @@ import ( "github.com/containers/image/v5/manifest" "github.com/containers/podman/v2/libpod/define" "github.com/containers/podman/v2/libpod/lock" + "github.com/containers/podman/v2/pkg/rootless" "github.com/containers/storage" "github.com/cri-o/ocicni/pkg/ocicni" spec "github.com/opencontainers/runtime-spec/specs-go" @@ -1095,13 +1096,17 @@ func (c *Container) Umask() string { // values at runtime via network connect and disconnect. // If the container is configured to use CNI and this function returns an empty // array, the container will still be connected to the default network. -func (c *Container) Networks() ([]string, error) { +// The second return parameter, a bool, indicates that the container container +// is joining the default CNI network - the network name will be included in the +// returned array of network names, but the container did not explicitly join +// this network. +func (c *Container) Networks() ([]string, bool, error) { if !c.batched { c.lock.Lock() defer c.lock.Unlock() if err := c.syncContainer(); err != nil { - return nil, err + return nil, false, err } } @@ -1109,19 +1114,22 @@ func (c *Container) Networks() ([]string, error) { } // Unlocked accessor for networks -func (c *Container) networks() ([]string, error) { +func (c *Container) networks() ([]string, bool, error) { networks, err := c.runtime.state.GetNetworks(c) if err != nil && errors.Cause(err) == define.ErrNoSuchNetwork { - return c.config.Networks, nil + if len(c.config.Networks) == 0 && !rootless.IsRootless() { + return []string{c.runtime.netPlugin.GetDefaultNetworkName()}, true, nil + } + return c.config.Networks, false, nil } - return networks, err + return networks, false, err } // networksByNameIndex provides us with a map of container networks where key // is network name and value is the index position func (c *Container) networksByNameIndex() (map[string]int, error) { - networks, err := c.networks() + networks, _, err := c.networks() if err != nil { return nil, err } diff --git a/libpod/container_internal.go b/libpod/container_internal.go index 108954bad..823e5fb3a 100644 --- a/libpod/container_internal.go +++ b/libpod/container_internal.go @@ -641,18 +641,13 @@ func (c *Container) removeIPv4Allocations() error { cniDefaultNetwork = c.runtime.netPlugin.GetDefaultNetworkName() } - networks, err := c.networks() + networks, _, err := c.networks() if err != nil { return err } - switch { - case len(networks) > 0 && len(networks) != len(c.state.NetworkStatus): + if len(networks) != len(c.state.NetworkStatus) { return errors.Wrapf(define.ErrInternal, "network mismatch: asked to join %d CNI networks but got %d CNI results", len(networks), len(c.state.NetworkStatus)) - case len(networks) == 0 && len(c.state.NetworkStatus) != 1: - return errors.Wrapf(define.ErrInternal, "network mismatch: did not specify CNI networks but joined more than one (%d)", len(c.state.NetworkStatus)) - case len(networks) == 0 && cniDefaultNetwork == "": - return errors.Wrapf(define.ErrInternal, "could not retrieve name of CNI default network") } for index, result := range c.state.NetworkStatus { diff --git a/libpod/networking_linux.go b/libpod/networking_linux.go index 8dce7c9fe..7a0bebd95 100644 --- a/libpod/networking_linux.go +++ b/libpod/networking_linux.go @@ -110,10 +110,15 @@ func (r *Runtime) configureNetNS(ctr *Container, ctrNS ns.NetNS) ([]*cnitypes.Re podName := getCNIPodName(ctr) - networks, err := ctr.networks() + networks, _, err := ctr.networks() if err != nil { return nil, err } + // All networks have been removed from the container. + // This is effectively forcing net=none. + if len(networks) == 0 { + return nil, nil + } // Update container map of interface descriptions if err := ctr.setupNetworkDescriptions(networks); err != nil { @@ -224,7 +229,7 @@ func (r *Runtime) setupRootlessNetNS(ctr *Container) error { if ctr.config.NetMode.IsSlirp4netns() { return r.setupSlirp4netns(ctr) } - networks, err := ctr.networks() + networks, _, err := ctr.networks() if err != nil { return err } @@ -744,13 +749,13 @@ func (r *Runtime) teardownNetNS(ctr *Container) error { logrus.Debugf("Tearing down network namespace at %s for container %s", ctr.state.NetNS.Path(), ctr.ID()) - networks, err := ctr.networks() + networks, _, err := ctr.networks() if err != nil { return err } // rootless containers do not use the CNI plugin directly - if !rootless.IsRootless() && !ctr.config.NetMode.IsSlirp4netns() { + if !rootless.IsRootless() && !ctr.config.NetMode.IsSlirp4netns() && len(networks) > 0 { var requestedIP net.IP if ctr.requestedIP != nil { requestedIP = ctr.requestedIP @@ -863,7 +868,7 @@ func (c *Container) getContainerNetworkInfo() (*define.InspectNetworkSettings, e settings := new(define.InspectNetworkSettings) settings.Ports = makeInspectPortBindings(c.config.PortMappings) - networks, err := c.networks() + networks, isDefault, err := c.networks() if err != nil { return nil, err } @@ -872,7 +877,7 @@ func (c *Container) getContainerNetworkInfo() (*define.InspectNetworkSettings, e if c.state.NetNS == nil { // We still want to make dummy configurations for each CNI net // the container joined. - if len(networks) > 0 { + if len(networks) > 0 && !isDefault { settings.Networks = make(map[string]*define.InspectAdditionalNetwork, len(networks)) for _, net := range networks { cniNet := new(define.InspectAdditionalNetwork) @@ -893,7 +898,7 @@ func (c *Container) getContainerNetworkInfo() (*define.InspectNetworkSettings, e } // If we have CNI networks - handle that here - if len(networks) > 0 { + if len(networks) > 0 && !isDefault { if len(networks) != len(c.state.NetworkStatus) { return nil, errors.Wrapf(define.ErrInternal, "network inspection mismatch: asked to join %d CNI networks but have information on %d networks", len(networks), len(c.state.NetworkStatus)) } @@ -1101,7 +1106,7 @@ func (c *Container) NetworkConnect(nameOrID, netName string, aliases []string) e return err } - ctrNetworks, err := c.networks() + ctrNetworks, _, err := c.networks() if err != nil { return err } diff --git a/libpod/rootless_cni_linux.go b/libpod/rootless_cni_linux.go index 1d6158cc2..2c2977f9f 100644 --- a/libpod/rootless_cni_linux.go +++ b/libpod/rootless_cni_linux.go @@ -40,7 +40,7 @@ const ( // // AllocRootlessCNI does not lock c. c should be already locked. func AllocRootlessCNI(ctx context.Context, c *Container) (ns.NetNS, []*cnitypes.Result, error) { - networks, err := c.networks() + networks, _, err := c.networks() if err != nil { return nil, nil, err } @@ -81,7 +81,7 @@ func AllocRootlessCNI(ctx context.Context, c *Container) (ns.NetNS, []*cnitypes. // // DeallocRootlessCNI does not lock c. c should be already locked. func DeallocRootlessCNI(ctx context.Context, c *Container) error { - networks, err := c.networks() + networks, _, err := c.networks() if err != nil { return err } -- cgit v1.2.3-54-g00ecf From 70e7acdb23715298624d123f7cc7c9dbcdf0465c Mon Sep 17 00:00:00 2001 From: Paul Holzinger Date: Mon, 23 Nov 2020 11:41:40 +0100 Subject: Fix ip-range for classless subnet masks The `LastIPInSubnet` function worked only for classful subnet masks (e.g. /8, /16, /24). For non standard subnet masks this returned the wrong ip address. This works now for all subnet mask. A unit test is added to ensure this. Signed-off-by: Paul Holzinger --- libpod/network/subnet.go | 14 +++++----- libpod/network/subnet_test.go | 62 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 69 insertions(+), 7 deletions(-) (limited to 'libpod') diff --git a/libpod/network/subnet.go b/libpod/network/subnet.go index 90f0cdfce..120038e57 100644 --- a/libpod/network/subnet.go +++ b/libpod/network/subnet.go @@ -54,14 +54,10 @@ func LastIPInSubnet(addr *net.IPNet) (net.IP, error) { //nolint:interfacer ones, bits := cidr.Mask.Size() if ones == bits { - return FirstIPInSubnet(cidr) + return cidr.IP, nil } - 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 + for i := range cidr.IP { + cidr.IP[i] = cidr.IP[i] | ^cidr.Mask[i] } return cidr.IP, nil } @@ -73,6 +69,10 @@ func FirstIPInSubnet(addr *net.IPNet) (net.IP, error) { //nolint:interfacer if err != nil { return nil, err } + ones, bits := cidr.Mask.Size() + if ones == bits { + return cidr.IP, nil + } cidr.IP[len(cidr.IP)-1]++ return cidr.IP, nil } diff --git a/libpod/network/subnet_test.go b/libpod/network/subnet_test.go index 917c3be88..55b2443bd 100644 --- a/libpod/network/subnet_test.go +++ b/libpod/network/subnet_test.go @@ -33,3 +33,65 @@ func TestNextSubnet(t *testing.T) { }) } } + +func TestFirstIPInSubnet(t *testing.T) { + tests := []struct { + name string + args *net.IPNet + want net.IP + wantErr bool + }{ + {"class b", parseCIDR("192.168.0.0/16"), net.ParseIP("192.168.0.1"), false}, + {"class c", parseCIDR("192.168.1.0/24"), net.ParseIP("192.168.1.1"), false}, + {"cidr /23", parseCIDR("192.168.0.0/23"), net.ParseIP("192.168.0.1"), false}, + {"cidr /25", parseCIDR("192.168.1.0/25"), net.ParseIP("192.168.1.1"), false}, + {"cidr /26", parseCIDR("172.16.1.128/26"), net.ParseIP("172.16.1.129"), false}, + {"class a", parseCIDR("10.0.0.0/8"), net.ParseIP("10.0.0.1"), false}, + {"cidr /32", parseCIDR("192.168.255.4/32"), net.ParseIP("192.168.255.4"), false}, + {"cidr /31", parseCIDR("192.168.255.4/31"), net.ParseIP("192.168.255.5"), false}, + } + for _, tt := range tests { + test := tt + t.Run(test.name, func(t *testing.T) { + got, err := FirstIPInSubnet(test.args) + if (err != nil) != test.wantErr { + t.Errorf("FirstIPInSubnet() error = %v, wantErr %v", err, test.wantErr) + return + } + if !got.Equal(test.want) { + t.Errorf("FirstIPInSubnet() got = %v, want %v", got, test.want) + } + }) + } +} + +func TestLastIPInSubnet(t *testing.T) { + tests := []struct { + name string + args *net.IPNet + want net.IP + wantErr bool + }{ + {"class b", parseCIDR("192.168.0.0/16"), net.ParseIP("192.168.255.255"), false}, + {"class c", parseCIDR("192.168.1.0/24"), net.ParseIP("192.168.1.255"), false}, + {"cidr /23", parseCIDR("192.168.0.0/23"), net.ParseIP("192.168.1.255"), false}, + {"cidr /25", parseCIDR("192.168.1.0/25"), net.ParseIP("192.168.1.127"), false}, + {"cidr /26", parseCIDR("172.16.1.128/26"), net.ParseIP("172.16.1.191"), false}, + {"class a", parseCIDR("10.0.0.0/8"), net.ParseIP("10.255.255.255"), false}, + {"cidr /32", parseCIDR("192.168.255.4/32"), net.ParseIP("192.168.255.4"), false}, + {"cidr /31", parseCIDR("192.168.255.4/31"), net.ParseIP("192.168.255.5"), false}, + } + for _, tt := range tests { + test := tt + t.Run(test.name, func(t *testing.T) { + got, err := LastIPInSubnet(test.args) + if (err != nil) != test.wantErr { + t.Errorf("LastIPInSubnet() error = %v, wantErr %v", err, test.wantErr) + return + } + if !got.Equal(test.want) { + t.Errorf("LastIPInSubnet() got = %v, want %v", got, test.want) + } + }) + } +} -- cgit v1.2.3-54-g00ecf From a8cb43d3a96f173a6f33f7f11325a51b55587a37 Mon Sep 17 00:00:00 2001 From: Marco Munizaga Date: Thu, 19 Nov 2020 11:21:40 -0800 Subject: Set PATH env in systemd timer. This fixes an issue where binaries that are in the path of the original podman process are not found in the transient systemd timer for healthchecks. This showed up for me on a NixOS machine since binaries are not installed in the usual places. Signed-off-by: Marco Munizaga --- libpod/healthcheck_linux.go | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'libpod') diff --git a/libpod/healthcheck_linux.go b/libpod/healthcheck_linux.go index b0f1ff35d..0ad15da09 100644 --- a/libpod/healthcheck_linux.go +++ b/libpod/healthcheck_linux.go @@ -26,6 +26,10 @@ func (c *Container) createTimer() error { if rootless.IsRootless() { cmd = append(cmd, "--user") } + path := os.Getenv("PATH") + if path != "" { + cmd = append(cmd, "--setenv=PATH="+path) + } cmd = append(cmd, "--unit", c.ID(), fmt.Sprintf("--on-unit-inactive=%s", c.HealthCheckConfig().Interval.String()), "--timer-property=AccuracySec=1s", podman, "healthcheck", "run", c.ID()) conn, err := systemd.ConnectToDBUS() -- cgit v1.2.3-54-g00ecf From 44da01f45cd941f44d2025864c91b0d2942d2a20 Mon Sep 17 00:00:00 2001 From: Jhon Honce Date: Mon, 16 Nov 2020 16:11:31 -0700 Subject: Refactor compat container create endpoint * Make endpoint compatibile with docker-py network expectations * Update specgen helper when called from compat endpoint * Update godoc on types * Add test for network/container create using docker-py method * Add syslog logging when DEBUG=1 for tests Fixes #8361 Signed-off-by: Jhon Honce --- cmd/podman/common/create_opts.go | 80 +++++++++++++++---------- libpod/networking_linux.go | 2 +- pkg/api/handlers/compat/containers_create.go | 21 ++++--- pkg/api/handlers/types.go | 13 ++-- test/apiv2/rest_api/__init__.py | 15 ++--- test/apiv2/rest_api/test_rest_v2_0_0.py | 90 ++++++++++++++++++++++------ test/apiv2/rest_api/v1_test_rest_v1_0_0.py | 4 +- test/python/docker/__init__.py | 12 +--- test/python/docker/common.py | 4 +- test/python/docker/test_images.py | 4 +- 10 files changed, 151 insertions(+), 94 deletions(-) (limited to 'libpod') diff --git a/cmd/podman/common/create_opts.go b/cmd/podman/common/create_opts.go index dc3202c7f..6dc43dbc6 100644 --- a/cmd/podman/common/create_opts.go +++ b/cmd/podman/common/create_opts.go @@ -134,7 +134,6 @@ func stringMaptoArray(m map[string]string) []string { // a specgen spec. func ContainerCreateToContainerCLIOpts(cc handlers.CreateContainerConfig, cgroupsManager string) (*ContainerCLIOpts, []string, error) { var ( - aliases []string capAdd []string cappDrop []string entrypoint *string @@ -212,7 +211,7 @@ func ContainerCreateToContainerCLIOpts(cc handlers.CreateContainerConfig, cgroup mounts = append(mounts, mount) } - //volumes + // volumes volumes := make([]string, 0, len(cc.Config.Volumes)) for v := range cc.Config.Volumes { volumes = append(volumes, v) @@ -242,16 +241,6 @@ func ContainerCreateToContainerCLIOpts(cc handlers.CreateContainerConfig, cgroup } } - // network names - endpointsConfig := cc.NetworkingConfig.EndpointsConfig - cniNetworks := make([]string, 0, len(endpointsConfig)) - for netName, endpoint := range endpointsConfig { - cniNetworks = append(cniNetworks, netName) - if len(endpoint.Aliases) > 0 { - aliases = append(aliases, endpoint.Aliases...) - } - } - // netMode nsmode, _, err := specgen.ParseNetworkNamespace(cc.HostConfig.NetworkMode.NetworkName()) if err != nil { @@ -268,8 +257,6 @@ func ContainerCreateToContainerCLIOpts(cc handlers.CreateContainerConfig, cgroup // defined when there is only one network. netInfo := entities.NetOptions{ AddHosts: cc.HostConfig.ExtraHosts, - Aliases: aliases, - CNINetworks: cniNetworks, DNSOptions: cc.HostConfig.DNSOptions, DNSSearch: cc.HostConfig.DNSSearch, DNSServers: dns, @@ -277,31 +264,58 @@ func ContainerCreateToContainerCLIOpts(cc handlers.CreateContainerConfig, cgroup PublishPorts: specPorts, } - // static IP and MAC - if len(endpointsConfig) == 1 { - for _, ep := range endpointsConfig { - // if IP address is provided - if len(ep.IPAddress) > 0 { - staticIP := net.ParseIP(ep.IPAddress) - netInfo.StaticIP = &staticIP + // network names + switch { + case len(cc.NetworkingConfig.EndpointsConfig) > 0: + var aliases []string + + endpointsConfig := cc.NetworkingConfig.EndpointsConfig + cniNetworks := make([]string, 0, len(endpointsConfig)) + for netName, endpoint := range endpointsConfig { + + cniNetworks = append(cniNetworks, netName) + + if endpoint == nil { + continue + } + if len(endpoint.Aliases) > 0 { + aliases = append(aliases, endpoint.Aliases...) } - // If MAC address is provided - if len(ep.MacAddress) > 0 { - staticMac, err := net.ParseMAC(ep.MacAddress) - if err != nil { - return nil, nil, err + } + + // static IP and MAC + if len(endpointsConfig) == 1 { + for _, ep := range endpointsConfig { + if ep == nil { + continue + } + // if IP address is provided + if len(ep.IPAddress) > 0 { + staticIP := net.ParseIP(ep.IPAddress) + netInfo.StaticIP = &staticIP + } + // If MAC address is provided + if len(ep.MacAddress) > 0 { + staticMac, err := net.ParseMAC(ep.MacAddress) + if err != nil { + return nil, nil, err + } + netInfo.StaticMAC = &staticMac } - netInfo.StaticMAC = &staticMac + break } - break } + netInfo.Aliases = aliases + netInfo.CNINetworks = cniNetworks + case len(cc.HostConfig.NetworkMode) > 0: + netInfo.CNINetworks = []string{string(cc.HostConfig.NetworkMode)} } // Note: several options here are marked as "don't need". this is based // on speculation by Matt and I. We think that these come into play later // like with start. We believe this is just a difference in podman/compat cliOpts := ContainerCLIOpts{ - //Attach: nil, // dont need? + // Attach: nil, // dont need? Authfile: "", CapAdd: append(capAdd, cc.HostConfig.CapAdd...), CapDrop: append(cappDrop, cc.HostConfig.CapDrop...), @@ -312,11 +326,11 @@ func ContainerCreateToContainerCLIOpts(cc handlers.CreateContainerConfig, cgroup CPURTPeriod: uint64(cc.HostConfig.CPURealtimePeriod), CPURTRuntime: cc.HostConfig.CPURealtimeRuntime, CPUShares: uint64(cc.HostConfig.CPUShares), - //CPUS: 0, // dont need? + // CPUS: 0, // dont need? CPUSetCPUs: cc.HostConfig.CpusetCpus, CPUSetMems: cc.HostConfig.CpusetMems, - //Detach: false, // dont need - //DetachKeys: "", // dont need + // Detach: false, // dont need + // DetachKeys: "", // dont need Devices: devices, DeviceCGroupRule: nil, DeviceReadBPs: readBps, @@ -438,7 +452,7 @@ func ContainerCreateToContainerCLIOpts(cc handlers.CreateContainerConfig, cgroup } // specgen assumes the image name is arg[0] - cmd := []string{cc.Image} + cmd := []string{cc.Config.Image} cmd = append(cmd, cc.Config.Cmd...) return &cliOpts, cmd, nil } diff --git a/libpod/networking_linux.go b/libpod/networking_linux.go index 7a0bebd95..baa11935d 100644 --- a/libpod/networking_linux.go +++ b/libpod/networking_linux.go @@ -900,7 +900,7 @@ func (c *Container) getContainerNetworkInfo() (*define.InspectNetworkSettings, e // If we have CNI networks - handle that here if len(networks) > 0 && !isDefault { if len(networks) != len(c.state.NetworkStatus) { - return nil, errors.Wrapf(define.ErrInternal, "network inspection mismatch: asked to join %d CNI networks but have information on %d networks", len(networks), len(c.state.NetworkStatus)) + return nil, errors.Wrapf(define.ErrInternal, "network inspection mismatch: asked to join %d CNI network(s) %v, but have information on %d network(s)", len(networks), networks, len(c.state.NetworkStatus)) } settings.Networks = make(map[string]*define.InspectAdditionalNetwork) diff --git a/pkg/api/handlers/compat/containers_create.go b/pkg/api/handlers/compat/containers_create.go index 4efe770b3..729639928 100644 --- a/pkg/api/handlers/compat/containers_create.go +++ b/pkg/api/handlers/compat/containers_create.go @@ -19,7 +19,6 @@ import ( func CreateContainer(w http.ResponseWriter, r *http.Request) { runtime := r.Context().Value("runtime").(*libpod.Runtime) decoder := r.Context().Value("decoder").(*schema.Decoder) - input := handlers.CreateContainerConfig{} query := struct { Name string `schema:"name"` }{ @@ -30,11 +29,15 @@ func CreateContainer(w http.ResponseWriter, r *http.Request) { errors.Wrapf(err, "failed to parse parameters for %s", r.URL.String())) return } - if err := json.NewDecoder(r.Body).Decode(&input); err != nil { + + // compatible configuration + body := handlers.CreateContainerConfig{} + if err := json.NewDecoder(r.Body).Decode(&body); err != nil { utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "Decode()")) return } - if len(input.HostConfig.Links) > 0 { + + if len(body.HostConfig.Links) > 0 { utils.Error(w, utils.ErrLinkNotSupport.Error(), http.StatusBadRequest, errors.Wrapf(utils.ErrLinkNotSupport, "bad parameter")) return } @@ -43,7 +46,7 @@ func CreateContainer(w http.ResponseWriter, r *http.Request) { utils.Error(w, "unable to obtain runtime config", http.StatusInternalServerError, errors.Wrap(err, "unable to get runtime config")) } - newImage, err := runtime.ImageRuntime().NewFromLocal(input.Image) + newImage, err := runtime.ImageRuntime().NewFromLocal(body.Config.Image) if err != nil { if errors.Cause(err) == define.ErrNoSuchImage { utils.Error(w, "No such image", http.StatusNotFound, err) @@ -54,11 +57,8 @@ func CreateContainer(w http.ResponseWriter, r *http.Request) { return } - // Add the container name to the input struct - input.Name = query.Name - - // Take input structure and convert to cliopts - cliOpts, args, err := common.ContainerCreateToContainerCLIOpts(input, rtc.Engine.CgroupManager) + // Take body structure and convert to cliopts + cliOpts, args, err := common.ContainerCreateToContainerCLIOpts(body, rtc.Engine.CgroupManager) if err != nil { utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "make cli opts()")) return @@ -69,6 +69,9 @@ func CreateContainer(w http.ResponseWriter, r *http.Request) { return } + // Override the container name in the body struct + body.Name = query.Name + ic := abi.ContainerEngine{Libpod: runtime} report, err := ic.ContainerCreate(r.Context(), sg) if err != nil { diff --git a/pkg/api/handlers/types.go b/pkg/api/handlers/types.go index 6bb5f5101..40cf16807 100644 --- a/pkg/api/handlers/types.go +++ b/pkg/api/handlers/types.go @@ -110,11 +110,12 @@ type ContainerWaitOKBody struct { } } +// CreateContainerConfig used when compatible endpoint creates a container type CreateContainerConfig struct { - Name string - dockerContainer.Config - HostConfig dockerContainer.HostConfig - NetworkingConfig dockerNetwork.NetworkingConfig + Name string // container name + dockerContainer.Config // desired container configuration + HostConfig dockerContainer.HostConfig // host dependent configuration for container + NetworkingConfig dockerNetwork.NetworkingConfig // network configuration for container } // swagger:model IDResponse @@ -253,7 +254,7 @@ func ImageDataToImageInspect(ctx context.Context, l *libpodImage.Image) (*ImageI // StdinOnce: false, Env: info.Config.Env, Cmd: info.Config.Cmd, - //Healthcheck: l.ImageData.HealthCheck, + // Healthcheck: l.ImageData.HealthCheck, // ArgsEscaped: false, // Image: "", Volumes: info.Config.Volumes, @@ -261,7 +262,7 @@ func ImageDataToImageInspect(ctx context.Context, l *libpodImage.Image) (*ImageI Entrypoint: info.Config.Entrypoint, // NetworkDisabled: false, // MacAddress: "", - //OnBuild: info.Config.OnBuild, + // OnBuild: info.Config.OnBuild, Labels: info.Labels, StopSignal: info.Config.StopSignal, // StopTimeout: nil, diff --git a/test/apiv2/rest_api/__init__.py b/test/apiv2/rest_api/__init__.py index 8100a4df5..db0257f03 100644 --- a/test/apiv2/rest_api/__init__.py +++ b/test/apiv2/rest_api/__init__.py @@ -16,19 +16,18 @@ class Podman(object): binary = os.getenv("PODMAN", "bin/podman") self.cmd = [binary, "--storage-driver=vfs"] - cgroupfs = os.getenv("CGROUP_MANAGER", "cgroupfs") + cgroupfs = os.getenv("CGROUP_MANAGER", "systemd") self.cmd.append(f"--cgroup-manager={cgroupfs}") if os.getenv("DEBUG"): self.cmd.append("--log-level=debug") + self.cmd.append("--syslog=true") self.anchor_directory = tempfile.mkdtemp(prefix="podman_restapi_") self.cmd.append("--root=" + os.path.join(self.anchor_directory, "crio")) self.cmd.append("--runroot=" + os.path.join(self.anchor_directory, "crio-run")) - os.environ["REGISTRIES_CONFIG_PATH"] = os.path.join( - self.anchor_directory, "registry.conf" - ) + os.environ["REGISTRIES_CONFIG_PATH"] = os.path.join(self.anchor_directory, "registry.conf") p = configparser.ConfigParser() p.read_dict( { @@ -40,14 +39,10 @@ class Podman(object): with open(os.environ["REGISTRIES_CONFIG_PATH"], "w") as w: p.write(w) - os.environ["CNI_CONFIG_PATH"] = os.path.join( - self.anchor_directory, "cni", "net.d" - ) + os.environ["CNI_CONFIG_PATH"] = os.path.join(self.anchor_directory, "cni", "net.d") os.makedirs(os.environ["CNI_CONFIG_PATH"], exist_ok=True) self.cmd.append("--cni-config-dir=" + os.environ["CNI_CONFIG_PATH"]) - cni_cfg = os.path.join( - os.environ["CNI_CONFIG_PATH"], "87-podman-bridge.conflist" - ) + cni_cfg = os.path.join(os.environ["CNI_CONFIG_PATH"], "87-podman-bridge.conflist") # json decoded and encoded to ensure legal json buf = json.loads( """ diff --git a/test/apiv2/rest_api/test_rest_v2_0_0.py b/test/apiv2/rest_api/test_rest_v2_0_0.py index 49e18f063..52348d4f4 100644 --- a/test/apiv2/rest_api/test_rest_v2_0_0.py +++ b/test/apiv2/rest_api/test_rest_v2_0_0.py @@ -61,9 +61,7 @@ class TestApi(unittest.TestCase): super().setUpClass() TestApi.podman = Podman() - TestApi.service = TestApi.podman.open( - "system", "service", "tcp:localhost:8080", "--time=0" - ) + TestApi.service = TestApi.podman.open("system", "service", "tcp:localhost:8080", "--time=0") # give the service some time to be ready... time.sleep(2) @@ -165,11 +163,71 @@ class TestApi(unittest.TestCase): r = requests.get(_url(ctnr("/containers/{}/logs?stdout=true"))) self.assertEqual(r.status_code, 200, r.text) - def test_post_create_compat(self): + # TODO Need to support Docker-py order of network/container creates + def test_post_create_compat_connect(self): """Create network and container then connect to network""" - net = requests.post( - PODMAN_URL + "/v1.40/networks/create", json={"Name": "TestNetwork"} + net_default = requests.post( + PODMAN_URL + "/v1.40/networks/create", json={"Name": "TestDefaultNetwork"} + ) + self.assertEqual(net_default.status_code, 201, net_default.text) + + create = requests.post( + PODMAN_URL + "/v1.40/containers/create?name=postCreate", + json={ + "Cmd": ["top"], + "Image": "alpine:latest", + "NetworkDisabled": False, + # FIXME adding these 2 lines cause: (This is sampled from docker-py) + # "network already exists","message":"container + # 01306e499df5441560d70071a54342611e422a94de20865add50a9565fd79fb9 is already connected to CNI network \"TestDefaultNetwork\": network already exists" + # "HostConfig": {"NetworkMode": "TestDefaultNetwork"}, + # "NetworkingConfig": {"EndpointsConfig": {"TestDefaultNetwork": None}}, + # FIXME These two lines cause: + # CNI network \"TestNetwork\" not found","message":"error configuring network namespace for container 369ddfa7d3211ebf1fbd5ddbff91bd33fa948858cea2985c133d6b6507546dff: CNI network \"TestNetwork\" not found" + # "HostConfig": {"NetworkMode": "TestNetwork"}, + # "NetworkingConfig": {"EndpointsConfig": {"TestNetwork": None}}, + # FIXME no networking defined cause: (note this error is from the container inspect below) + # "internal libpod error","message":"network inspection mismatch: asked to join 2 CNI network(s) [TestDefaultNetwork podman], but have information on 1 network(s): internal libpod error" + }, + ) + self.assertEqual(create.status_code, 201, create.text) + payload = json.loads(create.text) + self.assertIsNotNone(payload["Id"]) + + start = requests.post(PODMAN_URL + f"/v1.40/containers/{payload['Id']}/start") + self.assertEqual(start.status_code, 204, start.text) + + connect = requests.post( + PODMAN_URL + "/v1.40/networks/TestDefaultNetwork/connect", + json={"Container": payload["Id"]}, + ) + self.assertEqual(connect.status_code, 200, connect.text) + self.assertEqual(connect.text, "OK\n") + + inspect = requests.get(f"{PODMAN_URL}/v1.40/containers/{payload['Id']}/json") + self.assertEqual(inspect.status_code, 200, inspect.text) + + payload = json.loads(inspect.text) + self.assertFalse(payload["Config"].get("NetworkDisabled", False)) + + self.assertEqual( + "TestDefaultNetwork", + payload["NetworkSettings"]["Networks"]["TestDefaultNetwork"]["NetworkID"], ) + # TODO restore this to test, when joining multiple networks possible + # self.assertEqual( + # "TestNetwork", + # payload["NetworkSettings"]["Networks"]["TestNetwork"]["NetworkID"], + # ) + # TODO Need to support network aliases + # self.assertIn( + # "test_post_create", + # payload["NetworkSettings"]["Networks"]["TestNetwork"]["Aliases"], + # ) + + def test_post_create_compat(self): + """Create network and connect container during create""" + net = requests.post(PODMAN_URL + "/v1.40/networks/create", json={"Name": "TestNetwork"}) self.assertEqual(net.status_code, 201, net.text) create = requests.post( @@ -178,23 +236,21 @@ class TestApi(unittest.TestCase): "Cmd": ["date"], "Image": "alpine:latest", "NetworkDisabled": False, - "NetworkConfig": { - "EndpointConfig": {"TestNetwork": {"Aliases": ["test_post_create"]}} - }, + "HostConfig": {"NetworkMode": "TestNetwork"}, }, ) self.assertEqual(create.status_code, 201, create.text) payload = json.loads(create.text) self.assertIsNotNone(payload["Id"]) - # This cannot be done until full completion of the network connect - # stack and network disconnect stack are complete - # connect = requests.post( - # PODMAN_URL + "/v1.40/networks/TestNetwork/connect", - # json={"Container": payload["Id"]}, - # ) - # self.assertEqual(connect.status_code, 200, connect.text) - # self.assertEqual(connect.text, "OK\n") + inspect = requests.get(f"{PODMAN_URL}/v1.40/containers/{payload['Id']}/json") + self.assertEqual(inspect.status_code, 200, inspect.text) + payload = json.loads(inspect.text) + self.assertFalse(payload["Config"].get("NetworkDisabled", False)) + self.assertEqual( + "TestNetwork", + payload["NetworkSettings"]["Networks"]["TestNetwork"]["NetworkID"], + ) def test_commit(self): r = requests.post(_url(ctnr("/commit?container={}"))) diff --git a/test/apiv2/rest_api/v1_test_rest_v1_0_0.py b/test/apiv2/rest_api/v1_test_rest_v1_0_0.py index acd6273ef..23528a246 100644 --- a/test/apiv2/rest_api/v1_test_rest_v1_0_0.py +++ b/test/apiv2/rest_api/v1_test_rest_v1_0_0.py @@ -84,9 +84,7 @@ class TestApi(unittest.TestCase): print("\nService Stderr:\n" + stderr.decode("utf-8")) if TestApi.podman.returncode > 0: - sys.stderr.write( - "podman exited with error code {}\n".format(TestApi.podman.returncode) - ) + sys.stderr.write("podman exited with error code {}\n".format(TestApi.podman.returncode)) sys.exit(2) return super().tearDownClass() diff --git a/test/python/docker/__init__.py b/test/python/docker/__init__.py index 316b102f4..a52f0742c 100644 --- a/test/python/docker/__init__.py +++ b/test/python/docker/__init__.py @@ -39,9 +39,7 @@ class Podman(object): self.cmd.append("--root=" + os.path.join(self.anchor_directory, "crio")) self.cmd.append("--runroot=" + os.path.join(self.anchor_directory, "crio-run")) - os.environ["REGISTRIES_CONFIG_PATH"] = os.path.join( - self.anchor_directory, "registry.conf" - ) + os.environ["REGISTRIES_CONFIG_PATH"] = os.path.join(self.anchor_directory, "registry.conf") p = configparser.ConfigParser() p.read_dict( { @@ -53,14 +51,10 @@ class Podman(object): with open(os.environ["REGISTRIES_CONFIG_PATH"], "w") as w: p.write(w) - os.environ["CNI_CONFIG_PATH"] = os.path.join( - self.anchor_directory, "cni", "net.d" - ) + os.environ["CNI_CONFIG_PATH"] = os.path.join(self.anchor_directory, "cni", "net.d") os.makedirs(os.environ["CNI_CONFIG_PATH"], exist_ok=True) self.cmd.append("--cni-config-dir=" + os.environ["CNI_CONFIG_PATH"]) - cni_cfg = os.path.join( - os.environ["CNI_CONFIG_PATH"], "87-podman-bridge.conflist" - ) + cni_cfg = os.path.join(os.environ["CNI_CONFIG_PATH"], "87-podman-bridge.conflist") # json decoded and encoded to ensure legal json buf = json.loads( """ diff --git a/test/python/docker/common.py b/test/python/docker/common.py index e79d64a9b..11f512495 100644 --- a/test/python/docker/common.py +++ b/test/python/docker/common.py @@ -4,9 +4,7 @@ from test.python.docker import constant def run_top_container(client: DockerClient): - c = client.containers.create( - constant.ALPINE, command="top", detach=True, tty=True, name="top" - ) + c = client.containers.create(constant.ALPINE, command="top", detach=True, tty=True, name="top") c.start() return c.id diff --git a/test/python/docker/test_images.py b/test/python/docker/test_images.py index 7ef3d708b..1fa4aade9 100644 --- a/test/python/docker/test_images.py +++ b/test/python/docker/test_images.py @@ -78,9 +78,7 @@ class TestImages(unittest.TestCase): self.assertEqual(len(self.client.images.list()), 2) # List images with filter - self.assertEqual( - len(self.client.images.list(filters={"reference": "alpine"})), 1 - ) + self.assertEqual(len(self.client.images.list(filters={"reference": "alpine"})), 1) def test_search_image(self): """Search for image""" -- cgit v1.2.3-54-g00ecf From 1ddb19bc8e5ba036a3539fe63c6b9fb241eea099 Mon Sep 17 00:00:00 2001 From: baude Date: Mon, 23 Nov 2020 12:25:29 -0600 Subject: update container status with new results a bug was being caused by the fact that the container network results were not being updated properly. given that jhon is on PTO, this PR will replace #8362 Signed-off-by: baude --- libpod/networking_linux.go | 5 +++-- test/python/docker/__init__.py | 2 +- test/python/docker/test_containers.py | 2 +- 3 files changed, 5 insertions(+), 4 deletions(-) (limited to 'libpod') diff --git a/libpod/networking_linux.go b/libpod/networking_linux.go index baa11935d..4e7ffaf81 100644 --- a/libpod/networking_linux.go +++ b/libpod/networking_linux.go @@ -1144,8 +1144,8 @@ func (c *Container) NetworkConnect(nameOrID, netName string, aliases []string) e // build a list of network names so we can sort and // get the new name's index var networkNames []string - for netName := range networks { - networkNames = append(networkNames, netName) + for name := range networks { + networkNames = append(networkNames, name) } networkNames = append(networkNames, netName) // sort @@ -1157,6 +1157,7 @@ func (c *Container) NetworkConnect(nameOrID, netName string, aliases []string) e // populate network status copy(networkStatus[index+1:], networkStatus[index:]) networkStatus[index] = networkResults[0] + c.state.NetworkStatus = networkStatus } c.newNetworkEvent(events.NetworkConnect, netName) return c.save() diff --git a/test/python/docker/__init__.py b/test/python/docker/__init__.py index a52f0742c..351834316 100644 --- a/test/python/docker/__init__.py +++ b/test/python/docker/__init__.py @@ -60,7 +60,7 @@ class Podman(object): """ { "cniVersion": "0.3.0", - "name": "podman", + "name": "default", "plugins": [{ "type": "bridge", "bridge": "cni0", diff --git a/test/python/docker/test_containers.py b/test/python/docker/test_containers.py index 0fd419d9d..20d8417c3 100644 --- a/test/python/docker/test_containers.py +++ b/test/python/docker/test_containers.py @@ -87,7 +87,7 @@ class TestContainers(unittest.TestCase): self.assertEqual(len(containers), 2) def test_stop_container(self): - top = self.client.containers.get("top") + top = self.client.containers.get(TestContainers.topContainerId) self.assertEqual(top.status, "running") # Stop a running container and validate the state -- cgit v1.2.3-54-g00ecf From 9602e290de75bdd7fb5b42f6d36069dd19271735 Mon Sep 17 00:00:00 2001 From: Paul Holzinger Date: Tue, 24 Nov 2020 15:09:20 +0100 Subject: Fix custom mac address with a custom cni network The cni plugin `tuning` is required to set a custom mac address. This plugin is configured in the default cni config file which is packaged with podman but was not included the generated config form `podman network create`. Fixes #8385 Signed-off-by: Paul Holzinger --- libpod/network/config.go | 10 ++++++++++ libpod/network/create.go | 1 + libpod/network/netconflist.go | 7 +++++++ test/e2e/create_staticmac_test.go | 18 ++++++++++++++++++ 4 files changed, 36 insertions(+) (limited to 'libpod') diff --git a/libpod/network/config.go b/libpod/network/config.go index ce8a4446c..ce351129e 100644 --- a/libpod/network/config.go +++ b/libpod/network/config.go @@ -129,6 +129,16 @@ func (f FirewallConfig) Bytes() ([]byte, error) { return json.MarshalIndent(f, "", "\t") } +// TuningConfig describes the tuning plugin +type TuningConfig struct { + PluginType string `json:"type"` +} + +// Bytes outputs the configuration as []byte +func (f TuningConfig) Bytes() ([]byte, error) { + return json.MarshalIndent(f, "", "\t") +} + // DNSNameConfig describes the dns container name resolution plugin config type DNSNameConfig struct { PluginType string `json:"type"` diff --git a/libpod/network/create.go b/libpod/network/create.go index 387f4fcd3..7e4fc574a 100644 --- a/libpod/network/create.go +++ b/libpod/network/create.go @@ -176,6 +176,7 @@ func createBridge(name string, options entities.NetworkCreateOptions, runtimeCon plugins = append(plugins, bridge) plugins = append(plugins, NewPortMapPlugin()) plugins = append(plugins, NewFirewallPlugin()) + plugins = append(plugins, NewTuningPlugin()) // if we find the dnsname plugin or are rootless, we add configuration for it // the rootless-cni-infra container has the dnsname plugin always installed if (HasDNSNamePlugin(runtimeConfig.Network.CNIPluginDirs) || rootless.IsRootless()) && !options.DisableDNS { diff --git a/libpod/network/netconflist.go b/libpod/network/netconflist.go index 111f1715c..ee9adce14 100644 --- a/libpod/network/netconflist.go +++ b/libpod/network/netconflist.go @@ -119,6 +119,13 @@ func NewFirewallPlugin() FirewallConfig { } } +// NewTuningPlugin creates a generic tuning section +func NewTuningPlugin() TuningConfig { + return TuningConfig{ + PluginType: "tuning", + } +} + // NewDNSNamePlugin creates the dnsname config with a given // domainname func NewDNSNamePlugin(domainName string) DNSNameConfig { diff --git a/test/e2e/create_staticmac_test.go b/test/e2e/create_staticmac_test.go index adffdc1ca..1ac431da2 100644 --- a/test/e2e/create_staticmac_test.go +++ b/test/e2e/create_staticmac_test.go @@ -5,6 +5,7 @@ import ( "github.com/containers/podman/v2/pkg/rootless" . "github.com/containers/podman/v2/test/utils" + "github.com/containers/storage/pkg/stringid" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" ) @@ -45,4 +46,21 @@ var _ = Describe("Podman run with --mac-address flag", func() { Expect(result.OutputToString()).To(ContainSubstring("92:d0:c6:0a:29:34")) } }) + + It("Podman run --mac-address with custom network", func() { + net := "n1" + stringid.GenerateNonCryptoID() + session := podmanTest.Podman([]string{"network", "create", net}) + session.WaitWithDefaultTimeout() + defer podmanTest.removeCNINetwork(net) + Expect(session.ExitCode()).To(BeZero()) + + result := podmanTest.Podman([]string{"run", "--network", net, "--mac-address", "92:d0:c6:00:29:34", ALPINE, "ip", "addr"}) + result.WaitWithDefaultTimeout() + if rootless.IsRootless() { + Expect(result.ExitCode()).To(Equal(125)) + } else { + Expect(result.ExitCode()).To(Equal(0)) + Expect(result.OutputToString()).To(ContainSubstring("92:d0:c6:00:29:34")) + } + }) }) -- cgit v1.2.3-54-g00ecf From 5231997f9763265873a24691ec1ac4f842f4744b Mon Sep 17 00:00:00 2001 From: Qi Wang Date: Tue, 24 Nov 2020 14:17:49 -0500 Subject: Not use local image create/add manifest Avoid using the image from load storage for `manifest create` and `manifest add` since the local image does not include other entries of the list from the registry. `--all` flag of `manifest create` and `manifest add` can not add all of the lists as expected. Signed-off-by: Qi Wang --- libpod/image/manifests.go | 36 ++++++++++-------------------------- 1 file changed, 10 insertions(+), 26 deletions(-) (limited to 'libpod') diff --git a/libpod/image/manifests.go b/libpod/image/manifests.go index 59678fdb2..14f7c2f83 100644 --- a/libpod/image/manifests.go +++ b/libpod/image/manifests.go @@ -2,13 +2,14 @@ package image import ( "context" + "fmt" "github.com/containers/buildah/manifests" + "github.com/containers/image/v5/docker" "github.com/containers/image/v5/manifest" "github.com/containers/image/v5/transports/alltransports" "github.com/containers/image/v5/types" "github.com/opencontainers/go-digest" - "github.com/pkg/errors" ) // Options for adding a manifest @@ -69,19 +70,10 @@ func CreateManifestList(rt *Runtime, systemContext types.SystemContext, names [] list := manifests.Create() opts := ManifestAddOpts{Images: names, All: all} for _, img := range imgs { - var ref types.ImageReference - newImage, err := rt.NewFromLocal(img) - if err == nil { - ir, err := newImage.toImageRef(context.Background()) - if err != nil { - return "", err - } - if ir == nil { - return "", errors.New("unable to convert image to ImageReference") - } - ref = ir.Reference() - } else { - ref, err = alltransports.ParseImageName(img) + ref, err := alltransports.ParseImageName(img) + if err != nil { + dockerPrefix := fmt.Sprintf("%s://", docker.Transport.Name()) + ref, err = alltransports.ParseImageName(fmt.Sprintf("%s%s", dockerPrefix, img)) if err != nil { return "", err } @@ -134,18 +126,10 @@ func addManifestToList(ref types.ImageReference, list manifests.List, systemCont // AddManifest adds a manifest to a given manifest list. func (i *Image) AddManifest(systemContext types.SystemContext, opts ManifestAddOpts) (string, error) { - var ( - ref types.ImageReference - ) - newImage, err := i.imageruntime.NewFromLocal(opts.Images[0]) - if err == nil { - ir, err := newImage.toImageRef(context.Background()) - if err != nil { - return "", err - } - ref = ir.Reference() - } else { - ref, err = alltransports.ParseImageName(opts.Images[0]) + ref, err := alltransports.ParseImageName(opts.Images[0]) + if err != nil { + dockerPrefix := fmt.Sprintf("%s://", docker.Transport.Name()) + ref, err = alltransports.ParseImageName(fmt.Sprintf("%s%s", dockerPrefix, opts.Images[0])) if err != nil { return "", err } -- cgit v1.2.3-54-g00ecf From 2f7bca0685c611d118db98f4c1a5f06370a9e286 Mon Sep 17 00:00:00 2001 From: Paul Holzinger Date: Thu, 26 Nov 2020 17:15:06 +0100 Subject: Fix problems with network remove First, make sure we are only trying to remove the network interface if we are root. Second, if we cannot get the interface name (e.g macvlan config) then we should not fail. Just remove the config file. Signed-off-by: Paul Holzinger --- libpod/network/files.go | 5 ++++- libpod/network/network.go | 28 +++++++++++++++++----------- test/e2e/network_test.go | 11 +++++++++++ 3 files changed, 32 insertions(+), 12 deletions(-) (limited to 'libpod') diff --git a/libpod/network/files.go b/libpod/network/files.go index 846e5c62d..7f1e3ee18 100644 --- a/libpod/network/files.go +++ b/libpod/network/files.go @@ -14,6 +14,9 @@ import ( "github.com/pkg/errors" ) +// ErrNoSuchNetworkInterface indicates that no network interface exists +var ErrNoSuchNetworkInterface = errors.New("unable to find interface name for network") + // GetCNIConfDir get CNI configuration directory func GetCNIConfDir(configArg *config.Config) string { if len(configArg.Network.NetworkConfigDir) < 1 { @@ -142,7 +145,7 @@ func GetInterfaceNameFromConfig(path string) (string, error) { } } if len(name) == 0 { - return "", errors.New("unable to find interface name for network") + return "", ErrNoSuchNetworkInterface } return name, nil } diff --git a/libpod/network/network.go b/libpod/network/network.go index 7327a1a7d..0febb52f6 100644 --- a/libpod/network/network.go +++ b/libpod/network/network.go @@ -10,6 +10,7 @@ import ( "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/rootless" "github.com/containers/podman/v2/pkg/util" "github.com/pkg/errors" "github.com/sirupsen/logrus" @@ -181,21 +182,26 @@ func RemoveNetwork(config *config.Config, name string) error { // 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) + if err == nil { + // Don't try to remove the network interface if we are not root + if !rootless.IsRootless() { + 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) + } + } } + } else if err != ErrNoSuchNetworkInterface { + // Don't error if we couldn't find the network interface name + return err } // Remove the configuration file if err := os.Remove(cniPath); err != nil { - return errors.Wrapf(err, "failed to remove network configuration file %q", cniPath) + return errors.Wrap(err, "failed to remove network configuration") } return nil } diff --git a/test/e2e/network_test.go b/test/e2e/network_test.go index 139a90ac7..adcf74f7e 100644 --- a/test/e2e/network_test.go +++ b/test/e2e/network_test.go @@ -499,4 +499,15 @@ var _ = Describe("Podman network", func() { exec.WaitWithDefaultTimeout() Expect(exec.ExitCode()).To(BeZero()) }) + + It("podman network create/remove macvlan", func() { + net := "macvlan" + stringid.GenerateNonCryptoID() + nc := podmanTest.Podman([]string{"network", "create", "--macvlan", "lo", net}) + nc.WaitWithDefaultTimeout() + Expect(nc.ExitCode()).To(Equal(0)) + + nc = podmanTest.Podman([]string{"network", "rm", net}) + nc.WaitWithDefaultTimeout() + Expect(nc.ExitCode()).To(Equal(0)) + }) }) -- cgit v1.2.3-54-g00ecf From 3c6dca2f873b14c1b046db993580a90c2af323c8 Mon Sep 17 00:00:00 2001 From: Giuseppe Scrivano Date: Fri, 27 Nov 2020 14:33:42 +0100 Subject: runtime: set XDG_* env variables if missing regression introduced when moving to Podman 2.0. Closes: https://bugzilla.redhat.com/show_bug.cgi?id=1877228 Signed-off-by: Giuseppe Scrivano --- libpod/runtime.go | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'libpod') diff --git a/libpod/runtime.go b/libpod/runtime.go index 792492db6..df3dfae2b 100644 --- a/libpod/runtime.go +++ b/libpod/runtime.go @@ -162,6 +162,10 @@ func newRuntimeFromConfig(ctx context.Context, conf *config.Config, options ...R runtime.config = conf + if err := SetXdgDirs(); err != nil { + return nil, err + } + storeOpts, err := storage.DefaultStoreOptions(rootless.IsRootless(), rootless.GetRootlessUID()) if err != nil { return nil, err -- cgit v1.2.3-54-g00ecf