From 3667a47a30c28eb18715e3a12a3f4a230d2f8165 Mon Sep 17 00:00:00 2001 From: TheDiveO <6920158+thediveo@users.noreply.github.com> Date: Thu, 18 Jan 2024 17:14:34 +0100 Subject: [PATCH 1/2] fix: libpod API perf issue due to using /info instead of /_ping (#40) * test: log list-network timing for Docker, podman Signed-off-by: thediveo * perf: don't use /info libpod endpoint for API version detection, as it is very slow Signed-off-by: thediveo * fix: use _ping endpoint for libpod API version detection Signed-off-by: thediveo * chore: refactoring Signed-off-by: thediveo * fix: podmannet test failing on Ubuntu 22.04LTS Signed-off-by: thediveo --------- Signed-off-by: thediveo --- decorator/podmannet/_test/pind/Dockerfile | 13 ++++++++ decorator/podmannet/libpodclient.go | 34 ++++++++++----------- decorator/podmannet/podmannet.go | 8 +---- decorator/podmannet/podmannet_test.go | 36 +++++++++++------------ defs_version.go | 2 +- 5 files changed, 48 insertions(+), 45 deletions(-) diff --git a/decorator/podmannet/_test/pind/Dockerfile b/decorator/podmannet/_test/pind/Dockerfile index bf0b1a1..f263829 100644 --- a/decorator/podmannet/_test/pind/Dockerfile +++ b/decorator/podmannet/_test/pind/Dockerfile @@ -8,4 +8,17 @@ RUN dnf -y install \ dnf clean all && \ rm -rf /var/cache /var/log/dnf* /var/log/yum.* && \ systemctl enable podman.socket +RUN echo $'[containers]\n\ +netns="host"\n\ +userns="host"\n\ +ipcns="host"\n\ +utsns="host"\n\ +cgroupns="host"\n\ +cgroups="disabled"\n\ +log_driver = "k8s-file"\n\ +[engine]\n\ +cgroup_manager = "cgroupfs"\n\ +events_logger="file"\n\ +runtime="crun"\n\ +' > /etc/containers/containers.conf CMD [ "/usr/sbin/init" ] diff --git a/decorator/podmannet/libpodclient.go b/decorator/podmannet/libpodclient.go index ba05be9..0f790d4 100644 --- a/decorator/podmannet/libpodclient.go +++ b/decorator/podmannet/libpodclient.go @@ -80,11 +80,11 @@ func (c *Client) Close() error { // this seems to be version-independent, but still needs any version in its // endpoint path. func (c *Client) apiPath(apipath string) string { - if c.libpodVersion == "" { + if apipath == "/_ping" { // use only for initial libpod info (API version) retrieval; please note - // that all libpod API endpoints are versioned, there are not + // that all libpod API endpoints are versioned, there are no // un-versioned endpoints like the Docker API does. - return path.Join("/v0/libpod", apipath) + return apipath } return path.Join("/v"+c.libpodVersion+"/libpod", apipath) } @@ -121,24 +121,20 @@ func ensureReaderClosed(resp *http.Response) { resp.Body.Close() } -// essentialLibpodInformation grabs just the API version information from the -// JSON salad returned by a “/vX/libpod/info” endpoint. -type essentialLibpodInformation struct { - Version struct { - APIVersion string // major.minor.patch, without "v" prefix - } `json:"version"` -} - -// info returns the “essential” libpod information, that is, the libpod API -// version. -func (c *Client) info(ctx context.Context) (essentialLibpodInformation, error) { - resp, err := c.get(ctx, "/info") - var info essentialLibpodInformation +// ping the /_ping API endpoint (which is unversioned) and return the value of +// the “Libpod-Api-Version” header that came back from this endpoint, or an +// empty string. The libpod API version is in semver format, without any “v” +// prefix. +// +// Use the returned API version to set Client.libpodVersion so that following +// libpod endpoint calls are properly versioned. +func (c *Client) ping(ctx context.Context) (libpodAPIVersion string) { + resp, err := c.get(ctx, "/_ping") + defer ensureReaderClosed(resp) if err != nil { - return info, err + return "" } - err = json.NewDecoder(resp.Body).Decode(&info) - return info, err + return resp.Header.Get("Libpod-Api-Version") } // NetworkResource grabs just the few things from a podman network we're diff --git a/decorator/podmannet/podmannet.go b/decorator/podmannet/podmannet.go index 5ba14ec..f604792 100644 --- a/decorator/podmannet/podmannet.go +++ b/decorator/podmannet/podmannet.go @@ -53,13 +53,7 @@ func makePodmanNetworks(ctx context.Context, engine *model.ContainerEngine, alln engine.API, err.Error()) return } - info, err := libpodclient.info(ctx) - if err != nil { - log.Warnf("cannot discover podman-managed networks from API %s, reason: %s", - engine.API, err.Error()) - return - } - libpodclient.libpodVersion = info.Version.APIVersion + libpodclient.libpodVersion = libpodclient.ping(ctx) networks, _ := libpodclient.networkList(ctx) _ = libpodclient.Close() netnsid, _ := ops.NamespacePath(fmt.Sprintf("/proc/%d/ns/net", engine.PID)).ID() diff --git a/decorator/podmannet/podmannet_test.go b/decorator/podmannet/podmannet_test.go index 369211c..e813b72 100644 --- a/decorator/podmannet/podmannet_test.go +++ b/decorator/podmannet/podmannet_test.go @@ -13,13 +13,14 @@ import ( "github.com/ory/dockertest/v3" "github.com/ory/dockertest/v3/docker" "github.com/siemens/ghostwire/v2/internal/discover" + "github.com/siemens/ghostwire/v2/network" "github.com/siemens/turtlefinder" + "github.com/thediveo/lxkns/model" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" . "github.com/onsi/gomega/gleak" . "github.com/thediveo/fdooze" - "github.com/thediveo/lxkns/model" . "github.com/thediveo/success" ) @@ -29,8 +30,8 @@ const ( pindName = "ghostwire-pind" pindImageName = "siemens/ghostwire-pind" - spinupTimeout = 10 * time.Second - spinupPolling = 500 * time.Millisecond + nifDiscoveryTimeout = 5 * time.Second + nifDiscoveryPolling = 250 * time.Millisecond goroutinesUnwindTimeout = 2 * time.Second goroutinesUnwindPolling = 250 * time.Millisecond @@ -92,9 +93,9 @@ var _ = Describe("turtle finder", Ordered, Serial, func() { Repository: pindImageName, Privileged: true, Mounts: []string{ - "/var", // well, this actually is an unnamed volume + "/var/lib/containers", // well, this actually is an unnamed volume }, - Tty: true, + Tty: false, }, func(hc *docker.HostConfig) { hc.Init = false hc.Tmpfs = map[string]string{ @@ -138,7 +139,11 @@ var _ = Describe("turtle finder", Ordered, Serial, func() { By("running a canary container connected to the default 'podman' network") Expect(pindCntr.Exec([]string{ - "podman", "run", "-d", "-it", "--rm", "--name", "canary", "busybox", + "podman", "run", "-d", "--rm", + "--name", "canary", + "--net", "podman", /* WHAT?? otherwise doesn't connect the container??? */ + "busybox", + "/bin/sh", "-c", "while true; do sleep 1; done", }, dockertest.ExecOptions{ StdOut: GinkgoWriter, StdErr: GinkgoWriter, @@ -172,18 +177,12 @@ var _ = Describe("turtle finder", Ordered, Serial, func() { defer cizer.Close() By("running a full Ghostwire discovery that should pick up the podman networks") - allnetns, lxknsdisco := discover.Discover(ctx, cizer, nil) - Expect(lxknsdisco.Processes).To(HaveKey(model.PIDType(pindCntr.Container.State.Pid))) - pindNetnsID := lxknsdisco.Processes[model.PIDType(pindCntr.Container.State.Pid)]. - Namespaces[model.NetNS].ID() - Expect(pindNetnsID).NotTo(BeZero()) - Expect(allnetns).To(HaveKey(pindNetnsID)) - pindNetns := allnetns[pindNetnsID] - // We expect the following network interfaces to be present inside our - // podman-in-docker container: - // - eth0 ... a.k.a. the "mcwielahm" network - // - podman0 ... a.k.a. the "podman" network - Expect(pindNetns.Nifs).To(ContainElements( + Eventually(ctx, func() map[int]network.Interface { + allnetns, lxknsdisco := discover.Discover(ctx, cizer, nil) + pindNetnsID := lxknsdisco.Processes[model.PIDType(pindCntr.Container.State.Pid)]. + Namespaces[model.NetNS].ID() + return allnetns[pindNetnsID].Nifs + }).Within(nifDiscoveryPolling).ProbeEvery(nifDiscoveryPolling).Should(ContainElements( HaveField("Nif()", And( HaveField("Name", "eth0"), HaveField("Alias", "mcwielahm"))), @@ -191,6 +190,7 @@ var _ = Describe("turtle finder", Ordered, Serial, func() { HaveField("Name", "podman0"), HaveField("Alias", "podman"))), )) + }) }) diff --git a/defs_version.go b/defs_version.go index 843e2b9..c6109bc 100644 --- a/defs_version.go +++ b/defs_version.go @@ -4,4 +4,4 @@ package gostwire // SemVersion is the semantic version string of the ghostwire module. -const SemVersion = "2.1.18-12-gede2f48" +const SemVersion = "2.3.0-4-g1927622" From 5e4223e07b73baaff44989f117ee9fcade45d671 Mon Sep 17 00:00:00 2001 From: thediveo Date: Thu, 1 Feb 2024 14:35:53 +0100 Subject: [PATCH 2/2] fix: crash in UI when VXLAN underlay is unknown, fixes #39 Signed-off-by: thediveo --- webui/src/models/gw/model.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/webui/src/models/gw/model.ts b/webui/src/models/gw/model.ts index 5019193..07fe9f5 100644 --- a/webui/src/models/gw/model.ts +++ b/webui/src/models/gw/model.ts @@ -286,10 +286,12 @@ export const fromjson = (jsondata: JSONObject) => { // this VXLAN is the overlay, resolve our underlay reference, // and then backlink the underlay to us (the overlay). nif.underlay = nifmap[(jnif.vxlan as JSONObject).idref as string] - if (!nif.underlay.overlays) { - nif.underlay.overlays = [] + if (nif.underlay) { + if (!nif.underlay.overlays) { + nif.underlay.overlays = [] + } + nif.underlay.overlays.push(nif) } - nif.underlay.overlays.push(nif) } // TAP/TUNs don't reference other network interfaces, but processes // ... but hey. we need to resolve this relation, too!