diff --git a/Makefile b/Makefile index 2c46e44be5..1867b3e30a 100644 --- a/Makefile +++ b/Makefile @@ -252,16 +252,19 @@ QEMU_OPTS_TPM=$(QEMU_OPTS_TPM_$(TPM:%=Y)_$(ZARCH)) QEMU_OPTS_amd64=-smbios type=1,serial=$(QEMU_EVE_SERIAL) QEMU_OPTS_arm64=-smbios type=1,serial=$(QEMU_EVE_SERIAL) -drive file=fat:rw:$(dir $(DEVICETREE_DTB)),label=QEMU_DTB,format=vvfat QEMU_OPTS_riscv64=-kernel $(UBOOT_IMG)/u-boot.bin -device virtio-blk,drive=uefi-disk -QEMU_OPTS_COMMON= -m $(QEMU_MEMORY) -smp 4 -display none $(QEMU_OPTS_BIOS) \ +QEMU_OPTS_NO_DISPLAY=-display none +QEMU_OPTS_VGA_DISPLAY=-vga std +QEMU_OPTS_COMMON= -m $(QEMU_MEMORY) -smp 4 $(QEMU_OPTS_BIOS) \ -serial mon:stdio \ - -global ICH9-LPC.noreboot=false -watchdog-action reset \ + -global ICH9-LPC.noreboot=false -watchdog-action reset \ -rtc base=utc,clock=rt \ -netdev user,id=eth0,net=$(QEMU_OPTS_NET1),dhcpstart=$(QEMU_OPTS_NET1_FIRST_IP),hostfwd=tcp::$(SSH_PORT)-:22$(QEMU_TFTP_OPTS) -device virtio-net-pci,netdev=eth0,romfile="" \ -netdev user,id=eth1,net=$(QEMU_OPTS_NET2),dhcpstart=$(QEMU_OPTS_NET2_FIRST_IP) -device virtio-net-pci,netdev=eth1,romfile="" \ -device nec-usb-xhci,id=xhci \ -qmp unix:$(CURDIR)/qmp.sock,server,wait=off QEMU_OPTS_CONF_PART=$(shell [ -d "$(CONF_PART)" ] && echo '-drive file=fat:rw:$(CONF_PART),format=raw') -QEMU_OPTS=$(QEMU_OPTS_COMMON) $(QEMU_ACCEL) $(QEMU_OPTS_$(ZARCH)) $(QEMU_OPTS_CONF_PART) $(QEMU_OPTS_TPM) +QEMU_OPTS=$(QEMU_OPTS_NO_DISPLAY) $(QEMU_OPTS_COMMON) $(QEMU_ACCEL) $(QEMU_OPTS_$(ZARCH)) $(QEMU_OPTS_CONF_PART) $(QEMU_OPTS_TPM) +QEMU_OPTS_GUI=$(QEMU_OPTS_VGA_DISPLAY) $(QEMU_OPTS_COMMON) $(QEMU_ACCEL) $(QEMU_OPTS_$(ZARCH)) $(QEMU_OPTS_CONF_PART) $(QEMU_OPTS_TPM) # -device virtio-blk-device,drive=image -drive if=none,id=image,file=X # -device virtio-net-device,netdev=user0 -netdev user,id=user0,hostfwd=tcp::1234-:22 @@ -566,6 +569,8 @@ run-installer-net: $(BIOS_IMG) $(IPXE_IMG) $(DEVICETREE_DTB) $(SWTPM) GETTY # run MUST NOT change the current dir; it depends on the output being correct from a previous build run-live run: $(BIOS_IMG) $(DEVICETREE_DTB) $(SWTPM) GETTY $(QEMU_SYSTEM) $(QEMU_OPTS) -drive file=$(CURRENT_IMG),format=$(IMG_FORMAT),id=uefi-disk +run-live-gui run: $(BIOS_IMG) $(DEVICETREE_DTB) $(SWTPM) GETTY + $(QEMU_SYSTEM) $(QEMU_OPTS_GUI) -drive file=$(CURRENT_IMG),format=$(IMG_FORMAT),id=uefi-disk run-target: $(BIOS_IMG) $(DEVICETREE_DTB) $(SWTPM) GETTY $(QEMU_SYSTEM) $(QEMU_OPTS) -drive file=$(TARGET_IMG),format=$(IMG_FORMAT) diff --git a/docs/LOCAL-TUI.md b/docs/LOCAL-TUI.md new file mode 100644 index 0000000000..f2f5867e31 --- /dev/null +++ b/docs/LOCAL-TUI.md @@ -0,0 +1,41 @@ +# TUI (Text User Interface) for the local operator + +EVE has a user-friendly TUI (Text User Interface) that can be used to interact with the system. +The implementation is consists of two parts + +1. Client application responsible for rendering the TUI, sending user input to the server, and handling asynchronous server notification. The client is written in Rust and hosted at [https://github.com/lf-edge/eve-monitor-rs](https://github.com/lf-edge/eve-monitor-rs). Corresponding Dockerfile and LinuxKit build files are located at `pkg/monitor` +2. Server part is implemented inside [pkg/pillar/cmd/monitor](../pkg/pillar/cmd/monitor/) + +The client communicates with the server over UNIX socket + +## TTY and serial console + +The UI is rendered on a local TTY (/dev/tty2) only i.e. on a physical monitor attached to the system. Neither Serial Console nor SSH connection has access to TUI. It is done to ensure the physical presence of the operator. + +## /dev/ttyX vs /dev/console + +There are two distinguishable console devices in Linux `/dev/console` and `/dev/ttyX`. The later points to a particular virtual terminal device and the former points to *currently active* TTY device. The user can switch between virtual terminals by using `Alt+Fx` or `Alt+<,>` keys. When the current TTY is set `/dev/console` tracks this change and always points to to the current terminal + +Monitor application is spawned on a `/dev/tty2` using a `openvt` utility while the rest of the applications are spawned on the default kernel console defined by `console=` parameters on the kernel command line. When the application is in focus (`/dev/tty2` is an active console) writing to `/dev/console` which points to the same device corrupts TUI thus it cannot be used by other services in the system to produce the output. At least when `/dev/tty2` is a current console. + +From the other hand `/dev/tty` (no digit at the end!) device always points to a TTY *in the context of running process*. This device can be used instead of `/dev/console` by other services for the output. + +## Limitations of linux terminal + +Rust application can be built and run on Linux host for testing and development purposes. When running on a host its terminal is used for rendering. Host terminals ( e.g. `TERM=xterm`) are very different in capabilities compared to the built in Linux terminal which is used for `/dev/ttyX` (`TERM=linux`) devices. The major differences important for monitor application are + +* Number of supported colors. + + `linux` terminal can use only 8 colors for foreground and 8 colors for background colors. In contrast host terminals can easily display 256 colors and more + +* Limited number of pseudo-graphics glyphs. + + These limitations can be relaxed by using a custom font with 256 glyphs compared to the standard one that uses 512 glyphs. In this case an extra bit can be used to render 16 colors. Besides, extra pseudo-graphics glyphs can be added instead of unused characters to display e.g. rounded boxes. + + As of now a standard font is used so the look of the application on the host and on EVE is different + +* Key handling. + + By default `linux` terminal cannot properly handle many key combinations e.g. `PgDwn`, `Ctrl+left, Ctrl + right`, etc. A custom key map must be set to properly handle required combinations. It is done in [pkg/monitor/run-monitor.sh](../pkg/monitor/run-monitor.sh) by calling `loadkeys` utility + + As of now only `Ctrl + [left|right|up|down]` are properly handled. diff --git a/images/rootfs.yml.in b/images/rootfs.yml.in index 49c93275e2..98d171bfdf 100644 --- a/images/rootfs.yml.in +++ b/images/rootfs.yml.in @@ -42,6 +42,10 @@ onboot: - name: measure-config image: MEASURE_CONFIG_TAG services: + - name: monitor + image: MONITOR_TAG + cgroupsPath: /eve/services/monitor + oomScoreAdj: -999 - name: newlogd image: NEWLOGD_TAG cgroupsPath: /eve/services/newlogd diff --git a/pkg/monitor/Dockerfile b/pkg/monitor/Dockerfile new file mode 100644 index 0000000000..ad8c9a6a45 --- /dev/null +++ b/pkg/monitor/Dockerfile @@ -0,0 +1,78 @@ +# Copyright (c) 2024 Zededa, Inc. +# SPDX-License-Identifier: Apache-2.0 + +ARG MONITOR_RS_VERSION=v0.1.1 +ARG RUST_VERSION=lfedge/eve-rust:1.80.1 +FROM --platform=$BUILDPLATFORM ${RUST_VERSION} AS toolchain-base +ARG TARGETARCH + +FROM toolchain-base AS target-amd64 +ENV CARGO_BUILD_TARGET="x86_64-unknown-linux-musl" + +FROM toolchain-base AS target-arm64 +ENV CARGO_BUILD_TARGET="aarch64-unknown-linux-musl" + +FROM toolchain-base AS target-riscv64 +ENV CARGO_BUILD_TARGET="riscv64gc-unknown-linux-gnu" + +FROM target-$TARGETARCH AS toolchain +ARG MONITOR_RS_VERSION +RUN echo "Cargo target: $CARGO_BUILD_TARGET" + +FROM toolchain AS planer +ADD --keep-git-dir=true https://github.com/lf-edge/eve-monitor-rs.git#${MONITOR_RS_VERSION} /app + +WORKDIR /app +# create a recipe +RUN cargo chef prepare --recipe-path recipe.json + +FROM toolchain AS cacher +# copy the recipe +WORKDIR /app +COPY --from=planer /app/recipe.json recipe.json +# build the dependencies +RUN cargo chef cook --release --recipe-path recipe.json + +# building the final image +FROM toolchain AS builder +ADD --keep-git-dir=true https://github.com/lf-edge/eve-monitor-rs.git#${MONITOR_RS_VERSION} /app +WORKDIR /app + +# copy prebuilt dependencies +# and the cargo directory with crates.io index +COPY --from=cacher /app/target /app/target +COPY --from=cacher $CARGO_HOME $CARGO_HOME +RUN echo "Cargo target: $CARGO_BUILD_TARGET" + +RUN cargo build --release +RUN cargo sbom > sbom.spdx.json +RUN cp /app/target/$CARGO_BUILD_TARGET/release/monitor /app/target/ + + +FROM lfedge/eve-alpine:591df01e581889c3027514c8a91feaca1c8ad49f AS runtime +ENV PKGS="kbd pciutils usbutils" +RUN eve-alpine-deploy.sh + +FROM scratch +COPY --from=runtime /out/usr/bin/openvt /usr/bin/openvt +COPY --from=runtime /out/usr/bin/loadkeys /usr/bin/loadkeys +COPY --from=runtime /out/bin/dmesg /usr/bin/dmesg +COPY --from=runtime /out/usr/bin/lsusb /usr/bin/lsusb +COPY --from=runtime /out/usr/bin/lspci /usr/bin/lspci +COPY --from=runtime /out/usr/share/keymaps/xkb/us.map.gz /usr/share/keymaps/xkb/us.map.gz +COPY --from=runtime /out/lib /lib +COPY --from=runtime /out/usr/lib /usr/lib + +# copy busybox and install all symbolic links but just for debugging +COPY --from=runtime /out/bin/busybox /bin/busybox +# using an 'exec' form of RUN, shell form of 'RUN' +# command expects /bin/sh which doesn't yet exist +RUN [ "/bin/busybox", "--install", "-s", "/bin" ] +COPY --from=runtime /out/usr/bin/du /usr/bin/du + +COPY --from=builder /app/target/monitor /sbin/monitor +COPY --from=builder /app/sbom.spdx.json /sbin/sbom.spdx.json +COPY run-monitor.sh /sbin/run-monitor.sh +COPY monitor-wrapper.sh /sbin/monitor-wrapper.sh + +CMD ["/sbin/run-monitor.sh"] diff --git a/pkg/monitor/build.yml b/pkg/monitor/build.yml new file mode 100644 index 0000000000..2019432b3f --- /dev/null +++ b/pkg/monitor/build.yml @@ -0,0 +1,57 @@ +# Copyright (c) 2024 Zededa, Inc. +# SPDX-License-Identifier: Apache-2.0 + +org: lfedge +image: eve-monitor +network: yes +config: + pid: host + binds: + # for tty/console devices + - /dev:/dev + # for temporary files and UNIX socket + - /run:/run + # to save logs + - /persist:/persist:rshared,rbind + + devices: + - path: "/dev/tty" + type: c + major: 5 + minor: 0 + mode: 0666 + - path: "/dev/console" + type: c + major: 5 + minor: 1 + mode: 0666 + - path: "/dev/tty0" + type: c + major: 4 + minor: 0 + mode: 0666 + - path: "/dev/ttyS0" + type: c + major: 4 + minor: 64 + mode: 0666 + - path: "/dev/ttyAMA0" + type: c + major: 204 + minor: 64 + mode: 0666 + # we run the monitor on tty2 + - path: "/dev/tty2" + type: c + major: 4 + minor: 2 + mode: 0666 + # direct access to the kernel log + - path: "/dev/kmsg" + type: c + major: 1 + minor: 11 + mode: 0660 + capabilities: + - all + rootfsPropagation: shared diff --git a/pkg/monitor/monitor-wrapper.sh b/pkg/monitor/monitor-wrapper.sh new file mode 100755 index 0000000000..afb40ff332 --- /dev/null +++ b/pkg/monitor/monitor-wrapper.sh @@ -0,0 +1,9 @@ +#!/bin/sh + +# Copyright (c) 2024 Zededa, Inc. +# SPDX-License-Identifier: Apache-2.0 + +/sbin/monitor +# wait for key press so user can see the panic info +# shellcheck disable=SC3045,SC2162 +read -r -p "Press any key to continue... " -n1 -s diff --git a/pkg/monitor/run-monitor.sh b/pkg/monitor/run-monitor.sh new file mode 100755 index 0000000000..20a8c20fcd --- /dev/null +++ b/pkg/monitor/run-monitor.sh @@ -0,0 +1,27 @@ +#!/bin/sh + +# Copyright (c) 2024 Zededa, Inc. +# SPDX-License-Identifier: Apache-2.0 + +echo "Running EVE monitor" +export RUST_BACKTRACE=1 + +# leave only panic on console +dmesg -n 1 + +# setup keymap +loadkeys -s us - < type + Ok string `json:"Ok,omitempty"` + Err string `json:"Err,omitempty"` + ID uint64 `json:"id"` // Id is the id of the request that this response is for +} + +type ipcMessage struct { + Type string `json:"type"` + Message json.RawMessage `json:"message"` +} + +type monitorIPCServer struct { + codec *framed.ReadWriteCloser + // dataReady chan bool + ctx *monitor + sync.Mutex + clientConnected chan bool +} + +// factory method +func newIPCServer(ctx *monitor) *monitorIPCServer { + return &monitorIPCServer{ + ctx: ctx, + clientConnected: make(chan bool), + } +} + +func (s *monitorIPCServer) c() chan bool { + return s.clientConnected +} + +func (s *monitorIPCServer) handleConnection(conn net.Conn) { + s.Lock() + defer s.Unlock() + // the format of the frame is length + data + // where the length is 16 bit unsigned integer + s.codec = framed.NewReadWriteCloser(conn) + + go func() { + defer s.close() + s.run() + }() + + // notify the monitor that the client is connected + s.ctx.clientConnected <- true +} + +// close the server +func (s *monitorIPCServer) close() { + s.codec.Close() +} + +// main loop +func (s *monitorIPCServer) run() { + // we never exit from the loop until the connection is closed + // other errors are logged and we continue + for { + // read request + req, err := s.readRequest() + if err != nil { + log.Warnf("Error reading request: %v", err) + // exit if EOF + if errors.Is(err, io.EOF) { + return + } + continue + } + // handle request + resp := s.handleRequest(req) + log.Noticef("Response: %v", resp) + // send response + if err := s.sendResponse(resp); err != nil { + if errors.Is(err, io.EOF) { + log.Notice("Connection closed by client") + return + } + log.Warnf("Error sending response: %v", err) + } + } +} + +// read request +func (s *monitorIPCServer) readRequest() (*request, error) { + frame, err := s.codec.ReadFrame() + if err != nil { + return nil, err + } + log.Noticef("Received frame: %v", string(frame)) + + // following code is used for debugging when #[serde(untagged)] line + // is commented out in the rust code in the IpcMessage struct + // unmarshal IpcMessage first + // var ipcMessage IpcMessage + // if err := json.Unmarshal(frame, &ipcMessage); err != nil { + // return nil, err + // } + + var request request + if err := json.Unmarshal(frame, &request); err != nil { + return nil, err + } + return &request, nil +} + +// send response +func (s *monitorIPCServer) sendResponse(resp *response) error { + return s.sendIpcMessage("Response", resp) +} + +func (s *monitorIPCServer) sendIpcMessage(t string, msg any) error { + s.Lock() + defer s.Unlock() + + var err error + + if data, err := json.Marshal(msg); err == nil { + ipcMessage := ipcMessage{Type: t, Message: json.RawMessage(data)} + if data, err = json.Marshal(ipcMessage); err == nil { + log.Noticef("Sending IPC message: %s", string(data)) + _, err = s.codec.Write(data) + } + } + return err +} + +func (r *request) errResponse(errorText string, err error) *response { + if err != nil { + errorText = errorText + ": " + err.Error() + } + return &response{ + Err: errorText, + ID: r.ID, + } +} + +func (r *request) okResponse() *response { + return &response{ + ID: r.ID, + Ok: "ok", + } +} + +func (r *request) unimplementedResponse() *response { + return r.errResponse("Unimplemented request", nil) +} + +func (r *request) unknownRequestResponse() *response { + return r.errResponse("Unknown request", nil) +} + +func (r *request) malformedRequestResponse(err error) *response { + errMessage := "Malformed request [" + string(r.RequestData) + "]" + return r.errResponse(errMessage, err) +} + +func (r *request) handleRequest(ctx *monitor) *response { + switch r.RequestType { + case "SetDPC": + // Unmarshal the request data + var dpc types.DevicePortConfig + if err := json.Unmarshal(r.RequestData, &dpc); err != nil { + return r.malformedRequestResponse(err) + } + if err := ctx.IPCServer.validateDPC(dpc); err != nil { + return r.errResponse("Failed to validate DPC", err) + } + // unpublish current manual DPC first + ctx.pubDevicePortConfig.Unpublish(dpc.Key) + // publish the DPC + if err := ctx.pubDevicePortConfig.Publish(dpc.Key, dpc); err != nil { + return r.errResponse("Failed to publish DPC", err) + } + return r.okResponse() + case "SetServer": + var server string + if err := json.Unmarshal(r.RequestData, &server); err != nil { + return r.malformedRequestResponse(err) + } + if err := ctx.updateServerFile(server); err != nil { + return r.errResponse("Failed to update server file", err) + } + return r.okResponse() + + default: + return r.unknownRequestResponse() + } +} + +func (s *monitorIPCServer) validateDPC(_ types.DevicePortConfig) error { + //TODO: validate DPC + return nil +} + +// handle request +func (s *monitorIPCServer) handleRequest(req *request) *response { + // validate request + if err := req.validate(); err != nil { + return req.errResponse("Failed to validate request", err) + } + // handle request + return req.handleRequest(s.ctx) +} + +func (ctx *monitor) startIPCServer() error { + // Start the RPC server + sockPath := "/run/monitor.sock" + log.Noticef("Starting RPC server on %s", sockPath) + + listener, err := net.Listen("unix", sockPath) + if err != nil { + return err + } + + log.Notice("RPC server started") + + go func() { + defer listener.Close() + for { + log.Notice("Waiting for IPC connection") + conn, err := listener.Accept() + + if err != nil { + log.Warnf("Accept for RPC call failed: %v", err) + continue + } + + log.Notice("IPC connection accepted") + + // handle remote requests + go ctx.IPCServer.handleConnection(conn) + } + }() + return nil +} diff --git a/pkg/pillar/cmd/monitor/messages.go b/pkg/pillar/cmd/monitor/messages.go new file mode 100644 index 0000000000..220e071f5f --- /dev/null +++ b/pkg/pillar/cmd/monitor/messages.go @@ -0,0 +1,95 @@ +// Copyright (c) 2024 Zededa, Inc. +// SPDX-License-Identifier: Apache-2.0 + +package monitor + +import ( + "github.com/lf-edge/eve/pkg/pillar/types" + uuid "github.com/satori/go.uuid" +) + +type nodeStatus struct { + Server string `json:"server,omitempty"` + NodeUUID uuid.UUID `json:"node_uuid,omitempty"` + Onboarded bool `json:"onboarded"` + AppSummary types.AppInstanceSummary `json:"app_summary,omitempty"` + ZedAgentStatus types.ZedAgentStatus `json:"zedagent_status,omitempty"` +} + +type appInstancesStatus struct { + Apps []types.AppInstanceStatus `json:"apps"` +} + +func (ctx *monitor) isOnboarded() (bool, uuid.UUID) { + sub := ctx.subscriptions["OnboardingStatus"] + if item, err := sub.Get("global"); err == nil { + onboardingStatus := item.(types.OnboardingStatus) + return true, onboardingStatus.DeviceUUID + } + return false, uuid.UUID{} +} + +func (ctx *monitor) getAppSummary() types.AppInstanceSummary { + // send the network status to the server + sub := ctx.subscriptions["AppSummary"] + if item, err := sub.Get("global"); err == nil { + appSummary := item.(types.AppInstanceSummary) + return appSummary + } + return types.AppInstanceSummary{} +} + +func (ctx *monitor) sendNodeStatus() { + // send the node status to the server + nodeStatus := nodeStatus{ + Server: ctx.serverNameAndPort, + } + if onboarded, nodeUUID := ctx.isOnboarded(); onboarded { + nodeStatus.NodeUUID = nodeUUID + nodeStatus.Onboarded = true + } + + nodeStatus.ZedAgentStatus = ctx.getZedAgentStatus() + nodeStatus.AppSummary = ctx.getAppSummary() + + if ctx.lastNodeStatus != nil && *ctx.lastNodeStatus == nodeStatus { + return + } + ctx.lastNodeStatus = &nodeStatus + + ctx.IPCServer.sendIpcMessage("NodeStatus", nodeStatus) +} + +func (ctx *monitor) getAppInstancesStatus() []types.AppInstanceStatus { + // send the network status to the server + sub := ctx.subscriptions["AppStatus"] + items := sub.GetAll() + apps := make([]types.AppInstanceStatus, 0) + for _, item := range items { + appSummary := item.(types.AppInstanceStatus) + apps = append(apps, appSummary) + } + return apps +} + +func (ctx *monitor) getZedAgentStatus() types.ZedAgentStatus { + var err error + sub := ctx.subscriptions["ZedAgentStatus"] + if item, err := sub.Get("global"); err == nil { + zedAgentStatus := item.(types.ZedAgentStatus) + return zedAgentStatus + } + log.Errorf("Failed to get ZedAgentStatus %s", err) + return types.ZedAgentStatus{} +} + +func (ctx *monitor) sendAppsList() { + // send the node status to the server + appStatus := ctx.getAppInstancesStatus() + if len(appStatus) > 0 { + apps := appInstancesStatus{ + Apps: appStatus, + } + ctx.IPCServer.sendIpcMessage("AppsList", apps) + } +} diff --git a/pkg/pillar/cmd/monitor/monitor.go b/pkg/pillar/cmd/monitor/monitor.go new file mode 100644 index 0000000000..8f9242bcee --- /dev/null +++ b/pkg/pillar/cmd/monitor/monitor.go @@ -0,0 +1,158 @@ +// Copyright (c) 2024 Zededa, Inc. +// SPDX-License-Identifier: Apache-2.0 + +package monitor + +import ( + "errors" + "fmt" + "io/fs" + "os" + "os/exec" + "path/filepath" + "strings" + "syscall" + "time" + + "github.com/lf-edge/eve/pkg/pillar/agentbase" + "github.com/lf-edge/eve/pkg/pillar/base" + "github.com/lf-edge/eve/pkg/pillar/pubsub" + "github.com/lf-edge/eve/pkg/pillar/types" + "github.com/sirupsen/logrus" +) + +const ( + agentName = "monitor" + errorTime = 3 * time.Minute + warningTime = 40 * time.Second + stillRunningInterval = 25 * time.Second +) + +var logger *logrus.Logger +var log *base.LogObject + +type monitor struct { + agentbase.AgentBase + + subscriptions map[string]pubsub.Subscription + pubDevicePortConfig pubsub.Publication + clientConnected chan bool + serverNameAndPort string + // cache last known data structures to avoid sending duplicate messages + lastNodeStatus *nodeStatus + + IPCServer *monitorIPCServer +} + +func (ctx *monitor) readServerFile() error { + if _, err := os.Stat(types.ServerFileName); errors.Is(err, fs.ErrNotExist) { + // server file does not exist. This is not an error but a possible case + ctx.serverNameAndPort = "" + return nil + } + server, err := os.ReadFile(types.ServerFileName) + if err != nil { + log.Fatal(err) + return err + } + ctx.serverNameAndPort = strings.TrimSpace(string(server)) + return nil +} + +func (ctx *monitor) updateServerFile(newServer string) error { + var remountErr error + // 1. Find the CONFIG partition + findCmd := exec.Command("/sbin/findfs", "PARTLABEL=CONFIG") + deviceBytes, err := findCmd.Output() + if err != nil { + return fmt.Errorf("failed to find CONFIG partition: %v -- output was: `%s'", err, string(deviceBytes)) + } + devicePath := strings.TrimRight(string(deviceBytes), "\n\r") + + // 2. Create temp directory under /run + tempDir, err := os.MkdirTemp("/run", "config-mount-") + if err != nil { + return fmt.Errorf("failed to create temp directory: %v", err) + } + defer os.RemoveAll(tempDir) + + // 3. Mount the CONFIG partition as read-write + if err := syscall.Mount(devicePath, + tempDir, "vfat", 0, "iocharset=iso8859-1"); err != nil { + return fmt.Errorf("failed to mount CONFIG partition on device %v: %v", devicePath, err) + } + defer syscall.Unmount(tempDir, 0) + + // 4. Write new server file + tempServerFile := filepath.Join(tempDir, filepath.Base(types.ServerFileName)) + if err := os.WriteFile(tempServerFile, []byte(newServer), 0644); err != nil { + return fmt.Errorf("failed to write new server file: %v", err) + } + + // 5. Unmount temp directory (handled by defer) + + // 6. remount /config as rw. this is a tmpfs + if err := syscall.Mount("none", "/config", "tmpfs", + syscall.MS_REMOUNT, ""); err != nil { + return fmt.Errorf("failed to remount /config RW: %v", err) + } + + defer func() { + // 7. remount /config as ro + if err := syscall.Mount("none", "/config", "tmpfs", + syscall.MS_REMOUNT|syscall.MS_RDONLY, ""); err != nil { + remountErr = fmt.Errorf("failed to remount /config RO: %v", err) + } + }() + + // 6. Update shadow copy in /config + if err := os.WriteFile(types.ServerFileName, []byte(newServer), 0644); err != nil { + return fmt.Errorf("failed to update shadow copy: %v", err) + } + + if remountErr != nil { + return remountErr + } + + ctx.serverNameAndPort = newServer + return nil +} + +func newMonitorContext() *monitor { + ctx := &monitor{ + subscriptions: make(map[string]pubsub.Subscription), + } + ctx.IPCServer = newIPCServer(ctx) + ctx.clientConnected = ctx.IPCServer.c() + ctx.lastNodeStatus = nil + + return ctx +} + +// Run starts the monitor process and handles its lifecycle. It initializes the monitor context, +// sets up logging and IPC server, subscribes to necessary events, and begins event processing. +func Run(ps *pubsub.PubSub, loggerArg *logrus.Logger, logArg *base.LogObject, arguments []string, baseDir string) int { //nolint:gocyclo + logger = loggerArg + log = logArg + var err error + + ctx := newMonitorContext() + + agentbase.Init(ctx, logger, log, agentName, + agentbase.WithPidFile(), + agentbase.WithBaseDir(baseDir), + agentbase.WithWatchdog(ps, warningTime, errorTime), + agentbase.WithArguments(arguments)) + + if err = ctx.readServerFile(); err != nil { + log.Fatalf("Server file exists but cannot be read `%v`", err) + } + + if err = ctx.startIPCServer(); err != nil { + log.Fatalf("Cannot start Monitor IPC server `%v`", err) + } + + ctx.subscribe(ps) + ctx.process(ps) + return 0 +} diff --git a/pkg/pillar/cmd/monitor/subscriptions.go b/pkg/pillar/cmd/monitor/subscriptions.go new file mode 100644 index 0000000000..ae92c77c15 --- /dev/null +++ b/pkg/pillar/cmd/monitor/subscriptions.go @@ -0,0 +1,446 @@ +// Copyright (c) 2024 Zededa, Inc. +// SPDX-License-Identifier: Apache-2.0 + +package monitor + +import ( + "reflect" + "time" + + "github.com/lf-edge/eve/pkg/pillar/pubsub" + "github.com/lf-edge/eve/pkg/pillar/types" +) + +func handlePhysicalIOAdapterCreate(ctxArg interface{}, key string, + statusArg interface{}) { + handlePhysicalIOAdapterUpdate(ctxArg, statusArg) +} + +func handlePhysicalIOAdapterModify(ctxArg interface{}, key string, + statusArg interface{}, oldStatusArg interface{}) { + handlePhysicalIOAdapterUpdate(ctxArg, statusArg) +} + +func handlePhysicalIOAdapterUpdate(ctxArg interface{}, statusArg interface{}) { + ctx := ctxArg.(*monitor) + status := statusArg.(types.PhysicalIOAdapterList) + ctx.IPCServer.sendIpcMessage("IOAdapters", status) +} + +func handlePhysicalIOAdapterDelete(ctxArg interface{}, key string, + statusArg interface{}) { +} + +func handleNetworkStatusCreate(ctxArg interface{}, key string, + statusArg interface{}) { + handleNetworStatusUpdate(statusArg, ctxArg) + +} +func handleNetworkStatusDelete(ctxArg interface{}, key string, + statusArg interface{}) { + +} + +func handleNetworkStatusModify(ctxArg interface{}, key string, + statusArg interface{}, _ interface{}) { + handleNetworStatusUpdate(statusArg, ctxArg) +} + +func handleNetworStatusUpdate(statusArg interface{}, ctxArg interface{}) { + status := statusArg.(types.DeviceNetworkStatus) + ctx := ctxArg.(*monitor) + ctx.IPCServer.sendIpcMessage("NetworkStatus", status) +} + +func handleDownloaderStatusCreate(ctxArg interface{}, key string, + statusArg interface{}) { + handleDownloaderStatusUpdate(statusArg, ctxArg) +} + +func handleDownloaderStatusModify(ctxArg interface{}, key string, + statusArg interface{}, _ interface{}) { + handleDownloaderStatusUpdate(statusArg, ctxArg) +} + +func handleDownloaderStatusUpdate(statusArg interface{}, ctxArg interface{}) { + status := statusArg.(types.DownloaderStatus) + ctx := ctxArg.(*monitor) + ctx.IPCServer.sendIpcMessage("DownloaderStatus", status) +} + +func handleDownloaderStatusDelete(ctxArg interface{}, key string, + statusArg interface{}) { +} + +func handleDPCCreate(ctxArg interface{}, key string, + statusArg interface{}) { + handleDPCUpdate(statusArg, ctxArg) + +} +func handleDPCDelete(ctxArg interface{}, key string, + statusArg interface{}) { +} +func handleDPCModify(ctxArg interface{}, key string, + statusArg interface{}, _ interface{}) { + handleDPCUpdate(statusArg, ctxArg) +} + +func handleDPCUpdate(statusArg interface{}, ctxArg interface{}) { + status := statusArg.(types.DevicePortConfigList) + if status.CurrentIndex == -1 { + return + } + ctx := ctxArg.(*monitor) + ctx.IPCServer.sendIpcMessage("DPCList", status) +} + +func handleAppInstanceStatusCreate(ctxArg interface{}, key string, + statusArg interface{}) { + handleAppInstanceStatusUpdate(statusArg, ctxArg) +} + +func handleAppInstanceStatusModify(ctxArg interface{}, key string, + statusArg interface{}, _ interface{}) { + handleAppInstanceStatusUpdate(statusArg, ctxArg) +} + +func handleAppInstanceStatusDelete(ctxArg interface{}, key string, + statusArg interface{}) { +} + +func handleAppInstanceStatusUpdate(statusArg interface{}, ctxArg interface{}) { + status := statusArg.(types.AppInstanceStatus) + ctx := ctxArg.(*monitor) + ctx.IPCServer.sendIpcMessage("AppStatus", status) +} + +func handleOnboardingStatusCreate(ctxArg interface{}, key string, + statusArg interface{}) { + handleOnboardingStatusUpdate(statusArg, ctxArg) +} + +func handleOnboardingStatusModify(ctxArg interface{}, key string, + statusArg interface{}, _ interface{}) { + handleOnboardingStatusUpdate(statusArg, ctxArg) +} + +func handleOnboardingStatusUpdate(statusArg interface{}, ctxArg interface{}) { + status := statusArg.(types.OnboardingStatus) + ctx := ctxArg.(*monitor) + ctx.IPCServer.sendIpcMessage("OnboardingStatus", status) +} + +func handleVaultStatusCreate(ctxArg interface{}, key string, + statusArg interface{}) { + handleVaultStatusUpdate(statusArg, ctxArg) +} + +func handleVaultStatusModify(ctxArg interface{}, key string, + statusArg interface{}, _ interface{}) { + handleVaultStatusUpdate(statusArg, ctxArg) +} + +func handleVaultStatusUpdate(statusArg interface{}, ctxArg interface{}) { + status := statusArg.(types.VaultStatus) + ctx := ctxArg.(*monitor) + ctx.IPCServer.sendIpcMessage("VaultStatus", status) +} + +func handleAppInstanceSummaryCreate(ctxArg interface{}, key string, + statusArg interface{}) { + handleAppInstanceSummaryUpdate(statusArg, ctxArg) +} + +func handleAppInstanceSummaryModify(ctxArg interface{}, key string, + statusArg interface{}, _ interface{}) { + handleAppInstanceSummaryUpdate(statusArg, ctxArg) +} +func handleAppInstanceSummaryUpdate(statusArg interface{}, ctxArg interface{}) { + status := statusArg.(types.AppInstanceSummary) + ctx := ctxArg.(*monitor) + ctx.IPCServer.sendIpcMessage("AppSummary", status) +} + +func handleLedBlinkCreate(ctxArg interface{}, key string, + statusArg interface{}) { + handleLedBlinkUpdate(statusArg, ctxArg) +} + +func handleLedBlinkModify(ctxArg interface{}, key string, + statusArg interface{}, _ interface{}) { + handleLedBlinkUpdate(statusArg, ctxArg) +} +func handleLedBlinkUpdate(statusArg interface{}, ctxArg interface{}) { + status := statusArg.(types.LedBlinkCounter) + ctx := ctxArg.(*monitor) + ctx.IPCServer.sendIpcMessage("LedBlinkCounter", status) +} + +func handleZedAgentStatusCreate(ctxArg interface{}, key string, + statusArg interface{}) { + handleZedAgentStatusUpdate(statusArg, ctxArg) +} + +func handleZedAgentStatusModify(ctxArg interface{}, key string, + statusArg interface{}, _ interface{}) { + handleZedAgentStatusUpdate(statusArg, ctxArg) +} + +func handleZedAgentStatusUpdate(statusArg interface{}, ctxArg interface{}) { + status := statusArg.(types.ZedAgentStatus) + // Ignore if ConfigGetStatus is 0 which is incorrect value + if status.ConfigGetStatus == 0 { + return + } + ctx := ctxArg.(*monitor) + ctx.IPCServer.sendIpcMessage("ZedAgentStatus", status) +} + +func (ctx *monitor) subscribe(ps *pubsub.PubSub) error { + var err error + + ctx.pubDevicePortConfig, err = ps.NewPublication( + pubsub.PublicationOptions{ + AgentName: agentName, + TopicType: types.DevicePortConfig{}, + Persistent: true, + }) + if err != nil { + log.Error("Cannot create DevicePortConfig publication") + return err + } + if err = ctx.pubDevicePortConfig.ClearRestarted(); err != nil { + log.Error("Cannot clear restarted for DevicePortConfig publication") + return err + } + + // Look for PhysicalIOAdapter from zedagent + subPhysicalIOAdapter, err := ps.NewSubscription(pubsub.SubscriptionOptions{ + AgentName: "zedagent", + MyAgentName: agentName, + TopicImpl: types.PhysicalIOAdapterList{}, + Activate: false, + Ctx: ctx, + WarningTime: warningTime, + ErrorTime: errorTime, + CreateHandler: handlePhysicalIOAdapterCreate, + ModifyHandler: handlePhysicalIOAdapterModify, + DeleteHandler: handlePhysicalIOAdapterDelete, + }) + if err != nil { + log.Error("Cannot create subscription for PhysicalIOAdapter") + return err + } + + subVaultStatus, err := ps.NewSubscription(pubsub.SubscriptionOptions{ + AgentName: "vaultmgr", + MyAgentName: agentName, + TopicImpl: types.VaultStatus{}, + Activate: false, + Ctx: ctx, + CreateHandler: handleVaultStatusCreate, + ModifyHandler: handleVaultStatusModify, + WarningTime: warningTime, + ErrorTime: errorTime, + }) + if err != nil { + log.Error("Cannot create subscription for VaultStatus") + return err + } + + subOnboardStatus, err := ps.NewSubscription(pubsub.SubscriptionOptions{ + AgentName: "zedclient", + MyAgentName: agentName, + TopicImpl: types.OnboardingStatus{}, + Activate: false, + Persistent: true, + Ctx: ctx, + CreateHandler: handleOnboardingStatusCreate, + ModifyHandler: handleOnboardingStatusModify, + WarningTime: warningTime, + ErrorTime: errorTime, + }) + if err != nil { + log.Error("Cannot create subscription for OnboardingStatus") + return err + } + + subDeviceNetworkStatus, err := ps.NewSubscription(pubsub.SubscriptionOptions{ + AgentName: "nim", + MyAgentName: agentName, + TopicImpl: types.DeviceNetworkStatus{}, + Activate: false, + Ctx: ctx, + CreateHandler: handleNetworkStatusCreate, + ModifyHandler: handleNetworkStatusModify, + DeleteHandler: handleNetworkStatusDelete, + WarningTime: warningTime, + ErrorTime: errorTime, + }) + if err != nil { + log.Error("Cannot create subscription for DeviceNetworkStatus") + return err + } + + subDevicePortConfigList, err := ps.NewSubscription(pubsub.SubscriptionOptions{ + AgentName: "nim", + MyAgentName: agentName, + Persistent: true, + TopicImpl: types.DevicePortConfigList{}, + Activate: false, + Ctx: ctx, + CreateHandler: handleDPCCreate, + ModifyHandler: handleDPCModify, + DeleteHandler: handleDPCDelete, + WarningTime: warningTime, + ErrorTime: errorTime, + }) + if err != nil { + log.Error("Cannot create subscription for DevicePortConfigList") + return err + } + + subAppInstanceSummary, err := ps.NewSubscription(pubsub.SubscriptionOptions{ + AgentName: "zedmanager", + MyAgentName: agentName, + TopicImpl: types.AppInstanceSummary{}, + Activate: false, + Ctx: ctx, + CreateHandler: handleAppInstanceSummaryCreate, + ModifyHandler: handleAppInstanceSummaryModify, + WarningTime: warningTime, + ErrorTime: errorTime, + }) + if err != nil { + log.Error("Cannot create subscription for AppInstanceSummary") + return err + } + + subAppInstanceStatus, err := ps.NewSubscription(pubsub.SubscriptionOptions{ + AgentName: "zedmanager", + MyAgentName: agentName, + TopicImpl: types.AppInstanceStatus{}, + Activate: false, + Ctx: ctx, + CreateHandler: handleAppInstanceStatusCreate, + ModifyHandler: handleAppInstanceStatusModify, + DeleteHandler: handleAppInstanceStatusDelete, + WarningTime: warningTime, + ErrorTime: errorTime, + }) + if err != nil { + log.Error("Cannot create subscription for AppInstanceStatus") + return err + } + + subDownloaderStatus, err := ps.NewSubscription(pubsub.SubscriptionOptions{ + AgentName: "downloader", + MyAgentName: agentName, + TopicImpl: types.DownloaderStatus{}, + Activate: false, + Ctx: ctx, + CreateHandler: handleDownloaderStatusCreate, + ModifyHandler: handleDownloaderStatusModify, + DeleteHandler: handleDownloaderStatusDelete, + WarningTime: warningTime, + ErrorTime: errorTime, + }) + if err != nil { + log.Error("Cannot create subscription for DownloaderStatus") + return err + } + + subLedBlinkCounter, err := ps.NewSubscription( + pubsub.SubscriptionOptions{ + AgentName: "", + MyAgentName: agentName, + TopicImpl: types.LedBlinkCounter{}, + Activate: false, + Ctx: ctx, + CreateHandler: handleLedBlinkCreate, + ModifyHandler: handleLedBlinkModify, + WarningTime: warningTime, + ErrorTime: errorTime, + }) + if err != nil { + log.Error("Cannot create subscription for LedBlinkCounter") + return err + } + + subZedAgentStatus, err := ps.NewSubscription(pubsub.SubscriptionOptions{ + AgentName: "zedagent", + MyAgentName: agentName, + TopicImpl: types.ZedAgentStatus{}, + Activate: false, + Ctx: ctx, + CreateHandler: handleZedAgentStatusCreate, + ModifyHandler: handleZedAgentStatusModify, + WarningTime: warningTime, + ErrorTime: errorTime, + }) + if err != nil { + log.Error("Cannot create subscription for ZedAgentStatus") + return err + } + + ctx.subscriptions["IOAdapters"] = subPhysicalIOAdapter + ctx.subscriptions["VaultStatus"] = subVaultStatus + ctx.subscriptions["OnboardingStatus"] = subOnboardStatus + ctx.subscriptions["NetworkStatus"] = subDeviceNetworkStatus + ctx.subscriptions["DPCList"] = subDevicePortConfigList + ctx.subscriptions["AppStatus"] = subAppInstanceStatus + ctx.subscriptions["DownloaderStatus"] = subDownloaderStatus + ctx.subscriptions["AppSummary"] = subAppInstanceSummary + ctx.subscriptions["LedBlinkCounter"] = subLedBlinkCounter + ctx.subscriptions["ZedAgentStatus"] = subZedAgentStatus + return nil +} + +func (ctx *monitor) handleClientConnected() { + // go over all the subscriptions and process the current state + log.Noticef("Client connected. Activating subscriptions") + + ctx.sendNodeStatus() + ctx.sendAppsList() + + for _, sub := range ctx.subscriptions { + if err := sub.Activate(); err != nil { + log.Errorf("Failed to activate subscription %s", err) + } + } +} + +func (ctx *monitor) process(ps *pubsub.PubSub) { + stillRunning := time.NewTicker(stillRunningInterval) + + watches := make([]pubsub.ChannelWatch, 0) + for i := range ctx.subscriptions { + sub := ctx.subscriptions[i] + watches = append(watches, pubsub.ChannelWatch{ + Chan: reflect.ValueOf(sub.MsgChan()), + Callback: func(value interface{}) { + change, ok := value.(pubsub.Change) + if !ok { + return + } + sub.ProcessChange(change) + }, + }) + } + + watches = append(watches, pubsub.ChannelWatch{ + Chan: reflect.ValueOf(stillRunning.C), + Callback: func(_ interface{}) { + ps.StillRunning(agentName, warningTime, errorTime) + }, + }) + + watches = append(watches, pubsub.ChannelWatch{ + Chan: reflect.ValueOf(ctx.clientConnected), + Callback: func(_ interface{}) { + ctx.handleClientConnected() + }, + }) + + pubsub.MultiChannelWatch(watches) +} diff --git a/pkg/pillar/cmd/nim/nim.go b/pkg/pillar/cmd/nim/nim.go index 032c916e96..e47b910f29 100644 --- a/pkg/pillar/cmd/nim/nim.go +++ b/pkg/pillar/cmd/nim/nim.go @@ -79,6 +79,7 @@ type nim struct { subDevicePortConfigA pubsub.Subscription subDevicePortConfigO pubsub.Subscription subDevicePortConfigS pubsub.Subscription + subDevicePortConfigM pubsub.Subscription subZedAgentStatus pubsub.Subscription subAssignableAdapters pubsub.Subscription subOnboardStatus pubsub.Subscription @@ -288,6 +289,9 @@ func (n *nim) run(ctx context.Context) (err error) { waitForLastResort := n.enabledLastResort lastResortIsReady := func() error { + if err = n.subDevicePortConfigM.Activate(); err != nil { + return err + } if err = n.subDevicePortConfigO.Activate(); err != nil { return err } @@ -335,6 +339,8 @@ func (n *nim) run(ctx context.Context) (err error) { } } + case change := <-n.subDevicePortConfigM.MsgChan(): + n.subDevicePortConfigM.ProcessChange(change) case change := <-n.subDevicePortConfigA.MsgChan(): n.subDevicePortConfigA.ProcessChange(change) @@ -554,9 +560,25 @@ func (n *nim) initSubscriptions() (err error) { } // We get DevicePortConfig from three sources in this priority: + // 0. A request from monitor TUI application (manual override) // 1. zedagent publishing DevicePortConfig // 2. override file in /run/global/DevicePortConfig/*.json // 3. "lastresort" derived from the set of network interfaces + n.subDevicePortConfigM, err = n.PubSub.NewSubscription(pubsub.SubscriptionOptions{ + AgentName: "monitor", + MyAgentName: agentName, + TopicImpl: types.DevicePortConfig{}, + Persistent: false, + Activate: false, + CreateHandler: n.handleDPCCreate, + ModifyHandler: n.handleDPCModify, + DeleteHandler: n.handleDPCDelete, + WarningTime: warningTime, + ErrorTime: errorTime, + }) + if err != nil { + return err + } n.subDevicePortConfigA, err = n.PubSub.NewSubscription(pubsub.SubscriptionOptions{ AgentName: "zedagent", MyAgentName: agentName, @@ -726,7 +748,8 @@ func (n *nim) applyGlobalConfig(gcp *types.ConfigItemValueMap) { n.gcInitialized = true } -// handleDPCCreate handles three different sources in this priority order: +// handleDPCCreate handles four different sources in this priority order: +// 0. A request from monitor TUI application // 1. zedagent with any key // 2. "usb" key from build or USB stick file // 3. "lastresort" derived from the set of network interfaces @@ -782,6 +805,12 @@ func (n *nim) handleDPCImpl(key string, configArg interface{}, fromFile bool) { n.forceLastResort = false n.reevaluateLastResortDPC() } + // if device can connect to controller it may get a new DPC in global config. This global DPC + // will have higher priority but can be invalid and the device will loose connectivity again + // at least temporarily while DPC is being tested. To avoid this we reset the timestamp on + // the Manual DPC to the current time + // TODO: do it. or check for ManualDPCKey in DPCManager + // TODO 2: we should not try lastresort DPC if the user set the DPC to manual n.dpcManager.AddDPC(dpc) } diff --git a/pkg/pillar/docs/monitor.md b/pkg/pillar/docs/monitor.md new file mode 100644 index 0000000000..daeec3407f --- /dev/null +++ b/pkg/pillar/docs/monitor.md @@ -0,0 +1,17 @@ +# Monitor service implementation + +The monitor service is a simple IPC server which uses a unix socket to communicate with external [rust client](../../monitor/Dockerfile) located at `pkg/monitor`. The server can send asynchronous updates about EVE status to the +connected client. The information is then used by rust client to display a TUI for the user. + +## Client requests + +Following requests are supported at the moment: + +* `SetDPC` - sets a the current DPC with a key set to `manual`. It is used to apply a network configuration specified by local user through TUI. `NIM` service has a special handling of `manual` DPC +* `SetServer` - updates server URL in `/config/server` file. The request fails if the node is already onboarded. + +## Request/response representation + +All requests and responses are sent in JSON format. Some internal EVE structures e.g. DPCList are serialized into JSON as-is and deserialized on the rust application side. +It introduces a problem in case a structure is updated on EVE side, but the rust application is not updated. +To avoid this problem a proxy structures should be created on EVE side in future. diff --git a/pkg/pillar/docs/nim.md b/pkg/pillar/docs/nim.md index 7df1b6ca39..244e1fd457 100644 --- a/pkg/pillar/docs/nim.md +++ b/pkg/pillar/docs/nim.md @@ -22,7 +22,7 @@ to move to the most recent, aka the highest-priority configuration. * DPC is received from different sources, such as zedagent (bootstrap config or config from the controller), the `/config` partition with `override.json`, specially formatted USB stick with `usb.json` and even from NIM itself, - which builds and publishes the *last-resort* config if enabled + which builds and publishes the *last-resort* config if enabled. If `monitor` application is enabled the user can also change current network settings manually from the local TUI. In this case [monitor](./monitor.md) service sets a DPC with a key set to `manual`. Only one instance of the DPC with key `manual` may exists at a given time thus it is always overwritten when the user changes network settings * global configuration properties * an instance of `ConfigItemValueMap` struct received from zedagent * used to determine if last-resort should be enabled, also to obtain time diff --git a/pkg/pillar/dpcmanager/dpc.go b/pkg/pillar/dpcmanager/dpc.go index 373942f8ce..5a9f5321b8 100644 --- a/pkg/pillar/dpcmanager/dpc.go +++ b/pkg/pillar/dpcmanager/dpc.go @@ -31,6 +31,12 @@ func (m *DpcManager) doAddDPC(ctx context.Context, dpc types.DevicePortConfig) { "will be ignored", dpc.Key) } + // always delete the existing manual DPC regardless of its time priority + // there can be only one! + if dpc.Key == ManualDPCKey { + m.removeAllDPCbyKey(ManualDPCKey) + } + // XXX really need to know whether anything with current or lower // index has changed. We don't care about inserts at the end of the list. configChanged := m.updateDPCListAndPublish(dpc, false) @@ -194,6 +200,17 @@ func (m *DpcManager) removeDPC(dpc types.DevicePortConfig) { m.dpcList.PortConfigList = newConfig } +// Remove all entries by Key +func (m *DpcManager) removeAllDPCbyKey(key string) { + var newConfig []types.DevicePortConfig + for _, port := range m.dpcList.PortConfigList { + if port.Key != key { + newConfig = append(newConfig, port) + } + } + m.dpcList.PortConfigList = newConfig +} + // First look for matching timestamp, then compare for identical content // This is needed since after a restart zedagent will provide new timestamps // even if we persisted the DevicePortConfig before the restart. @@ -319,9 +336,9 @@ func (m *DpcManager) compressDPCL() { m.Log.Tracef("compressDPCL: Adding Current Index: i = %d, dpc: %+v", i, dpc) } else { - // Retain the lastresort if enabled. Delete everything else. - if dpc.Key == LastResortKey && m.enableLastResort { - m.Log.Tracef("compressDPCL: Retaining last resort. i = %d, dpc: %+v", + // Retain the lastresort if enabled and manual if available. Delete everything else. + if (dpc.Key == LastResortKey && m.enableLastResort) || (dpc.Key == ManualDPCKey) { + m.Log.Tracef("compressDPCL: Retaining last resort or manual. i = %d, dpc: %+v", i, dpc) newConfig = append(newConfig, dpc) continue diff --git a/pkg/pillar/dpcmanager/dpcmanager.go b/pkg/pillar/dpcmanager/dpcmanager.go index 30f06e8b8c..dca125f4bb 100644 --- a/pkg/pillar/dpcmanager/dpcmanager.go +++ b/pkg/pillar/dpcmanager/dpcmanager.go @@ -31,6 +31,9 @@ const ( // LastResortKey : key used for DPC used as a last-resort. const LastResortKey = "lastresort" +// ManualDPCKey : key used for DPC set manually by the user. +const ManualDPCKey = "manual" + var nilUUID = uuid.UUID{} // used as a constant // DpcManager manages a list of received device port configurations. diff --git a/pkg/pillar/evetpm/tpm.go b/pkg/pillar/evetpm/tpm.go index 9e70602b7c..ac0ae47212 100644 --- a/pkg/pillar/evetpm/tpm.go +++ b/pkg/pillar/evetpm/tpm.go @@ -876,13 +876,13 @@ func UnsealDiskKey(pcrSel tpm2.PCRSelection) ([]byte, error) { // a copy of TPM measurement log, it comes handy for diagnosing the issue. evtLogStat := "copied (failed unseal) TPM measurement log" if errEvtLog := copyMeasurementLog(measurementLogUnsealFail); errEvtLog != nil { - // just report the failure, still give findMismatchingPCRs a chance so + // just report the failure, still give FindMismatchingPCRs a chance so // we can at least have some partial information about why unseal failed. evtLogStat = fmt.Sprintf("copying (failed unseal) TPM measurement log failed: %v", errEvtLog) } // try to find out the mismatching PCR index - mismatch, errPcrMiss := findMismatchingPCRs() + mismatch, errPcrMiss := FindMismatchingPCRs() if errPcrMiss != nil { return nil, fmt.Errorf("UnsealWithSession failed: %w, %s, finding mismatching PCR failed: %v", err, evtLogStat, errPcrMiss) } @@ -1072,7 +1072,10 @@ func saveDiskKeySealingPCRs() error { return fileutils.WriteRename(savedSealingPcrsFile, buff.Bytes()) } -func findMismatchingPCRs() ([]int, error) { +// FindMismatchingPCRs compares saved PCR values with current PCR values and returns a +// list of PCR indices that have different values. Returns an error if PCR values cannot +// be retrieved. +func FindMismatchingPCRs() ([]int, error) { frw, err := os.Open(savedSealingPcrsFile) if err != nil { return nil, err diff --git a/pkg/pillar/go.mod b/pkg/pillar/go.mod index 160b21ac86..9cb83ce8c9 100644 --- a/pkg/pillar/go.mod +++ b/pkg/pillar/go.mod @@ -14,6 +14,7 @@ require ( github.com/eshard/uevent v1.0.2-0.20220110110621-d8d2be286cec github.com/facebook/time v0.0.0-20240605113323-bdee26e8523f github.com/fsnotify/fsnotify v1.6.0 + github.com/getlantern/framed v0.0.0-20190601192238-ceb6431eeede github.com/go-chi/chi/v5 v5.0.10 github.com/go-playground/validator/v10 v10.15.5 github.com/golang-jwt/jwt v3.2.2+incompatible @@ -207,6 +208,7 @@ require ( github.com/openshift/api v0.0.0-20230503133300-8bbcb7ca7183 // indirect github.com/openshift/client-go v0.0.0-20210112165513-ebc401615f47 // indirect github.com/openshift/custom-resource-status v1.1.2 // indirect + github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c // indirect github.com/pborman/uuid v1.2.1 // indirect github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring v0.68.0 // indirect github.com/spf13/pflag v1.0.5 // indirect diff --git a/pkg/pillar/go.sum b/pkg/pillar/go.sum index 63408af435..0311b6d5ba 100644 --- a/pkg/pillar/go.sum +++ b/pkg/pillar/go.sum @@ -1021,6 +1021,10 @@ github.com/fxamacker/cbor/v2 v2.4.0/go.mod h1:TA1xS00nchWmaBnEIxPSE5oHLuJBAVvqrt github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU= github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA= github.com/garyburd/redigo v0.0.0-20150301180006-535138d7bcd7/go.mod h1:NR3MbYisc3/PwhQ00EMzDiPmrwpPxAn5GI05/YaO1SY= +github.com/getlantern/framed v0.0.0-20190601192238-ceb6431eeede h1:yrU6Px3ZkvCsDLPryPGi6FN+2iqFPq+JeCb7EFoDBhw= +github.com/getlantern/framed v0.0.0-20190601192238-ceb6431eeede/go.mod h1:nhnoiS6DE6zfe+BaCMU4YI01UpsuiXnDqM5S8jxHuuI= +github.com/getlantern/testify v0.0.0-20160317154340-2eeb3906e78f h1:R6iv+VzCrbxlBm2TcgyJi9c7tQguNXi9JmIvuUJKrdc= +github.com/getlantern/testify v0.0.0-20160317154340-2eeb3906e78f/go.mod h1:uKKI9HSwZ4C8tx1vV+ovbG32Lw9LixtzPLNiS8U/ddA= github.com/getsentry/raven-go v0.2.0/go.mod h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49PX4NzFV5kcQ= github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= @@ -1639,6 +1643,8 @@ github.com/openshift/client-go v0.0.0-20210112165513-ebc401615f47/go.mod h1:u7NR github.com/openshift/custom-resource-status v1.1.2 h1:C3DL44LEbvlbItfd8mT5jWrqPfHnSOQoQf/sypqA6A4= github.com/openshift/custom-resource-status v1.1.2/go.mod h1:DB/Mf2oTeiAmVVX1gN+NEqweonAPY0TKUwADizj8+ZA= github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= +github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c h1:rp5dCmg/yLR3mgFuSOe4oEnDDmGLROTvMragMUXpTQw= +github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c/go.mod h1:X07ZCGwUbLaax7L0S3Tw4hpejzu63ZrrQiUe6W0hcy0= github.com/packetcap/go-pcap v0.0.0-20230717110547-c34843f9206d h1:3VdKDbNiQZ5CzDF1PNMwsM03BZUR0AEoRzt9sMyB8kQ= github.com/packetcap/go-pcap v0.0.0-20230717110547-c34843f9206d/go.mod h1:IwL7NJSMD5mvRco6A6uxPca1Zv0OJp0tY5Gf++9LBYQ= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= diff --git a/pkg/pillar/pubsub/pubsub.go b/pkg/pillar/pubsub/pubsub.go index 1eea7caf16..72da889bbb 100644 --- a/pkg/pillar/pubsub/pubsub.go +++ b/pkg/pillar/pubsub/pubsub.go @@ -1,4 +1,4 @@ -// Copyright (c) 2017,2018 Zededa, Inc. +// Copyright (c) 2017,2018,2024 Zededa, Inc. // SPDX-License-Identifier: Apache-2.0 // Provide for a pubsub mechanism for config and status which is @@ -23,8 +23,8 @@ type SubscriptionOptions struct { DeleteHandler SubDeleteHandler RestartHandler SubRestartHandler SyncHandler SubSyncHandler - WarningTime time.Duration - ErrorTime time.Duration + WarningTime time.Duration // we log a warning if the subscription handler took longer than this to run + ErrorTime time.Duration // we log an error if the subscription handler took longer than this to run AgentName string AgentScope string TopicImpl interface{} @@ -86,7 +86,7 @@ func New(driver Driver, logger *logrus.Logger, log *base.LogObject) *PubSub { func (p *PubSub) NewSubscription(options SubscriptionOptions) (Subscription, error) { if options.TopicImpl == nil { - return nil, fmt.Errorf("cannot create a subcription with a nil "+ + return nil, fmt.Errorf("cannot create a subscription with a nil "+ " topicImpl. options: %+v", options) } diff --git a/pkg/pillar/scripts/device-steps.sh b/pkg/pillar/scripts/device-steps.sh index 254449a15e..f21fc5da54 100755 --- a/pkg/pillar/scripts/device-steps.sh +++ b/pkg/pillar/scripts/device-steps.sh @@ -18,7 +18,7 @@ ZTMPDIR=/run/global DPCDIR=$ZTMPDIR/DevicePortConfig FIRSTBOOTFILE=$ZTMPDIR/first-boot FIRSTBOOT= -AGENTS="diag zedagent ledmanager nim nodeagent domainmgr loguploader tpmmgr vaultmgr zedmanager zedrouter downloader verifier baseosmgr wstunnelclient volumemgr watcher zfsmanager usbmanager zedkube vcomlink" +AGENTS="diag monitor zedagent ledmanager nim nodeagent domainmgr loguploader tpmmgr vaultmgr zedmanager zedrouter downloader verifier baseosmgr wstunnelclient volumemgr watcher zfsmanager usbmanager zedkube vcomlink" TPM_DEVICE_PATH="/dev/tpmrm0" PATH=$BINDIR:$PATH TPMINFOTEMPFILE=/var/tmp/tpminfo.txt @@ -319,7 +319,7 @@ for AGENT in $AGENTS; do # NOTE: it is safe to do either kill -STOP or an outright # kill -9 on the following cat process if you want to stop # receiving those messages on the console. - size="$(stty -F /dev/console size)" + size="$(stty -F /dev/tty1 size)" rows=$(echo "$size" | awk '{print $1}') columns=$(echo "$size" | awk '{print $2}') [ "$rows" != 0 ] || rows="" @@ -327,7 +327,7 @@ for AGENT in $AGENTS; do [ "$columns" != 0 ] || columns="" [ -z "$columns" ] || columns="-c $columns" mkfifo /run/diag.pipe - (while true; do cat; done) < /run/diag.pipe >/dev/console 2>&1 & + (while true; do cat; done) < /run/diag.pipe >/dev/tty1 2>&1 & # shellcheck disable=SC2086 $BINDIR/diag -f -o /run/diag.pipe -s /run/diag.out $rows $columns & else @@ -441,7 +441,7 @@ if [ ! -s "$DEVICE_CERT_NAME" ]; then umount $CONFIGDIR_PERSIST # Did we fail to generate a certificate? if [ ! -s "$DEVICE_CERT_NAME" ]; then - echo "$(date -Ins -u) Failed to generate a device certificate. Done" | tee /dev/console + echo "$(date -Ins -u) Failed to generate a device certificate. Done" | tee /dev/tty exit 0 fi else diff --git a/pkg/pillar/scripts/onboot.sh b/pkg/pillar/scripts/onboot.sh index 88bd6ce128..807a8ff10d 100755 --- a/pkg/pillar/scripts/onboot.sh +++ b/pkg/pillar/scripts/onboot.sh @@ -100,11 +100,11 @@ if [ -c $TPM_DEVICE_PATH ] && ! [ -f $DEVICE_KEY_NAME ]; then fi if [ -f $PERSISTDIR/reboot-reason ]; then - echo "Reboot reason: $(cat $PERSISTDIR/reboot-reason)" | tee /dev/console + echo "Reboot reason: $(cat $PERSISTDIR/reboot-reason)" | tee /dev/tty elif [ -n "$FIRSTBOOT" ]; then - echo "Reboot reason: NORMAL: First boot of device - at $(date -Ins -u)" | tee /dev/console + echo "Reboot reason: NORMAL: First boot of device - at $(date -Ins -u)" | tee /dev/tty else - echo "Reboot reason: UNKNOWN: reboot reason - power failure or crash - at $(date -Ins -u)" | tee /dev/console + echo "Reboot reason: UNKNOWN: reboot reason - power failure or crash - at $(date -Ins -u)" | tee /dev/tty fi if [ ! -d $PERSISTDIR/log ]; then diff --git a/pkg/pillar/types/vaultmgrtypes.go b/pkg/pillar/types/vaultmgrtypes.go index e5975ddd47..229f691cda 100644 --- a/pkg/pillar/types/vaultmgrtypes.go +++ b/pkg/pillar/types/vaultmgrtypes.go @@ -16,6 +16,8 @@ type VaultStatus struct { Status info.DataSecAtRestStatus PCRStatus info.PCRStatus ConversionComplete bool + // only valid if TPM is enabled and Sealed key is used + MismatchingPCRs []int // ErrorAndTime provides SetErrorNow() and ClearError() ErrorAndTime } diff --git a/pkg/pillar/vault/handler_ext4.go b/pkg/pillar/vault/handler_ext4.go index 1fff726be3..d628fc4a6c 100644 --- a/pkg/pillar/vault/handler_ext4.go +++ b/pkg/pillar/vault/handler_ext4.go @@ -424,6 +424,11 @@ func (h *Ext4Handler) getVaultStatus(vaultName string, vaultPath string, status.Status = info.DataSecAtRestStatus_DATASEC_AT_REST_ENABLED } else { status.Status = info.DataSecAtRestStatus_DATASEC_AT_REST_ERROR + if status.PCRStatus == info.PCRStatus_PCR_ENABLED { + if pcrs, err := etpm.FindMismatchingPCRs(); err == nil { + status.MismatchingPCRs = pcrs + } + } status.SetErrorDescription(types.ErrorDescription{Error: "Vault key unavailable"}) } } diff --git a/pkg/pillar/vault/handler_zfs.go b/pkg/pillar/vault/handler_zfs.go index 99db238f11..9c1322ec6a 100644 --- a/pkg/pillar/vault/handler_zfs.go +++ b/pkg/pillar/vault/handler_zfs.go @@ -247,6 +247,11 @@ func (h *ZFSHandler) getVaultStatus(vaultName, vaultPath string) *types.VaultSta if err := h.checkOperationalStatus(vaultPath); err != nil { h.log.Errorf("Status failed, %s", err) status.Status = info.DataSecAtRestStatus_DATASEC_AT_REST_ERROR + if status.PCRStatus == info.PCRStatus_PCR_ENABLED { + if pcrs, err := etpm.FindMismatchingPCRs(); err == nil { + status.MismatchingPCRs = pcrs + } + } status.SetErrorDescription(types.ErrorDescription{Error: err.Error()}) } else { h.log.Functionf("checkOperStatus returns ok for %s", vaultPath) diff --git a/pkg/pillar/vendor/github.com/getlantern/framed/.travis.yml b/pkg/pillar/vendor/github.com/getlantern/framed/.travis.yml new file mode 100644 index 0000000000..e9a764db4e --- /dev/null +++ b/pkg/pillar/vendor/github.com/getlantern/framed/.travis.yml @@ -0,0 +1,14 @@ +language: go + +go: + - 1.4.1 + +install: + - go get -d -t -v ./... + - go build -v ./... + - go get golang.org/x/tools/cmd/cover + - go get -v github.com/axw/gocov/gocov + - go get -v github.com/mattn/goveralls + +script: + - $HOME/gopath/bin/goveralls -v -service travis-ci github.com/getlantern/framed \ No newline at end of file diff --git a/pkg/pillar/vendor/github.com/getlantern/framed/LICENSE b/pkg/pillar/vendor/github.com/getlantern/framed/LICENSE new file mode 100644 index 0000000000..3ee01626e1 --- /dev/null +++ b/pkg/pillar/vendor/github.com/getlantern/framed/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2014 Brave New Software Project, Inc. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/pkg/pillar/vendor/github.com/getlantern/framed/README.md b/pkg/pillar/vendor/github.com/getlantern/framed/README.md new file mode 100644 index 0000000000..28ae8aa7cf --- /dev/null +++ b/pkg/pillar/vendor/github.com/getlantern/framed/README.md @@ -0,0 +1,9 @@ +framed [![Travis CI Status](https://travis-ci.org/getlantern/framed.svg?branch=master)](https://travis-ci.org/getlantern/framed) [![Coverage Status](https://coveralls.io/repos/getlantern/framed/badge.png)](https://coveralls.io/r/getlantern/framed) [![GoDoc](https://godoc.org/github.com/getlantern/framed?status.png)](http://godoc.org/github.com/getlantern/framed) +========== +To install: + +`go get github.com/getlantern/framed` + +For docs: + +`godoc github.com/getlantern/framed` \ No newline at end of file diff --git a/pkg/pillar/vendor/github.com/getlantern/framed/framed.go b/pkg/pillar/vendor/github.com/getlantern/framed/framed.go new file mode 100644 index 0000000000..9c50907ba6 --- /dev/null +++ b/pkg/pillar/vendor/github.com/getlantern/framed/framed.go @@ -0,0 +1,329 @@ +/* +Package framed provides an implementations of io.Writer and io.Reader that write +and read whole frames only. + +Frames are length-prefixed. The first two bytes are an unsigned 16 bit int +stored in little-endian byte order indicating the length of the content. The +remaining bytes are the actual content of the frame. + +The use of a uint16 means that the maximum possible frame size (MaxFrameSize) +is 65535. + +The frame size can be increased to 4294967295 bytes by calling EnableBigFrames() +on the corresponding Reader and Writer. +*/ +package framed + +import ( + "bufio" + "encoding/binary" + "fmt" + "io" + "sync" + + "github.com/oxtoacart/bpool" +) + +var endianness = binary.LittleEndian + +const ( + // FrameHeaderBits is the size of the frame header in bits + FrameHeaderBits = 16 + + // FrameHeaderBitsBig is the size of the frame header in bits when big frames are enabled + FrameHeaderBitsBig = 32 + + // FrameHeaderLength is the size of the frame header in bytes + FrameHeaderLength = FrameHeaderBits / 8 + + // FrameHeaderLengthBig is the size of the frame header in bytes when big frames are enabled + FrameHeaderLengthBig = FrameHeaderBitsBig / 8 + + // MaxFrameLength is the maximum possible size of a frame (not including the + // length prefix) + MaxFrameLength = 1< bufferSize { + return 0, fmt.Errorf("Buffer of size %d is too small to hold frame of size %d", bufferSize, n) + } + + // Read into buffer + n, err = io.ReadFull(fr.Stream, buffer[:n]) + return +} + +// ReadFrame reads the next frame, using a new buffer sized to hold the frame. +func (fr *Reader) ReadFrame() (frame []byte, err error) { + if !fr.threadSafetyDisabled { + fr.mutex.Lock() + defer fr.mutex.Unlock() + } + + var n int + n, err = fr.readLength() + if err != nil { + return + } + + frame = make([]byte, n) + + // Read into buffer + _, err = io.ReadFull(fr.Stream, frame) + return +} + +func (fr *Reader) readLength() (int, error) { + _, err := io.ReadFull(fr.Stream, fr.lb) + if err != nil { + return 0, err + } + if fr.bigFramesEnabled { + return int(endianness.Uint32(fr.lb)), nil + } + return int(endianness.Uint16(fr.lb)), nil +} + +/* +Write implements the Write method from io.Writer. It prepends a frame length +header that allows the fr.Reader on the other end to read the whole frame. +*/ +func (fr *Writer) Write(frame []byte) (n int, err error) { + if !fr.threadSafetyDisabled { + fr.mutex.Lock() + defer fr.mutex.Unlock() + } + + n = len(frame) + if n, err = fr.writeHeaderLength(n); err != nil { + return + } + + // Write the data + var written int + if written, err = fr.Stream.Write(frame); err != nil { + return + } + if written != n { + err = fmt.Errorf("%d bytes written, expected to write %d", written, n) + } + return +} + +// WriteAtomic writes a the frame and its length header in a single write. This requires +// that the frame was read into a buffer obtained from a pool created with +// NewHeaderPreservingBufferPool(). +func (fr *Writer) WriteAtomic(frame bpool.ByteSlice) (n int, err error) { + if !fr.threadSafetyDisabled { + fr.mutex.Lock() + defer fr.mutex.Unlock() + } + + n = len(frame.Bytes()) + _frame := frame.BytesWithHeader() + + switch fr.bigFramesEnabled { + case true: + endianness.PutUint32(_frame, uint32(n)) + default: + endianness.PutUint16(_frame, uint16(n)) + } + + // Write frame and data atomically + _, err = fr.Stream.Write(_frame) + if err != nil { + n = 0 + } + return +} + +func (fr *Writer) WritePieces(pieces ...[]byte) (n int, err error) { + if !fr.threadSafetyDisabled { + fr.mutex.Lock() + defer fr.mutex.Unlock() + } + + for _, piece := range pieces { + n = n + len(piece) + } + + if n, err = fr.writeHeaderLength(n); err != nil { + return + } + + // Write the data + var written int + for _, piece := range pieces { + var nw int + if nw, err = fr.Stream.Write(piece); err != nil { + return + } + written = written + nw + } + if written != n { + err = fmt.Errorf("%d bytes written, expected to write %d", written, n) + } + return +} + +func (fr *Writer) writeHeaderLength(n int) (int, error) { + if int64(n) > fr.maxFrameLength { + return 0, fmt.Errorf(tooLongError, n, MaxFrameLength) + } + + if fr.bigFramesEnabled { + return n, binary.Write(fr.Stream, endianness, uint32(n)) + } + return n, binary.Write(fr.Stream, endianness, uint16(n)) +} + +// NewHeaderPreservingBufferPool creates a BufferPool that leaves room at the beginning +// of buffers for the framed header. This allows use of the WriteAtomic() capability. +func NewHeaderPreservingBufferPool(maxSize int, width int, enableBigFrames bool) bpool.ByteSlicePool { + headerLength := FrameHeaderLength + if enableBigFrames { + headerLength = FrameHeaderLengthBig + } + return bpool.NewHeaderPreservingByteSlicePool(maxSize/(width+headerLength), width, headerLength) +} diff --git a/pkg/pillar/vendor/github.com/oxtoacart/bpool/LICENSE b/pkg/pillar/vendor/github.com/oxtoacart/bpool/LICENSE new file mode 100644 index 0000000000..f94e97c736 --- /dev/null +++ b/pkg/pillar/vendor/github.com/oxtoacart/bpool/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2014 Percy Wegmann + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/pkg/pillar/vendor/github.com/oxtoacart/bpool/README.md b/pkg/pillar/vendor/github.com/oxtoacart/bpool/README.md new file mode 100644 index 0000000000..9acf3f928a --- /dev/null +++ b/pkg/pillar/vendor/github.com/oxtoacart/bpool/README.md @@ -0,0 +1,65 @@ +# bpool [![GoDoc](https://godoc.org/github.com/oxtoacart/bpool?status.png)](https://godoc.org/github.com/oxtoacart/bpool) + +Package bpool implements leaky pools of byte arrays and Buffers as bounded channels. +It is based on the leaky buffer example from the Effective Go documentation: http://golang.org/doc/effective_go.html#leaky_buffer + +bpool provides the following pool types: + +* [bpool.BufferPool](https://godoc.org/github.com/oxtoacart/bpool#BufferPool) + which provides a fixed-size pool of + [bytes.Buffers](http://golang.org/pkg/bytes/#Buffer). +* [bpool.BytePool](https://godoc.org/github.com/oxtoacart/bpool#BytePool) which + provides a fixed-size pool of `[]byte` slices with a pre-set width (length). +* [bpool.SizedBufferPool](https://godoc.org/github.com/oxtoacart/bpool#SizedBufferPool), + which is an alternative to `bpool.BufferPool` that pre-sizes the capacity of + buffers issued from the pool and discards buffers that have grown too large + upon return. + +A common use case for this package is to use buffers to execute HTML templates +against (via ExecuteTemplate) or encode JSON into (via json.NewEncoder). This +allows you to catch any rendering or marshalling errors prior to writing to a +`http.ResponseWriter`, which helps to avoid writing incomplete or malformed data +to the response. + +## Install + +`go get github.com/oxtoacart/bpool` + +## Documentation + +See [godoc.org](http://godoc.org/github.com/oxtoacart/bpool) or use `godoc github.com/oxtoacart/bpool` + +## Example + +Here's a quick example for using `bpool.BufferPool`. We create a pool of the +desired size, call the `Get()` method to obtain a buffer for use, and call +`Put(buf)` to return the buffer to the pool. + +```go + +var bufpool *bpool.BufferPool + +func main() { + + bufpool = bpool.NewBufferPool(48) + +} + +func someFunction() error { + + // Get a buffer from the pool + buf := bufpool.Get() + ... + ... + ... + // Return the buffer to the pool + bufpool.Put(buf) + + return nil +} +``` + +## License + +Apache 2.0 Licensed. See the LICENSE file for details. + diff --git a/pkg/pillar/vendor/github.com/oxtoacart/bpool/bpool.go b/pkg/pillar/vendor/github.com/oxtoacart/bpool/bpool.go new file mode 100644 index 0000000000..6232a38bec --- /dev/null +++ b/pkg/pillar/vendor/github.com/oxtoacart/bpool/bpool.go @@ -0,0 +1,6 @@ +/* +Package bpool implements leaky pools of byte arrays and Buffers as bounded +channels. It is based on the leaky buffer example from the Effective Go +documentation: http://golang.org/doc/effective_go.html#leaky_buffer +*/ +package bpool diff --git a/pkg/pillar/vendor/github.com/oxtoacart/bpool/bufferpool.go b/pkg/pillar/vendor/github.com/oxtoacart/bpool/bufferpool.go new file mode 100644 index 0000000000..249a078ed3 --- /dev/null +++ b/pkg/pillar/vendor/github.com/oxtoacart/bpool/bufferpool.go @@ -0,0 +1,45 @@ +package bpool + +import ( + "bytes" +) + +// BufferPool implements a pool of bytes.Buffers in the form of a bounded +// channel. +type BufferPool struct { + c chan *bytes.Buffer +} + +// NewBufferPool creates a new BufferPool bounded to the given size. +func NewBufferPool(size int) (bp *BufferPool) { + return &BufferPool{ + c: make(chan *bytes.Buffer, size), + } +} + +// Get gets a Buffer from the BufferPool, or creates a new one if none are +// available in the pool. +func (bp *BufferPool) Get() (b *bytes.Buffer) { + select { + case b = <-bp.c: + // reuse existing buffer + default: + // create new buffer + b = bytes.NewBuffer([]byte{}) + } + return +} + +// Put returns the given Buffer to the BufferPool. +func (bp *BufferPool) Put(b *bytes.Buffer) { + b.Reset() + select { + case bp.c <- b: + default: // Discard the buffer if the pool is full. + } +} + +// NumPooled returns the number of items currently pooled. +func (bp *BufferPool) NumPooled() int { + return len(bp.c) +} diff --git a/pkg/pillar/vendor/github.com/oxtoacart/bpool/bytepool.go b/pkg/pillar/vendor/github.com/oxtoacart/bpool/bytepool.go new file mode 100644 index 0000000000..a5ebe0515c --- /dev/null +++ b/pkg/pillar/vendor/github.com/oxtoacart/bpool/bytepool.go @@ -0,0 +1,56 @@ +package bpool + +// BytePool implements a leaky pool of []byte in the form of a bounded +// channel. +type BytePool struct { + c chan []byte + w int + h int +} + +// NewBytePool creates a new BytePool bounded to the given maxSize, with new +// byte arrays sized based on width. +func NewBytePool(maxSize int, width int) (bp *BytePool) { + return &BytePool{ + c: make(chan []byte, maxSize), + w: width, + } +} + +// Get gets a []byte from the BytePool, or creates a new one if none are +// available in the pool. +func (bp *BytePool) Get() (b []byte) { + select { + case b = <-bp.c: + // reuse existing buffer + default: + // create new buffer + b = make([]byte, bp.w) + } + return +} + +// Put returns the given Buffer to the BytePool. +func (bp *BytePool) Put(b []byte) { + if cap(b) < bp.w { + // someone tried to put back a too small buffer, discard it + return + } + + select { + case bp.c <- b[:bp.w]: + // buffer went back into pool + default: + // buffer didn't go back into pool, just discard + } +} + +// NumPooled returns the number of items currently pooled. +func (bp *BytePool) NumPooled() int { + return len(bp.c) +} + +// Width returns the width of the byte arrays in this pool. +func (bp *BytePool) Width() (n int) { + return bp.w +} diff --git a/pkg/pillar/vendor/github.com/oxtoacart/bpool/byteslice.go b/pkg/pillar/vendor/github.com/oxtoacart/bpool/byteslice.go new file mode 100644 index 0000000000..7b94b39018 --- /dev/null +++ b/pkg/pillar/vendor/github.com/oxtoacart/bpool/byteslice.go @@ -0,0 +1,81 @@ +package bpool + +// WrapByteSlice wraps a []byte as a ByteSlice +func WrapByteSlice(full []byte, headerLength int) ByteSlice { + return ByteSlice{ + full: full, + current: full[headerLength:], + head: headerLength, + end: len(full), + } +} + +// ByteSlice provides a wrapper around []byte with some added convenience +type ByteSlice struct { + full []byte + current []byte + head int + end int +} + +// ResliceTo reslices the end of the current slice. +func (b ByteSlice) ResliceTo(end int) ByteSlice { + return ByteSlice{ + full: b.full, + current: b.current[:end], + head: b.head, + end: b.head + end, + } +} + +// Bytes returns the current slice +func (b ByteSlice) Bytes() []byte { + return b.current +} + +// BytesWithHeader returns the current slice preceded by the header +func (b ByteSlice) BytesWithHeader() []byte { + return b.full[:b.end] +} + +// Full returns the full original buffer underlying the ByteSlice +func (b ByteSlice) Full() []byte { + return b.full +} + +// ByteSlicePool is a bool of byte slices +type ByteSlicePool interface { + // Get gets a byte slice from the pool + GetSlice() ByteSlice + // Put returns a byte slice to the pool + PutSlice(ByteSlice) + // NumPooled returns the number of currently pooled items + NumPooled() int +} + +// NewByteSlicePool creates a new ByteSlicePool bounded to the +// given maxSize, with new byte arrays sized based on width +func NewByteSlicePool(maxSize int, width int) ByteSlicePool { + return NewHeaderPreservingByteSlicePool(maxSize, width, 0) +} + +// NewHeaderPreservingByteSlicePool creates a new ByteSlicePool bounded to the +// given maxSize, with new byte arrays sized based on width and headerLength +// preserved at the beginning of the slice. +func NewHeaderPreservingByteSlicePool(maxSize int, width int, headerLength int) ByteSlicePool { + return &BytePool{ + c: make(chan []byte, maxSize), + w: width + headerLength, + h: headerLength, + } +} + +// GetSlice implements the method from interface ByteSlicePool +func (bp *BytePool) GetSlice() ByteSlice { + return WrapByteSlice(bp.Get(), bp.h) +} + +// PutSlice implements the method from interface ByteSlicePool +func (bp *BytePool) PutSlice(b ByteSlice) { + bp.Put(b.Full()) +} diff --git a/pkg/pillar/vendor/github.com/oxtoacart/bpool/sizedbufferpool.go b/pkg/pillar/vendor/github.com/oxtoacart/bpool/sizedbufferpool.go new file mode 100644 index 0000000000..8519acafde --- /dev/null +++ b/pkg/pillar/vendor/github.com/oxtoacart/bpool/sizedbufferpool.go @@ -0,0 +1,60 @@ +package bpool + +import ( + "bytes" +) + +// SizedBufferPool implements a pool of bytes.Buffers in the form of a bounded +// channel. Buffers are pre-allocated to the requested size. +type SizedBufferPool struct { + c chan *bytes.Buffer + a int +} + +// SizedBufferPool creates a new BufferPool bounded to the given size. +// size defines the number of buffers to be retained in the pool and alloc sets +// the initial capacity of new buffers to minimize calls to make(). +// +// The value of alloc should seek to provide a buffer that is representative of +// most data written to the the buffer (i.e. 95th percentile) without being +// overly large (which will increase static memory consumption). You may wish to +// track the capacity of your last N buffers (i.e. using an []int) prior to +// returning them to the pool as input into calculating a suitable alloc value. +func NewSizedBufferPool(size int, alloc int) (bp *SizedBufferPool) { + return &SizedBufferPool{ + c: make(chan *bytes.Buffer, size), + a: alloc, + } +} + +// Get gets a Buffer from the SizedBufferPool, or creates a new one if none are +// available in the pool. Buffers have a pre-allocated capacity. +func (bp *SizedBufferPool) Get() (b *bytes.Buffer) { + select { + case b = <-bp.c: + // reuse existing buffer + default: + // create new buffer + b = bytes.NewBuffer(make([]byte, 0, bp.a)) + } + return +} + +// Put returns the given Buffer to the SizedBufferPool. +func (bp *SizedBufferPool) Put(b *bytes.Buffer) { + b.Reset() + + // Release buffers over our maximum capacity and re-create a pre-sized + // buffer to replace it. + // Note that the cap(b.Bytes()) provides the capacity from the read off-set + // only, but as we've called b.Reset() the full capacity of the underlying + // byte slice is returned. + if cap(b.Bytes()) > bp.a { + b = bytes.NewBuffer(make([]byte, 0, bp.a)) + } + + select { + case bp.c <- b: + default: // Discard the buffer if the pool is full. + } +} diff --git a/pkg/pillar/vendor/modules.txt b/pkg/pillar/vendor/modules.txt index 17e9279c35..4db00ab75b 100644 --- a/pkg/pillar/vendor/modules.txt +++ b/pkg/pillar/vendor/modules.txt @@ -371,6 +371,9 @@ github.com/gabriel-vasile/mimetype github.com/gabriel-vasile/mimetype/internal/charset github.com/gabriel-vasile/mimetype/internal/json github.com/gabriel-vasile/mimetype/internal/magic +# github.com/getlantern/framed v0.0.0-20190601192238-ceb6431eeede +## explicit; go 1.12 +github.com/getlantern/framed # github.com/ghodss/yaml v1.0.0 ## explicit github.com/ghodss/yaml @@ -770,6 +773,9 @@ github.com/openshift/client-go/security/clientset/versioned/typed/security/v1 # github.com/openshift/custom-resource-status v1.1.2 ## explicit; go 1.12 github.com/openshift/custom-resource-status/conditions/v1 +# github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c +## explicit; go 1.12 +github.com/oxtoacart/bpool # github.com/packetcap/go-pcap v0.0.0-20230717110547-c34843f9206d ## explicit; go 1.13 github.com/packetcap/go-pcap diff --git a/pkg/pillar/zedbox/zedbox.go b/pkg/pillar/zedbox/zedbox.go index e41b266d5c..ad75295e09 100644 --- a/pkg/pillar/zedbox/zedbox.go +++ b/pkg/pillar/zedbox/zedbox.go @@ -29,6 +29,7 @@ import ( "github.com/lf-edge/eve/pkg/pillar/cmd/ipcmonitor" "github.com/lf-edge/eve/pkg/pillar/cmd/ledmanager" "github.com/lf-edge/eve/pkg/pillar/cmd/loguploader" + "github.com/lf-edge/eve/pkg/pillar/cmd/monitor" "github.com/lf-edge/eve/pkg/pillar/cmd/nim" "github.com/lf-edge/eve/pkg/pillar/cmd/nodeagent" "github.com/lf-edge/eve/pkg/pillar/cmd/pbuf" @@ -109,6 +110,7 @@ var ( "zfsmanager": {f: zfsmanager.Run}, "usbmanager": {f: usbmanager.Run}, "vcomlink": {f: vcomlink.Run}, + "monitor": {f: monitor.Run}, } logger *logrus.Logger log *base.LogObject diff --git a/tools/parse-pkgs.sh b/tools/parse-pkgs.sh index 34f3d690d6..32f97e4003 100755 --- a/tools/parse-pkgs.sh +++ b/tools/parse-pkgs.sh @@ -152,6 +152,7 @@ KUBE_TAG=${KUBE_TAG} RECOVERTPM_TAG=${RECOVERTPM_TAG} UDEV_TAG=${UDEV_TAG} INSTALLER_TAG=${INSTALLER_TAG} +MONITOR_TAG=${MONITOR_TAG} EOF } @@ -210,6 +211,7 @@ KUBE_TAG=$(linuxkit_tag pkg/kube) RECOVERTPM_TAG=$(linuxkit_tag pkg/recovertpm) UDEV_TAG=$(linuxkit_tag pkg/udev) INSTALLER_TAG=$(linuxkit_tag pkg/installer) +MONITOR_TAG=$(linuxkit_tag pkg/monitor) # Synthetic tags: the following tags are based on hashing # the contents of all the Dockerfile.in that we can find.