From 5d4d5542a8b6836a94dade1a69560ea87ad2a6e1 Mon Sep 17 00:00:00 2001 From: Jack Lin Date: Mon, 14 Oct 2024 16:34:36 +0800 Subject: [PATCH] feat(backing image): support backing image in spdk engine ref: longhorn/longhorn 6341 Signed-off-by: Jack Lin --- go.mod | 28 +- go.sum | 59 +- pkg/api/types.go | 100 +- pkg/client/client.go | 136 +- pkg/spdk/backing_image.go | 735 ++++ pkg/spdk/backup.go | 11 +- pkg/spdk/engine.go | 94 +- pkg/spdk/replica.go | 57 +- pkg/spdk/server.go | 322 +- pkg/spdk/types.go | 51 + pkg/spdk/util.go | 77 + pkg/spdk/util_test.go | 66 + pkg/spdk_test.go | 55 +- pkg/types/types.go | 24 +- pkg/util/http_handler.go | 187 + pkg/util/util.go | 15 + .../bits-and-blooms/bitset/README.md | 17 + .../bits-and-blooms/bitset/bitset.go | 271 +- .../bitset/leading_zeros_18.go | 43 + .../bitset/leading_zeros_19.go | 14 + .../bits-and-blooms/bitset/select.go | 45 + .../github.com/coreos/go-systemd/v22/LICENSE | 191 - .../github.com/coreos/go-systemd/v22/NOTICE | 5 - .../coreos/go-systemd/v22/dbus/dbus.go | 266 -- .../coreos/go-systemd/v22/dbus/methods.go | 864 ----- .../coreos/go-systemd/v22/dbus/properties.go | 237 -- .../coreos/go-systemd/v22/dbus/set.go | 47 - .../go-systemd/v22/dbus/subscription.go | 333 -- .../go-systemd/v22/dbus/subscription_set.go | 57 - vendor/github.com/gammazero/deque/README.md | 12 +- vendor/github.com/gammazero/deque/deque.go | 213 +- vendor/github.com/gammazero/deque/doc.go | 13 +- .../github.com/godbus/dbus/v5/CONTRIBUTING.md | 50 - vendor/github.com/godbus/dbus/v5/LICENSE | 25 - vendor/github.com/godbus/dbus/v5/MAINTAINERS | 3 - vendor/github.com/godbus/dbus/v5/README.md | 46 - vendor/github.com/godbus/dbus/v5/auth.go | 257 -- .../godbus/dbus/v5/auth_anonymous.go | 16 - .../godbus/dbus/v5/auth_external.go | 26 - vendor/github.com/godbus/dbus/v5/auth_sha1.go | 102 - vendor/github.com/godbus/dbus/v5/call.go | 69 - vendor/github.com/godbus/dbus/v5/conn.go | 996 ------ .../github.com/godbus/dbus/v5/conn_darwin.go | 37 - .../github.com/godbus/dbus/v5/conn_other.go | 90 - vendor/github.com/godbus/dbus/v5/conn_unix.go | 17 - .../github.com/godbus/dbus/v5/conn_windows.go | 15 - vendor/github.com/godbus/dbus/v5/dbus.go | 430 --- vendor/github.com/godbus/dbus/v5/decoder.go | 292 -- .../godbus/dbus/v5/default_handler.go | 342 -- vendor/github.com/godbus/dbus/v5/doc.go | 71 - vendor/github.com/godbus/dbus/v5/encoder.go | 235 -- vendor/github.com/godbus/dbus/v5/escape.go | 84 - vendor/github.com/godbus/dbus/v5/export.go | 463 --- vendor/github.com/godbus/dbus/v5/homedir.go | 25 - vendor/github.com/godbus/dbus/v5/match.go | 89 - vendor/github.com/godbus/dbus/v5/message.go | 390 --- vendor/github.com/godbus/dbus/v5/object.go | 174 - vendor/github.com/godbus/dbus/v5/sequence.go | 24 - .../godbus/dbus/v5/sequential_handler.go | 125 - .../godbus/dbus/v5/server_interfaces.go | 107 - vendor/github.com/godbus/dbus/v5/sig.go | 293 -- .../godbus/dbus/v5/transport_darwin.go | 6 - .../godbus/dbus/v5/transport_generic.go | 52 - .../godbus/dbus/v5/transport_nonce_tcp.go | 39 - .../godbus/dbus/v5/transport_tcp.go | 41 - .../godbus/dbus/v5/transport_unix.go | 212 -- .../dbus/v5/transport_unixcred_dragonfly.go | 95 - .../dbus/v5/transport_unixcred_freebsd.go | 92 - .../dbus/v5/transport_unixcred_linux.go | 25 - .../dbus/v5/transport_unixcred_netbsd.go | 14 - .../dbus/v5/transport_unixcred_openbsd.go | 14 - .../godbus/dbus/v5/transport_zos.go | 6 - vendor/github.com/godbus/dbus/v5/variant.go | 150 - .../godbus/dbus/v5/variant_lexer.go | 284 -- .../godbus/dbus/v5/variant_parser.go | 817 ----- .../types/pkg/generated/spdkrpc/spdk.pb.go | 3115 ++++++++++------- .../pkg/generated/spdkrpc/spdk_grpc.pb.go | 286 ++ .../golang_protobuf_extensions/v2/NOTICE | 1 - .../v2/pbutil/.gitignore | 1 - .../v2/pbutil/Makefile | 7 - .../v2/pbutil/decode.go | 81 - .../v2/pbutil/doc.go | 16 - .../v2/pbutil/encode.go | 49 - .../moby/sys/mountinfo/mounted_linux.go | 2 +- .../v2 => moby/sys/userns}/LICENSE | 5 +- vendor/github.com/moby/sys/userns/userns.go | 16 + .../moby/sys/userns/userns_linux.go | 53 + .../moby/sys/userns/userns_linux_fuzzer.go | 8 + .../moby/sys/userns/userns_unsupported.go | 6 + vendor/github.com/munnerz/goautoneg/LICENSE | 31 + vendor/github.com/munnerz/goautoneg/Makefile | 13 + .../ww => munnerz}/goautoneg/README.txt | 0 .../ww => munnerz}/goautoneg/autoneg.go | 127 +- vendor/github.com/opencontainers/runc/NOTICE | 4 +- .../runc/libcontainer/configs/blkio_device.go | 66 - .../runc/libcontainer/configs/cgroup_linux.go | 158 - .../configs/cgroup_unsupported.go | 9 - .../runc/libcontainer/configs/config.go | 414 --- .../runc/libcontainer/configs/config_linux.go | 86 - .../libcontainer/configs/configs_fuzzer.go | 10 - .../libcontainer/configs/hugepage_limit.go | 9 - .../runc/libcontainer/configs/intelrdt.go | 16 - .../configs/interface_priority_map.go | 14 - .../runc/libcontainer/configs/mount.go | 48 - .../runc/libcontainer/configs/namespaces.go | 5 - .../libcontainer/configs/namespaces_linux.go | 126 - .../configs/namespaces_syscall.go | 33 - .../configs/namespaces_syscall_unsupported.go | 14 - .../configs/namespaces_unsupported.go | 8 - .../runc/libcontainer/configs/network.go | 75 - .../runc/libcontainer/configs/rdma.go | 9 - .../runc/libcontainer/devices/device.go | 174 - .../runc/libcontainer/devices/device_unix.go | 120 - .../runc/libcontainer/user/lookup_unix.go | 157 - .../runc/libcontainer/user/user.go | 604 ---- .../runc/libcontainer/user/user_fuzzer.go | 43 - .../runc/libcontainer/userns/userns.go | 5 - .../libcontainer/userns/userns_deprecated.go | 13 + .../runc/libcontainer/userns/userns_fuzzer.go | 16 - .../runc/libcontainer/userns/userns_linux.go | 37 - .../runc/libcontainer/userns/userns_maps.c | 79 - .../libcontainer/userns/userns_maps_linux.go | 186 - .../libcontainer/userns/userns_unsupported.go | 18 - .../opencontainers/runtime-spec/LICENSE | 191 - .../runtime-spec/specs-go/config.go | 879 ----- .../runtime-spec/specs-go/state.go | 56 - .../runtime-spec/specs-go/version.go | 18 - .../prometheus/client_golang/NOTICE | 5 - .../client_golang/prometheus/go_collector.go | 55 +- .../prometheus/go_collector_latest.go | 19 +- .../client_golang/prometheus/histogram.go | 268 +- .../internal/go_collector_options.go | 2 + .../client_golang/prometheus/metric.go | 2 +- .../prometheus/process_collector.go | 29 +- .../prometheus/process_collector_other.go | 14 + .../client_golang/prometheus/registry.go | 17 +- .../client_golang/prometheus/summary.go | 42 + .../client_golang/prometheus/vec.go | 2 +- .../prometheus/client_model/go/metrics.pb.go | 195 +- .../prometheus/common/expfmt/decode.go | 17 +- .../prometheus/common/expfmt/encode.go | 87 +- .../prometheus/common/expfmt/expfmt.go | 184 +- .../common/expfmt/openmetrics_create.go | 283 +- .../prometheus/common/expfmt/text_create.go | 118 +- .../prometheus/common/expfmt/text_parse.go | 168 +- .../prometheus/common/model/alert.go | 31 +- .../prometheus/common/model/labels.go | 25 +- .../prometheus/common/model/labelset.go | 11 - .../common/model/labelset_string.go | 43 + .../prometheus/common/model/metadata.go | 28 + .../prometheus/common/model/metric.go | 372 +- .../prometheus/common/model/signature.go | 6 +- .../prometheus/common/model/silence.go | 2 +- .../prometheus/common/model/value.go | 16 +- .../prometheus/common/model/value_float.go | 14 +- .../prometheus/procfs/.golangci.yml | 7 + .../prometheus/procfs/MAINTAINERS.md | 3 +- .../prometheus/procfs/Makefile.common | 26 +- vendor/github.com/prometheus/procfs/arp.go | 6 +- .../github.com/prometheus/procfs/buddyinfo.go | 6 +- .../github.com/prometheus/procfs/cpuinfo.go | 4 +- vendor/github.com/prometheus/procfs/crypto.go | 6 +- .../github.com/prometheus/procfs/fscache.go | 4 +- vendor/github.com/prometheus/procfs/ipvs.go | 6 +- .../github.com/prometheus/procfs/loadavg.go | 2 +- vendor/github.com/prometheus/procfs/mdstat.go | 60 +- .../github.com/prometheus/procfs/meminfo.go | 220 +- .../github.com/prometheus/procfs/mountinfo.go | 2 +- .../prometheus/procfs/mountstats.go | 11 +- .../prometheus/procfs/net_conntrackstat.go | 4 +- .../prometheus/procfs/net_ip_socket.go | 46 +- .../prometheus/procfs/net_sockstat.go | 4 +- .../prometheus/procfs/net_softnet.go | 2 +- .../prometheus/procfs/net_tls_stat.go | 119 + .../github.com/prometheus/procfs/net_unix.go | 14 +- .../prometheus/procfs/net_wireless.go | 22 +- vendor/github.com/prometheus/procfs/proc.go | 8 +- .../prometheus/procfs/proc_limits.go | 2 +- .../github.com/prometheus/procfs/proc_ns.go | 4 +- .../github.com/prometheus/procfs/proc_psi.go | 2 +- .../prometheus/procfs/proc_smaps.go | 2 +- .../github.com/prometheus/procfs/proc_stat.go | 7 + .../prometheus/procfs/proc_status.go | 29 +- .../github.com/prometheus/procfs/proc_sys.go | 2 +- .../github.com/prometheus/procfs/softirqs.go | 22 +- vendor/github.com/prometheus/procfs/stat.go | 22 +- vendor/github.com/prometheus/procfs/swaps.go | 6 +- vendor/github.com/prometheus/procfs/thread.go | 2 +- .../github.com/prometheus/procfs/zoneinfo.go | 4 +- .../encoding/protodelim/protodelim.go | 160 + vendor/modules.txt | 59 +- 191 files changed, 7150 insertions(+), 14712 deletions(-) create mode 100644 pkg/spdk/backing_image.go create mode 100644 pkg/util/http_handler.go create mode 100644 vendor/github.com/bits-and-blooms/bitset/leading_zeros_18.go create mode 100644 vendor/github.com/bits-and-blooms/bitset/leading_zeros_19.go create mode 100644 vendor/github.com/bits-and-blooms/bitset/select.go delete mode 100644 vendor/github.com/coreos/go-systemd/v22/LICENSE delete mode 100644 vendor/github.com/coreos/go-systemd/v22/NOTICE delete mode 100644 vendor/github.com/coreos/go-systemd/v22/dbus/dbus.go delete mode 100644 vendor/github.com/coreos/go-systemd/v22/dbus/methods.go delete mode 100644 vendor/github.com/coreos/go-systemd/v22/dbus/properties.go delete mode 100644 vendor/github.com/coreos/go-systemd/v22/dbus/set.go delete mode 100644 vendor/github.com/coreos/go-systemd/v22/dbus/subscription.go delete mode 100644 vendor/github.com/coreos/go-systemd/v22/dbus/subscription_set.go delete mode 100644 vendor/github.com/godbus/dbus/v5/CONTRIBUTING.md delete mode 100644 vendor/github.com/godbus/dbus/v5/LICENSE delete mode 100644 vendor/github.com/godbus/dbus/v5/MAINTAINERS delete mode 100644 vendor/github.com/godbus/dbus/v5/README.md delete mode 100644 vendor/github.com/godbus/dbus/v5/auth.go delete mode 100644 vendor/github.com/godbus/dbus/v5/auth_anonymous.go delete mode 100644 vendor/github.com/godbus/dbus/v5/auth_external.go delete mode 100644 vendor/github.com/godbus/dbus/v5/auth_sha1.go delete mode 100644 vendor/github.com/godbus/dbus/v5/call.go delete mode 100644 vendor/github.com/godbus/dbus/v5/conn.go delete mode 100644 vendor/github.com/godbus/dbus/v5/conn_darwin.go delete mode 100644 vendor/github.com/godbus/dbus/v5/conn_other.go delete mode 100644 vendor/github.com/godbus/dbus/v5/conn_unix.go delete mode 100644 vendor/github.com/godbus/dbus/v5/conn_windows.go delete mode 100644 vendor/github.com/godbus/dbus/v5/dbus.go delete mode 100644 vendor/github.com/godbus/dbus/v5/decoder.go delete mode 100644 vendor/github.com/godbus/dbus/v5/default_handler.go delete mode 100644 vendor/github.com/godbus/dbus/v5/doc.go delete mode 100644 vendor/github.com/godbus/dbus/v5/encoder.go delete mode 100644 vendor/github.com/godbus/dbus/v5/escape.go delete mode 100644 vendor/github.com/godbus/dbus/v5/export.go delete mode 100644 vendor/github.com/godbus/dbus/v5/homedir.go delete mode 100644 vendor/github.com/godbus/dbus/v5/match.go delete mode 100644 vendor/github.com/godbus/dbus/v5/message.go delete mode 100644 vendor/github.com/godbus/dbus/v5/object.go delete mode 100644 vendor/github.com/godbus/dbus/v5/sequence.go delete mode 100644 vendor/github.com/godbus/dbus/v5/sequential_handler.go delete mode 100644 vendor/github.com/godbus/dbus/v5/server_interfaces.go delete mode 100644 vendor/github.com/godbus/dbus/v5/sig.go delete mode 100644 vendor/github.com/godbus/dbus/v5/transport_darwin.go delete mode 100644 vendor/github.com/godbus/dbus/v5/transport_generic.go delete mode 100644 vendor/github.com/godbus/dbus/v5/transport_nonce_tcp.go delete mode 100644 vendor/github.com/godbus/dbus/v5/transport_tcp.go delete mode 100644 vendor/github.com/godbus/dbus/v5/transport_unix.go delete mode 100644 vendor/github.com/godbus/dbus/v5/transport_unixcred_dragonfly.go delete mode 100644 vendor/github.com/godbus/dbus/v5/transport_unixcred_freebsd.go delete mode 100644 vendor/github.com/godbus/dbus/v5/transport_unixcred_linux.go delete mode 100644 vendor/github.com/godbus/dbus/v5/transport_unixcred_netbsd.go delete mode 100644 vendor/github.com/godbus/dbus/v5/transport_unixcred_openbsd.go delete mode 100644 vendor/github.com/godbus/dbus/v5/transport_zos.go delete mode 100644 vendor/github.com/godbus/dbus/v5/variant.go delete mode 100644 vendor/github.com/godbus/dbus/v5/variant_lexer.go delete mode 100644 vendor/github.com/godbus/dbus/v5/variant_parser.go delete mode 100644 vendor/github.com/matttproud/golang_protobuf_extensions/v2/NOTICE delete mode 100644 vendor/github.com/matttproud/golang_protobuf_extensions/v2/pbutil/.gitignore delete mode 100644 vendor/github.com/matttproud/golang_protobuf_extensions/v2/pbutil/Makefile delete mode 100644 vendor/github.com/matttproud/golang_protobuf_extensions/v2/pbutil/decode.go delete mode 100644 vendor/github.com/matttproud/golang_protobuf_extensions/v2/pbutil/doc.go delete mode 100644 vendor/github.com/matttproud/golang_protobuf_extensions/v2/pbutil/encode.go rename vendor/github.com/{matttproud/golang_protobuf_extensions/v2 => moby/sys/userns}/LICENSE (99%) create mode 100644 vendor/github.com/moby/sys/userns/userns.go create mode 100644 vendor/github.com/moby/sys/userns/userns_linux.go create mode 100644 vendor/github.com/moby/sys/userns/userns_linux_fuzzer.go create mode 100644 vendor/github.com/moby/sys/userns/userns_unsupported.go create mode 100644 vendor/github.com/munnerz/goautoneg/LICENSE create mode 100644 vendor/github.com/munnerz/goautoneg/Makefile rename vendor/github.com/{prometheus/common/internal/bitbucket.org/ww => munnerz}/goautoneg/README.txt (100%) rename vendor/github.com/{prometheus/common/internal/bitbucket.org/ww => munnerz}/goautoneg/autoneg.go (52%) delete mode 100644 vendor/github.com/opencontainers/runc/libcontainer/configs/blkio_device.go delete mode 100644 vendor/github.com/opencontainers/runc/libcontainer/configs/cgroup_linux.go delete mode 100644 vendor/github.com/opencontainers/runc/libcontainer/configs/cgroup_unsupported.go delete mode 100644 vendor/github.com/opencontainers/runc/libcontainer/configs/config.go delete mode 100644 vendor/github.com/opencontainers/runc/libcontainer/configs/config_linux.go delete mode 100644 vendor/github.com/opencontainers/runc/libcontainer/configs/configs_fuzzer.go delete mode 100644 vendor/github.com/opencontainers/runc/libcontainer/configs/hugepage_limit.go delete mode 100644 vendor/github.com/opencontainers/runc/libcontainer/configs/intelrdt.go delete mode 100644 vendor/github.com/opencontainers/runc/libcontainer/configs/interface_priority_map.go delete mode 100644 vendor/github.com/opencontainers/runc/libcontainer/configs/mount.go delete mode 100644 vendor/github.com/opencontainers/runc/libcontainer/configs/namespaces.go delete mode 100644 vendor/github.com/opencontainers/runc/libcontainer/configs/namespaces_linux.go delete mode 100644 vendor/github.com/opencontainers/runc/libcontainer/configs/namespaces_syscall.go delete mode 100644 vendor/github.com/opencontainers/runc/libcontainer/configs/namespaces_syscall_unsupported.go delete mode 100644 vendor/github.com/opencontainers/runc/libcontainer/configs/namespaces_unsupported.go delete mode 100644 vendor/github.com/opencontainers/runc/libcontainer/configs/network.go delete mode 100644 vendor/github.com/opencontainers/runc/libcontainer/configs/rdma.go delete mode 100644 vendor/github.com/opencontainers/runc/libcontainer/devices/device.go delete mode 100644 vendor/github.com/opencontainers/runc/libcontainer/devices/device_unix.go delete mode 100644 vendor/github.com/opencontainers/runc/libcontainer/user/lookup_unix.go delete mode 100644 vendor/github.com/opencontainers/runc/libcontainer/user/user.go delete mode 100644 vendor/github.com/opencontainers/runc/libcontainer/user/user_fuzzer.go delete mode 100644 vendor/github.com/opencontainers/runc/libcontainer/userns/userns.go create mode 100644 vendor/github.com/opencontainers/runc/libcontainer/userns/userns_deprecated.go delete mode 100644 vendor/github.com/opencontainers/runc/libcontainer/userns/userns_fuzzer.go delete mode 100644 vendor/github.com/opencontainers/runc/libcontainer/userns/userns_linux.go delete mode 100644 vendor/github.com/opencontainers/runc/libcontainer/userns/userns_maps.c delete mode 100644 vendor/github.com/opencontainers/runc/libcontainer/userns/userns_maps_linux.go delete mode 100644 vendor/github.com/opencontainers/runc/libcontainer/userns/userns_unsupported.go delete mode 100644 vendor/github.com/opencontainers/runtime-spec/LICENSE delete mode 100644 vendor/github.com/opencontainers/runtime-spec/specs-go/config.go delete mode 100644 vendor/github.com/opencontainers/runtime-spec/specs-go/state.go delete mode 100644 vendor/github.com/opencontainers/runtime-spec/specs-go/version.go create mode 100644 vendor/github.com/prometheus/common/model/labelset_string.go create mode 100644 vendor/github.com/prometheus/common/model/metadata.go create mode 100644 vendor/github.com/prometheus/procfs/net_tls_stat.go create mode 100644 vendor/google.golang.org/protobuf/encoding/protodelim/protodelim.go diff --git a/go.mod b/go.mod index 512b4959..b6c17106 100644 --- a/go.mod +++ b/go.mod @@ -10,7 +10,7 @@ require ( github.com/longhorn/backupstore v0.0.0-20241208060255-5c474bb003bd github.com/longhorn/go-common-libs v0.0.0-20241208100509-e1932c65c078 github.com/longhorn/go-spdk-helper v0.0.0-20241210055426-92898a883955 - github.com/longhorn/types v0.0.0-20241210031954-9a7c220696fd + github.com/longhorn/types v0.0.0-20241212070310-f6797e59f9c9 github.com/pkg/errors v0.9.1 github.com/sirupsen/logrus v1.9.3 go.uber.org/multierr v1.11.0 @@ -24,30 +24,28 @@ require ( require ( github.com/RoaringBitmap/roaring v1.9.4 // indirect github.com/beorn7/perks v1.0.1 // indirect - github.com/bits-and-blooms/bitset v1.12.0 // indirect + github.com/bits-and-blooms/bitset v1.16.0 // indirect github.com/c9s/goprocinfo v0.0.0-20210130143923-c95fcf8c64a8 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect - github.com/coreos/go-systemd/v22 v22.5.0 // indirect - github.com/gammazero/deque v0.2.0 // indirect + github.com/gammazero/deque v1.0.0 // indirect github.com/gammazero/workerpool v1.1.3 // indirect github.com/go-logr/logr v1.4.2 // indirect github.com/go-ole/go-ole v1.3.0 // indirect - github.com/godbus/dbus/v5 v5.1.0 // indirect github.com/kr/pretty v0.3.1 // indirect github.com/kr/text v0.2.0 // indirect - github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 // indirect github.com/mitchellh/go-ps v1.0.0 // indirect - github.com/moby/sys/mountinfo v0.7.1 // indirect + github.com/moby/sys/mountinfo v0.7.2 // indirect + github.com/moby/sys/userns v0.1.0 // indirect github.com/mschoch/smat v0.2.0 // indirect - github.com/opencontainers/runc v1.1.14 // indirect - github.com/opencontainers/runtime-spec v1.1.0 // indirect + github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect + github.com/opencontainers/runc v1.2.2 // indirect github.com/pierrec/lz4/v4 v4.1.21 // indirect github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect - github.com/prometheus/client_golang v1.18.0 // indirect - github.com/prometheus/client_model v0.5.0 // indirect - github.com/prometheus/common v0.45.0 // indirect - github.com/prometheus/procfs v0.12.0 // indirect - github.com/rogpeppe/go-internal v1.12.0 // indirect + github.com/prometheus/client_golang v1.20.5 // indirect + github.com/prometheus/client_model v0.6.1 // indirect + github.com/prometheus/common v0.60.1 // indirect + github.com/prometheus/procfs v0.15.1 // indirect + github.com/rogpeppe/go-internal v1.13.1 // indirect github.com/shirou/gopsutil/v3 v3.24.5 // indirect github.com/slok/goresilience v0.2.0 // indirect github.com/yusufpapurcu/wmi v1.2.4 // indirect @@ -57,5 +55,5 @@ require ( google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 // indirect k8s.io/klog/v2 v2.130.1 // indirect k8s.io/mount-utils v0.31.3 // indirect - k8s.io/utils v0.0.0-20240711033017-18e509b52bc8 // indirect + k8s.io/utils v0.0.0-20241104163129-6fe5fd82f078 // indirect ) diff --git a/go.sum b/go.sum index db38a92b..a4823127 100644 --- a/go.sum +++ b/go.sum @@ -5,21 +5,20 @@ github.com/RoaringBitmap/roaring v1.9.4/go.mod h1:6AXUsoIEzDTFFQCe1RbGA6uFONMhve github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= -github.com/bits-and-blooms/bitset v1.12.0 h1:U/q1fAF7xXRhFCrhROzIfffYnu+dlS38vCZtmFVPHmA= github.com/bits-and-blooms/bitset v1.12.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8= +github.com/bits-and-blooms/bitset v1.16.0 h1:G3lirLlhFTcW/7ym/SLtYYLHQS0hBOcC8fPNJxbTYm4= +github.com/bits-and-blooms/bitset v1.16.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8= github.com/c9s/goprocinfo v0.0.0-20210130143923-c95fcf8c64a8 h1:SjZ2GvvOononHOpK84APFuMvxqsk3tEIaKH/z4Rpu3g= github.com/c9s/goprocinfo v0.0.0-20210130143923-c95fcf8c64a8/go.mod h1:uEyr4WpAH4hio6LFriaPkL938XnrvLpNPmQHBdrmbIE= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs= -github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/gammazero/deque v0.2.0 h1:SkieyNB4bg2/uZZLxvya0Pq6diUlwx7m2TeT7GAIWaA= -github.com/gammazero/deque v0.2.0/go.mod h1:LFroj8x4cMYCukHJDbxFCkT+r9AndaJnFMuZDV34tuU= +github.com/gammazero/deque v1.0.0 h1:LTmimT8H7bXkkCy6gZX7zNLtkbz4NdS2z8LZuor3j34= +github.com/gammazero/deque v1.0.0/go.mod h1:iflpYvtGfM3U8S8j+sZEKIak3SAKYpA5/SQewgfXDKo= github.com/gammazero/workerpool v1.1.3 h1:WixN4xzukFoN0XSeXF6puqEqFTl2mECI9S6W44HWy9Q= github.com/gammazero/workerpool v1.1.3/go.mod h1:wPjyBLDbyKnUn2XwwyD3EEwo9dHutia9/fwNmSHWACc= github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= @@ -27,9 +26,6 @@ github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ4 github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= -github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= -github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk= -github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= @@ -37,6 +33,8 @@ github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA= +github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= @@ -50,21 +48,21 @@ github.com/longhorn/go-common-libs v0.0.0-20241208100509-e1932c65c078 h1:QnN9bPR github.com/longhorn/go-common-libs v0.0.0-20241208100509-e1932c65c078/go.mod h1:whDcaYDin1L7uaKTpr86RxpfOT+VJQbubDWdEGPnvVs= github.com/longhorn/go-spdk-helper v0.0.0-20241210055426-92898a883955 h1:QcnR9b2GlS/jyYss5FlRWm9QFT1KP2K5g5nCNyROysg= github.com/longhorn/go-spdk-helper v0.0.0-20241210055426-92898a883955/go.mod h1:isAM1U36SWOh7XWfktlbveHWSLXV3HfEF7p/tyNqAUQ= -github.com/longhorn/types v0.0.0-20241210031954-9a7c220696fd h1:cuX5B+R2o67CXzy3+6ZCu388bb/UTETZ8ND1nPu0Zy0= -github.com/longhorn/types v0.0.0-20241210031954-9a7c220696fd/go.mod h1:ZElOIs7s/Cjaw7P9kY+uvTzh87mfO34pk39B6TVmg0g= +github.com/longhorn/types v0.0.0-20241212070310-f6797e59f9c9 h1:XFz8YVuRuhpp8lrkHAuc20H12SrGAIY1K4Ni2/6gaXk= +github.com/longhorn/types v0.0.0-20241212070310-f6797e59f9c9/go.mod h1:ZElOIs7s/Cjaw7P9kY+uvTzh87mfO34pk39B6TVmg0g= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= -github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 h1:jWpvCLoY8Z/e3VKvlsiIGKtc+UG6U5vzxaoagmhXfyg= -github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0/go.mod h1:QUyp042oQthUoa9bqDv0ER0wrtXnBruoNd7aNjkbP+k= github.com/mitchellh/go-ps v1.0.0 h1:i6ampVEEF4wQFF+bkYfwYgY+F/uYJDktmvLPf7qIgjc= github.com/mitchellh/go-ps v1.0.0/go.mod h1:J4lOc8z8yJs6vUwklHw2XEIiT4z4C40KtWVN3nvg8Pg= -github.com/moby/sys/mountinfo v0.7.1 h1:/tTvQaSJRr2FshkhXiIpux6fQ2Zvc4j7tAhMTStAG2g= -github.com/moby/sys/mountinfo v0.7.1/go.mod h1:IJb6JQeOklcdMU9F5xQ8ZALD+CUr5VlGpwtX+VE0rpI= +github.com/moby/sys/mountinfo v0.7.2 h1:1shs6aH5s4o5H2zQLn796ADW1wMrIwHsyJ2v9KouLrg= +github.com/moby/sys/mountinfo v0.7.2/go.mod h1:1YOa8w8Ih7uW0wALDUgT1dTTSBrZ+HiBLGws92L2RU4= +github.com/moby/sys/userns v0.1.0 h1:tVLXkFOxVu9A64/yh59slHVv9ahO9UIev4JZusOLG/g= +github.com/moby/sys/userns v0.1.0/go.mod h1:IHUYgu/kao6N8YZlp9Cf444ySSvCmDlmzUcYfDHOl28= github.com/mschoch/smat v0.2.0 h1:8imxQsjDm8yFEAVBe7azKmKSgzSkZXDuKkSq9374khM= github.com/mschoch/smat v0.2.0/go.mod h1:kc9mz7DoBKqDyiRL7VZN8KvXQMWeTaVnttLRXOlotKw= -github.com/opencontainers/runc v1.1.14 h1:rgSuzbmgz5DUJjeSnw337TxDbRuqjs6iqQck/2weR6w= -github.com/opencontainers/runc v1.1.14/go.mod h1:E4C2z+7BxR7GHXp0hAY53mek+x49X1LjPNeMTfRGvOA= -github.com/opencontainers/runtime-spec v1.1.0 h1:HHUyrt9mwHUjtasSbXSMvs4cyFxh+Bll4AjJ9odEGpg= -github.com/opencontainers/runtime-spec v1.1.0/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/opencontainers/runc v1.2.2 h1:jTg3Vw2A5f0N9PoxFTEwUhvpANGaNPT3689Yfd/zaX0= +github.com/opencontainers/runc v1.2.2/go.mod h1:/PXzF0h531HTMsYQnmxXkBD7YaGShm/2zcRB79dksUc= github.com/pierrec/lz4/v4 v4.1.21 h1:yOVMLb6qSIDP67pl/5F7RepeKYu/VmTyEXvuMI5d9mQ= github.com/pierrec/lz4/v4 v4.1.21/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= @@ -76,20 +74,20 @@ github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU= github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= github.com/prometheus/client_golang v0.9.2/go.mod h1:OsXs2jCmiKlQ1lTBmv21f2mNfw4xf/QclQDMrYNZzcM= -github.com/prometheus/client_golang v1.18.0 h1:HzFfmkOzH5Q8L8G+kSJKUx5dtG87sewO+FoDDqP5Tbk= -github.com/prometheus/client_golang v1.18.0/go.mod h1:T+GXkCk5wSJyOqMIzVgvvjFDlkOQntgjkJWKrN5txjA= +github.com/prometheus/client_golang v1.20.5 h1:cxppBPuYhUnsO6yo/aoRol4L7q7UFfdm+bR9r+8l63Y= +github.com/prometheus/client_golang v1.20.5/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= -github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw= -github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI= +github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= +github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= github.com/prometheus/common v0.0.0-20181126121408-4724e9255275/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= -github.com/prometheus/common v0.45.0 h1:2BGz0eBc2hdMDLnO/8n0jeB3oPrt2D08CekT0lneoxM= -github.com/prometheus/common v0.45.0/go.mod h1:YJmSTw9BoKxJplESWWxlbyttQR4uaEcGyv9MZjVOJsY= +github.com/prometheus/common v0.60.1 h1:FUas6GcOw66yB/73KC+BOZoFJmbo/1pojoILArPAaSc= +github.com/prometheus/common v0.60.1/go.mod h1:h0LYf1R1deLSKtD4Vdg8gy4RuOvENW2J/h19V5NADQw= github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= -github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo= -github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo= +github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= +github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= -github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= -github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= +github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= +github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= github.com/shirou/gopsutil/v3 v3.24.5 h1:i0t8kL+kQTvpAYToeuiVk3TgDeKOFioZO3Ztz/iZ9pI= github.com/shirou/gopsutil/v3 v3.24.5/go.mod h1:bsoOS1aStSs9ErQ1WWfxllSeS1K5D+U30r2NfcubMVk= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= @@ -118,7 +116,6 @@ golang.org/x/net v0.32.0/go.mod h1:CwU0IoeOlnQQWJ6ioyFrfRuomB8GKF6KbYXZVyeXNfs= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= @@ -143,5 +140,5 @@ k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= k8s.io/mount-utils v0.31.3 h1:CANy3prUYvvDCc2X7ZKgpjpDhAidx4gjGh/WwDrCPq8= k8s.io/mount-utils v0.31.3/go.mod h1:HV/VYBUGqYUj4vt82YltzpWvgv8FPg0G9ItyInT3NPU= -k8s.io/utils v0.0.0-20240711033017-18e509b52bc8 h1:pUdcCO1Lk/tbT5ztQWOBi5HBgbBP1J8+AsQnQCKsi8A= -k8s.io/utils v0.0.0-20240711033017-18e509b52bc8/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +k8s.io/utils v0.0.0-20241104163129-6fe5fd82f078 h1:jGnCPejIetjiy2gqaJ5V0NLwTpF4wbQ6cZIItJCSHno= +k8s.io/utils v0.0.0-20241104163129-6fe5fd82f078/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= diff --git a/pkg/api/types.go b/pkg/api/types.go index f5ed96cd..43fa65ac 100644 --- a/pkg/api/types.go +++ b/pkg/api/types.go @@ -14,19 +14,20 @@ type SnapshotOptions struct { } type Replica struct { - Name string `json:"name"` - LvsName string `json:"lvs_name"` - LvsUUID string `json:"lvs_uuid"` - SpecSize uint64 `json:"spec_size"` - ActualSize uint64 `json:"actual_size"` - Head *Lvol `json:"head"` - Snapshots map[string]*Lvol `json:"snapshots"` - IP string `json:"ip"` - PortStart int32 `json:"port_start"` - PortEnd int32 `json:"port_end"` - State string `json:"state"` - ErrorMsg string `json:"error_msg"` - Rebuilding bool `json:"rebuilding"` + Name string `json:"name"` + LvsName string `json:"lvs_name"` + LvsUUID string `json:"lvs_uuid"` + SpecSize uint64 `json:"spec_size"` + ActualSize uint64 `json:"actual_size"` + Head *Lvol `json:"head"` + Snapshots map[string]*Lvol `json:"snapshots"` + IP string `json:"ip"` + PortStart int32 `json:"port_start"` + PortEnd int32 `json:"port_end"` + State string `json:"state"` + ErrorMsg string `json:"error_msg"` + Rebuilding bool `json:"rebuilding"` + BackingImageName string `json:"backing_image_name"` } type Lvol struct { @@ -95,6 +96,10 @@ func ProtoReplicaToReplica(r *spdkrpc.Replica) *Replica { res.Snapshots[snapName] = ProtoLvolToLvol(snapProtoLvol) } + if r.BackingImageName != "" { + res.BackingImageName = r.BackingImageName + } + return res } @@ -104,7 +109,7 @@ func ReplicaToProtoReplica(r *Replica) *spdkrpc.Replica { snapshots[name] = LvolToProtoLvol(snapshot) } - return &spdkrpc.Replica{ + res := &spdkrpc.Replica{ Name: r.Name, LvsName: r.LvsName, LvsUuid: r.LvsUUID, @@ -119,6 +124,11 @@ func ReplicaToProtoReplica(r *Replica) *spdkrpc.Replica { State: r.State, ErrorMsg: r.ErrorMsg, } + + if r.BackingImageName != "" { + res.BackingImageName = r.BackingImageName + } + return res } type Engine struct { @@ -171,6 +181,54 @@ func ProtoEngineToEngine(e *spdkrpc.Engine) *Engine { return res } +type BackingImage struct { + Name string `json:"name"` + BackingImageUUID string `json:"backing_image_uuid"` + LvsName string `json:"lvs_name"` + LvsUUID string `json:"lvs_uuid"` + Size uint64 `json:"size"` + ExpectedChecksum string `json:"expected_checksum"` + Snapshot *Lvol `json:"snapshot"` + Progress int32 `json:"progress"` + State string `json:"state"` + CurrentChecksum string `json:"current_checksum"` + ErrorMsg string `json:"error_msg"` +} + +func ProtoBackingImageToBackingImage(bi *spdkrpc.BackingImage) *BackingImage { + res := &BackingImage{ + Name: bi.Name, + BackingImageUUID: bi.BackingImageUuid, + LvsName: bi.LvsName, + LvsUUID: bi.LvsUuid, + Size: bi.Size, + ExpectedChecksum: bi.ExpectedChecksum, + Snapshot: ProtoLvolToLvol(bi.Snapshot), + Progress: bi.Progress, + State: bi.State, + CurrentChecksum: bi.CurrentChecksum, + ErrorMsg: bi.ErrorMsg, + } + + return res +} + +func BackingImageToProtoBackingImage(bi *BackingImage) *spdkrpc.BackingImage { + return &spdkrpc.BackingImage{ + Name: bi.Name, + BackingImageUuid: bi.BackingImageUUID, + LvsName: bi.LvsName, + LvsUuid: bi.LvsUUID, + Size: bi.Size, + ExpectedChecksum: bi.ExpectedChecksum, + Snapshot: LvolToProtoLvol(bi.Snapshot), + Progress: bi.Progress, + State: bi.State, + CurrentChecksum: bi.CurrentChecksum, + ErrorMsg: bi.ErrorMsg, + } +} + type DiskInfo struct { ID string Name string @@ -240,3 +298,17 @@ func NewEngineStream(stream spdkrpc.SPDKService_EngineWatchClient) *EngineStream func (s *EngineStream) Recv() (*emptypb.Empty, error) { return s.stream.Recv() } + +type BackingImageStream struct { + stream spdkrpc.SPDKService_BackingImageWatchClient +} + +func NewBackingImageStream(stream spdkrpc.SPDKService_BackingImageWatchClient) *BackingImageStream { + return &BackingImageStream{ + stream, + } +} + +func (s *BackingImageStream) Recv() (*emptypb.Empty, error) { + return s.stream.Recv() +} diff --git a/pkg/client/client.go b/pkg/client/client.go index 8662fad1..920618fb 100644 --- a/pkg/client/client.go +++ b/pkg/client/client.go @@ -53,7 +53,7 @@ func NewSPDKClient(serviceURL string) (*SPDKClient, error) { }, nil } -func (c *SPDKClient) ReplicaCreate(name, lvsName, lvsUUID string, specSize uint64, portCount int32) (*api.Replica, error) { +func (c *SPDKClient) ReplicaCreate(name, lvsName, lvsUUID string, specSize uint64, portCount int32, backingImageName string) (*api.Replica, error) { if name == "" || lvsName == "" || lvsUUID == "" { return nil, fmt.Errorf("failed to start SPDK replica: missing required parameters") } @@ -63,11 +63,12 @@ func (c *SPDKClient) ReplicaCreate(name, lvsName, lvsUUID string, specSize uint6 defer cancel() resp, err := client.ReplicaCreate(ctx, &spdkrpc.ReplicaCreateRequest{ - Name: name, - LvsName: lvsName, - LvsUuid: lvsUUID, - SpecSize: specSize, - PortCount: portCount, + Name: name, + LvsName: lvsName, + LvsUuid: lvsUUID, + SpecSize: specSize, + PortCount: portCount, + BackingImageName: backingImageName, }) if err != nil { return nil, errors.Wrap(err, "failed to start SPDK replica") @@ -883,6 +884,129 @@ func (c *SPDKClient) ReplicaRestoreStatus(replicaName string) (*spdkrpc.ReplicaR }) } +func (c *SPDKClient) BackingImageCreate(name, backingImageUUID, lvsUUID string, size uint64, checksum string, fromAddress string, srcLvsUUID string) (*api.BackingImage, error) { + if name == "" || backingImageUUID == "" || checksum == "" || lvsUUID == "" || size == 0 { + return nil, fmt.Errorf("failed to start SPDK backing image: missing required parameters") + } + client := c.getSPDKServiceClient() + ctx, cancel := context.WithTimeout(context.Background(), GRPCServiceTimeout) + defer cancel() + + resp, err := client.BackingImageCreate(ctx, &spdkrpc.BackingImageCreateRequest{ + Name: name, + BackingImageUuid: backingImageUUID, + LvsUuid: lvsUUID, + Size: size, + Checksum: checksum, + FromAddress: fromAddress, + SrcLvsUuid: srcLvsUUID, + }) + if err != nil { + return nil, errors.Wrap(err, "failed to start SPDK backing image") + } + return api.ProtoBackingImageToBackingImage(resp), nil +} + +func (c *SPDKClient) BackingImageDelete(name, lvsUUID string) error { + if name == "" || lvsUUID == "" { + return fmt.Errorf("failed to delete SPDK backingImage: missing required parameter") + } + + client := c.getSPDKServiceClient() + ctx, cancel := context.WithTimeout(context.Background(), GRPCServiceTimeout) + defer cancel() + + _, err := client.BackingImageDelete(ctx, &spdkrpc.BackingImageDeleteRequest{ + Name: name, + LvsUuid: lvsUUID, + }) + return errors.Wrapf(err, "failed to delete SPDK backing image %v", name) +} + +func (c *SPDKClient) BackingImageGet(name, lvsUUID string) (*api.BackingImage, error) { + if name == "" || lvsUUID == "" { + return nil, fmt.Errorf("failed to get SPDK BackingImage: missing required parameter") + } + + client := c.getSPDKServiceClient() + ctx, cancel := context.WithTimeout(context.Background(), GRPCServiceTimeout) + defer cancel() + + resp, err := client.BackingImageGet(ctx, &spdkrpc.BackingImageGetRequest{ + Name: name, + LvsUuid: lvsUUID, + }) + if err != nil { + return nil, errors.Wrapf(err, "failed to get SPDK backing image %v", name) + } + return api.ProtoBackingImageToBackingImage(resp), nil +} + +func (c *SPDKClient) BackingImageList() (map[string]*api.BackingImage, error) { + client := c.getSPDKServiceClient() + ctx, cancel := context.WithTimeout(context.Background(), GRPCServiceTimeout) + defer cancel() + + resp, err := client.BackingImageList(ctx, &emptypb.Empty{}) + if err != nil { + return nil, errors.Wrap(err, "failed to list SPDK backing images") + } + + res := map[string]*api.BackingImage{} + for name, backingImage := range resp.BackingImages { + res[name] = api.ProtoBackingImageToBackingImage(backingImage) + } + return res, nil +} + +func (c *SPDKClient) BackingImageWatch(ctx context.Context) (*api.BackingImageStream, error) { + client := c.getSPDKServiceClient() + stream, err := client.BackingImageWatch(ctx, &emptypb.Empty{}) + if err != nil { + return nil, errors.Wrap(err, "failed to open backing image watch stream") + } + + return api.NewBackingImageStream(stream), nil +} + +func (c *SPDKClient) BackingImageExpose(name, lvsUUID string) (exposedSnapshotLvolAddress string, err error) { + if name == "" || lvsUUID == "" { + return "", fmt.Errorf("failed to expose SPDK backing image: missing required parameter") + } + + client := c.getSPDKServiceClient() + ctx, cancel := context.WithTimeout(context.Background(), GRPCServiceTimeout) + defer cancel() + + resp, err := client.BackingImageExpose(ctx, &spdkrpc.BackingImageGetRequest{ + Name: name, + LvsUuid: lvsUUID, + }) + if err != nil { + return "", errors.Wrapf(err, "failed to expose SPDK backing image %v in lvstore: %v", name, lvsUUID) + } + return resp.ExposedSnapshotLvolAddress, nil +} + +func (c *SPDKClient) BackingImageUnexpose(name, lvsUUID string) error { + if name == "" || lvsUUID == "" { + return fmt.Errorf("failed to unexpose SPDK backing image: missing required parameter") + } + + client := c.getSPDKServiceClient() + ctx, cancel := context.WithTimeout(context.Background(), GRPCServiceTimeout) + defer cancel() + + _, err := client.BackingImageUnexpose(ctx, &spdkrpc.BackingImageGetRequest{ + Name: name, + LvsUuid: lvsUUID, + }) + if err != nil { + return errors.Wrapf(err, "failed to unexpose SPDK backing image %v in lvstore %v", name, lvsUUID) + } + return nil +} + // DiskCreate creates a disk with the given name and path. // diskUUID is optional, if not provided, it indicates the disk is newly added. func (c *SPDKClient) DiskCreate(diskName, diskUUID, diskPath, diskDriver string, blockSize int64) (*spdkrpc.Disk, error) { diff --git a/pkg/spdk/backing_image.go b/pkg/spdk/backing_image.go new file mode 100644 index 00000000..1ff8c076 --- /dev/null +++ b/pkg/spdk/backing_image.go @@ -0,0 +1,735 @@ +package spdk + +import ( + "context" + "fmt" + "net" + "net/url" + "os" + "strconv" + "strings" + "sync" + + "github.com/pkg/errors" + "github.com/sirupsen/logrus" + + grpccodes "google.golang.org/grpc/codes" + grpcstatus "google.golang.org/grpc/status" + + "github.com/longhorn/go-spdk-helper/pkg/jsonrpc" + "github.com/longhorn/go-spdk-helper/pkg/nvme" + "github.com/longhorn/types/pkg/generated/spdkrpc" + + commonbitmap "github.com/longhorn/go-common-libs/bitmap" + commonnet "github.com/longhorn/go-common-libs/net" + commontypes "github.com/longhorn/go-common-libs/types" + spdkclient "github.com/longhorn/go-spdk-helper/pkg/spdk/client" + spdktypes "github.com/longhorn/go-spdk-helper/pkg/spdk/types" + helpertypes "github.com/longhorn/go-spdk-helper/pkg/types" + helperutil "github.com/longhorn/go-spdk-helper/pkg/util" + + "github.com/longhorn/longhorn-spdk-engine/pkg/types" + "github.com/longhorn/longhorn-spdk-engine/pkg/util" +) + +type BackingImage struct { + sync.RWMutex + + ctx context.Context + + // Name of the BackingImage, e.g. "parrot" + Name string + BackingImageUUID string + LvsName string + LvsUUID string + Size uint64 + ProcessedSize int64 + ExpectedChecksum string + + IsExposed bool + Port int32 + + // We need to create a snapshot from the BackingImage lvol so we can create the replica from that snapshot. + Snapshot *Lvol + // This is the lvol alias for the snapshot lvol, e.g. "n1v2disk/bi-parrot-disk-${lvsuuid}" + Alias string + + Progress int32 + State types.BackingImageState + CurrentChecksum string + ErrorMsg string + + UpdateCh chan interface{} + + log logrus.FieldLogger +} + +func ServiceBackingImageToProtoBackingImage(bi *BackingImage) *spdkrpc.BackingImage { + res := &spdkrpc.BackingImage{ + Name: bi.Name, + BackingImageUuid: bi.BackingImageUUID, + LvsName: bi.LvsName, + LvsUuid: bi.LvsUUID, + Size: bi.Size, + ExpectedChecksum: bi.ExpectedChecksum, + Snapshot: nil, + Progress: bi.Progress, + State: string(bi.State), + CurrentChecksum: bi.CurrentChecksum, + ErrorMsg: bi.ErrorMsg, + } + if bi.Snapshot != nil { + res.Snapshot = ServiceBackingImageLvolToProtoBackingImageLvol(bi.Snapshot) + } + return res +} + +func NewBackingImage(ctx context.Context, backingImageName, backingImageUUID, lvsUUID string, size uint64, checksum string, updateCh chan interface{}) *BackingImage { + log := logrus.StandardLogger().WithFields(logrus.Fields{ + "backingImagename": backingImageName, + "lvsUUID": lvsUUID, + }) + + return &BackingImage{ + ctx: ctx, + Name: backingImageName, + BackingImageUUID: backingImageUUID, + LvsUUID: lvsUUID, + Size: size, + ExpectedChecksum: checksum, + State: types.BackingImageStateStarting, + UpdateCh: updateCh, + log: log, + } +} + +// Create initiates the backing image, prepare the lvol, copy the data from the local backing file and create the snapshot. +func (bi *BackingImage) Create(spdkClient *spdkclient.Client, superiorPortAllocator *commonbitmap.Bitmap, fromAddress string, srcLvsUUID string) (ret *spdkrpc.BackingImage, err error) { + updateRequired := true + + bi.Lock() + defer func() { + bi.Unlock() + + if updateRequired { + bi.UpdateCh <- nil + } + }() + if bi.State == types.BackingImageStateReady { + updateRequired = false + return nil, grpcstatus.Errorf(grpccodes.AlreadyExists, "backing image %v already exists and running", bi.Name) + } + if bi.State != types.BackingImageStateStarting { + updateRequired = false + return nil, grpcstatus.Errorf(grpccodes.Internal, "invalid state %s for backing image %s creation", bi.State, bi.Name) + } + + go func() { + err := bi.prepareBackingImageSnapshot(spdkClient, superiorPortAllocator, fromAddress, srcLvsUUID) + if err != nil { + bi.log.WithError(err).Warnf("Failed to create backing image") + } + // update the backing image afte preparing + bi.UpdateCh <- nil + }() + + return ServiceBackingImageToProtoBackingImage(bi), nil +} + +func (bi *BackingImage) Get() (pBackingImage *spdkrpc.BackingImage) { + bi.RLock() + defer bi.RUnlock() + return ServiceBackingImageToProtoBackingImage(bi) +} + +func (bi *BackingImage) Delete(spdkClient *spdkclient.Client, superiorPortAllocator *commonbitmap.Bitmap) (err error) { + updateRequired := false + + bi.Lock() + defer func() { + if err != nil { + bi.log.WithError(err).Errorf("Failed to delete backing image") + bi.State = types.BackingImageStateFailed + bi.ErrorMsg = err.Error() + updateRequired = true + } + bi.Unlock() + + if updateRequired { + bi.UpdateCh <- nil + } + }() + + // blindly unexpose the lvol bdevs + backingImageSnapLvolName := GetBackingImageSnapLvolName(bi.Name, bi.LvsUUID) + if err = spdkClient.StopExposeBdev(helpertypes.GetNQN(backingImageSnapLvolName)); err != nil && !jsonrpc.IsJSONRPCRespErrorNoSuchDevice(err) { + return errors.Wrapf(err, "failed to unexpose lvol bdev %v when deleting backing image %v", backingImageSnapLvolName, bi.Name) + } + + backingImageTempHeadName := GetBackingImageTempHeadLvolName(bi.Name, bi.LvsUUID) + if err = spdkClient.StopExposeBdev(helpertypes.GetNQN(backingImageTempHeadName)); err != nil && !jsonrpc.IsJSONRPCRespErrorNoSuchDevice(err) { + return errors.Wrapf(err, "failed to unexpose lvol bdev %v when deleting backing image %v", backingImageTempHeadName, bi.Name) + } + bi.IsExposed = false + if err := superiorPortAllocator.ReleaseRange(bi.Port, bi.Port); err != nil { + return errors.Wrapf(err, "failed to release port %v after when deleting backing image %v", bi.Port, bi.Name) + } + bi.Port = 0 + + biTempHeadAlias := fmt.Sprintf("%s/%s", bi.LvsName, GetBackingImageTempHeadLvolName(bi.Name, bi.LvsUUID)) + if _, err := spdkClient.BdevLvolDelete(biTempHeadAlias); err != nil && !jsonrpc.IsJSONRPCRespErrorNoSuchDevice(err) { + return err + } + + if _, err := spdkClient.BdevLvolDelete(bi.Alias); err != nil && !jsonrpc.IsJSONRPCRespErrorNoSuchDevice(err) { + return err + } + + bi.log.Info("Deleted backing image") + updateRequired = true + return nil +} + +func (bi *BackingImage) ValidateAndUpdate(spdkClient *spdkclient.Client) (err error) { + updateRequired := false + bi.Lock() + defer func() { + bi.Unlock() + + if updateRequired { + bi.UpdateCh <- nil + } + }() + + // Backing image normal state transition: starting -> in-progress -> ready/failed + // If backing image is in ready or failed state, that means we are validating a record already cached. + // We check if the corresponding lvol existence. + // If backing image is in starting and in-progress state, we don't need to do anything because it is still preparing. + // For uncache backing image lvol, the state will be pending, and we will need to reconstruct the record. + if bi.State == types.BackingImageStateReady || bi.State == types.BackingImageStateFailed { + defer func() { + if err != nil { + bi.State = types.BackingImageStateFailed + bi.ErrorMsg = err.Error() + updateRequired = true + } + }() + + // check if the lvol sill exists + bdevLvolList, err := spdkClient.BdevLvolGet(bi.Alias, 0) + if err != nil { + return err + } + if len(bdevLvolList) != 1 { + return fmt.Errorf("zero or multiple snap lvols with alias %v found when validating", bi.Alias) + } + return nil + } + + if bi.State == types.BackingImageStateInProgress || bi.State == types.BackingImageStateStarting { + return nil + } + + biLvolName := GetBackingImageSnapLvolName(bi.Name, bi.LvsUUID) + + lvsName, err := GetLvsNameByUUID(spdkClient, bi.LvsUUID) + if err != nil { + return errors.Wrapf(err, "failed to get the lvs name for backing image %v with lvs uuid %v", bi.Name, bi.LvsUUID) + } + + bi.log = logrus.StandardLogger().WithFields(logrus.Fields{ + "backingImagename": bi.Name, + "lvsName": bi.LvsName, + "lvsUUID": bi.LvsUUID, + }) + bi.LvsName = lvsName + + bi.Alias = spdktypes.GetLvolAlias(bi.LvsName, biLvolName) + bdevLvolList, err := spdkClient.BdevLvolGet(bi.Alias, 0) + if err != nil { + return err + } + if len(bdevLvolList) != 1 { + return fmt.Errorf("zero or multiple snap lvols with alias %v found after lvol snapshot", bi.Alias) + } + snapSvcLvol := BdevLvolInfoToServiceLvol(&bdevLvolList[0]) + bi.Snapshot = snapSvcLvol + state, err := GetSnapXattr(spdkClient, bi.Alias, types.BackingImageSnapshotAttrPrepareState) + if err != nil { + return errors.Wrapf(err, "failed to get the prepare state for backing image snapshot %v", bi.Name) + } + bi.State = types.BackingImageState(state) + + // TODO: recheck the checksum when pick up the backing image snapshot lvol + bi.CurrentChecksum = bi.ExpectedChecksum + bi.Progress = 100 + updateRequired = true + + return nil +} + +func (bi *BackingImage) BackingImageExpose(spdkClient *spdkclient.Client, superiorPortAllocator *commonbitmap.Bitmap) (exposedSnapshotLvolAddress string, err error) { + bi.log.Infof("Exposing backing image %v for syncing", bi.Name) + + updateRequired := false + + bi.Lock() + defer func() { + bi.Unlock() + if updateRequired { + bi.UpdateCh <- nil + } + }() + + defer func() { + if err != nil { + // We don't need to handle exposed bdev here since it is the last step. + // If it failed to expose the bdev, we only need to release the port. + opErr := superiorPortAllocator.ReleaseRange(bi.Port, bi.Port) + if opErr != nil { + err = errors.Wrapf(err, "Failed to release port %v with error %v", bi.Port, opErr) + } else { + bi.Port = 0 + } + updateRequired = true + } + }() + + if bi.State != types.BackingImageStateReady { + return "", fmt.Errorf("invalid state %v for backing image %s to be exposed for syncing", bi.State, bi.Name) + } + backingImageSnapLvolName := GetBackingImageSnapLvolName(bi.Name, bi.LvsUUID) + + snapLvol := bi.Snapshot + if snapLvol == nil { + return "", fmt.Errorf("cannot find snapshot for the backing image %s to be exposed for syncing", bi.Name) + } + + // Expose the bdev using nvmf + podIP, err := commonnet.GetIPForPod() + if err != nil { + return "", err + } + + if bi.IsExposed { + exposedSnapshotLvolAddress = net.JoinHostPort(podIP, strconv.Itoa(int(bi.Port))) + bi.log.Infof("Backing image lvol bdev %v is already exposed with exposedSnapshotLvolAddress %v", backingImageSnapLvolName, exposedSnapshotLvolAddress) + return exposedSnapshotLvolAddress, nil + } + + port, _, err := superiorPortAllocator.AllocateRange(int32(types.BackingImagePortCount)) + if err != nil { + return "", err + } + bi.Port = port + bi.log.Infof("Allocated port %v", port) + + executor, err := helperutil.NewExecutor(commontypes.ProcDirectory) + if err != nil { + return "", errors.Wrapf(err, "failed to create executor") + } + + subsystemNQN, controllerName, err := exposeSnapshotLvolBdev(spdkClient, bi.LvsName, backingImageSnapLvolName, podIP, port, executor) + if err != nil { + bi.log.WithError(err).Errorf("Failed to expose lvol bdev") + return "", err + } + bi.IsExposed = true + + exposedSnapshotLvolAddress = net.JoinHostPort(podIP, strconv.Itoa(int(port))) + bi.log.Infof("Exposed lvol bdev %v, subsystemNQN %v, controllerName %v, exposedSnapshotLvolAddress: %v", backingImageSnapLvolName, subsystemNQN, controllerName, exposedSnapshotLvolAddress) + + updateRequired = true + + return exposedSnapshotLvolAddress, nil +} + +func (bi *BackingImage) BackingImageUnexpose(spdkClient *spdkclient.Client, superiorPortAllocator *commonbitmap.Bitmap) (err error) { + bi.log.Infof("Stop exposing backing image %v", bi.Name) + + updateRequired := false + + bi.Lock() + defer func() { + bi.Unlock() + + if updateRequired { + bi.UpdateCh <- nil + } + }() + + if !bi.IsExposed { + return nil + } + + backingImageSnapLvolName := GetBackingImageSnapLvolName(bi.Name, bi.LvsUUID) + // Unexpose the bdev + bi.log.Info("Unexposing lvol bdev") + if err := spdkClient.StopExposeBdev(helpertypes.GetNQN(backingImageSnapLvolName)); err != nil { + return errors.Wrapf(err, "Failed to unexpose lvol bdev %v", backingImageSnapLvolName) + } + bi.IsExposed = false + + if err := superiorPortAllocator.ReleaseRange(bi.Port, bi.Port); err != nil { + return errors.Wrapf(err, "Failed to release port %v after failed to create backing image", bi.Port) + } + bi.Port = 0 + updateRequired = true + + return +} + +func checkIsSourceFromBIM(fromAddress string) bool { + return strings.HasPrefix(fromAddress, "http") +} + +func (bi *BackingImage) UpdateProgress(processedSize int64) { + bi.updateProgress(processedSize) + bi.UpdateCh <- nil +} + +func (bi *BackingImage) updateProgress(processedSize int64) { + bi.Lock() + defer bi.Unlock() + + if bi.State == types.BackingImageStateStarting { + bi.State = types.BackingImageStateInProgress + } + if bi.State == types.BackingImageStateReady { + return + } + + bi.ProcessedSize = bi.ProcessedSize + processedSize + if bi.Size > 0 { + bi.Progress = int32((float32(bi.ProcessedSize) / float32(bi.Size)) * 100) + } +} + +func (bi *BackingImage) prepareBackingImageSnapshot(spdkClient *spdkclient.Client, superiorPortAllocator *commonbitmap.Bitmap, fromAddress string, srcLvsUUID string) (err error) { + // If we already create the temp head lvol, no matter it fails to prepare or not, we should create the snapshot for it. + // The state will be recorded in the xattr of the snapshot lvol so when the node is rebooted, we can pick it up and reconstruct the record. + biTempHeadUUID := "" + + defer func() { + bi.Lock() + defer bi.Unlock() + + if bi.IsExposed { + bi.log.Info("Unexposing lvol bdev") + backingImageTempHeadName := GetBackingImageTempHeadLvolName(bi.Name, bi.LvsUUID) + if err = spdkClient.StopExposeBdev(helpertypes.GetNQN(backingImageTempHeadName)); err != nil { + logrus.Errorf("Failed to unexpose lvol bdev %v after failed to create backing image", backingImageTempHeadName) + } + bi.IsExposed = false + } + if bi.Port != 0 { + if err := superiorPortAllocator.ReleaseRange(bi.Port, bi.Port); err != nil { + logrus.Errorf("Failed to release port %v after failed to create backing image", bi.Port) + } + bi.Port = 0 + } + + if err == nil { + if bi.State != types.BackingImageStateInProgress { + err = fmt.Errorf("invalid state %v for backing image %s creation after processing", bi.State, bi.Name) + } + + if bi.Size > 0 && bi.ProcessedSize != int64(bi.Size) { + err = fmt.Errorf("processed data size %v does not match the expected file size %v", bi.ProcessedSize, bi.Size) + } + + if bi.CurrentChecksum != bi.ExpectedChecksum { + err = fmt.Errorf("current checksum %v does not match the expected checksum %v", bi.CurrentChecksum, bi.ExpectedChecksum) + } + } + + if err != nil { + bi.log.WithError(err).Errorf("Failed to create backing image %v", bi.Name) + if bi.State != types.BackingImageStateFailed { + bi.State = types.BackingImageStateFailed + } + bi.ErrorMsg = err.Error() + } else { + bi.Progress = 100 + bi.State = types.BackingImageStateReady + bi.ErrorMsg = "" + } + + if biTempHeadUUID != "" { + // Create a snapshot with backing image name from the backing image temp head lvol + // the snapshot lvol name will be "bi-${biName}-disk-${lvsUUID}" + // and the alias will be "${lvsName}/bi-${biName}-disk-${lvsUUID}" + + ne, err := helperutil.NewExecutor(commontypes.HostProcDirectory) + if err != nil { + bi.log.WithError(err).Errorf("WARNING: failed to get the executor for snapshot backing image %v, will skip the sync and continue", bi.Name) + } else { + bi.log.Infof("Requesting system sync before snapshot backin image %v", bi.Name) + // TODO: only sync the device path rather than all filesystems + if _, err := ne.Execute(nil, "sync", []string{}, SyncTimeout); err != nil { + // sync should never fail though, so it more like due to the nsenter + bi.log.WithError(err).Errorf("WARNING: failed to sync for snapshot backing image %v, will skip the sync and continue", bi.Name) + } + } + + biSnapLvolName := GetBackingImageSnapLvolName(bi.Name, bi.LvsUUID) + bi.Alias = spdktypes.GetLvolAlias(bi.LvsName, biSnapLvolName) + + var xattrs []spdkclient.Xattr + checksum := spdkclient.Xattr{ + Name: types.BackingImageSnapshotAttrChecksum, + Value: bi.ExpectedChecksum, + } + xattrs = append(xattrs, checksum) + backingImageUUID := spdkclient.Xattr{ + Name: types.BackingImageSnapshotAttrBackingImageUUID, + Value: bi.BackingImageUUID, + } + xattrs = append(xattrs, backingImageUUID) + prepareState := spdkclient.Xattr{ + Name: types.BackingImageSnapshotAttrPrepareState, + Value: string(bi.State), + } + xattrs = append(xattrs, prepareState) + + snapUUID, err := spdkClient.BdevLvolSnapshot(biTempHeadUUID, biSnapLvolName, xattrs) + if err != nil { + bi.State = types.BackingImageStateFailed + if bi.ErrorMsg != "" { + bi.ErrorMsg = fmt.Sprintf("%v; %v", bi.ErrorMsg, err.Error()) + } else { + bi.ErrorMsg = err.Error() + } + return + } + + bdevLvolList, err := spdkClient.BdevLvolGet(snapUUID, 0) + if err != nil { + bi.State = types.BackingImageStateFailed + if bi.ErrorMsg != "" { + bi.ErrorMsg = fmt.Sprintf("%v; %v", bi.ErrorMsg, err.Error()) + } else { + bi.ErrorMsg = err.Error() + } + return + } + if len(bdevLvolList) != 1 { + bi.State = types.BackingImageStateFailed + err = fmt.Errorf("zero or multiple snap lvols with UUID %s found after lvol snapshot", snapUUID) + if bi.ErrorMsg != "" { + bi.ErrorMsg = fmt.Sprintf("%v; %v", bi.ErrorMsg, err.Error()) + } else { + bi.ErrorMsg = err.Error() + } + return + } + snapSvcLvol := BdevLvolInfoToServiceLvol(&bdevLvolList[0]) + bi.Snapshot = snapSvcLvol + + backingImageTempHeadName := GetBackingImageTempHeadLvolName(bi.Name, bi.LvsUUID) + if _, opErr := spdkClient.BdevLvolDelete(biTempHeadUUID); opErr != nil && !jsonrpc.IsJSONRPCRespErrorNoSuchDevice(opErr) { + bi.log.Errorf("Failed to delete the temp head %v of backing image ", backingImageTempHeadName) + } + } + }() + + bi.Lock() + lvsName, err := GetLvsNameByUUID(spdkClient, bi.LvsUUID) + if err != nil { + return errors.Wrapf(err, "failed to get the lvs name for backing image %v with lvs uuid %v", bi.Name, bi.LvsUUID) + } + bi.LvsName = lvsName + bi.log = logrus.StandardLogger().WithFields(logrus.Fields{ + "backingImagename": bi.Name, + "lvsName": bi.LvsName, + "lvsUUID": bi.LvsUUID, + }) + bi.Unlock() + + // backingImageTempHeadName will be "bi-${biName}-disk-${lvsUUID}-temp-head" + backingImageTempHeadName := GetBackingImageTempHeadLvolName(bi.Name, bi.LvsUUID) + biTempHeadUUID, err = spdkClient.BdevLvolCreate("", bi.LvsUUID, backingImageTempHeadName, util.BytesToMiB(bi.Size), "", true) + if err != nil { + return err + } + bdevLvolList, err := spdkClient.BdevLvolGet(biTempHeadUUID, 0) + if err != nil { + return err + } + if len(bdevLvolList) < 1 { + return fmt.Errorf("cannot find lvol %v after creation", backingImageTempHeadName) + } + bi.log.Info("Created a head lvol %v for the new backing image", backingImageTempHeadName) + + podIP, err := commonnet.GetIPForPod() + if err != nil { + return err + } + port, _, err := superiorPortAllocator.AllocateRange(int32(types.BackingImagePortCount)) + if err != nil { + return err + } + bi.Lock() + bi.Port = port + bi.Unlock() + bi.log.Infof("Allocated port %v", port) + + executor, err := helperutil.NewExecutor(commontypes.ProcDirectory) + if err != nil { + return errors.Wrapf(err, "failed to create executor") + } + subsystemNQN, controllerName, err := exposeSnapshotLvolBdev(spdkClient, bi.LvsName, backingImageTempHeadName, podIP, port, executor) + if err != nil { + bi.log.WithError(err).Errorf("Failed to expose head lvol") + return err + } + bi.Lock() + bi.IsExposed = true + bi.Unlock() + bi.log.Infof("Exposed head lvol %v, subsystemNQN %v, controllerName %v", backingImageTempHeadName, subsystemNQN, controllerName) + + headInitiator, err := nvme.NewInitiator(backingImageTempHeadName, helpertypes.GetNQN(backingImageTempHeadName), nvme.HostProc) + if err != nil { + return errors.Wrapf(err, "failed to create NVMe initiator for head lvol %v", backingImageTempHeadName) + } + if _, err := headInitiator.Start(podIP, strconv.Itoa(int(port)), true); err != nil { + return errors.Wrapf(err, "failed to start NVMe initiator for head lvol %v", backingImageTempHeadName) + } + bi.log.Info("Created NVMe initiator for head lvol %v", backingImageTempHeadName) + + headFh, err := os.OpenFile(headInitiator.Endpoint, os.O_RDWR, 0666) + defer func() { + // Stop the initiator + headFh.Close() + bi.log.Info("Stopping NVMe initiator") + if _, opErr := headInitiator.Stop(true, true, false); opErr != nil { + bi.log.WithError(opErr).Errorf("Failed to stop the backing image head NVMe initiator") + } + }() + if err != nil { + return errors.Wrapf(err, "failed to open NVMe device %v for lvol bdev %v", headInitiator.Endpoint, backingImageTempHeadName) + } + + // An SPDK backing image should only be created by downloading it from BIM if it's a first copy, + // or by syncing it from another SPDK server. + isSourceFromBIM := checkIsSourceFromBIM(fromAddress) + if isSourceFromBIM { + if err := bi.prepareFromURL(headFh, fromAddress); err != nil { + bi.log.WithError(err).Warnf("Failed to prepare the backing image %v from URL %v", bi.Name, fromAddress) + return errors.Wrapf(err, "failed to prepare the backing image %v from URL %v", bi.Name, fromAddress) + } + } else { + if err := bi.prepareFromSync(headFh, fromAddress, srcLvsUUID); err != nil { + bi.log.WithError(err).Warnf("Failed to prepare the backing image %v by syncing from %v and srcLvsUUID %v", bi.Name, fromAddress, srcLvsUUID) + return errors.Wrapf(err, "failed to prepare the backing image %v by syncing from %v and srcLvsUUID %v", bi.Name, fromAddress, srcLvsUUID) + } + } + + currentChecksum, err := util.GetFileChecksum(headInitiator.Endpoint) + if err != nil { + return errors.Wrapf(err, "failed to get the current checksum of the backing image %v target device %v", bi.Name, headInitiator.Endpoint) + } + bi.log.Infof("Get the current checksum of the backing image %v: %v", bi.Name, currentChecksum) + bi.CurrentChecksum = currentChecksum + + return nil +} + +func (bi *BackingImage) prepareFromURL(targetFh *os.File, fromAddress string) (err error) { + httpHandler := util.HTTPHandler{} + + // Parse the base URL into a URL object + parsedURL, err := url.Parse(fromAddress) + if err != nil { + fmt.Println("Error parsing URL:", err) + return errors.Wrapf(err, "failed to parse the URL %v", fromAddress) + } + // Add query parameters + query := parsedURL.Query() + query.Set("forV2Creation", "true") + parsedURL.RawQuery = query.Encode() + + size, err := httpHandler.GetSizeFromURL(parsedURL.String()) + if err != nil { + return errors.Wrapf(err, "failed to get the file size from %v", fromAddress) + } + if size != int64(bi.Size) { + return errors.Wrapf(err, "download file %v size %v is not the same as the backing image size %v", fromAddress, size, bi.Size) + } + + if _, err := httpHandler.DownloadFromURL(bi.ctx, parsedURL.String(), targetFh, bi); err != nil { + return errors.Wrapf(err, "failed to download the file from %v", fromAddress) + } + return nil +} + +func (bi *BackingImage) prepareFromSync(targetFh *os.File, fromAddress, srcLvsUUID string) (err error) { + if fromAddress == "" || srcLvsUUID == "" { + return errors.Wrapf(err, "missing required source backing image service address %v or source lvsUUID %v", fromAddress, srcLvsUUID) + } + srcBackingImageServiceCli, err := GetServiceClient(fromAddress) + if err != nil { + return errors.Wrapf(err, "failed to init the source backing image spdk service client") + } + exposedSnapshotLvolAddress, err := srcBackingImageServiceCli.BackingImageExpose(bi.Name, srcLvsUUID) + if err != nil { + return errors.Wrapf(err, "failed to expose the source backing image %v", bi.Name) + } + externalSnapshotLvolName := GetBackingImageSnapLvolName(bi.Name, srcLvsUUID) + + srcIP, srcPort, err := splitHostPort(exposedSnapshotLvolAddress) + if err != nil { + return errors.Wrapf(err, "failed to split host and port from address %v", exposedSnapshotLvolAddress) + } + _, _, err = connectNVMeTarget(srcIP, srcPort, maxNumRetries, retryInterval) + if err != nil { + return errors.Wrapf(err, "failed to connect to NVMe target for source backing image %v in lvsUUID %v with address %v", bi.Name, srcLvsUUID, exposedSnapshotLvolAddress) + } + + bi.log.Info("Creating NVMe initiator for source backing image %v", bi.Name) + initiator, err := nvme.NewInitiator(externalSnapshotLvolName, helpertypes.GetNQN(externalSnapshotLvolName), nvme.HostProc) + if err != nil { + return errors.Wrapf(err, "failed to create NVMe initiator for source backing image %v in lvsUUID %v with address %v", bi.Name, srcLvsUUID, exposedSnapshotLvolAddress) + } + if _, err := initiator.Start(srcIP, strconv.Itoa(int(srcPort)), true); err != nil { + return errors.Wrapf(err, "failed to start NVMe initiator for source backing image %v in lvsUUID %v with address %v", bi.Name, srcLvsUUID, exposedSnapshotLvolAddress) + } + + bi.log.Infof("Opening NVMe device %v", initiator.Endpoint) + srcFh, err := os.OpenFile(initiator.Endpoint, os.O_RDWR, 0666) + defer func() { + srcFh.Close() + // Stop the source initiator + bi.log.Info("Stopping NVMe initiator") + if _, err := initiator.Stop(true, true, false); err != nil { + bi.log.WithError(err).Warnf("failed to stop NVMe initiator") + } + // Unexpose the source snapshot + if err := srcBackingImageServiceCli.BackingImageUnexpose(bi.Name, srcLvsUUID); err != nil { + bi.log.WithError(err).Warnf("failed to unsexpose the source backing image %v", bi.Name) + } + }() + if err != nil { + return errors.Wrapf(err, "failed to open NVMe device %v for source backing image %v in lvsUUID %v with address %v", initiator.Endpoint, bi.Name, srcLvsUUID, exposedSnapshotLvolAddress) + } + + ctx, cancel := context.WithCancel(bi.ctx) + defer cancel() + _, err = util.IdleTimeoutCopy(ctx, cancel, srcFh, targetFh, bi, true) + if err != nil { + return errors.Wrapf(err, "failed to copy the source backing image %v in lvsUUID %v with address %v", bi.Name, srcLvsUUID, exposedSnapshotLvolAddress) + } + + return nil +} + +func cleanupOrphanBackingImageTempHead(spdkClient *spdkclient.Client, lvsName, backingImageTempHeadName string) { + if err := spdkClient.StopExposeBdev(helpertypes.GetNQN(backingImageTempHeadName)); err != nil && !jsonrpc.IsJSONRPCRespErrorNoSuchDevice(err) { + logrus.WithError(err).Warnf("Failed to unexpose orphan backing image temp head %v", backingImageTempHeadName) + } + + biTempHeadAlias := fmt.Sprintf("%s/%s", lvsName, backingImageTempHeadName) + if _, err := spdkClient.BdevLvolDelete(biTempHeadAlias); err != nil && !jsonrpc.IsJSONRPCRespErrorNoSuchDevice(err) { + logrus.WithError(err).Warnf("Failed to delete orphan backing image temp head %v", backingImageTempHeadName) + } +} diff --git a/pkg/spdk/backup.go b/pkg/spdk/backup.go index 307a8f22..76730d05 100644 --- a/pkg/spdk/backup.go +++ b/pkg/spdk/backup.go @@ -408,13 +408,10 @@ func (b *Backup) constructMappings() *btypes.Mappings { func (b *Backup) findIndex(lvolName string) int { if lvolName == "" { - // TODO: return index 1 if backing image is supported - // if b.backingFile == nil { - // return 0 - // } - // return 1 - - return 0 + if b.replica.BackingImage != nil { + return 0 + } + return 1 } for i, lvol := range b.replica.ActiveChain { diff --git a/pkg/spdk/engine.go b/pkg/spdk/engine.go index 69af6295..01bb7b92 100644 --- a/pkg/spdk/engine.go +++ b/pkg/spdk/engine.go @@ -803,7 +803,7 @@ func (e *Engine) ValidateAndUpdate(spdkClient *spdkclient.Client) (err error) { func (e *Engine) checkAndUpdateInfoFromReplicaNoLock() { replicaMap := map[string]*api.Replica{} replicaAncestorMap := map[string]*api.Lvol{} - // hasBackingImage := false + hasBackingImage := false hasSnapshot := false for replicaName, replicaStatus := range e.ReplicaStatusMap { @@ -851,28 +851,37 @@ func (e *Engine) checkAndUpdateInfoFromReplicaNoLock() { } // The ancestor check sequence: the backing image, then the oldest snapshot, finally head - // TODO: Check the backing image first - - // if replica.BackingImage != nil { - // hasBackingImage = true - // replicaAncestorMap[replicaName] = replica.BackingImage - // } else - if len(replica.Snapshots) != 0 { - // if hasBackingImage { - // e.log.Warnf("Found replica %s does not have a backing image while other replicas have during info update for other replicas", replicaName) - // } else {} - hasSnapshot = true - for snapshotName, snapApiLvol := range replica.Snapshots { - if snapApiLvol.Parent == "" { - replicaAncestorMap[replicaName] = replica.Snapshots[snapshotName] - break - } + if replica.BackingImageName != "" { + hasBackingImage = true + backingImage, err := replicaServiceCli.BackingImageGet(replica.BackingImageName, replica.LvsUUID) + if err != nil { + e.log.WithError(err).Warnf("Failed to get backing image %s with disk UUID %s from replica %s head parent %s, will mark the mode from %v to ERR and continue info update for other replicas", replica.BackingImageName, replica.LvsUUID, replicaName, replica.Head.Parent, replicaStatus.Mode) + replicaStatus.Mode = types.ModeERR + return + } + replicaAncestorMap[replicaName] = backingImage.Snapshot + if len(replica.Snapshots) > 0 { + hasSnapshot = true } } else { - if hasSnapshot { - e.log.Warnf("Engine found replica %s does not have a snapshot while other replicas have during info update for other replicas", replicaName) + if len(replica.Snapshots) > 0 { + if hasBackingImage { + e.log.Warnf("Engine found replica %s does not have a backing image while other replicas have during info update for other replicas", replicaName) + } else { + hasSnapshot = true + for snapshotName, snapApiLvol := range replica.Snapshots { + if snapApiLvol.Parent == "" { + replicaAncestorMap[replicaName] = replica.Snapshots[snapshotName] + break + } + } + } } else { - replicaAncestorMap[replicaName] = replica.Head + if hasSnapshot { + e.log.Warnf("Engine found replica %s does not have a snapshot while other replicas have during info update for other replicas", replicaName) + } else { + replicaAncestorMap[replicaName] = replica.Head + } } } if replicaAncestorMap[replicaName] == nil { @@ -890,18 +899,19 @@ func (e *Engine) checkAndUpdateInfoFromReplicaNoLock() { candidateReplicaName := "" earliestCreationTime := time.Now() for replicaName, ancestorApiLvol := range replicaAncestorMap { - // if hasBackingImage { - // if ancestorApiLvol.Name == types.VolumeHead || IsReplicaSnapshotLvol(replicaName, ancestorApiLvol.Name) { - // continue - // } - // } else - if hasSnapshot { - if ancestorApiLvol.Name == types.VolumeHead { + if hasBackingImage { + if ancestorApiLvol.Name == types.VolumeHead || IsReplicaSnapshotLvol(replicaName, ancestorApiLvol.Name) { continue } } else { - if ancestorApiLvol.Name != types.VolumeHead { - continue + if hasSnapshot { + if ancestorApiLvol.Name == types.VolumeHead { + continue + } + } else { + if ancestorApiLvol.Name != types.VolumeHead { + continue + } } } @@ -916,8 +926,27 @@ func (e *Engine) checkAndUpdateInfoFromReplicaNoLock() { e.Head = replicaMap[replicaName].Head e.ActualSize = replicaMap[replicaName].ActualSize if candidateReplicaName != replicaName { - if candidateReplicaName != "" && replicaAncestorMap[candidateReplicaName].Name != ancestorApiLvol.Name { - e.log.Warnf("Comparing with replica %s ancestor %s, replica %s has a different and earlier ancestor %s, will update info from this replica", candidateReplicaName, replicaAncestorMap[candidateReplicaName].Name, replicaName, ancestorApiLvol.Name) + if candidateReplicaName != "" { + candidateReplicaAncestorName := replicaAncestorMap[candidateReplicaName].Name + currentReplicaAncestorName := ancestorApiLvol.Name + // The ancestor can be backing image, so we need to extract the backing image name from the lvol name + // Notice that, the disks are not the same for all the replicas, so their backing image lvol names are not the same. + if IsBackingImageSnapLvolName(candidateReplicaAncestorName) { + candidateReplicaAncestorName, _, err = ExtractBackingImageAndDiskUUID(candidateReplicaAncestorName) + if err != nil { + logrus.Warnf("BUG: ancestor name %v is from backingImage.Snapshot lvol name, it should be a valid backing image lvol name", candidateReplicaAncestorName) + } + } + if IsBackingImageSnapLvolName(currentReplicaAncestorName) { + currentReplicaAncestorName, _, err = ExtractBackingImageAndDiskUUID(currentReplicaAncestorName) + if err != nil { + logrus.Warnf("BUG: ancestor name %v is from backingImage.Snapshot lvol name, it should be a valid backing image lvol name", currentReplicaAncestorName) + } + } + + if candidateReplicaName != "" && candidateReplicaAncestorName != currentReplicaAncestorName { + e.log.Warnf("Comparing with replica %s ancestor %s, replica %s has a different and earlier ancestor %s, will update info from this replica", candidateReplicaName, replicaAncestorMap[candidateReplicaName].Name, replicaName, ancestorApiLvol.Name) + } } candidateReplicaName = replicaName } @@ -1422,7 +1451,8 @@ func getRebuildingSnapshotList(srcReplicaServiceCli *client.SPDKClient, srcRepli } ancestorSnapshotName, latestSnapshotName := "", "" for snapshotName, snapApiLvol := range rpcSrcReplica.Snapshots { - if snapApiLvol.Parent == "" { + // If parent is empty or the parent is a backing image snapshot, it's the ancestor snapshot + if snapApiLvol.Parent == "" || IsBackingImageSnapLvolName(snapApiLvol.Parent) { ancestorSnapshotName = snapshotName } if snapApiLvol.Children[types.VolumeHead] { diff --git a/pkg/spdk/replica.go b/pkg/spdk/replica.go index a339c6a0..3238a318 100644 --- a/pkg/spdk/replica.go +++ b/pkg/spdk/replica.go @@ -52,6 +52,7 @@ type Replica struct { ActiveChain []*Lvol // SnapshotLvolMap map[]. consists of `-snap-` SnapshotLvolMap map[string]*Lvol + BackingImage *Lvol Name string Alias string @@ -139,6 +140,17 @@ func ServiceReplicaToProtoReplica(r *Replica) *spdkrpc.Replica { res.Snapshots[GetSnapshotNameFromReplicaSnapshotLvolName(r.Name, lvolName)] = ServiceLvolToProtoLvol(r.Name, lvol) } + if r.BackingImage != nil { + backingImageName, _, err := ExtractBackingImageAndDiskUUID(r.BackingImage.Name) + if err != nil { + // The BackingImageName will be "" when getting the result from grpc if there is an error. + // We handle the empty backing image name in the caller. + // This field is currently only used when engine updating info from replicas or rebuilding the replica. + logrus.WithError(err).Warnf("Failed to extract backing image name from %v", r.BackingImage.Name) + } + res.BackingImageName = backingImageName + } + return res } @@ -195,7 +207,8 @@ func (r *Replica) replicaLvolFilter(bdev *spdktypes.BdevInfo) bool { return false } lvolName := spdktypes.GetLvolNameFromAlias(bdev.Aliases[0]) - return IsReplicaLvol(r.Name, lvolName) || (len(r.ActiveChain) > 0 && r.ActiveChain[0] != nil && r.ActiveChain[0].Name == lvolName) + // it is okay to have backing image snapshot in the results, because we exclude it when finding root or construct the snapshot map + return IsReplicaLvol(r.Name, lvolName) || IsBackingImageSnapLvolName(lvolName) } func (r *Replica) Sync(spdkClient *spdkclient.Client) (err error) { @@ -263,6 +276,7 @@ func (r *Replica) construct(bdevLvolMap map[string]*spdktypes.BdevInfo) (err err r.Head = newChain[len(newChain)-1] r.ActiveChain = newChain r.SnapshotLvolMap = newSnapshotLvolMap + r.BackingImage = newChain[0] if r.State == types.InstanceStatePending { r.State = types.InstanceStateStopped @@ -335,6 +349,8 @@ func (r *Replica) validateAndUpdate(bdevLvolMap map[string]*spdktypes.BdevInfo, if svcLvol == nil && newSvcLvol != nil { return fmt.Errorf("replica current backing image is nil while the latest chain contains backing image %v", newSvcLvol.Name) } + // no need to compare the backing image + continue } if err := compareSvcLvols(svcLvol, newSvcLvol, true, svcLvol.Name != r.Name); err != nil { @@ -501,20 +517,26 @@ func (r *Replica) updateHeadCache(spdkClient *spdkclient.Client) (err error) { return nil } -func (r *Replica) prepareHead(spdkClient *spdkclient.Client) (err error) { +func (r *Replica) prepareHead(spdkClient *spdkclient.Client, backingImage *BackingImage) (err error) { isHeadAvailable, err := r.IsHeadLvolAvailable(spdkClient) if err != nil { return err } + if backingImage != nil { + r.ActiveChain[0] = backingImage.Snapshot + r.BackingImage = r.ActiveChain[0] + } + if !isHeadAvailable { r.log.Info("Creating a lvol bdev as replica head") if r.ActiveChain[len(r.ActiveChain)-1] != nil { // The replica has a backing image or somehow there are already snapshots in the chain - if _, err := spdkClient.BdevLvolClone(r.ActiveChain[len(r.ActiveChain)-1].UUID, r.Name); err != nil { + uuid, err := spdkClient.BdevLvolClone(r.ActiveChain[len(r.ActiveChain)-1].UUID, r.Name) + if err != nil { return err } if r.ActiveChain[len(r.ActiveChain)-1].SpecSize != r.SpecSize { - if _, err := spdkClient.BdevLvolResize(r.Alias, r.SpecSize); err != nil { + if _, err := spdkClient.BdevLvolResize(uuid, util.BytesToMiB(r.SpecSize)); err != nil { return err } } @@ -642,7 +664,7 @@ func constructActiveChainFromSnapshotLvolMap(replicaName string, snapshotLvolMap } // Create initiates the replica, prepares the head lvol bdev then blindly exposes it for the replica. -func (r *Replica) Create(spdkClient *spdkclient.Client, portCount int32, superiorPortAllocator *commonbitmap.Bitmap) (ret *spdkrpc.Replica, err error) { +func (r *Replica) Create(spdkClient *spdkclient.Client, portCount int32, superiorPortAllocator *commonbitmap.Bitmap, backingImage *BackingImage) (ret *spdkrpc.Replica, err error) { updateRequired := true r.Lock() @@ -710,7 +732,7 @@ func (r *Replica) Create(spdkClient *spdkclient.Client, portCount int32, superio } // A stopped replica may be a broken one. We need to make sure the head lvol is ready first. - if err := r.prepareHead(spdkClient); err != nil { + if err := r.prepareHead(spdkClient, backingImage); err != nil { return nil, err } r.State = types.InstanceStateStopped @@ -940,8 +962,10 @@ func (r *Replica) SnapshotCreate(spdkClient *spdkclient.Client, snapshotName str // Already contain a valid snapshot lvol or backing image lvol before this snapshot creation if len(r.ActiveChain) > 1 && r.ActiveChain[len(r.ActiveChain)-2] != nil { prevSvcLvol := r.ActiveChain[len(r.ActiveChain)-2] + prevSvcLvol.Lock() delete(prevSvcLvol.Children, r.Head.Name) prevSvcLvol.Children[snapSvcLvol.Name] = snapSvcLvol + prevSvcLvol.Unlock() } r.ActiveChain[len(r.ActiveChain)-1] = snapSvcLvol r.ActiveChain = append(r.ActiveChain, r.Head) @@ -1022,6 +1046,7 @@ func (r *Replica) removeLvolFromSnapshotLvolMapWithoutLock(snapsLvolName string) if IsReplicaSnapshotLvol(r.Name, deletingSvcLvol.Parent) { parentSvcLvol = r.SnapshotLvolMap[deletingSvcLvol.Parent] } else { + // Parent is either backing image or nil parentSvcLvol = r.ActiveChain[0] } if parentSvcLvol != nil { @@ -1232,7 +1257,7 @@ func (r *Replica) RebuildingSrcStart(spdkClient *spdkclient.Client, dstReplicaNa snapLvol := r.SnapshotLvolMap[GetReplicaSnapshotLvolName(r.Name, exposedSnapshotName)] if snapLvol == nil { - return "", fmt.Errorf("cannot find snapshot %s for for replica %s rebuilding src start", exposedSnapshotName, r.Name) + return "", fmt.Errorf("cannot find snapshot %s for the replica %s rebuilding src start", exposedSnapshotName, r.Name) } if r.rebuildingSrcCache.dstReplicaName != "" || r.rebuildingSrcCache.exposedSnapshotAlias != "" { @@ -1931,10 +1956,28 @@ func (r *Replica) RebuildingDstSnapshotRevert(spdkClient *spdkclient.Client, sna rebuildingLvolUUID := "" if snapshotName != "" { snapLvolAlias := spdktypes.GetLvolAlias(r.LvsName, GetReplicaSnapshotLvolName(r.Name, snapshotName)) + if IsBackingImageSnapLvolName(snapshotName) { + // The snapshot name here is the one in the src SPDK server. + // On the current node, this backing image snapshot lvol name is not the same due to the different lvs UUID. + backingImageName, _, err := ExtractBackingImageAndDiskUUID(snapshotName) + if err != nil { + return "", errors.Wrapf(err, "failed to extract backing image name and disk UUID from snapshot name %s", snapshotName) + } + backingImageSnapLvolName := GetBackingImageSnapLvolName(backingImageName, r.LvsUUID) + snapLvolAlias = spdktypes.GetLvolAlias(r.LvsName, backingImageSnapLvolName) + } if rebuildingLvolUUID, err = spdkClient.BdevLvolClone(snapLvolAlias, rebuildingLvolName); err != nil { return "", err } + + if IsBackingImageSnapLvolName(snapshotName) { + // Resize the replica head to the spec size + if _, err := spdkClient.BdevLvolResize(rebuildingLvolUUID, util.BytesToMiB(r.SpecSize)); err != nil { + logrus.WithError(err).Errorf("failed to resize the rebuildingLvolUUID %v to size %v", rebuildingLvolUUID, r.SpecSize) + return "", err + } + } } else { if rebuildingLvolUUID, err = spdkClient.BdevLvolCreate("", r.LvsUUID, rebuildingLvolName, util.BytesToMiB(r.SpecSize), "", true); err != nil { return "", err diff --git a/pkg/spdk/server.go b/pkg/spdk/server.go index b64d1db3..77e974fd 100644 --- a/pkg/spdk/server.go +++ b/pkg/spdk/server.go @@ -50,6 +50,9 @@ type Server struct { backupMap map[string]*Backup + // We store BackingImage in each lvstore + backingImageMap map[string]*BackingImage + broadcasters map[types.InstanceType]*broadcaster.Broadcaster broadcastChs map[types.InstanceType]chan interface{} updateChs map[types.InstanceType]chan interface{} @@ -78,7 +81,7 @@ func NewServer(ctx context.Context, portStart, portEnd int32) (*Server, error) { broadcasters := map[types.InstanceType]*broadcaster.Broadcaster{} broadcastChs := map[types.InstanceType]chan interface{}{} updateChs := map[types.InstanceType]chan interface{}{} - for _, t := range []types.InstanceType{types.InstanceTypeReplica, types.InstanceTypeEngine} { + for _, t := range []types.InstanceType{types.InstanceTypeReplica, types.InstanceTypeEngine, types.InstanceTypeBackingImage} { broadcasters[t] = &broadcaster.Broadcaster{} broadcastChs[t] = make(chan interface{}) updateChs[t] = make(chan interface{}) @@ -95,6 +98,8 @@ func NewServer(ctx context.Context, portStart, portEnd int32) (*Server, error) { backupMap: map[string]*Backup{}, + backingImageMap: map[string]*BackingImage{}, + broadcasters: broadcasters, broadcastChs: broadcastChs, updateChs: updateChs, @@ -106,6 +111,9 @@ func NewServer(ctx context.Context, portStart, portEnd int32) (*Server, error) { if _, err := s.broadcasters[types.InstanceTypeEngine].Subscribe(ctx, s.engineBroadcastConnector); err != nil { return nil, err } + if _, err := s.broadcasters[types.InstanceTypeBackingImage].Subscribe(ctx, s.backingImageBroadcastConnector); err != nil { + return nil, err + } // TODO: There is no need to maintain the replica map in cache when we can use one SPDK JSON API call to fetch the Lvol tree/chain info go s.monitoring() @@ -182,6 +190,8 @@ func (s *Server) verify() (err error) { replicaMap := map[string]*Replica{} replicaMapForSync := map[string]*Replica{} engineMapForSync := map[string]*Engine{} + backingImageMap := map[string]*BackingImage{} + backingImageMapForSync := map[string]*BackingImage{} s.Lock() for k, v := range s.replicaMap { @@ -191,6 +201,11 @@ func (s *Server) verify() (err error) { for k, v := range s.engineMap { engineMapForSync[k] = v } + for k, v := range s.backingImageMap { + backingImageMap[k] = v + backingImageMapForSync[k] = v + } + spdkClient := s.spdkClient defer func() { @@ -208,7 +223,7 @@ func (s *Server) verify() (err error) { } }() - // Detect if the lvol bdev is an uncached replica. + // Detect if the lvol bdev is an uncached replica or backing image. // But cannot detect if a RAID bdev is an engine since: // 1. we don't know the frontend // 2. RAID bdevs are not persist objects in SPDK. After spdk_tgt start/restart, there is no RAID bdev hence there is no need to do detection. @@ -238,23 +253,63 @@ func (s *Server) verify() (err error) { for _, lvs := range lvsList { lvsUUIDNameMap[lvs.UUID] = lvs.Name } + // Backing image lvol name will be "bi-${biName}-disk-${lvsUUID}" + // Backing image temp lvol name will be "bi-${biName}-disk-${lvsUUID}-temp-head" for lvolName, bdevLvol := range bdevLvolMap { - if bdevLvol.DriverSpecific.Lvol.Snapshot { + if bdevLvol.DriverSpecific.Lvol.Snapshot && !IsBackingImageSnapLvolName(lvolName) { + continue + } + if IsBackingImageTempHead(lvolName) { + if s.backingImageMap[GetBackingImageSnapLvolNameFromTempHeadLvolName(lvolName)] == nil { + lvsUUID := bdevLvol.DriverSpecific.Lvol.LvolStoreUUID + logrus.Infof("Found one backing image temp head lvol %v while there is no backing image record in the server", lvolName) + cleanupOrphanBackingImageTempHead(spdkClient, lvsUUIDNameMap[lvsUUID], lvolName) + } continue } if replicaMap[lvolName] != nil { continue } + if s.backingImageMap[lvolName] != nil { + continue + } if IsRebuildingLvol(lvolName) { if replicaMap[GetReplicaNameFromRebuildingLvolName(lvolName)] != nil { continue } } - lvsUUID := bdevLvol.DriverSpecific.Lvol.LvolStoreUUID - specSize := bdevLvol.NumBlocks * uint64(bdevLvol.BlockSize) - actualSize := bdevLvol.DriverSpecific.Lvol.NumAllocatedClusters * uint64(defaultClusterSize) - replicaMap[lvolName] = NewReplica(s.ctx, lvolName, lvsUUIDNameMap[lvsUUID], lvsUUID, specSize, actualSize, s.updateChs[types.InstanceTypeReplica]) - replicaMapForSync[lvolName] = replicaMap[lvolName] + if IsBackingImageSnapLvolName(lvolName) { + lvsUUID := bdevLvol.DriverSpecific.Lvol.LvolStoreUUID + backingImageName, _, err := ExtractBackingImageAndDiskUUID(lvolName) + if err != nil { + logrus.WithError(err).Warnf("failed to extract backing image name and disk UUID from lvol name %v", lvolName) + continue + } + actualSize := bdevLvol.DriverSpecific.Lvol.NumAllocatedClusters * uint64(defaultClusterSize) + alias := bdevLvol.Aliases[0] + expectedChecksum, err := GetSnapXattr(spdkClient, alias, types.BackingImageSnapshotAttrChecksum) + if err != nil { + logrus.WithError(err).Warnf("failed to retrieve checksum attribute for backing image snapshot %v", alias) + continue + } + backingImageUUID, err := GetSnapXattr(spdkClient, alias, types.BackingImageSnapshotAttrBackingImageUUID) + if err != nil { + logrus.WithError(err).Warnf("failed to retrieve backing image UUID attribute for snapshot %v", alias) + continue + } + backingImage := NewBackingImage(s.ctx, backingImageName, backingImageUUID, lvsUUID, actualSize, expectedChecksum, s.updateChs[types.InstanceTypeBackingImage]) + backingImage.Alias = alias + // For uncahced backing image, we set the state to pending first, so we can distinguish it from the cached but starting backing image + backingImage.State = types.BackingImageStatePending + backingImageMapForSync[lvolName] = backingImage + backingImageMap[lvolName] = backingImage + } else { + lvsUUID := bdevLvol.DriverSpecific.Lvol.LvolStoreUUID + specSize := bdevLvol.NumBlocks * uint64(bdevLvol.BlockSize) + actualSize := bdevLvol.DriverSpecific.Lvol.NumAllocatedClusters * uint64(defaultClusterSize) + replicaMap[lvolName] = NewReplica(s.ctx, lvolName, lvsUUIDNameMap[lvsUUID], lvsUUID, specSize, actualSize, s.updateChs[types.InstanceTypeReplica]) + replicaMapForSync[lvolName] = replicaMap[lvolName] + } } for replicaName, r := range replicaMap { // Try the best to avoid eliminating broken replicas or rebuilding replicas without head lvols @@ -279,6 +334,7 @@ func (s *Server) verify() (err error) { logrus.Infof("spdk gRPC server: Replica map updated, map count is changed from %d to %d", len(s.replicaMap), len(replicaMap)) } s.replicaMap = replicaMap + s.backingImageMap = backingImageMap s.Unlock() for _, r := range replicaMapForSync { @@ -295,6 +351,16 @@ func (s *Server) verify() (err error) { } } + for _, bi := range backingImageMapForSync { + err = bi.ValidateAndUpdate(spdkClient) + if err != nil { + if jsonrpc.IsJSONRPCRespErrorBrokenPipe(err) { + return err + } + continue + } + } + // TODO: send update signals if there is a Replica/Replica change return nil @@ -311,6 +377,8 @@ func (s *Server) broadcasting() { s.broadcastChs[types.InstanceTypeReplica] <- nil case <-s.updateChs[types.InstanceTypeEngine]: s.broadcastChs[types.InstanceTypeEngine] <- nil + case <-s.updateChs[types.InstanceTypeBackingImage]: + s.broadcastChs[types.InstanceTypeBackingImage] <- nil } if done { break @@ -324,6 +392,8 @@ func (s *Server) Subscribe(instanceType types.InstanceType) (<-chan interface{}, return s.broadcasters[types.InstanceTypeEngine].Subscribe(context.TODO(), s.engineBroadcastConnector) case types.InstanceTypeReplica: return s.broadcasters[types.InstanceTypeReplica].Subscribe(context.TODO(), s.replicaBroadcastConnector) + case types.InstanceTypeBackingImage: + return s.broadcasters[types.InstanceTypeBackingImage].Subscribe(context.TODO(), s.backingImageBroadcastConnector) } return nil, fmt.Errorf("invalid instance type %v for subscription", instanceType) } @@ -336,6 +406,10 @@ func (s *Server) engineBroadcastConnector() (chan interface{}, error) { return s.broadcastChs[types.InstanceTypeEngine], nil } +func (s *Server) backingImageBroadcastConnector() (chan interface{}, error) { + return s.broadcastChs[types.InstanceTypeBackingImage], nil +} + func (s *Server) checkLvsReadiness(lvsUUID, lvsName string) (bool, error) { var err error var lvsList []spdktypes.LvstoreInfo @@ -388,7 +462,18 @@ func (s *Server) ReplicaCreate(ctx context.Context, req *spdkrpc.ReplicaCreateRe spdkClient := s.spdkClient s.RUnlock() - return r.Create(spdkClient, req.PortCount, s.portAllocator) + var backingImage *BackingImage + if req.BackingImageName != "" { + backingImageSnapLvolName := GetBackingImageSnapLvolName(req.BackingImageName, req.LvsUuid) + s.RLock() + backingImage = s.backingImageMap[backingImageSnapLvolName] + s.RUnlock() + if backingImage == nil { + return nil, grpcstatus.Error(grpccodes.NotFound, "failed to find the backing image in the spdk server") + } + } + + return r.Create(spdkClient, req.PortCount, s.portAllocator, backingImage) } func (s *Server) ReplicaDelete(ctx context.Context, req *spdkrpc.ReplicaDeleteRequest) (ret *emptypb.Empty, err error) { @@ -1504,3 +1589,222 @@ func (s *Server) VersionDetailGet(context.Context, *emptypb.Empty) (*spdkrpc.Ver Version: &spdkrpc.VersionOutput{}, }, nil } + +func (s *Server) newBackingImage(req *spdkrpc.BackingImageCreateRequest) (*BackingImage, error) { + s.Lock() + defer s.Unlock() + + // The backing image key is in this form "bi-%s-disk-%s" to distinguish different disks. + backingImageSnapLvolName := GetBackingImageSnapLvolName(req.Name, req.LvsUuid) + if _, ok := s.backingImageMap[backingImageSnapLvolName]; !ok { + ready, err := s.checkLvsReadiness(req.LvsUuid, "") + if err != nil || !ready { + return nil, err + } + s.backingImageMap[backingImageSnapLvolName] = NewBackingImage(s.ctx, req.Name, req.BackingImageUuid, req.LvsUuid, req.Size, req.Checksum, s.updateChs[types.InstanceTypeBackingImage]) + } + + return s.backingImageMap[backingImageSnapLvolName], nil +} + +func (s *Server) BackingImageCreate(ctx context.Context, req *spdkrpc.BackingImageCreateRequest) (ret *spdkrpc.BackingImage, err error) { + if req.Name == "" { + return nil, grpcstatus.Error(grpccodes.InvalidArgument, "backing image name is required") + } + if req.BackingImageUuid == "" { + return nil, grpcstatus.Error(grpccodes.InvalidArgument, "backing image UUID is required") + } + if req.Size == uint64(0) { + return nil, grpcstatus.Error(grpccodes.InvalidArgument, "backing image size is required") + } + if req.LvsUuid == "" { + return nil, grpcstatus.Error(grpccodes.InvalidArgument, "lvs UUID is required") + } + if req.Checksum == "" { + return nil, grpcstatus.Error(grpccodes.InvalidArgument, "checksum is required") + } + + // Don't recreate the backing image + backingImageSnapLvolName := GetBackingImageSnapLvolName(req.Name, req.LvsUuid) + if _, ok := s.backingImageMap[backingImageSnapLvolName]; ok { + return nil, grpcstatus.Errorf(grpccodes.AlreadyExists, "backing image %v already exists", req.Name) + } + + bi, err := s.newBackingImage(req) + if err != nil { + return nil, err + } + + s.RLock() + spdkClient := s.spdkClient + s.RUnlock() + + return bi.Create(spdkClient, s.portAllocator, req.FromAddress, req.SrcLvsUuid) +} + +func (s *Server) BackingImageDelete(ctx context.Context, req *spdkrpc.BackingImageDeleteRequest) (ret *emptypb.Empty, err error) { + if req.Name == "" { + return nil, grpcstatus.Error(grpccodes.InvalidArgument, "backing image name is required") + } + if req.LvsUuid == "" { + return nil, grpcstatus.Error(grpccodes.InvalidArgument, "lvs UUID is required") + } + + s.RLock() + bi := s.backingImageMap[GetBackingImageSnapLvolName(req.Name, req.LvsUuid)] + spdkClient := s.spdkClient + s.RUnlock() + + defer func() { + if err == nil { + s.Lock() + delete(s.backingImageMap, GetBackingImageSnapLvolName(req.Name, req.LvsUuid)) + s.Unlock() + } + }() + + if bi != nil { + if err := bi.Delete(spdkClient, s.portAllocator); err != nil { + return nil, grpcstatus.Error(grpccodes.Internal, errors.Wrapf(err, "failed to delete backing image %v in lvs %v", req.Name, req.LvsUuid).Error()) + } + } + + return &emptypb.Empty{}, nil +} + +func (s *Server) BackingImageGet(ctx context.Context, req *spdkrpc.BackingImageGetRequest) (ret *spdkrpc.BackingImage, err error) { + if req.Name == "" { + return nil, grpcstatus.Error(grpccodes.InvalidArgument, "backing image name is required") + } + if req.LvsUuid == "" { + return nil, grpcstatus.Error(grpccodes.InvalidArgument, "lvs UUID is required") + } + + backingImageSnapLvolName := GetBackingImageSnapLvolName(req.Name, req.LvsUuid) + + s.RLock() + bi := s.backingImageMap[backingImageSnapLvolName] + s.RUnlock() + + if bi == nil { + lvsName, err := GetLvsNameByUUID(s.spdkClient, req.LvsUuid) + if err != nil { + return nil, grpcstatus.Errorf(grpccodes.NotFound, "failed to get the lvs name with lvs uuid %v", req.LvsUuid) + } + + if lvsName != "" { + backingImageSnapLvolAlias := spdktypes.GetLvolAlias(lvsName, backingImageSnapLvolName) + bdevLvolList, err := s.spdkClient.BdevLvolGet(backingImageSnapLvolAlias, 0) + if err != nil { + return nil, grpcstatus.Errorf(grpccodes.NotFound, "got error %v when getting lvol %v in the lvs %v", err, req.Name, req.LvsUuid) + } + if len(bdevLvolList) != 1 { + return nil, grpcstatus.Errorf(grpccodes.NotFound, "zero or multiple lvols with alias %s found when finding backing image %v in lvs %v", backingImageSnapLvolAlias, req.Name, req.LvsUuid) + } + // If we can get the lvol, verify() will reconstruct the backing image record in the server, should inform the caller + return nil, grpcstatus.Errorf(grpccodes.NotFound, "backing image %v lvol found in the lvs %v but failed to find the record in the server", req.Name, req.LvsUuid) + } + return nil, grpcstatus.Errorf(grpccodes.NotFound, "cannot find backing image %v in lvs %v", req.Name, req.LvsUuid) + } + + return bi.Get(), nil +} + +func (s *Server) BackingImageList(ctx context.Context, req *emptypb.Empty) (ret *spdkrpc.BackingImageListResponse, err error) { + backingImageMap := map[string]*BackingImage{} + res := map[string]*spdkrpc.BackingImage{} + + s.RLock() + for k, v := range s.backingImageMap { + backingImageMap[k] = v + } + s.RUnlock() + + // backingImageName is in the form of "bi-%s-disk-%s" + for backingImageName, bi := range backingImageMap { + res[backingImageName] = bi.Get() + } + + return &spdkrpc.BackingImageListResponse{BackingImages: res}, nil +} + +func (s *Server) BackingImageWatch(req *emptypb.Empty, srv spdkrpc.SPDKService_BackingImageWatchServer) error { + responseCh, err := s.Subscribe(types.InstanceTypeBackingImage) + if err != nil { + return err + } + + defer func() { + if err != nil { + logrus.WithError(err).Error("SPDK service backing image watch errored out") + } else { + logrus.Info("SPDK service backing image watch ended successfully") + } + }() + logrus.Info("Started new SPDK service backing image update watch") + + done := false + for { + select { + case <-s.ctx.Done(): + logrus.Info("spdk gRPC server: stopped backing image watch due to the context done") + done = true + case <-responseCh: + if err := srv.Send(&emptypb.Empty{}); err != nil { + return err + } + } + if done { + break + } + } + + return nil +} + +func (s *Server) BackingImageExpose(ctx context.Context, req *spdkrpc.BackingImageGetRequest) (ret *spdkrpc.BackingImageExposeResponse, err error) { + if req.Name == "" { + return nil, grpcstatus.Error(grpccodes.InvalidArgument, "backing image name is required") + } + if req.LvsUuid == "" { + return nil, grpcstatus.Error(grpccodes.InvalidArgument, "lvs UUID is required") + } + s.RLock() + bi := s.backingImageMap[GetBackingImageSnapLvolName(req.Name, req.LvsUuid)] + spdkClient := s.spdkClient + s.RUnlock() + + if bi == nil { + return nil, grpcstatus.Errorf(grpccodes.NotFound, "cannot find backing image %v in lvs %v", req.Name, req.LvsUuid) + } + + exposedSnapshotLvolAddress, err := bi.BackingImageExpose(spdkClient, s.portAllocator) + if err != nil { + return nil, err + } + return &spdkrpc.BackingImageExposeResponse{ExposedSnapshotLvolAddress: exposedSnapshotLvolAddress}, nil + +} + +func (s *Server) BackingImageUnexpose(ctx context.Context, req *spdkrpc.BackingImageGetRequest) (ret *emptypb.Empty, err error) { + if req.Name == "" { + return nil, grpcstatus.Error(grpccodes.InvalidArgument, "backing image name is required") + } + if req.LvsUuid == "" { + return nil, grpcstatus.Error(grpccodes.InvalidArgument, "lvs UUID is required") + } + s.RLock() + bi := s.backingImageMap[GetBackingImageSnapLvolName(req.Name, req.LvsUuid)] + spdkClient := s.spdkClient + s.RUnlock() + + if bi == nil { + return nil, grpcstatus.Errorf(grpccodes.NotFound, "cannot find backing image %v in lvs %v", req.Name, req.LvsUuid) + } + + err = bi.BackingImageUnexpose(spdkClient, s.portAllocator) + if err != nil { + return nil, grpcstatus.Error(grpccodes.Internal, errors.Wrapf(err, "failed to unexpose backing image %v in lvs %v", req.Name, req.LvsUuid).Error()) + } + return &emptypb.Empty{}, nil +} diff --git a/pkg/spdk/types.go b/pkg/spdk/types.go index 004cdb58..259bec15 100644 --- a/pkg/spdk/types.go +++ b/pkg/spdk/types.go @@ -5,6 +5,7 @@ import ( "net" "strconv" "strings" + "sync" "time" "github.com/longhorn/types/pkg/generated/spdkrpc" @@ -24,6 +25,8 @@ const ( ReplicaRebuildingLvolSuffix = "rebuilding" RebuildingSnapshotNamePrefix = "rebuild" + BackingImageTempHeadLvolSuffix = "temp-head" + SyncTimeout = 60 * time.Minute nvmeNguidLength = 32 @@ -55,6 +58,8 @@ const ( ) type Lvol struct { + sync.RWMutex + Name string UUID string Alias string @@ -69,6 +74,32 @@ type Lvol struct { SnapshotTimestamp string } +func ServiceBackingImageLvolToProtoBackingImageLvol(lvol *Lvol) *spdkrpc.Lvol { + lvol.RLock() + defer lvol.RUnlock() + + res := &spdkrpc.Lvol{ + Uuid: lvol.UUID, + Name: lvol.Name, + SpecSize: lvol.SpecSize, + ActualSize: lvol.ActualSize, + // BackingImage has no parent + Parent: "", + Children: map[string]bool{}, + CreationTime: lvol.CreationTime, + UserCreated: false, + // Use creation time instead + SnapshotTimestamp: "", + } + + for childLvolName := range lvol.Children { + // For backing image, the children is map[] + res.Children[childLvolName] = true + } + + return res +} + func ServiceLvolToProtoLvol(replicaName string, lvol *Lvol) *spdkrpc.Lvol { if lvol == nil { return nil @@ -118,6 +149,26 @@ func BdevLvolInfoToServiceLvol(bdev *spdktypes.BdevInfo) *Lvol { } } +func GetBackingImageSnapLvolName(backingImageName string, lvsUUID string) string { + return fmt.Sprintf("bi-%s-disk-%s", backingImageName, lvsUUID) +} + +func IsBackingImageSnapLvolName(lvolName string) bool { + return strings.HasPrefix(lvolName, "bi-") && !strings.HasSuffix(lvolName, BackingImageTempHeadLvolSuffix) +} + +func GetBackingImageTempHeadLvolName(backingImageName string, lvsUUID string) string { + return fmt.Sprintf("bi-%s-disk-%s-temp-head", backingImageName, lvsUUID) +} + +func GetBackingImageSnapLvolNameFromTempHeadLvolName(lvolName string) string { + return strings.TrimSuffix(lvolName, fmt.Sprintf("-%s", BackingImageTempHeadLvolSuffix)) +} + +func IsBackingImageTempHead(lvolName string) bool { + return strings.HasSuffix(lvolName, BackingImageTempHeadLvolSuffix) +} + func GetReplicaSnapshotLvolNamePrefix(replicaName string) string { return fmt.Sprintf("%s-snap-", replicaName) } diff --git a/pkg/spdk/util.go b/pkg/spdk/util.go index b5c154e8..6fabd033 100644 --- a/pkg/spdk/util.go +++ b/pkg/spdk/util.go @@ -3,6 +3,7 @@ package spdk import ( "fmt" "net" + "regexp" "strconv" "strings" "time" @@ -14,6 +15,7 @@ import ( "github.com/longhorn/go-spdk-helper/pkg/nvme" commonns "github.com/longhorn/go-common-libs/ns" + commontypes "github.com/longhorn/go-common-libs/types" commonutils "github.com/longhorn/go-common-libs/utils" spdkclient "github.com/longhorn/go-spdk-helper/pkg/spdk/client" spdktypes "github.com/longhorn/go-spdk-helper/pkg/spdk/types" @@ -21,6 +23,37 @@ import ( helperutil "github.com/longhorn/go-spdk-helper/pkg/util" ) +func connectNVMeTarget(srcIP string, srcPort int32, maxRetries int, retryInterval time.Duration) (string, string, error) { + executor, err := helperutil.NewExecutor(commontypes.ProcDirectory) + if err != nil { + return "", "", errors.Wrapf(err, "failed to create executor") + } + + subsystemNQN := "" + controllerName := "" + for r := 0; r < maxRetries; r++ { + subsystemNQN, err = nvme.DiscoverTarget(srcIP, strconv.Itoa(int(srcPort)), executor) + if err != nil { + logrus.WithError(err).Warnf("Failed to discover target for with address %v:%v", srcIP, srcPort) + time.Sleep(retryInterval) + continue + } + + controllerName, err = nvme.ConnectTarget(srcIP, strconv.Itoa(int(srcPort)), subsystemNQN, executor) + if err != nil { + logrus.WithError(err).Warnf("Failed to connect target with address %v:%v", srcIP, srcPort) + time.Sleep(retryInterval) + continue + } + // break when it successfully discover and connect the target + break + } + if subsystemNQN == "" || controllerName == "" { + return "", "", errors.Wrapf(err, "timeout connecting target with address %v:%v", srcIP, srcPort) + } + return subsystemNQN, controllerName, nil +} + func exposeSnapshotLvolBdev(spdkClient *spdkclient.Client, lvsName, lvolName, ip string, port int32, executor *commonns.Executor) (subsystemNQN, controllerName string, err error) { bdevLvolList, err := spdkClient.BdevLvolGet(spdktypes.GetLvolAlias(lvsName, lvolName), 0) if err != nil { @@ -51,6 +84,8 @@ func exposeSnapshotLvolBdev(spdkClient *spdkclient.Client, lvsName, lvolName, ip time.Sleep(retryInterval) continue } + // break when it successfully discover and connect the target + break } return subsystemNQN, controllerName, nil } @@ -132,3 +167,45 @@ func CleanupLvolTree(spdkClient *spdkclient.Client, rootLvolName string, bdevLvo } } } + +func GetSnapXattr(spdkClient *spdkclient.Client, alias, key string) (string, error) { + value, err := spdkClient.BdevLvolGetXattr(alias, key) + if err != nil { + return "", err + } + return value, nil +} + +func GetLvsNameByUUID(spdkClient *spdkclient.Client, lvsUUID string) (string, error) { + if lvsUUID == "" { + return "", fmt.Errorf("empty UUID provided when getting logical volume store name") + } + var lvsList []spdktypes.LvstoreInfo + lvsList, err := spdkClient.BdevLvolGetLvstore("", lvsUUID) + if err != nil { + return "", err + } + if len(lvsList) != 1 { + return "", fmt.Errorf("expected exactly one lvstore for UUID %s, but found %d", lvsUUID, len(lvsList)) + } + return lvsList[0].Name, nil +} + +// ExtractBackingImageAndDiskUUID extracts the BackingImageName and DiskUUID from the string pattern "bi-${BackingImageName}-disk-${DiskUUID}" +func ExtractBackingImageAndDiskUUID(lvolName string) (string, string, error) { + // Define the regular expression pattern + // This captures the BackingImageName and DiskUUID while allowing for hyphens in both. + re := regexp.MustCompile(`^bi-([a-zA-Z0-9-]+)-disk-([a-zA-Z0-9-]+)$`) + + // Try to find a match + matches := re.FindStringSubmatch(lvolName) + if matches == nil { + return "", "", fmt.Errorf("lvolName does not match the expected pattern") + } + + // Extract BackingImageName and DiskUUID from the matches + backingImageName := matches[1] + diskUUID := matches[2] + + return backingImageName, diskUUID, nil +} diff --git a/pkg/spdk/util_test.go b/pkg/spdk/util_test.go index 9ae1fb56..a06e1cf5 100644 --- a/pkg/spdk/util_test.go +++ b/pkg/spdk/util_test.go @@ -54,3 +54,69 @@ func (s *TestSuite) TestSplitHostPort(c *C) { c.Assert(port, Equals, testCase.expectedPort) } } + +func (s *TestSuite) TestExtractBackingImageAndDiskUUID(c *C) { + type testCase struct { + lvolName string + expectedBIName string + expectedDiskUUID string + expectError bool + } + testCases := map[string]testCase{ + "ExtractBackingImageAndDiskUUID(...): only disk with hyphens": { + lvolName: "bi-MyBackingImage-disk-12345-abcde", + expectedBIName: "MyBackingImage", + expectedDiskUUID: "12345-abcde", + expectError: false, + }, + "ExtractBackingImageAndDiskUUID(...): backing image name and disk with hyphens": { + lvolName: "bi-My-Backing-Image-disk-12345-abcde-xyz", + expectedBIName: "My-Backing-Image", + expectedDiskUUID: "12345-abcde-xyz", + expectError: false, + }, + "ExtractBackingImageAndDiskUUID(...): backing image name and disk without hyphens": { + lvolName: "bi-MyBackingImage-disk-12345", + expectedBIName: "MyBackingImage", + expectedDiskUUID: "12345", + expectError: false, + }, + "ExtractBackingImageAndDiskUUID(...): doesn't start with bi- and doesn't contain -disk-": { + lvolName: "myWrongPattern", + expectedBIName: "", + expectedDiskUUID: "", + expectError: true, + }, + "ExtractBackingImageAndDiskUUID(...): doesn't have disk in the lvol name": { + lvolName: "bi-MyBackingImage-", + expectedBIName: "", + expectedDiskUUID: "", + expectError: true, + }, + "ExtractBackingImageAndDiskUUID(...): doesn't have backing image in the lvol name": { + lvolName: "-disk-123456-bi-abcdefg", + expectedBIName: "", + expectedDiskUUID: "", + expectError: true, + }, + "ExtractBackingImageAndDiskUUID(...): empty string": { + lvolName: "", + expectedBIName: "", + expectedDiskUUID: "", + expectError: true, + }, + } + for testName, testCase := range testCases { + c.Logf("testing ExtractBackingImageAndDiskUUID.%v", testName) + // Call the function being tested + biName, diskUUID, err := ExtractBackingImageAndDiskUUID(testCase.lvolName) + + if testCase.expectError { + c.Assert(err, NotNil) + } else { + c.Assert(err, IsNil) + c.Assert(testCase.expectedBIName, Equals, biName) + c.Assert(testCase.expectedDiskUUID, Equals, diskUUID) + } + } +} diff --git a/pkg/spdk_test.go b/pkg/spdk_test.go index cc6e243c..80af9e53 100644 --- a/pkg/spdk_test.go +++ b/pkg/spdk_test.go @@ -17,21 +17,21 @@ import ( "google.golang.org/grpc/keepalive" "google.golang.org/grpc/reflection" + "github.com/longhorn/types/pkg/generated/spdkrpc" + commonnet "github.com/longhorn/go-common-libs/net" commontypes "github.com/longhorn/go-common-libs/types" helperclient "github.com/longhorn/go-spdk-helper/pkg/spdk/client" helpertypes "github.com/longhorn/go-spdk-helper/pkg/types" helperutil "github.com/longhorn/go-spdk-helper/pkg/util" - "github.com/longhorn/types/pkg/generated/spdkrpc" - - server "github.com/longhorn/longhorn-spdk-engine/pkg/spdk" - "github.com/longhorn/longhorn-spdk-engine/pkg/api" "github.com/longhorn/longhorn-spdk-engine/pkg/client" "github.com/longhorn/longhorn-spdk-engine/pkg/types" "github.com/longhorn/longhorn-spdk-engine/pkg/util" + server "github.com/longhorn/longhorn-spdk-engine/pkg/spdk" + . "gopkg.in/check.v1" ) @@ -44,6 +44,13 @@ var ( defaultTestLvolSizeInMiB = uint64(500) defaultTestLvolSize = defaultTestLvolSizeInMiB * helpertypes.MiB + defaultTestBackingImageName = "parrot" + defaultTestBackingImageUUID = "12345" + defaultTestBackingImageChecksum = "304f3ed30ca6878e9056ee6f1b02b328239f0d0c2c1272840998212f9734b196371560b3b939037e4f4c2884ce457c2cbc9f0621f4f5d1ca983983c8cdf8cd9a" + defaultTestBackingImageDownloadURL = "https://longhorn-backing-image.s3-us-west-1.amazonaws.com/parrot.raw" + defaultTestBackingImageSizeInMiB = uint64(32) + defaultTestBackingImageSize = defaultTestBackingImageSizeInMiB * helpertypes.MiB + defaultTestStartPort = int32(20000) defaultTestEndPort = int32(30000) defaultTestReplicaPortCount = int32(5) @@ -240,7 +247,7 @@ func (s *TestSuite) TestSPDKMultipleThread(c *C) { wg.Done() }() - replica1, err := spdkCli.ReplicaCreate(replicaName1, defaultTestDiskName, disk.Uuid, defaultTestLvolSize, defaultTestReplicaPortCount) + replica1, err := spdkCli.ReplicaCreate(replicaName1, defaultTestDiskName, disk.Uuid, defaultTestLvolSize, defaultTestReplicaPortCount, "") c.Assert(err, IsNil) c.Assert(replica1.LvsName, Equals, defaultTestDiskName) c.Assert(replica1.LvsUUID, Equals, disk.Uuid) @@ -249,7 +256,7 @@ func (s *TestSuite) TestSPDKMultipleThread(c *C) { c.Assert(replica1.Head, NotNil) c.Assert(replica1.Head.CreationTime, Not(Equals), "") c.Assert(replica1.Head.Parent, Equals, "") - replica2, err := spdkCli.ReplicaCreate(replicaName2, defaultTestDiskName, disk.Uuid, defaultTestLvolSize, defaultTestReplicaPortCount) + replica2, err := spdkCli.ReplicaCreate(replicaName2, defaultTestDiskName, disk.Uuid, defaultTestLvolSize, defaultTestReplicaPortCount, "") c.Assert(err, IsNil) c.Assert(replica2.LvsName, Equals, defaultTestDiskName) c.Assert(replica2.LvsUUID, Equals, disk.Uuid) @@ -340,10 +347,10 @@ func (s *TestSuite) TestSPDKMultipleThread(c *C) { c.Assert(replica2.PortStart, Equals, int32(0)) c.Assert(replica2.PortEnd, Equals, int32(0)) - replica1, err = spdkCli.ReplicaCreate(replicaName1, defaultTestDiskName, disk.Uuid, defaultTestLvolSize, defaultTestReplicaPortCount) + replica1, err = spdkCli.ReplicaCreate(replicaName1, defaultTestDiskName, disk.Uuid, defaultTestLvolSize, defaultTestReplicaPortCount, "") c.Assert(err, IsNil) c.Assert(replica1.State, Equals, types.InstanceStateRunning) - replica2, err = spdkCli.ReplicaCreate(replicaName2, defaultTestDiskName, disk.Uuid, defaultTestLvolSize, defaultTestReplicaPortCount) + replica2, err = spdkCli.ReplicaCreate(replicaName2, defaultTestDiskName, disk.Uuid, defaultTestLvolSize, defaultTestReplicaPortCount, "") c.Assert(err, IsNil) c.Assert(replica2.State, Equals, types.InstanceStateRunning) @@ -390,7 +397,7 @@ func (s *TestSuite) TestSPDKMultipleThread(c *C) { // Start testing online rebuilding // Launch a new replica then ask the engine to rebuild it - replica3, err := spdkCli.ReplicaCreate(replicaName3, defaultTestDiskName, disk.Uuid, defaultTestLvolSize, defaultTestReplicaPortCount) + replica3, err := spdkCli.ReplicaCreate(replicaName3, defaultTestDiskName, disk.Uuid, defaultTestLvolSize, defaultTestReplicaPortCount, "") c.Assert(err, IsNil) c.Assert(replica3.LvsName, Equals, defaultTestDiskName) c.Assert(replica3.LvsUUID, Equals, disk.Uuid) @@ -477,6 +484,24 @@ func (s *TestSuite) TestSPDKMultipleThreadSnapshotOpsAndRebuilding(c *C) { c.Assert(err, IsNil) }() + bi, err := spdkCli.BackingImageCreate(defaultTestBackingImageName, defaultTestBackingImageUUID, disk.Uuid, defaultTestBackingImageSize, defaultTestBackingImageChecksum, defaultTestBackingImageDownloadURL, "") + c.Assert(err, IsNil) + c.Assert(bi, NotNil) + defer func() { + err := spdkCli.BackingImageDelete(defaultTestBackingImageName, disk.Uuid) + c.Assert(err, IsNil) + }() + + // check if bi.State is "ready" in 300 seconds + for i := 0; i < 300; i++ { + bi, err = spdkCli.BackingImageGet(defaultTestBackingImageName, disk.Uuid) + c.Assert(err, IsNil) + if bi.State == string(types.BackingImageStateReady) { + break + } + time.Sleep(5 * time.Second) + } + concurrentCount := 10 dataCountInMB := int64(10) wg := sync.WaitGroup{} @@ -505,13 +530,13 @@ func (s *TestSuite) TestSPDKMultipleThreadSnapshotOpsAndRebuilding(c *C) { wg.Done() }() - replica1, err := spdkCli.ReplicaCreate(replicaName1, defaultTestDiskName, disk.Uuid, defaultTestLvolSize, defaultTestReplicaPortCount) + replica1, err := spdkCli.ReplicaCreate(replicaName1, defaultTestDiskName, disk.Uuid, defaultTestLvolSize, defaultTestReplicaPortCount, bi.Name) c.Assert(err, IsNil) c.Assert(replica1.LvsName, Equals, defaultTestDiskName) c.Assert(replica1.LvsUUID, Equals, disk.Uuid) c.Assert(replica1.State, Equals, types.InstanceStateRunning) c.Assert(replica1.PortStart, Not(Equals), int32(0)) - replica2, err := spdkCli.ReplicaCreate(replicaName2, defaultTestDiskName, disk.Uuid, defaultTestLvolSize, defaultTestReplicaPortCount) + replica2, err := spdkCli.ReplicaCreate(replicaName2, defaultTestDiskName, disk.Uuid, defaultTestLvolSize, defaultTestReplicaPortCount, bi.Name) c.Assert(err, IsNil) c.Assert(replica2.LvsName, Equals, defaultTestDiskName) c.Assert(replica2.LvsUUID, Equals, disk.Uuid) @@ -867,7 +892,7 @@ func (s *TestSuite) TestSPDKMultipleThreadSnapshotOpsAndRebuilding(c *C) { c.Assert(engine.ReplicaAddressMap, DeepEquals, replicaAddressMap) c.Assert(engine.ReplicaModeMap, DeepEquals, map[string]types.Mode{replicaName2: types.ModeRW}) // Launch the 1st rebuilding replica as the replacement of the crashed replica1 - replica3, err := spdkCli.ReplicaCreate(replicaName3, defaultTestDiskName, disk.Uuid, defaultTestLvolSize, defaultTestReplicaPortCount) + replica3, err := spdkCli.ReplicaCreate(replicaName3, defaultTestDiskName, disk.Uuid, defaultTestLvolSize, defaultTestReplicaPortCount, bi.Name) c.Assert(err, IsNil) c.Assert(replica3.LvsName, Equals, defaultTestDiskName) c.Assert(replica3.LvsUUID, Equals, disk.Uuid) @@ -961,7 +986,7 @@ func (s *TestSuite) TestSPDKMultipleThreadSnapshotOpsAndRebuilding(c *C) { c.Assert(engine.ReplicaAddressMap, DeepEquals, replicaAddressMap) c.Assert(engine.ReplicaModeMap, DeepEquals, map[string]types.Mode{replicaName3: types.ModeRW}) // Launch the 2nd rebuilding replica as the replacement of the crashed replica2 - replica4, err := spdkCli.ReplicaCreate(replicaName4, defaultTestDiskName, disk.Uuid, defaultTestLvolSize, defaultTestReplicaPortCount) + replica4, err := spdkCli.ReplicaCreate(replicaName4, defaultTestDiskName, disk.Uuid, defaultTestLvolSize, defaultTestReplicaPortCount, bi.Name) c.Assert(err, IsNil) c.Assert(replica4.LvsName, Equals, defaultTestDiskName) c.Assert(replica4.LvsUUID, Equals, disk.Uuid) @@ -1341,9 +1366,9 @@ func (s *TestSuite) TestSPDKEngineOnlyWithTarget(c *C) { replicaName1 := fmt.Sprintf("%s-replica-1", volumeName) replicaName2 := fmt.Sprintf("%s-replica-2", volumeName) - replica1, err := spdkCli.ReplicaCreate(replicaName1, defaultTestDiskName, disk.Uuid, defaultTestLvolSize, defaultTestReplicaPortCount) + replica1, err := spdkCli.ReplicaCreate(replicaName1, defaultTestDiskName, disk.Uuid, defaultTestLvolSize, defaultTestReplicaPortCount, "") c.Assert(err, IsNil) - replica2, err := spdkCli.ReplicaCreate(replicaName2, defaultTestDiskName, disk.Uuid, defaultTestLvolSize, defaultTestReplicaPortCount) + replica2, err := spdkCli.ReplicaCreate(replicaName2, defaultTestDiskName, disk.Uuid, defaultTestLvolSize, defaultTestReplicaPortCount, "") c.Assert(err, IsNil) replicaAddressMap := map[string]string{ diff --git a/pkg/types/types.go b/pkg/types/types.go index 7b7d7853..1a258a03 100644 --- a/pkg/types/types.go +++ b/pkg/types/types.go @@ -30,8 +30,28 @@ const ( type InstanceType string const ( - InstanceTypeReplica = InstanceType("replica") - InstanceTypeEngine = InstanceType("engine") + InstanceTypeReplica = InstanceType("replica") + InstanceTypeEngine = InstanceType("engine") + InstanceTypeBackingImage = InstanceType("backingImage") +) + +type BackingImageState string + +const ( + BackingImageStatePending = BackingImageState("pending") + BackingImageStateStarting = BackingImageState("starting") + BackingImageStateReady = BackingImageState("ready") + BackingImageStateInProgress = BackingImageState("in-progress") + BackingImageStateFailed = BackingImageState("failed") + BackingImageStateUnknown = BackingImageState("unknown") +) + +const ( + BackingImagePortCount = 1 + + BackingImageSnapshotAttrChecksum = "checksum" + BackingImageSnapshotAttrBackingImageUUID = "backing_image_uuid" + BackingImageSnapshotAttrPrepareState = "backing_image_prepare_state" ) const VolumeHead = "volume-head" diff --git a/pkg/util/http_handler.go b/pkg/util/http_handler.go new file mode 100644 index 00000000..73c55009 --- /dev/null +++ b/pkg/util/http_handler.go @@ -0,0 +1,187 @@ +package util + +import ( + "bytes" + "context" + "fmt" + "io" + "net/http" + "os" + "strconv" + "strings" + "time" +) + +const ( + DownloadBufferSize = 1 << 12 + HTTPTimeout = 4 * time.Second +) + +type ProgressUpdater interface { + UpdateProgress(size int64) +} + +type Handler interface { + GetSizeFromURL(url string) (fileSize int64, err error) + DownloadFromURL(ctx context.Context, url, outFh *os.File, updater ProgressUpdater) (written int64, err error) +} + +type HTTPHandler struct{} + +func (h *HTTPHandler) GetSizeFromURL(url string) (size int64, err error) { + ctx, cancel := context.WithTimeout(context.Background(), HTTPTimeout) + defer cancel() + + rr, err := http.NewRequestWithContext(ctx, http.MethodHead, url, nil) + if err != nil { + return 0, err + } + + client := NewDownloadHttpClient() + resp, err := client.Do(rr) + if err != nil { + return 0, err + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + return 0, fmt.Errorf("expected status code 200 from %s, got %s", url, resp.Status) + } + + contentLength := resp.Header.Get("Content-Length") + if contentLength == "" { + // -1 indicates unknown size + size = -1 + } else { + size, err = strconv.ParseInt(contentLength, 10, 64) + if err != nil { + return 0, err + } + } + + return size, nil +} + +func (h *HTTPHandler) DownloadFromURL(ctx context.Context, url string, outFh *os.File, updater ProgressUpdater) (written int64, err error) { + ctx, cancel := context.WithCancel(ctx) + defer cancel() + + rr, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) + if err != nil { + return 0, err + } + + client := NewDownloadHttpClient() + resp, err := client.Do(rr) + if err != nil { + return 0, err + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + return 0, fmt.Errorf("expected status code 200 from %s, got %s", url, resp.Status) + } + + copied, err := IdleTimeoutCopy(ctx, cancel, resp.Body, outFh, updater, true) + if err != nil { + return 0, err + } + + return copied, nil +} + +// IdleTimeoutCopy relies on ctx of the reader/src or a separate timer to interrupt the processing. +func IdleTimeoutCopy(ctx context.Context, cancel context.CancelFunc, src io.ReadCloser, dst io.WriteSeeker, updater ProgressUpdater, writeZero bool) (copied int64, err error) { + writeSeekCh := make(chan int64, 100) + defer close(writeSeekCh) + + go func() { + t := time.NewTimer(HTTPTimeout) + done := false + for !done { + select { + case <-ctx.Done(): + done = true + case <-t.C: + cancel() + done = true + case _, writeChOpen := <-writeSeekCh: + if !writeChOpen { + done = true + break + } + if !t.Stop() { + <-t.C + } + t.Reset(HTTPTimeout) + } + } + + // Still need to make sure to clean up the signals in writeSeekCh + // so that they won't block the below sender. + for writeChOpen := true; writeChOpen; { + _, writeChOpen = <-writeSeekCh + } + }() + + var nr, nw int + var nws int64 + var rErr, handleErr error + buf := make([]byte, DownloadBufferSize) + zeroByteArray := make([]byte, DownloadBufferSize) + for rErr == nil && err == nil { + select { + case <-ctx.Done(): + err = fmt.Errorf("context cancelled during the copy") + default: + // Read will error out once the context is cancelled. + nr, rErr = src.Read(buf) + if nr > 0 { + // Skip writing zero data + if !writeZero && bytes.Equal(buf[0:nr], zeroByteArray[0:nr]) { + _, handleErr = dst.Seek(int64(nr), io.SeekCurrent) + nws = int64(nr) + } else { + nw, handleErr = dst.Write(buf[0:nr]) + nws = int64(nw) + } + if handleErr != nil { + err = handleErr + break + } + writeSeekCh <- nws + copied += nws + updater.UpdateProgress(nws) + } + if rErr != nil { + if rErr != io.EOF { + err = rErr + } + break + } + } + } + + return copied, err +} + +func removeReferer(req *http.Request) { + for k := range req.Header { + if strings.ToLower(k) == "referer" { + delete(req.Header, k) + } + } +} + +func NewDownloadHttpClient() http.Client { + return http.Client{ + CheckRedirect: func(req *http.Request, via []*http.Request) error { + // Remove the "Referer" header to enable downloads of files + // that are delivered via CDN and therefore may be redirected + // several times. This is the same behaviour of curl or wget + // in their default configuration. + removeReferer(req) + return nil + }, + } +} diff --git a/pkg/util/util.go b/pkg/util/util.go index 8eb3e163..263565c8 100644 --- a/pkg/util/util.go +++ b/pkg/util/util.go @@ -252,3 +252,18 @@ func (t *TaskError) Append(re ReplicaError) { func (t *TaskError) HasError() bool { return len(t.ReplicaErrors) != 0 } + +func GetFileChecksum(filePath string) (string, error) { + f, err := os.Open(filePath) + if err != nil { + return "", err + } + defer f.Close() + + h := sha512.New() + if _, err := io.Copy(h, f); err != nil { + return "", err + } + + return hex.EncodeToString(h.Sum(nil)), nil +} diff --git a/vendor/github.com/bits-and-blooms/bitset/README.md b/vendor/github.com/bits-and-blooms/bitset/README.md index 848234e2..f48d52a1 100644 --- a/vendor/github.com/bits-and-blooms/bitset/README.md +++ b/vendor/github.com/bits-and-blooms/bitset/README.md @@ -127,6 +127,23 @@ E.g., The memory usage of a bitset using `N` bits is at least `N/8` bytes. The number of bits in a bitset is at least as large as one plus the greatest bit index you have accessed. Thus it is possible to run out of memory while using a bitset. If you have lots of bits, you might prefer compressed bitsets, like the [Roaring bitmaps](http://roaringbitmap.org) and its [Go implementation](https://github.com/RoaringBitmap/roaring). +The `roaring` library allows you to go back and forth between compressed Roaring bitmaps and the conventional bitset instances: +```Go + mybitset := roaringbitmap.ToBitSet() + newroaringbitmap := roaring.FromBitSet(mybitset) +``` + + +### Goroutine safety + +In general, it not safe to access +the same BitSet using different goroutines--they are +unsynchronized for performance. Should you want to access +a BitSet from more than one goroutine, you should +provide synchronization. Typically this is done by using channels to pass +the *BitSet around (in Go style; so there is only ever one owner), +or by using `sync.Mutex` to serialize operations on BitSets. + ## Implementation Note Go 1.9 introduced a native `math/bits` library. We provide backward compatibility to Go 1.7, which might be removed. diff --git a/vendor/github.com/bits-and-blooms/bitset/bitset.go b/vendor/github.com/bits-and-blooms/bitset/bitset.go index 8fb9e9fa..5ad1745d 100644 --- a/vendor/github.com/bits-and-blooms/bitset/bitset.go +++ b/vendor/github.com/bits-and-blooms/bitset/bitset.go @@ -68,9 +68,16 @@ var base64Encoding = base64.URLEncoding // Base64StdEncoding Marshal/Unmarshal BitSet with base64.StdEncoding(Default: base64.URLEncoding) func Base64StdEncoding() { base64Encoding = base64.StdEncoding } -// LittleEndian Marshal/Unmarshal Binary as Little Endian(Default: binary.BigEndian) +// LittleEndian sets Marshal/Unmarshal Binary as Little Endian (Default: binary.BigEndian) func LittleEndian() { binaryOrder = binary.LittleEndian } +// BigEndian sets Marshal/Unmarshal Binary as Big Endian (Default: binary.BigEndian) +func BigEndian() { binaryOrder = binary.BigEndian } + +// BinaryOrder returns the current binary order, see also LittleEndian() +// and BigEndian() to change the order. +func BinaryOrder() binary.ByteOrder { return binaryOrder } + // A BitSet is a set of bits. The zero value of a BitSet is an empty set of length 0. type BitSet struct { length uint @@ -94,17 +101,27 @@ func (b *BitSet) SetBitsetFrom(buf []uint64) { b.set = buf } -// From is a constructor used to create a BitSet from an array of integers +// From is a constructor used to create a BitSet from an array of words func From(buf []uint64) *BitSet { return FromWithLength(uint(len(buf))*64, buf) } -// FromWithLength constructs from an array of integers and length. -func FromWithLength(len uint, set []uint64) *BitSet { - return &BitSet{len, set} +// FromWithLength constructs from an array of words and length in bits. +// This function is for advanced users, most users should prefer +// the From function. +// As a user of FromWithLength, you are responsible for ensuring +// that the length is correct: your slice should have length at +// least (length+63)/64 in 64-bit words. +func FromWithLength(length uint, set []uint64) *BitSet { + if len(set) < wordsNeeded(length) { + panic("BitSet.FromWithLength: slice is too short") + } + return &BitSet{length, set} } -// Bytes returns the bitset as array of integers +// Bytes returns the bitset as array of 64-bit words, giving direct access to the internal representation. +// It is not a copy, so changes to the returned slice will affect the bitset. +// It is meant for advanced users. func (b *BitSet) Bytes() []uint64 { return b.set } @@ -118,7 +135,7 @@ func wordsNeeded(i uint) int { } // wordsNeededUnbound calculates the number of words needed for i bits, possibly exceeding the capacity. -// This function is useful if you know that the capacity cannot be exceeded (e.g., you have an existing bitmap). +// This function is useful if you know that the capacity cannot be exceeded (e.g., you have an existing BitSet). func wordsNeededUnbound(i uint) int { return int((i + (wordSize - 1)) >> log2WordSize) } @@ -147,13 +164,30 @@ func New(length uint) (bset *BitSet) { return bset } +// MustNew creates a new BitSet with the given length bits. +// It panics if length exceeds the possible capacity or by a lack of memory. +func MustNew(length uint) (bset *BitSet) { + if length >= Cap() { + panic("You are exceeding the capacity") + } + + return &BitSet{ + length, + make([]uint64, wordsNeeded(length)), // may panic on lack of memory + } +} + // Cap returns the total possible capacity, or number of bits +// that can be stored in the BitSet theoretically. Under 32-bit system, +// it is 4294967295 and under 64-bit system, it is 18446744073709551615. +// Note that this is further limited by the maximum allocation size in Go, +// and your available memory, as any Go data structure. func Cap() uint { return ^uint(0) } // Len returns the number of bits in the BitSet. -// Note the difference to method Count, see example. +// Note that it differ from Count function. func (b *BitSet) Len() uint { return b.length } @@ -184,12 +218,32 @@ func (b *BitSet) Test(i uint) bool { return b.set[i>>log2WordSize]&(1<> log2WordSize) + subWordIndex := wordsIndex(i) + + // The word that the index falls within, shifted so the index is at bit 0 + var firstWord, secondWord uint64 + if firstWordIndex < len(b.set) { + firstWord = b.set[firstWordIndex] >> subWordIndex + } + + // The next word, masked to only include the necessary bits and shifted to cover the + // top of the word + if (firstWordIndex + 1) < len(b.set) { + secondWord = b.set[firstWordIndex+1] << uint64(wordSize-subWordIndex) + } + + return firstWord | secondWord +} + // Set bit i to 1, the capacity of the bitset is automatically // increased accordingly. -// If i>= Cap(), this function will panic. // Warning: using a very large value for 'i' // may lead to a memory shortage and a panic: the caller is responsible // for providing sensible parameters in line with their memory capacity. +// The memory usage is at least slightly over i/8 bytes. func (b *BitSet) Set(i uint) *BitSet { if i >= b.length { // if we need more bits, make 'em b.extendSet(i) @@ -198,7 +252,7 @@ func (b *BitSet) Set(i uint) *BitSet { return b } -// Clear bit i to 0 +// Clear bit i to 0. This never cause a memory allocation. It is always safe. func (b *BitSet) Clear(i uint) *BitSet { if i >= b.length { return b @@ -208,7 +262,6 @@ func (b *BitSet) Clear(i uint) *BitSet { } // SetTo sets bit i to value. -// If i>= Cap(), this function will panic. // Warning: using a very large value for 'i' // may lead to a memory shortage and a panic: the caller is responsible // for providing sensible parameters in line with their memory capacity. @@ -220,7 +273,6 @@ func (b *BitSet) SetTo(i uint, value bool) *BitSet { } // Flip bit at i. -// If i>= Cap(), this function will panic. // Warning: using a very large value for 'i' // may lead to a memory shortage and a panic: the caller is responsible // for providing sensible parameters in line with their memory capacity. @@ -233,7 +285,6 @@ func (b *BitSet) Flip(i uint) *BitSet { } // FlipRange bit in [start, end). -// If end>= Cap(), this function will panic. // Warning: using a very large value for 'end' // may lead to a memory shortage and a panic: the caller is responsible // for providing sensible parameters in line with their memory capacity. @@ -275,6 +326,7 @@ func (b *BitSet) FlipRange(start, end uint) *BitSet { // memory usage until the GC runs. Normally this should not be a problem, but if you // have an extremely large BitSet its important to understand that the old BitSet will // remain in memory until the GC frees it. +// If you are memory constrained, this function may cause a panic. func (b *BitSet) Shrink(lastbitindex uint) *BitSet { length := lastbitindex + 1 idx := wordsNeeded(length) @@ -294,6 +346,11 @@ func (b *BitSet) Shrink(lastbitindex uint) *BitSet { // Compact shrinks BitSet to so that we preserve all set bits, while minimizing // memory usage. Compact calls Shrink. +// A new slice is allocated to store the new bits, so you may see an increase in +// memory usage until the GC runs. Normally this should not be a problem, but if you +// have an extremely large BitSet its important to understand that the old BitSet will +// remain in memory until the GC frees it. +// If you are memory constrained, this function may cause a panic. func (b *BitSet) Compact() *BitSet { idx := len(b.set) - 1 for ; idx >= 0 && b.set[idx] == 0; idx-- { @@ -353,7 +410,8 @@ func (b *BitSet) InsertAt(idx uint) *BitSet { return b } -// String creates a string representation of the Bitmap +// String creates a string representation of the BitSet. It is only intended for +// human-readable output and not for serialization. func (b *BitSet) String() string { // follows code from https://github.com/RoaringBitmap/roaring var buffer bytes.Buffer @@ -541,7 +599,8 @@ func (b *BitSet) NextClear(i uint) (uint, bool) { return 0, false } -// ClearAll clears the entire BitSet +// ClearAll clears the entire BitSet. +// It does not free the memory. func (b *BitSet) ClearAll() *BitSet { if b != nil && b.set != nil { for i := range b.set { @@ -551,6 +610,18 @@ func (b *BitSet) ClearAll() *BitSet { return b } +// SetAll sets the entire BitSet +func (b *BitSet) SetAll() *BitSet { + if b != nil && b.set != nil { + for i := range b.set { + b.set[i] = allBits + } + + b.cleanLastWord() + } + return b +} + // wordCount returns the number of words used in a bit set func (b *BitSet) wordCount() int { return wordsNeededUnbound(b.length) @@ -725,7 +796,7 @@ func (b *BitSet) Intersection(compare *BitSet) (result *BitSet) { return } -// IntersectionCardinality computes the cardinality of the union +// IntersectionCardinality computes the cardinality of the intersection func (b *BitSet) IntersectionCardinality(compare *BitSet) uint { panicIfNull(b) panicIfNull(compare) @@ -944,7 +1015,9 @@ func (b *BitSet) IsStrictSuperSet(other *BitSet) bool { return b.Count() > other.Count() && b.IsSuperSet(other) } -// DumpAsBits dumps a bit set as a string of bits +// DumpAsBits dumps a bit set as a string of bits. Following the usual convention in Go, +// the least significant bits are printed last (index 0 is at the end of the string). +// This is useful for debugging and testing. It is not suitable for serialization. func (b *BitSet) DumpAsBits() string { if b.set == nil { return "." @@ -1007,6 +1080,15 @@ func writeUint64Array(writer io.Writer, data []uint64) error { // WriteTo writes a BitSet to a stream. The format is: // 1. uint64 length // 2. []uint64 set +// The length is the number of bits in the BitSet. +// +// The set is a slice of uint64s containing between length and length + 63 bits. +// It is interpreted as a big-endian array of uint64s by default (see BinaryOrder()) +// meaning that the first 8 bits are stored at byte index 7, the next 8 bits are stored +// at byte index 6... the bits 64 to 71 are stored at byte index 8, etc. +// If you change the binary order, you need to do so for both reading and writing. +// We recommend using the default binary order. +// // Upon success, the number of bytes written is returned. // // Performance: if this function is used to write to a disk or network @@ -1037,6 +1119,7 @@ func (b *BitSet) WriteTo(stream io.Writer) (int64, error) { // The format is: // 1. uint64 length // 2. []uint64 set +// See WriteTo for details. // Upon success, the number of bytes read is returned. // If the current BitSet is not large enough to hold the data, // it is extended. In case of error, the BitSet is either @@ -1088,6 +1171,7 @@ func (b *BitSet) ReadFrom(stream io.Reader) (int64, error) { } // MarshalBinary encodes a BitSet into a binary form and returns the result. +// Please see WriteTo for details. func (b *BitSet) MarshalBinary() ([]byte, error) { var buf bytes.Buffer _, err := b.WriteTo(&buf) @@ -1099,6 +1183,7 @@ func (b *BitSet) MarshalBinary() ([]byte, error) { } // UnmarshalBinary decodes the binary form generated by MarshalBinary. +// Please see WriteTo for details. func (b *BitSet) UnmarshalBinary(data []byte) error { buf := bytes.NewReader(data) _, err := b.ReadFrom(buf) @@ -1135,3 +1220,155 @@ func (b *BitSet) UnmarshalJSON(data []byte) error { _, err = b.ReadFrom(bytes.NewReader(buf)) return err } + +// Rank returns the nunber of set bits up to and including the index +// that are set in the bitset. +// See https://en.wikipedia.org/wiki/Ranking#Ranking_in_statistics +func (b *BitSet) Rank(index uint) uint { + if index >= b.length { + return b.Count() + } + leftover := (index + 1) & 63 + answer := uint(popcntSlice(b.set[:(index+1)>>6])) + if leftover != 0 { + answer += uint(popcount(b.set[(index+1)>>6] << (64 - leftover))) + } + return answer +} + +// Select returns the index of the jth set bit, where j is the argument. +// The caller is responsible to ensure that 0 <= j < Count(): when j is +// out of range, the function returns the length of the bitset (b.length). +// +// Note that this function differs in convention from the Rank function which +// returns 1 when ranking the smallest value. We follow the conventional +// textbook definition of Select and Rank. +func (b *BitSet) Select(index uint) uint { + leftover := index + for idx, word := range b.set { + w := uint(popcount(word)) + if w > leftover { + return uint(idx)*64 + select64(word, leftover) + } + leftover -= w + } + return b.length +} + +// top detects the top bit set +func (b *BitSet) top() (uint, bool) { + panicIfNull(b) + + idx := len(b.set) - 1 + for ; idx >= 0 && b.set[idx] == 0; idx-- { + } + + // no set bits + if idx < 0 { + return 0, false + } + + return uint(idx)*wordSize + len64(b.set[idx]) - 1, true +} + +// ShiftLeft shifts the bitset like << operation would do. +// +// Left shift may require bitset size extension. We try to avoid the +// unnecessary memory operations by detecting the leftmost set bit. +// The function will panic if shift causes excess of capacity. +func (b *BitSet) ShiftLeft(bits uint) { + panicIfNull(b) + + if bits == 0 { + return + } + + top, ok := b.top() + if !ok { + return + } + + // capacity check + if top+bits < bits { + panic("You are exceeding the capacity") + } + + // destination set + dst := b.set + + // not using extendSet() to avoid unneeded data copying + nsize := wordsNeeded(top + bits) + if len(b.set) < nsize { + dst = make([]uint64, nsize) + } + if top+bits >= b.length { + b.length = top + bits + 1 + } + + pad, idx := top%wordSize, top>>log2WordSize + shift, pages := bits%wordSize, bits>>log2WordSize + if bits%wordSize == 0 { // happy case: just add pages + copy(dst[pages:nsize], b.set) + } else { + if pad+shift >= wordSize { + dst[idx+pages+1] = b.set[idx] >> (wordSize - shift) + } + + for i := int(idx); i >= 0; i-- { + if i > 0 { + dst[i+int(pages)] = (b.set[i] << shift) | (b.set[i-1] >> (wordSize - shift)) + } else { + dst[i+int(pages)] = b.set[i] << shift + } + } + } + + // zeroing extra pages + for i := 0; i < int(pages); i++ { + dst[i] = 0 + } + + b.set = dst +} + +// ShiftRight shifts the bitset like >> operation would do. +func (b *BitSet) ShiftRight(bits uint) { + panicIfNull(b) + + if bits == 0 { + return + } + + top, ok := b.top() + if !ok { + return + } + + if bits >= top { + b.set = make([]uint64, wordsNeeded(b.length)) + return + } + + pad, idx := top%wordSize, top>>log2WordSize + shift, pages := bits%wordSize, bits>>log2WordSize + if bits%wordSize == 0 { // happy case: just clear pages + b.set = b.set[pages:] + b.length -= pages * wordSize + } else { + for i := 0; i <= int(idx-pages); i++ { + if i < int(idx-pages) { + b.set[i] = (b.set[i+int(pages)] >> shift) | (b.set[i+int(pages)+1] << (wordSize - shift)) + } else { + b.set[i] = b.set[i+int(pages)] >> shift + } + } + + if pad < shift { + b.set[int(idx-pages)] = 0 + } + } + + for i := int(idx-pages) + 1; i <= int(idx); i++ { + b.set[i] = 0 + } +} diff --git a/vendor/github.com/bits-and-blooms/bitset/leading_zeros_18.go b/vendor/github.com/bits-and-blooms/bitset/leading_zeros_18.go new file mode 100644 index 00000000..72af1d6f --- /dev/null +++ b/vendor/github.com/bits-and-blooms/bitset/leading_zeros_18.go @@ -0,0 +1,43 @@ +//go:build !go1.9 +// +build !go1.9 + +package bitset + +var len8tab = "" + + "\x00\x01\x02\x02\x03\x03\x03\x03\x04\x04\x04\x04\x04\x04\x04\x04" + + "\x05\x05\x05\x05\x05\x05\x05\x05\x05\x05\x05\x05\x05\x05\x05\x05" + + "\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06" + + "\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06" + + "\x07\x07\x07\x07\x07\x07\x07\x07\x07\x07\x07\x07\x07\x07\x07\x07" + + "\x07\x07\x07\x07\x07\x07\x07\x07\x07\x07\x07\x07\x07\x07\x07\x07" + + "\x07\x07\x07\x07\x07\x07\x07\x07\x07\x07\x07\x07\x07\x07\x07\x07" + + "\x07\x07\x07\x07\x07\x07\x07\x07\x07\x07\x07\x07\x07\x07\x07\x07" + + "\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08" + + "\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08" + + "\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08" + + "\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08" + + "\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08" + + "\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08" + + "\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08" + + "\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08" + +// len64 returns the minimum number of bits required to represent x; the result is 0 for x == 0. +func len64(x uint64) (n uint) { + if x >= 1<<32 { + x >>= 32 + n = 32 + } + if x >= 1<<16 { + x >>= 16 + n += 16 + } + if x >= 1<<8 { + x >>= 8 + n += 8 + } + return n + uint(len8tab[x]) +} + +func leadingZeroes64(v uint64) uint { + return 64 - len64(v) +} diff --git a/vendor/github.com/bits-and-blooms/bitset/leading_zeros_19.go b/vendor/github.com/bits-and-blooms/bitset/leading_zeros_19.go new file mode 100644 index 00000000..74a79424 --- /dev/null +++ b/vendor/github.com/bits-and-blooms/bitset/leading_zeros_19.go @@ -0,0 +1,14 @@ +//go:build go1.9 +// +build go1.9 + +package bitset + +import "math/bits" + +func len64(v uint64) uint { + return uint(bits.Len64(v)) +} + +func leadingZeroes64(v uint64) uint { + return uint(bits.LeadingZeros64(v)) +} diff --git a/vendor/github.com/bits-and-blooms/bitset/select.go b/vendor/github.com/bits-and-blooms/bitset/select.go new file mode 100644 index 00000000..f15e74a2 --- /dev/null +++ b/vendor/github.com/bits-and-blooms/bitset/select.go @@ -0,0 +1,45 @@ +package bitset + +func select64(w uint64, j uint) uint { + seen := 0 + // Divide 64bit + part := w & 0xFFFFFFFF + n := uint(popcount(part)) + if n <= j { + part = w >> 32 + seen += 32 + j -= n + } + ww := part + + // Divide 32bit + part = ww & 0xFFFF + + n = uint(popcount(part)) + if n <= j { + part = ww >> 16 + seen += 16 + j -= n + } + ww = part + + // Divide 16bit + part = ww & 0xFF + n = uint(popcount(part)) + if n <= j { + part = ww >> 8 + seen += 8 + j -= n + } + ww = part + + // Lookup in final byte + counter := 0 + for ; counter < 8; counter++ { + j -= uint((ww >> counter) & 1) + if j+1 == 0 { + break + } + } + return uint(seen + counter) +} diff --git a/vendor/github.com/coreos/go-systemd/v22/LICENSE b/vendor/github.com/coreos/go-systemd/v22/LICENSE deleted file mode 100644 index 37ec93a1..00000000 --- a/vendor/github.com/coreos/go-systemd/v22/LICENSE +++ /dev/null @@ -1,191 +0,0 @@ -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: - -You must give any other recipients of the Work or Derivative Works a copy of -this License; and -You must cause any modified files to carry prominent notices stating that You -changed the files; and -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 -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 [yyyy] [name of copyright owner] - - 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/vendor/github.com/coreos/go-systemd/v22/NOTICE b/vendor/github.com/coreos/go-systemd/v22/NOTICE deleted file mode 100644 index 23a0ada2..00000000 --- a/vendor/github.com/coreos/go-systemd/v22/NOTICE +++ /dev/null @@ -1,5 +0,0 @@ -CoreOS Project -Copyright 2018 CoreOS, Inc - -This product includes software developed at CoreOS, Inc. -(http://www.coreos.com/). diff --git a/vendor/github.com/coreos/go-systemd/v22/dbus/dbus.go b/vendor/github.com/coreos/go-systemd/v22/dbus/dbus.go deleted file mode 100644 index 147f756f..00000000 --- a/vendor/github.com/coreos/go-systemd/v22/dbus/dbus.go +++ /dev/null @@ -1,266 +0,0 @@ -// Copyright 2015 CoreOS, 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. - -// Integration with the systemd D-Bus API. See http://www.freedesktop.org/wiki/Software/systemd/dbus/ -package dbus - -import ( - "context" - "encoding/hex" - "fmt" - "os" - "strconv" - "strings" - "sync" - - "github.com/godbus/dbus/v5" -) - -const ( - alpha = `abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ` - num = `0123456789` - alphanum = alpha + num - signalBuffer = 100 -) - -// needsEscape checks whether a byte in a potential dbus ObjectPath needs to be escaped -func needsEscape(i int, b byte) bool { - // Escape everything that is not a-z-A-Z-0-9 - // Also escape 0-9 if it's the first character - return strings.IndexByte(alphanum, b) == -1 || - (i == 0 && strings.IndexByte(num, b) != -1) -} - -// PathBusEscape sanitizes a constituent string of a dbus ObjectPath using the -// rules that systemd uses for serializing special characters. -func PathBusEscape(path string) string { - // Special case the empty string - if len(path) == 0 { - return "_" - } - n := []byte{} - for i := 0; i < len(path); i++ { - c := path[i] - if needsEscape(i, c) { - e := fmt.Sprintf("_%x", c) - n = append(n, []byte(e)...) - } else { - n = append(n, c) - } - } - return string(n) -} - -// pathBusUnescape is the inverse of PathBusEscape. -func pathBusUnescape(path string) string { - if path == "_" { - return "" - } - n := []byte{} - for i := 0; i < len(path); i++ { - c := path[i] - if c == '_' && i+2 < len(path) { - res, err := hex.DecodeString(path[i+1 : i+3]) - if err == nil { - n = append(n, res...) - } - i += 2 - } else { - n = append(n, c) - } - } - return string(n) -} - -// Conn is a connection to systemd's dbus endpoint. -type Conn struct { - // sysconn/sysobj are only used to call dbus methods - sysconn *dbus.Conn - sysobj dbus.BusObject - - // sigconn/sigobj are only used to receive dbus signals - sigconn *dbus.Conn - sigobj dbus.BusObject - - jobListener struct { - jobs map[dbus.ObjectPath]chan<- string - sync.Mutex - } - subStateSubscriber struct { - updateCh chan<- *SubStateUpdate - errCh chan<- error - sync.Mutex - ignore map[dbus.ObjectPath]int64 - cleanIgnore int64 - } - propertiesSubscriber struct { - updateCh chan<- *PropertiesUpdate - errCh chan<- error - sync.Mutex - } -} - -// Deprecated: use NewWithContext instead. -func New() (*Conn, error) { - return NewWithContext(context.Background()) -} - -// NewWithContext establishes a connection to any available bus and authenticates. -// Callers should call Close() when done with the connection. -func NewWithContext(ctx context.Context) (*Conn, error) { - conn, err := NewSystemConnectionContext(ctx) - if err != nil && os.Geteuid() == 0 { - return NewSystemdConnectionContext(ctx) - } - return conn, err -} - -// Deprecated: use NewSystemConnectionContext instead. -func NewSystemConnection() (*Conn, error) { - return NewSystemConnectionContext(context.Background()) -} - -// NewSystemConnectionContext establishes a connection to the system bus and authenticates. -// Callers should call Close() when done with the connection. -func NewSystemConnectionContext(ctx context.Context) (*Conn, error) { - return NewConnection(func() (*dbus.Conn, error) { - return dbusAuthHelloConnection(ctx, dbus.SystemBusPrivate) - }) -} - -// Deprecated: use NewUserConnectionContext instead. -func NewUserConnection() (*Conn, error) { - return NewUserConnectionContext(context.Background()) -} - -// NewUserConnectionContext establishes a connection to the session bus and -// authenticates. This can be used to connect to systemd user instances. -// Callers should call Close() when done with the connection. -func NewUserConnectionContext(ctx context.Context) (*Conn, error) { - return NewConnection(func() (*dbus.Conn, error) { - return dbusAuthHelloConnection(ctx, dbus.SessionBusPrivate) - }) -} - -// Deprecated: use NewSystemdConnectionContext instead. -func NewSystemdConnection() (*Conn, error) { - return NewSystemdConnectionContext(context.Background()) -} - -// NewSystemdConnectionContext establishes a private, direct connection to systemd. -// This can be used for communicating with systemd without a dbus daemon. -// Callers should call Close() when done with the connection. -func NewSystemdConnectionContext(ctx context.Context) (*Conn, error) { - return NewConnection(func() (*dbus.Conn, error) { - // We skip Hello when talking directly to systemd. - return dbusAuthConnection(ctx, func(opts ...dbus.ConnOption) (*dbus.Conn, error) { - return dbus.Dial("unix:path=/run/systemd/private", opts...) - }) - }) -} - -// Close closes an established connection. -func (c *Conn) Close() { - c.sysconn.Close() - c.sigconn.Close() -} - -// Connected returns whether conn is connected -func (c *Conn) Connected() bool { - return c.sysconn.Connected() && c.sigconn.Connected() -} - -// NewConnection establishes a connection to a bus using a caller-supplied function. -// This allows connecting to remote buses through a user-supplied mechanism. -// The supplied function may be called multiple times, and should return independent connections. -// The returned connection must be fully initialised: the org.freedesktop.DBus.Hello call must have succeeded, -// and any authentication should be handled by the function. -func NewConnection(dialBus func() (*dbus.Conn, error)) (*Conn, error) { - sysconn, err := dialBus() - if err != nil { - return nil, err - } - - sigconn, err := dialBus() - if err != nil { - sysconn.Close() - return nil, err - } - - c := &Conn{ - sysconn: sysconn, - sysobj: systemdObject(sysconn), - sigconn: sigconn, - sigobj: systemdObject(sigconn), - } - - c.subStateSubscriber.ignore = make(map[dbus.ObjectPath]int64) - c.jobListener.jobs = make(map[dbus.ObjectPath]chan<- string) - - // Setup the listeners on jobs so that we can get completions - c.sigconn.BusObject().Call("org.freedesktop.DBus.AddMatch", 0, - "type='signal', interface='org.freedesktop.systemd1.Manager', member='JobRemoved'") - - c.dispatch() - return c, nil -} - -// GetManagerProperty returns the value of a property on the org.freedesktop.systemd1.Manager -// interface. The value is returned in its string representation, as defined at -// https://developer.gnome.org/glib/unstable/gvariant-text.html. -func (c *Conn) GetManagerProperty(prop string) (string, error) { - variant, err := c.sysobj.GetProperty("org.freedesktop.systemd1.Manager." + prop) - if err != nil { - return "", err - } - return variant.String(), nil -} - -func dbusAuthConnection(ctx context.Context, createBus func(opts ...dbus.ConnOption) (*dbus.Conn, error)) (*dbus.Conn, error) { - conn, err := createBus(dbus.WithContext(ctx)) - if err != nil { - return nil, err - } - - // Only use EXTERNAL method, and hardcode the uid (not username) - // to avoid a username lookup (which requires a dynamically linked - // libc) - methods := []dbus.Auth{dbus.AuthExternal(strconv.Itoa(os.Getuid()))} - - err = conn.Auth(methods) - if err != nil { - conn.Close() - return nil, err - } - - return conn, nil -} - -func dbusAuthHelloConnection(ctx context.Context, createBus func(opts ...dbus.ConnOption) (*dbus.Conn, error)) (*dbus.Conn, error) { - conn, err := dbusAuthConnection(ctx, createBus) - if err != nil { - return nil, err - } - - if err = conn.Hello(); err != nil { - conn.Close() - return nil, err - } - - return conn, nil -} - -func systemdObject(conn *dbus.Conn) dbus.BusObject { - return conn.Object("org.freedesktop.systemd1", dbus.ObjectPath("/org/freedesktop/systemd1")) -} diff --git a/vendor/github.com/coreos/go-systemd/v22/dbus/methods.go b/vendor/github.com/coreos/go-systemd/v22/dbus/methods.go deleted file mode 100644 index 074148cb..00000000 --- a/vendor/github.com/coreos/go-systemd/v22/dbus/methods.go +++ /dev/null @@ -1,864 +0,0 @@ -// Copyright 2015, 2018 CoreOS, 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. - -package dbus - -import ( - "context" - "errors" - "fmt" - "path" - "strconv" - - "github.com/godbus/dbus/v5" -) - -// Who can be used to specify which process to kill in the unit via the KillUnitWithTarget API -type Who string - -const ( - // All sends the signal to all processes in the unit - All Who = "all" - // Main sends the signal to the main process of the unit - Main Who = "main" - // Control sends the signal to the control process of the unit - Control Who = "control" -) - -func (c *Conn) jobComplete(signal *dbus.Signal) { - var id uint32 - var job dbus.ObjectPath - var unit string - var result string - dbus.Store(signal.Body, &id, &job, &unit, &result) - c.jobListener.Lock() - out, ok := c.jobListener.jobs[job] - if ok { - out <- result - delete(c.jobListener.jobs, job) - } - c.jobListener.Unlock() -} - -func (c *Conn) startJob(ctx context.Context, ch chan<- string, job string, args ...interface{}) (int, error) { - if ch != nil { - c.jobListener.Lock() - defer c.jobListener.Unlock() - } - - var p dbus.ObjectPath - err := c.sysobj.CallWithContext(ctx, job, 0, args...).Store(&p) - if err != nil { - return 0, err - } - - if ch != nil { - c.jobListener.jobs[p] = ch - } - - // ignore error since 0 is fine if conversion fails - jobID, _ := strconv.Atoi(path.Base(string(p))) - - return jobID, nil -} - -// Deprecated: use StartUnitContext instead. -func (c *Conn) StartUnit(name string, mode string, ch chan<- string) (int, error) { - return c.StartUnitContext(context.Background(), name, mode, ch) -} - -// StartUnitContext enqueues a start job and depending jobs, if any (unless otherwise -// specified by the mode string). -// -// Takes the unit to activate, plus a mode string. The mode needs to be one of -// replace, fail, isolate, ignore-dependencies, ignore-requirements. If -// "replace" the call will start the unit and its dependencies, possibly -// replacing already queued jobs that conflict with this. If "fail" the call -// will start the unit and its dependencies, but will fail if this would change -// an already queued job. If "isolate" the call will start the unit in question -// and terminate all units that aren't dependencies of it. If -// "ignore-dependencies" it will start a unit but ignore all its dependencies. -// If "ignore-requirements" it will start a unit but only ignore the -// requirement dependencies. It is not recommended to make use of the latter -// two options. -// -// If the provided channel is non-nil, a result string will be sent to it upon -// job completion: one of done, canceled, timeout, failed, dependency, skipped. -// done indicates successful execution of a job. canceled indicates that a job -// has been canceled before it finished execution. timeout indicates that the -// job timeout was reached. failed indicates that the job failed. dependency -// indicates that a job this job has been depending on failed and the job hence -// has been removed too. skipped indicates that a job was skipped because it -// didn't apply to the units current state. -// -// If no error occurs, the ID of the underlying systemd job will be returned. There -// does exist the possibility for no error to be returned, but for the returned job -// ID to be 0. In this case, the actual underlying ID is not 0 and this datapoint -// should not be considered authoritative. -// -// If an error does occur, it will be returned to the user alongside a job ID of 0. -func (c *Conn) StartUnitContext(ctx context.Context, name string, mode string, ch chan<- string) (int, error) { - return c.startJob(ctx, ch, "org.freedesktop.systemd1.Manager.StartUnit", name, mode) -} - -// Deprecated: use StopUnitContext instead. -func (c *Conn) StopUnit(name string, mode string, ch chan<- string) (int, error) { - return c.StopUnitContext(context.Background(), name, mode, ch) -} - -// StopUnitContext is similar to StartUnitContext, but stops the specified unit -// rather than starting it. -func (c *Conn) StopUnitContext(ctx context.Context, name string, mode string, ch chan<- string) (int, error) { - return c.startJob(ctx, ch, "org.freedesktop.systemd1.Manager.StopUnit", name, mode) -} - -// Deprecated: use ReloadUnitContext instead. -func (c *Conn) ReloadUnit(name string, mode string, ch chan<- string) (int, error) { - return c.ReloadUnitContext(context.Background(), name, mode, ch) -} - -// ReloadUnitContext reloads a unit. Reloading is done only if the unit -// is already running, and fails otherwise. -func (c *Conn) ReloadUnitContext(ctx context.Context, name string, mode string, ch chan<- string) (int, error) { - return c.startJob(ctx, ch, "org.freedesktop.systemd1.Manager.ReloadUnit", name, mode) -} - -// Deprecated: use RestartUnitContext instead. -func (c *Conn) RestartUnit(name string, mode string, ch chan<- string) (int, error) { - return c.RestartUnitContext(context.Background(), name, mode, ch) -} - -// RestartUnitContext restarts a service. If a service is restarted that isn't -// running it will be started. -func (c *Conn) RestartUnitContext(ctx context.Context, name string, mode string, ch chan<- string) (int, error) { - return c.startJob(ctx, ch, "org.freedesktop.systemd1.Manager.RestartUnit", name, mode) -} - -// Deprecated: use TryRestartUnitContext instead. -func (c *Conn) TryRestartUnit(name string, mode string, ch chan<- string) (int, error) { - return c.TryRestartUnitContext(context.Background(), name, mode, ch) -} - -// TryRestartUnitContext is like RestartUnitContext, except that a service that -// isn't running is not affected by the restart. -func (c *Conn) TryRestartUnitContext(ctx context.Context, name string, mode string, ch chan<- string) (int, error) { - return c.startJob(ctx, ch, "org.freedesktop.systemd1.Manager.TryRestartUnit", name, mode) -} - -// Deprecated: use ReloadOrRestartUnitContext instead. -func (c *Conn) ReloadOrRestartUnit(name string, mode string, ch chan<- string) (int, error) { - return c.ReloadOrRestartUnitContext(context.Background(), name, mode, ch) -} - -// ReloadOrRestartUnitContext attempts a reload if the unit supports it and use -// a restart otherwise. -func (c *Conn) ReloadOrRestartUnitContext(ctx context.Context, name string, mode string, ch chan<- string) (int, error) { - return c.startJob(ctx, ch, "org.freedesktop.systemd1.Manager.ReloadOrRestartUnit", name, mode) -} - -// Deprecated: use ReloadOrTryRestartUnitContext instead. -func (c *Conn) ReloadOrTryRestartUnit(name string, mode string, ch chan<- string) (int, error) { - return c.ReloadOrTryRestartUnitContext(context.Background(), name, mode, ch) -} - -// ReloadOrTryRestartUnitContext attempts a reload if the unit supports it, -// and use a "Try" flavored restart otherwise. -func (c *Conn) ReloadOrTryRestartUnitContext(ctx context.Context, name string, mode string, ch chan<- string) (int, error) { - return c.startJob(ctx, ch, "org.freedesktop.systemd1.Manager.ReloadOrTryRestartUnit", name, mode) -} - -// Deprecated: use StartTransientUnitContext instead. -func (c *Conn) StartTransientUnit(name string, mode string, properties []Property, ch chan<- string) (int, error) { - return c.StartTransientUnitContext(context.Background(), name, mode, properties, ch) -} - -// StartTransientUnitContext may be used to create and start a transient unit, which -// will be released as soon as it is not running or referenced anymore or the -// system is rebooted. name is the unit name including suffix, and must be -// unique. mode is the same as in StartUnitContext, properties contains properties -// of the unit. -func (c *Conn) StartTransientUnitContext(ctx context.Context, name string, mode string, properties []Property, ch chan<- string) (int, error) { - return c.startJob(ctx, ch, "org.freedesktop.systemd1.Manager.StartTransientUnit", name, mode, properties, make([]PropertyCollection, 0)) -} - -// Deprecated: use KillUnitContext instead. -func (c *Conn) KillUnit(name string, signal int32) { - c.KillUnitContext(context.Background(), name, signal) -} - -// KillUnitContext takes the unit name and a UNIX signal number to send. -// All of the unit's processes are killed. -func (c *Conn) KillUnitContext(ctx context.Context, name string, signal int32) { - c.KillUnitWithTarget(ctx, name, All, signal) -} - -// KillUnitWithTarget is like KillUnitContext, but allows you to specify which -// process in the unit to send the signal to. -func (c *Conn) KillUnitWithTarget(ctx context.Context, name string, target Who, signal int32) error { - return c.sysobj.CallWithContext(ctx, "org.freedesktop.systemd1.Manager.KillUnit", 0, name, string(target), signal).Store() -} - -// Deprecated: use ResetFailedUnitContext instead. -func (c *Conn) ResetFailedUnit(name string) error { - return c.ResetFailedUnitContext(context.Background(), name) -} - -// ResetFailedUnitContext resets the "failed" state of a specific unit. -func (c *Conn) ResetFailedUnitContext(ctx context.Context, name string) error { - return c.sysobj.CallWithContext(ctx, "org.freedesktop.systemd1.Manager.ResetFailedUnit", 0, name).Store() -} - -// Deprecated: use SystemStateContext instead. -func (c *Conn) SystemState() (*Property, error) { - return c.SystemStateContext(context.Background()) -} - -// SystemStateContext returns the systemd state. Equivalent to -// systemctl is-system-running. -func (c *Conn) SystemStateContext(ctx context.Context) (*Property, error) { - var err error - var prop dbus.Variant - - obj := c.sysconn.Object("org.freedesktop.systemd1", "/org/freedesktop/systemd1") - err = obj.CallWithContext(ctx, "org.freedesktop.DBus.Properties.Get", 0, "org.freedesktop.systemd1.Manager", "SystemState").Store(&prop) - if err != nil { - return nil, err - } - - return &Property{Name: "SystemState", Value: prop}, nil -} - -// getProperties takes the unit path and returns all of its dbus object properties, for the given dbus interface. -func (c *Conn) getProperties(ctx context.Context, path dbus.ObjectPath, dbusInterface string) (map[string]interface{}, error) { - var err error - var props map[string]dbus.Variant - - if !path.IsValid() { - return nil, fmt.Errorf("invalid unit name: %v", path) - } - - obj := c.sysconn.Object("org.freedesktop.systemd1", path) - err = obj.CallWithContext(ctx, "org.freedesktop.DBus.Properties.GetAll", 0, dbusInterface).Store(&props) - if err != nil { - return nil, err - } - - out := make(map[string]interface{}, len(props)) - for k, v := range props { - out[k] = v.Value() - } - - return out, nil -} - -// Deprecated: use GetUnitPropertiesContext instead. -func (c *Conn) GetUnitProperties(unit string) (map[string]interface{}, error) { - return c.GetUnitPropertiesContext(context.Background(), unit) -} - -// GetUnitPropertiesContext takes the (unescaped) unit name and returns all of -// its dbus object properties. -func (c *Conn) GetUnitPropertiesContext(ctx context.Context, unit string) (map[string]interface{}, error) { - path := unitPath(unit) - return c.getProperties(ctx, path, "org.freedesktop.systemd1.Unit") -} - -// Deprecated: use GetUnitPathPropertiesContext instead. -func (c *Conn) GetUnitPathProperties(path dbus.ObjectPath) (map[string]interface{}, error) { - return c.GetUnitPathPropertiesContext(context.Background(), path) -} - -// GetUnitPathPropertiesContext takes the (escaped) unit path and returns all -// of its dbus object properties. -func (c *Conn) GetUnitPathPropertiesContext(ctx context.Context, path dbus.ObjectPath) (map[string]interface{}, error) { - return c.getProperties(ctx, path, "org.freedesktop.systemd1.Unit") -} - -// Deprecated: use GetAllPropertiesContext instead. -func (c *Conn) GetAllProperties(unit string) (map[string]interface{}, error) { - return c.GetAllPropertiesContext(context.Background(), unit) -} - -// GetAllPropertiesContext takes the (unescaped) unit name and returns all of -// its dbus object properties. -func (c *Conn) GetAllPropertiesContext(ctx context.Context, unit string) (map[string]interface{}, error) { - path := unitPath(unit) - return c.getProperties(ctx, path, "") -} - -func (c *Conn) getProperty(ctx context.Context, unit string, dbusInterface string, propertyName string) (*Property, error) { - var err error - var prop dbus.Variant - - path := unitPath(unit) - if !path.IsValid() { - return nil, errors.New("invalid unit name: " + unit) - } - - obj := c.sysconn.Object("org.freedesktop.systemd1", path) - err = obj.CallWithContext(ctx, "org.freedesktop.DBus.Properties.Get", 0, dbusInterface, propertyName).Store(&prop) - if err != nil { - return nil, err - } - - return &Property{Name: propertyName, Value: prop}, nil -} - -// Deprecated: use GetUnitPropertyContext instead. -func (c *Conn) GetUnitProperty(unit string, propertyName string) (*Property, error) { - return c.GetUnitPropertyContext(context.Background(), unit, propertyName) -} - -// GetUnitPropertyContext takes an (unescaped) unit name, and a property name, -// and returns the property value. -func (c *Conn) GetUnitPropertyContext(ctx context.Context, unit string, propertyName string) (*Property, error) { - return c.getProperty(ctx, unit, "org.freedesktop.systemd1.Unit", propertyName) -} - -// Deprecated: use GetServicePropertyContext instead. -func (c *Conn) GetServiceProperty(service string, propertyName string) (*Property, error) { - return c.GetServicePropertyContext(context.Background(), service, propertyName) -} - -// GetServiceProperty returns property for given service name and property name. -func (c *Conn) GetServicePropertyContext(ctx context.Context, service string, propertyName string) (*Property, error) { - return c.getProperty(ctx, service, "org.freedesktop.systemd1.Service", propertyName) -} - -// Deprecated: use GetUnitTypePropertiesContext instead. -func (c *Conn) GetUnitTypeProperties(unit string, unitType string) (map[string]interface{}, error) { - return c.GetUnitTypePropertiesContext(context.Background(), unit, unitType) -} - -// GetUnitTypePropertiesContext returns the extra properties for a unit, specific to the unit type. -// Valid values for unitType: Service, Socket, Target, Device, Mount, Automount, Snapshot, Timer, Swap, Path, Slice, Scope. -// Returns "dbus.Error: Unknown interface" error if the unitType is not the correct type of the unit. -func (c *Conn) GetUnitTypePropertiesContext(ctx context.Context, unit string, unitType string) (map[string]interface{}, error) { - path := unitPath(unit) - return c.getProperties(ctx, path, "org.freedesktop.systemd1."+unitType) -} - -// Deprecated: use SetUnitPropertiesContext instead. -func (c *Conn) SetUnitProperties(name string, runtime bool, properties ...Property) error { - return c.SetUnitPropertiesContext(context.Background(), name, runtime, properties...) -} - -// SetUnitPropertiesContext may be used to modify certain unit properties at runtime. -// Not all properties may be changed at runtime, but many resource management -// settings (primarily those in systemd.cgroup(5)) may. The changes are applied -// instantly, and stored on disk for future boots, unless runtime is true, in which -// case the settings only apply until the next reboot. name is the name of the unit -// to modify. properties are the settings to set, encoded as an array of property -// name and value pairs. -func (c *Conn) SetUnitPropertiesContext(ctx context.Context, name string, runtime bool, properties ...Property) error { - return c.sysobj.CallWithContext(ctx, "org.freedesktop.systemd1.Manager.SetUnitProperties", 0, name, runtime, properties).Store() -} - -// Deprecated: use GetUnitTypePropertyContext instead. -func (c *Conn) GetUnitTypeProperty(unit string, unitType string, propertyName string) (*Property, error) { - return c.GetUnitTypePropertyContext(context.Background(), unit, unitType, propertyName) -} - -// GetUnitTypePropertyContext takes a property name, a unit name, and a unit type, -// and returns a property value. For valid values of unitType, see GetUnitTypePropertiesContext. -func (c *Conn) GetUnitTypePropertyContext(ctx context.Context, unit string, unitType string, propertyName string) (*Property, error) { - return c.getProperty(ctx, unit, "org.freedesktop.systemd1."+unitType, propertyName) -} - -type UnitStatus struct { - Name string // The primary unit name as string - Description string // The human readable description string - LoadState string // The load state (i.e. whether the unit file has been loaded successfully) - ActiveState string // The active state (i.e. whether the unit is currently started or not) - SubState string // The sub state (a more fine-grained version of the active state that is specific to the unit type, which the active state is not) - Followed string // A unit that is being followed in its state by this unit, if there is any, otherwise the empty string. - Path dbus.ObjectPath // The unit object path - JobId uint32 // If there is a job queued for the job unit the numeric job id, 0 otherwise - JobType string // The job type as string - JobPath dbus.ObjectPath // The job object path -} - -type storeFunc func(retvalues ...interface{}) error - -func (c *Conn) listUnitsInternal(f storeFunc) ([]UnitStatus, error) { - result := make([][]interface{}, 0) - err := f(&result) - if err != nil { - return nil, err - } - - resultInterface := make([]interface{}, len(result)) - for i := range result { - resultInterface[i] = result[i] - } - - status := make([]UnitStatus, len(result)) - statusInterface := make([]interface{}, len(status)) - for i := range status { - statusInterface[i] = &status[i] - } - - err = dbus.Store(resultInterface, statusInterface...) - if err != nil { - return nil, err - } - - return status, nil -} - -// GetUnitByPID returns the unit object path of the unit a process ID -// belongs to. It takes a UNIX PID and returns the object path. The PID must -// refer to an existing system process -func (c *Conn) GetUnitByPID(ctx context.Context, pid uint32) (dbus.ObjectPath, error) { - var result dbus.ObjectPath - - err := c.sysobj.CallWithContext(ctx, "org.freedesktop.systemd1.Manager.GetUnitByPID", 0, pid).Store(&result) - - return result, err -} - -// GetUnitNameByPID returns the name of the unit a process ID belongs to. It -// takes a UNIX PID and returns the object path. The PID must refer to an -// existing system process -func (c *Conn) GetUnitNameByPID(ctx context.Context, pid uint32) (string, error) { - path, err := c.GetUnitByPID(ctx, pid) - if err != nil { - return "", err - } - - return unitName(path), nil -} - -// Deprecated: use ListUnitsContext instead. -func (c *Conn) ListUnits() ([]UnitStatus, error) { - return c.ListUnitsContext(context.Background()) -} - -// ListUnitsContext returns an array with all currently loaded units. Note that -// units may be known by multiple names at the same time, and hence there might -// be more unit names loaded than actual units behind them. -// Also note that a unit is only loaded if it is active and/or enabled. -// Units that are both disabled and inactive will thus not be returned. -func (c *Conn) ListUnitsContext(ctx context.Context) ([]UnitStatus, error) { - return c.listUnitsInternal(c.sysobj.CallWithContext(ctx, "org.freedesktop.systemd1.Manager.ListUnits", 0).Store) -} - -// Deprecated: use ListUnitsFilteredContext instead. -func (c *Conn) ListUnitsFiltered(states []string) ([]UnitStatus, error) { - return c.ListUnitsFilteredContext(context.Background(), states) -} - -// ListUnitsFilteredContext returns an array with units filtered by state. -// It takes a list of units' statuses to filter. -func (c *Conn) ListUnitsFilteredContext(ctx context.Context, states []string) ([]UnitStatus, error) { - return c.listUnitsInternal(c.sysobj.CallWithContext(ctx, "org.freedesktop.systemd1.Manager.ListUnitsFiltered", 0, states).Store) -} - -// Deprecated: use ListUnitsByPatternsContext instead. -func (c *Conn) ListUnitsByPatterns(states []string, patterns []string) ([]UnitStatus, error) { - return c.ListUnitsByPatternsContext(context.Background(), states, patterns) -} - -// ListUnitsByPatternsContext returns an array with units. -// It takes a list of units' statuses and names to filter. -// Note that units may be known by multiple names at the same time, -// and hence there might be more unit names loaded than actual units behind them. -func (c *Conn) ListUnitsByPatternsContext(ctx context.Context, states []string, patterns []string) ([]UnitStatus, error) { - return c.listUnitsInternal(c.sysobj.CallWithContext(ctx, "org.freedesktop.systemd1.Manager.ListUnitsByPatterns", 0, states, patterns).Store) -} - -// Deprecated: use ListUnitsByNamesContext instead. -func (c *Conn) ListUnitsByNames(units []string) ([]UnitStatus, error) { - return c.ListUnitsByNamesContext(context.Background(), units) -} - -// ListUnitsByNamesContext returns an array with units. It takes a list of units' -// names and returns an UnitStatus array. Comparing to ListUnitsByPatternsContext -// method, this method returns statuses even for inactive or non-existing -// units. Input array should contain exact unit names, but not patterns. -// -// Requires systemd v230 or higher. -func (c *Conn) ListUnitsByNamesContext(ctx context.Context, units []string) ([]UnitStatus, error) { - return c.listUnitsInternal(c.sysobj.CallWithContext(ctx, "org.freedesktop.systemd1.Manager.ListUnitsByNames", 0, units).Store) -} - -type UnitFile struct { - Path string - Type string -} - -func (c *Conn) listUnitFilesInternal(f storeFunc) ([]UnitFile, error) { - result := make([][]interface{}, 0) - err := f(&result) - if err != nil { - return nil, err - } - - resultInterface := make([]interface{}, len(result)) - for i := range result { - resultInterface[i] = result[i] - } - - files := make([]UnitFile, len(result)) - fileInterface := make([]interface{}, len(files)) - for i := range files { - fileInterface[i] = &files[i] - } - - err = dbus.Store(resultInterface, fileInterface...) - if err != nil { - return nil, err - } - - return files, nil -} - -// Deprecated: use ListUnitFilesContext instead. -func (c *Conn) ListUnitFiles() ([]UnitFile, error) { - return c.ListUnitFilesContext(context.Background()) -} - -// ListUnitFiles returns an array of all available units on disk. -func (c *Conn) ListUnitFilesContext(ctx context.Context) ([]UnitFile, error) { - return c.listUnitFilesInternal(c.sysobj.CallWithContext(ctx, "org.freedesktop.systemd1.Manager.ListUnitFiles", 0).Store) -} - -// Deprecated: use ListUnitFilesByPatternsContext instead. -func (c *Conn) ListUnitFilesByPatterns(states []string, patterns []string) ([]UnitFile, error) { - return c.ListUnitFilesByPatternsContext(context.Background(), states, patterns) -} - -// ListUnitFilesByPatternsContext returns an array of all available units on disk matched the patterns. -func (c *Conn) ListUnitFilesByPatternsContext(ctx context.Context, states []string, patterns []string) ([]UnitFile, error) { - return c.listUnitFilesInternal(c.sysobj.CallWithContext(ctx, "org.freedesktop.systemd1.Manager.ListUnitFilesByPatterns", 0, states, patterns).Store) -} - -type LinkUnitFileChange EnableUnitFileChange - -// Deprecated: use LinkUnitFilesContext instead. -func (c *Conn) LinkUnitFiles(files []string, runtime bool, force bool) ([]LinkUnitFileChange, error) { - return c.LinkUnitFilesContext(context.Background(), files, runtime, force) -} - -// LinkUnitFilesContext links unit files (that are located outside of the -// usual unit search paths) into the unit search path. -// -// It takes a list of absolute paths to unit files to link and two -// booleans. -// -// The first boolean controls whether the unit shall be -// enabled for runtime only (true, /run), or persistently (false, -// /etc). -// -// The second controls whether symlinks pointing to other units shall -// be replaced if necessary. -// -// This call returns a list of the changes made. The list consists of -// structures with three strings: the type of the change (one of symlink -// or unlink), the file name of the symlink and the destination of the -// symlink. -func (c *Conn) LinkUnitFilesContext(ctx context.Context, files []string, runtime bool, force bool) ([]LinkUnitFileChange, error) { - result := make([][]interface{}, 0) - err := c.sysobj.CallWithContext(ctx, "org.freedesktop.systemd1.Manager.LinkUnitFiles", 0, files, runtime, force).Store(&result) - if err != nil { - return nil, err - } - - resultInterface := make([]interface{}, len(result)) - for i := range result { - resultInterface[i] = result[i] - } - - changes := make([]LinkUnitFileChange, len(result)) - changesInterface := make([]interface{}, len(changes)) - for i := range changes { - changesInterface[i] = &changes[i] - } - - err = dbus.Store(resultInterface, changesInterface...) - if err != nil { - return nil, err - } - - return changes, nil -} - -// Deprecated: use EnableUnitFilesContext instead. -func (c *Conn) EnableUnitFiles(files []string, runtime bool, force bool) (bool, []EnableUnitFileChange, error) { - return c.EnableUnitFilesContext(context.Background(), files, runtime, force) -} - -// EnableUnitFilesContext may be used to enable one or more units in the system -// (by creating symlinks to them in /etc or /run). -// -// It takes a list of unit files to enable (either just file names or full -// absolute paths if the unit files are residing outside the usual unit -// search paths), and two booleans: the first controls whether the unit shall -// be enabled for runtime only (true, /run), or persistently (false, /etc). -// The second one controls whether symlinks pointing to other units shall -// be replaced if necessary. -// -// This call returns one boolean and an array with the changes made. The -// boolean signals whether the unit files contained any enablement -// information (i.e. an [Install]) section. The changes list consists of -// structures with three strings: the type of the change (one of symlink -// or unlink), the file name of the symlink and the destination of the -// symlink. -func (c *Conn) EnableUnitFilesContext(ctx context.Context, files []string, runtime bool, force bool) (bool, []EnableUnitFileChange, error) { - var carries_install_info bool - - result := make([][]interface{}, 0) - err := c.sysobj.CallWithContext(ctx, "org.freedesktop.systemd1.Manager.EnableUnitFiles", 0, files, runtime, force).Store(&carries_install_info, &result) - if err != nil { - return false, nil, err - } - - resultInterface := make([]interface{}, len(result)) - for i := range result { - resultInterface[i] = result[i] - } - - changes := make([]EnableUnitFileChange, len(result)) - changesInterface := make([]interface{}, len(changes)) - for i := range changes { - changesInterface[i] = &changes[i] - } - - err = dbus.Store(resultInterface, changesInterface...) - if err != nil { - return false, nil, err - } - - return carries_install_info, changes, nil -} - -type EnableUnitFileChange struct { - Type string // Type of the change (one of symlink or unlink) - Filename string // File name of the symlink - Destination string // Destination of the symlink -} - -// Deprecated: use DisableUnitFilesContext instead. -func (c *Conn) DisableUnitFiles(files []string, runtime bool) ([]DisableUnitFileChange, error) { - return c.DisableUnitFilesContext(context.Background(), files, runtime) -} - -// DisableUnitFilesContext may be used to disable one or more units in the -// system (by removing symlinks to them from /etc or /run). -// -// It takes a list of unit files to disable (either just file names or full -// absolute paths if the unit files are residing outside the usual unit -// search paths), and one boolean: whether the unit was enabled for runtime -// only (true, /run), or persistently (false, /etc). -// -// This call returns an array with the changes made. The changes list -// consists of structures with three strings: the type of the change (one of -// symlink or unlink), the file name of the symlink and the destination of the -// symlink. -func (c *Conn) DisableUnitFilesContext(ctx context.Context, files []string, runtime bool) ([]DisableUnitFileChange, error) { - result := make([][]interface{}, 0) - err := c.sysobj.CallWithContext(ctx, "org.freedesktop.systemd1.Manager.DisableUnitFiles", 0, files, runtime).Store(&result) - if err != nil { - return nil, err - } - - resultInterface := make([]interface{}, len(result)) - for i := range result { - resultInterface[i] = result[i] - } - - changes := make([]DisableUnitFileChange, len(result)) - changesInterface := make([]interface{}, len(changes)) - for i := range changes { - changesInterface[i] = &changes[i] - } - - err = dbus.Store(resultInterface, changesInterface...) - if err != nil { - return nil, err - } - - return changes, nil -} - -type DisableUnitFileChange struct { - Type string // Type of the change (one of symlink or unlink) - Filename string // File name of the symlink - Destination string // Destination of the symlink -} - -// Deprecated: use MaskUnitFilesContext instead. -func (c *Conn) MaskUnitFiles(files []string, runtime bool, force bool) ([]MaskUnitFileChange, error) { - return c.MaskUnitFilesContext(context.Background(), files, runtime, force) -} - -// MaskUnitFilesContext masks one or more units in the system. -// -// The files argument contains a list of units to mask (either just file names -// or full absolute paths if the unit files are residing outside the usual unit -// search paths). -// -// The runtime argument is used to specify whether the unit was enabled for -// runtime only (true, /run/systemd/..), or persistently (false, -// /etc/systemd/..). -func (c *Conn) MaskUnitFilesContext(ctx context.Context, files []string, runtime bool, force bool) ([]MaskUnitFileChange, error) { - result := make([][]interface{}, 0) - err := c.sysobj.CallWithContext(ctx, "org.freedesktop.systemd1.Manager.MaskUnitFiles", 0, files, runtime, force).Store(&result) - if err != nil { - return nil, err - } - - resultInterface := make([]interface{}, len(result)) - for i := range result { - resultInterface[i] = result[i] - } - - changes := make([]MaskUnitFileChange, len(result)) - changesInterface := make([]interface{}, len(changes)) - for i := range changes { - changesInterface[i] = &changes[i] - } - - err = dbus.Store(resultInterface, changesInterface...) - if err != nil { - return nil, err - } - - return changes, nil -} - -type MaskUnitFileChange struct { - Type string // Type of the change (one of symlink or unlink) - Filename string // File name of the symlink - Destination string // Destination of the symlink -} - -// Deprecated: use UnmaskUnitFilesContext instead. -func (c *Conn) UnmaskUnitFiles(files []string, runtime bool) ([]UnmaskUnitFileChange, error) { - return c.UnmaskUnitFilesContext(context.Background(), files, runtime) -} - -// UnmaskUnitFilesContext unmasks one or more units in the system. -// -// It takes the list of unit files to mask (either just file names or full -// absolute paths if the unit files are residing outside the usual unit search -// paths), and a boolean runtime flag to specify whether the unit was enabled -// for runtime only (true, /run/systemd/..), or persistently (false, -// /etc/systemd/..). -func (c *Conn) UnmaskUnitFilesContext(ctx context.Context, files []string, runtime bool) ([]UnmaskUnitFileChange, error) { - result := make([][]interface{}, 0) - err := c.sysobj.CallWithContext(ctx, "org.freedesktop.systemd1.Manager.UnmaskUnitFiles", 0, files, runtime).Store(&result) - if err != nil { - return nil, err - } - - resultInterface := make([]interface{}, len(result)) - for i := range result { - resultInterface[i] = result[i] - } - - changes := make([]UnmaskUnitFileChange, len(result)) - changesInterface := make([]interface{}, len(changes)) - for i := range changes { - changesInterface[i] = &changes[i] - } - - err = dbus.Store(resultInterface, changesInterface...) - if err != nil { - return nil, err - } - - return changes, nil -} - -type UnmaskUnitFileChange struct { - Type string // Type of the change (one of symlink or unlink) - Filename string // File name of the symlink - Destination string // Destination of the symlink -} - -// Deprecated: use ReloadContext instead. -func (c *Conn) Reload() error { - return c.ReloadContext(context.Background()) -} - -// ReloadContext instructs systemd to scan for and reload unit files. This is -// an equivalent to systemctl daemon-reload. -func (c *Conn) ReloadContext(ctx context.Context) error { - return c.sysobj.CallWithContext(ctx, "org.freedesktop.systemd1.Manager.Reload", 0).Store() -} - -func unitPath(name string) dbus.ObjectPath { - return dbus.ObjectPath("/org/freedesktop/systemd1/unit/" + PathBusEscape(name)) -} - -// unitName returns the unescaped base element of the supplied escaped path. -func unitName(dpath dbus.ObjectPath) string { - return pathBusUnescape(path.Base(string(dpath))) -} - -// JobStatus holds a currently queued job definition. -type JobStatus struct { - Id uint32 // The numeric job id - Unit string // The primary unit name for this job - JobType string // The job type as string - Status string // The job state as string - JobPath dbus.ObjectPath // The job object path - UnitPath dbus.ObjectPath // The unit object path -} - -// Deprecated: use ListJobsContext instead. -func (c *Conn) ListJobs() ([]JobStatus, error) { - return c.ListJobsContext(context.Background()) -} - -// ListJobsContext returns an array with all currently queued jobs. -func (c *Conn) ListJobsContext(ctx context.Context) ([]JobStatus, error) { - return c.listJobsInternal(ctx) -} - -func (c *Conn) listJobsInternal(ctx context.Context) ([]JobStatus, error) { - result := make([][]interface{}, 0) - if err := c.sysobj.CallWithContext(ctx, "org.freedesktop.systemd1.Manager.ListJobs", 0).Store(&result); err != nil { - return nil, err - } - - resultInterface := make([]interface{}, len(result)) - for i := range result { - resultInterface[i] = result[i] - } - - status := make([]JobStatus, len(result)) - statusInterface := make([]interface{}, len(status)) - for i := range status { - statusInterface[i] = &status[i] - } - - if err := dbus.Store(resultInterface, statusInterface...); err != nil { - return nil, err - } - - return status, nil -} - -// Freeze the cgroup associated with the unit. -// Note that FreezeUnit and ThawUnit are only supported on systems running with cgroup v2. -func (c *Conn) FreezeUnit(ctx context.Context, unit string) error { - return c.sysobj.CallWithContext(ctx, "org.freedesktop.systemd1.Manager.FreezeUnit", 0, unit).Store() -} - -// Unfreeze the cgroup associated with the unit. -func (c *Conn) ThawUnit(ctx context.Context, unit string) error { - return c.sysobj.CallWithContext(ctx, "org.freedesktop.systemd1.Manager.ThawUnit", 0, unit).Store() -} diff --git a/vendor/github.com/coreos/go-systemd/v22/dbus/properties.go b/vendor/github.com/coreos/go-systemd/v22/dbus/properties.go deleted file mode 100644 index fb42b627..00000000 --- a/vendor/github.com/coreos/go-systemd/v22/dbus/properties.go +++ /dev/null @@ -1,237 +0,0 @@ -// Copyright 2015 CoreOS, 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. - -package dbus - -import ( - "github.com/godbus/dbus/v5" -) - -// From the systemd docs: -// -// The properties array of StartTransientUnit() may take many of the settings -// that may also be configured in unit files. Not all parameters are currently -// accepted though, but we plan to cover more properties with future release. -// Currently you may set the Description, Slice and all dependency types of -// units, as well as RemainAfterExit, ExecStart for service units, -// TimeoutStopUSec and PIDs for scope units, and CPUAccounting, CPUShares, -// BlockIOAccounting, BlockIOWeight, BlockIOReadBandwidth, -// BlockIOWriteBandwidth, BlockIODeviceWeight, MemoryAccounting, MemoryLimit, -// DevicePolicy, DeviceAllow for services/scopes/slices. These fields map -// directly to their counterparts in unit files and as normal D-Bus object -// properties. The exception here is the PIDs field of scope units which is -// used for construction of the scope only and specifies the initial PIDs to -// add to the scope object. - -type Property struct { - Name string - Value dbus.Variant -} - -type PropertyCollection struct { - Name string - Properties []Property -} - -type execStart struct { - Path string // the binary path to execute - Args []string // an array with all arguments to pass to the executed command, starting with argument 0 - UncleanIsFailure bool // a boolean whether it should be considered a failure if the process exits uncleanly -} - -// PropExecStart sets the ExecStart service property. The first argument is a -// slice with the binary path to execute followed by the arguments to pass to -// the executed command. See -// http://www.freedesktop.org/software/systemd/man/systemd.service.html#ExecStart= -func PropExecStart(command []string, uncleanIsFailure bool) Property { - execStarts := []execStart{ - { - Path: command[0], - Args: command, - UncleanIsFailure: uncleanIsFailure, - }, - } - - return Property{ - Name: "ExecStart", - Value: dbus.MakeVariant(execStarts), - } -} - -// PropRemainAfterExit sets the RemainAfterExit service property. See -// http://www.freedesktop.org/software/systemd/man/systemd.service.html#RemainAfterExit= -func PropRemainAfterExit(b bool) Property { - return Property{ - Name: "RemainAfterExit", - Value: dbus.MakeVariant(b), - } -} - -// PropType sets the Type service property. See -// http://www.freedesktop.org/software/systemd/man/systemd.service.html#Type= -func PropType(t string) Property { - return Property{ - Name: "Type", - Value: dbus.MakeVariant(t), - } -} - -// PropDescription sets the Description unit property. See -// http://www.freedesktop.org/software/systemd/man/systemd.unit#Description= -func PropDescription(desc string) Property { - return Property{ - Name: "Description", - Value: dbus.MakeVariant(desc), - } -} - -func propDependency(name string, units []string) Property { - return Property{ - Name: name, - Value: dbus.MakeVariant(units), - } -} - -// PropRequires sets the Requires unit property. See -// http://www.freedesktop.org/software/systemd/man/systemd.unit.html#Requires= -func PropRequires(units ...string) Property { - return propDependency("Requires", units) -} - -// PropRequiresOverridable sets the RequiresOverridable unit property. See -// http://www.freedesktop.org/software/systemd/man/systemd.unit.html#RequiresOverridable= -func PropRequiresOverridable(units ...string) Property { - return propDependency("RequiresOverridable", units) -} - -// PropRequisite sets the Requisite unit property. See -// http://www.freedesktop.org/software/systemd/man/systemd.unit.html#Requisite= -func PropRequisite(units ...string) Property { - return propDependency("Requisite", units) -} - -// PropRequisiteOverridable sets the RequisiteOverridable unit property. See -// http://www.freedesktop.org/software/systemd/man/systemd.unit.html#RequisiteOverridable= -func PropRequisiteOverridable(units ...string) Property { - return propDependency("RequisiteOverridable", units) -} - -// PropWants sets the Wants unit property. See -// http://www.freedesktop.org/software/systemd/man/systemd.unit.html#Wants= -func PropWants(units ...string) Property { - return propDependency("Wants", units) -} - -// PropBindsTo sets the BindsTo unit property. See -// http://www.freedesktop.org/software/systemd/man/systemd.unit.html#BindsTo= -func PropBindsTo(units ...string) Property { - return propDependency("BindsTo", units) -} - -// PropRequiredBy sets the RequiredBy unit property. See -// http://www.freedesktop.org/software/systemd/man/systemd.unit.html#RequiredBy= -func PropRequiredBy(units ...string) Property { - return propDependency("RequiredBy", units) -} - -// PropRequiredByOverridable sets the RequiredByOverridable unit property. See -// http://www.freedesktop.org/software/systemd/man/systemd.unit.html#RequiredByOverridable= -func PropRequiredByOverridable(units ...string) Property { - return propDependency("RequiredByOverridable", units) -} - -// PropWantedBy sets the WantedBy unit property. See -// http://www.freedesktop.org/software/systemd/man/systemd.unit.html#WantedBy= -func PropWantedBy(units ...string) Property { - return propDependency("WantedBy", units) -} - -// PropBoundBy sets the BoundBy unit property. See -// http://www.freedesktop.org/software/systemd/main/systemd.unit.html#BoundBy= -func PropBoundBy(units ...string) Property { - return propDependency("BoundBy", units) -} - -// PropConflicts sets the Conflicts unit property. See -// http://www.freedesktop.org/software/systemd/man/systemd.unit.html#Conflicts= -func PropConflicts(units ...string) Property { - return propDependency("Conflicts", units) -} - -// PropConflictedBy sets the ConflictedBy unit property. See -// http://www.freedesktop.org/software/systemd/man/systemd.unit.html#ConflictedBy= -func PropConflictedBy(units ...string) Property { - return propDependency("ConflictedBy", units) -} - -// PropBefore sets the Before unit property. See -// http://www.freedesktop.org/software/systemd/man/systemd.unit.html#Before= -func PropBefore(units ...string) Property { - return propDependency("Before", units) -} - -// PropAfter sets the After unit property. See -// http://www.freedesktop.org/software/systemd/man/systemd.unit.html#After= -func PropAfter(units ...string) Property { - return propDependency("After", units) -} - -// PropOnFailure sets the OnFailure unit property. See -// http://www.freedesktop.org/software/systemd/man/systemd.unit.html#OnFailure= -func PropOnFailure(units ...string) Property { - return propDependency("OnFailure", units) -} - -// PropTriggers sets the Triggers unit property. See -// http://www.freedesktop.org/software/systemd/man/systemd.unit.html#Triggers= -func PropTriggers(units ...string) Property { - return propDependency("Triggers", units) -} - -// PropTriggeredBy sets the TriggeredBy unit property. See -// http://www.freedesktop.org/software/systemd/man/systemd.unit.html#TriggeredBy= -func PropTriggeredBy(units ...string) Property { - return propDependency("TriggeredBy", units) -} - -// PropPropagatesReloadTo sets the PropagatesReloadTo unit property. See -// http://www.freedesktop.org/software/systemd/man/systemd.unit.html#PropagatesReloadTo= -func PropPropagatesReloadTo(units ...string) Property { - return propDependency("PropagatesReloadTo", units) -} - -// PropRequiresMountsFor sets the RequiresMountsFor unit property. See -// http://www.freedesktop.org/software/systemd/man/systemd.unit.html#RequiresMountsFor= -func PropRequiresMountsFor(units ...string) Property { - return propDependency("RequiresMountsFor", units) -} - -// PropSlice sets the Slice unit property. See -// http://www.freedesktop.org/software/systemd/man/systemd.resource-control.html#Slice= -func PropSlice(slice string) Property { - return Property{ - Name: "Slice", - Value: dbus.MakeVariant(slice), - } -} - -// PropPids sets the PIDs field of scope units used in the initial construction -// of the scope only and specifies the initial PIDs to add to the scope object. -// See https://www.freedesktop.org/wiki/Software/systemd/ControlGroupInterface/#properties -func PropPids(pids ...uint32) Property { - return Property{ - Name: "PIDs", - Value: dbus.MakeVariant(pids), - } -} diff --git a/vendor/github.com/coreos/go-systemd/v22/dbus/set.go b/vendor/github.com/coreos/go-systemd/v22/dbus/set.go deleted file mode 100644 index 17c5d485..00000000 --- a/vendor/github.com/coreos/go-systemd/v22/dbus/set.go +++ /dev/null @@ -1,47 +0,0 @@ -// Copyright 2015 CoreOS, 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. - -package dbus - -type set struct { - data map[string]bool -} - -func (s *set) Add(value string) { - s.data[value] = true -} - -func (s *set) Remove(value string) { - delete(s.data, value) -} - -func (s *set) Contains(value string) (exists bool) { - _, exists = s.data[value] - return -} - -func (s *set) Length() int { - return len(s.data) -} - -func (s *set) Values() (values []string) { - for val := range s.data { - values = append(values, val) - } - return -} - -func newSet() *set { - return &set{make(map[string]bool)} -} diff --git a/vendor/github.com/coreos/go-systemd/v22/dbus/subscription.go b/vendor/github.com/coreos/go-systemd/v22/dbus/subscription.go deleted file mode 100644 index 7e370fea..00000000 --- a/vendor/github.com/coreos/go-systemd/v22/dbus/subscription.go +++ /dev/null @@ -1,333 +0,0 @@ -// Copyright 2015 CoreOS, 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. - -package dbus - -import ( - "errors" - "log" - "time" - - "github.com/godbus/dbus/v5" -) - -const ( - cleanIgnoreInterval = int64(10 * time.Second) - ignoreInterval = int64(30 * time.Millisecond) -) - -// Subscribe sets up this connection to subscribe to all systemd dbus events. -// This is required before calling SubscribeUnits. When the connection closes -// systemd will automatically stop sending signals so there is no need to -// explicitly call Unsubscribe(). -func (c *Conn) Subscribe() error { - c.sigconn.BusObject().Call("org.freedesktop.DBus.AddMatch", 0, - "type='signal',interface='org.freedesktop.systemd1.Manager',member='UnitNew'") - c.sigconn.BusObject().Call("org.freedesktop.DBus.AddMatch", 0, - "type='signal',interface='org.freedesktop.DBus.Properties',member='PropertiesChanged'") - - return c.sigobj.Call("org.freedesktop.systemd1.Manager.Subscribe", 0).Store() -} - -// Unsubscribe this connection from systemd dbus events. -func (c *Conn) Unsubscribe() error { - return c.sigobj.Call("org.freedesktop.systemd1.Manager.Unsubscribe", 0).Store() -} - -func (c *Conn) dispatch() { - ch := make(chan *dbus.Signal, signalBuffer) - - c.sigconn.Signal(ch) - - go func() { - for { - signal, ok := <-ch - if !ok { - return - } - - if signal.Name == "org.freedesktop.systemd1.Manager.JobRemoved" { - c.jobComplete(signal) - } - - if c.subStateSubscriber.updateCh == nil && - c.propertiesSubscriber.updateCh == nil { - continue - } - - var unitPath dbus.ObjectPath - switch signal.Name { - case "org.freedesktop.systemd1.Manager.JobRemoved": - unitName := signal.Body[2].(string) - c.sysobj.Call("org.freedesktop.systemd1.Manager.GetUnit", 0, unitName).Store(&unitPath) - case "org.freedesktop.systemd1.Manager.UnitNew": - unitPath = signal.Body[1].(dbus.ObjectPath) - case "org.freedesktop.DBus.Properties.PropertiesChanged": - if signal.Body[0].(string) == "org.freedesktop.systemd1.Unit" { - unitPath = signal.Path - - if len(signal.Body) >= 2 { - if changed, ok := signal.Body[1].(map[string]dbus.Variant); ok { - c.sendPropertiesUpdate(unitPath, changed) - } - } - } - } - - if unitPath == dbus.ObjectPath("") { - continue - } - - c.sendSubStateUpdate(unitPath) - } - }() -} - -// SubscribeUnits returns two unbuffered channels which will receive all changed units every -// interval. Deleted units are sent as nil. -func (c *Conn) SubscribeUnits(interval time.Duration) (<-chan map[string]*UnitStatus, <-chan error) { - return c.SubscribeUnitsCustom(interval, 0, func(u1, u2 *UnitStatus) bool { return *u1 != *u2 }, nil) -} - -// SubscribeUnitsCustom is like SubscribeUnits but lets you specify the buffer -// size of the channels, the comparison function for detecting changes and a filter -// function for cutting down on the noise that your channel receives. -func (c *Conn) SubscribeUnitsCustom(interval time.Duration, buffer int, isChanged func(*UnitStatus, *UnitStatus) bool, filterUnit func(string) bool) (<-chan map[string]*UnitStatus, <-chan error) { - old := make(map[string]*UnitStatus) - statusChan := make(chan map[string]*UnitStatus, buffer) - errChan := make(chan error, buffer) - - go func() { - for { - timerChan := time.After(interval) - - units, err := c.ListUnits() - if err == nil { - cur := make(map[string]*UnitStatus) - for i := range units { - if filterUnit != nil && filterUnit(units[i].Name) { - continue - } - cur[units[i].Name] = &units[i] - } - - // add all new or changed units - changed := make(map[string]*UnitStatus) - for n, u := range cur { - if oldU, ok := old[n]; !ok || isChanged(oldU, u) { - changed[n] = u - } - delete(old, n) - } - - // add all deleted units - for oldN := range old { - changed[oldN] = nil - } - - old = cur - - if len(changed) != 0 { - statusChan <- changed - } - } else { - errChan <- err - } - - <-timerChan - } - }() - - return statusChan, errChan -} - -type SubStateUpdate struct { - UnitName string - SubState string -} - -// SetSubStateSubscriber writes to updateCh when any unit's substate changes. -// Although this writes to updateCh on every state change, the reported state -// may be more recent than the change that generated it (due to an unavoidable -// race in the systemd dbus interface). That is, this method provides a good -// way to keep a current view of all units' states, but is not guaranteed to -// show every state transition they go through. Furthermore, state changes -// will only be written to the channel with non-blocking writes. If updateCh -// is full, it attempts to write an error to errCh; if errCh is full, the error -// passes silently. -func (c *Conn) SetSubStateSubscriber(updateCh chan<- *SubStateUpdate, errCh chan<- error) { - if c == nil { - msg := "nil receiver" - select { - case errCh <- errors.New(msg): - default: - log.Printf("full error channel while reporting: %s\n", msg) - } - return - } - - c.subStateSubscriber.Lock() - defer c.subStateSubscriber.Unlock() - c.subStateSubscriber.updateCh = updateCh - c.subStateSubscriber.errCh = errCh -} - -func (c *Conn) sendSubStateUpdate(unitPath dbus.ObjectPath) { - c.subStateSubscriber.Lock() - defer c.subStateSubscriber.Unlock() - - if c.subStateSubscriber.updateCh == nil { - return - } - - isIgnored := c.shouldIgnore(unitPath) - defer c.cleanIgnore() - if isIgnored { - return - } - - info, err := c.GetUnitPathProperties(unitPath) - if err != nil { - select { - case c.subStateSubscriber.errCh <- err: - default: - log.Printf("full error channel while reporting: %s\n", err) - } - return - } - defer c.updateIgnore(unitPath, info) - - name, ok := info["Id"].(string) - if !ok { - msg := "failed to cast info.Id" - select { - case c.subStateSubscriber.errCh <- errors.New(msg): - default: - log.Printf("full error channel while reporting: %s\n", err) - } - return - } - substate, ok := info["SubState"].(string) - if !ok { - msg := "failed to cast info.SubState" - select { - case c.subStateSubscriber.errCh <- errors.New(msg): - default: - log.Printf("full error channel while reporting: %s\n", msg) - } - return - } - - update := &SubStateUpdate{name, substate} - select { - case c.subStateSubscriber.updateCh <- update: - default: - msg := "update channel is full" - select { - case c.subStateSubscriber.errCh <- errors.New(msg): - default: - log.Printf("full error channel while reporting: %s\n", msg) - } - return - } -} - -// The ignore functions work around a wart in the systemd dbus interface. -// Requesting the properties of an unloaded unit will cause systemd to send a -// pair of UnitNew/UnitRemoved signals. Because we need to get a unit's -// properties on UnitNew (as that's the only indication of a new unit coming up -// for the first time), we would enter an infinite loop if we did not attempt -// to detect and ignore these spurious signals. The signal themselves are -// indistinguishable from relevant ones, so we (somewhat hackishly) ignore an -// unloaded unit's signals for a short time after requesting its properties. -// This means that we will miss e.g. a transient unit being restarted -// *immediately* upon failure and also a transient unit being started -// immediately after requesting its status (with systemctl status, for example, -// because this causes a UnitNew signal to be sent which then causes us to fetch -// the properties). - -func (c *Conn) shouldIgnore(path dbus.ObjectPath) bool { - t, ok := c.subStateSubscriber.ignore[path] - return ok && t >= time.Now().UnixNano() -} - -func (c *Conn) updateIgnore(path dbus.ObjectPath, info map[string]interface{}) { - loadState, ok := info["LoadState"].(string) - if !ok { - return - } - - // unit is unloaded - it will trigger bad systemd dbus behavior - if loadState == "not-found" { - c.subStateSubscriber.ignore[path] = time.Now().UnixNano() + ignoreInterval - } -} - -// without this, ignore would grow unboundedly over time -func (c *Conn) cleanIgnore() { - now := time.Now().UnixNano() - if c.subStateSubscriber.cleanIgnore < now { - c.subStateSubscriber.cleanIgnore = now + cleanIgnoreInterval - - for p, t := range c.subStateSubscriber.ignore { - if t < now { - delete(c.subStateSubscriber.ignore, p) - } - } - } -} - -// PropertiesUpdate holds a map of a unit's changed properties -type PropertiesUpdate struct { - UnitName string - Changed map[string]dbus.Variant -} - -// SetPropertiesSubscriber writes to updateCh when any unit's properties -// change. Every property change reported by systemd will be sent; that is, no -// transitions will be "missed" (as they might be with SetSubStateSubscriber). -// However, state changes will only be written to the channel with non-blocking -// writes. If updateCh is full, it attempts to write an error to errCh; if -// errCh is full, the error passes silently. -func (c *Conn) SetPropertiesSubscriber(updateCh chan<- *PropertiesUpdate, errCh chan<- error) { - c.propertiesSubscriber.Lock() - defer c.propertiesSubscriber.Unlock() - c.propertiesSubscriber.updateCh = updateCh - c.propertiesSubscriber.errCh = errCh -} - -// we don't need to worry about shouldIgnore() here because -// sendPropertiesUpdate doesn't call GetProperties() -func (c *Conn) sendPropertiesUpdate(unitPath dbus.ObjectPath, changedProps map[string]dbus.Variant) { - c.propertiesSubscriber.Lock() - defer c.propertiesSubscriber.Unlock() - - if c.propertiesSubscriber.updateCh == nil { - return - } - - update := &PropertiesUpdate{unitName(unitPath), changedProps} - - select { - case c.propertiesSubscriber.updateCh <- update: - default: - msg := "update channel is full" - select { - case c.propertiesSubscriber.errCh <- errors.New(msg): - default: - log.Printf("full error channel while reporting: %s\n", msg) - } - return - } -} diff --git a/vendor/github.com/coreos/go-systemd/v22/dbus/subscription_set.go b/vendor/github.com/coreos/go-systemd/v22/dbus/subscription_set.go deleted file mode 100644 index 5b408d58..00000000 --- a/vendor/github.com/coreos/go-systemd/v22/dbus/subscription_set.go +++ /dev/null @@ -1,57 +0,0 @@ -// Copyright 2015 CoreOS, 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. - -package dbus - -import ( - "time" -) - -// SubscriptionSet returns a subscription set which is like conn.Subscribe but -// can filter to only return events for a set of units. -type SubscriptionSet struct { - *set - conn *Conn -} - -func (s *SubscriptionSet) filter(unit string) bool { - return !s.Contains(unit) -} - -// Subscribe starts listening for dbus events for all of the units in the set. -// Returns channels identical to conn.SubscribeUnits. -func (s *SubscriptionSet) Subscribe() (<-chan map[string]*UnitStatus, <-chan error) { - // TODO: Make fully evented by using systemd 209 with properties changed values - return s.conn.SubscribeUnitsCustom(time.Second, 0, - mismatchUnitStatus, - func(unit string) bool { return s.filter(unit) }, - ) -} - -// NewSubscriptionSet returns a new subscription set. -func (conn *Conn) NewSubscriptionSet() *SubscriptionSet { - return &SubscriptionSet{newSet(), conn} -} - -// mismatchUnitStatus returns true if the provided UnitStatus objects -// are not equivalent. false is returned if the objects are equivalent. -// Only the Name, Description and state-related fields are used in -// the comparison. -func mismatchUnitStatus(u1, u2 *UnitStatus) bool { - return u1.Name != u2.Name || - u1.Description != u2.Description || - u1.LoadState != u2.LoadState || - u1.ActiveState != u2.ActiveState || - u1.SubState != u2.SubState -} diff --git a/vendor/github.com/gammazero/deque/README.md b/vendor/github.com/gammazero/deque/README.md index 78f99c4c..ee2dbb88 100644 --- a/vendor/github.com/gammazero/deque/README.md +++ b/vendor/github.com/gammazero/deque/README.md @@ -18,25 +18,25 @@ $ go get github.com/gammazero/deque ## Deque data structure -Deque generalizes a queue and a stack, to efficiently add and remove items at either end with O(1) performance. [Queue](https://en.wikipedia.org/wiki/Queue_(abstract_data_type)) (FIFO) operations are supported using `PushBack()` and `PopFront()`. [Stack](https://en.wikipedia.org/wiki/Stack_(abstract_data_type)) (LIFO) operations are supported using `PushBack()` and `PopBack()`. +Deque generalizes a queue and a stack, to efficiently add and remove items at either end with O(1) performance. [Queue](https://en.wikipedia.org/wiki/Queue_(abstract_data_type)) (FIFO) operations are supported using `PushBack` and `PopFront`. [Stack](https://en.wikipedia.org/wiki/Stack_(abstract_data_type)) (LIFO) operations are supported using `PushBack` and `PopBack`. ## Ring-buffer Performance -This deque implementation is optimized for CPU and GC performance. The circular buffer automatically re-sizes by powers of two, growing when additional capacity is needed and shrinking when only a quarter of the capacity is used, and uses bitwise arithmetic for all calculations. Since growth is by powers of two, adding elements will only cause O(log n) allocations. +This deque implementation is optimized for CPU and GC performance. The circular buffer automatically re-sizes by powers of two, growing when additional capacity is needed and shrinking when only a quarter of the capacity is used, and uses bitwise arithmetic for all calculations. Since growth is by powers of two, adding elements will only cause O(log n) allocations. A base capacity can be set, with `SetBaseCap`, so that there is no resizing at or below that specified amount. The Deque can also be grown, using `Grow`, to ensure sufficient storage for n additional items, to prevent resizing when adding a number of itmes. -The ring-buffer implementation improves memory and time performance with fewer GC pauses, compared to implementations based on slices and linked lists. By wrapping around the buffer, previously used space is reused, making allocation unnecessary until all buffer capacity is used. This is particularly efficient when data going into the dequeue is relatively balanced against data coming out. However, if size changes are very large and only fill and then empty then deque, the ring structure offers little benefit for memory reuse. For that usage pattern a different implementation may be preferable. +The ring-buffer implementation improves memory and time performance with fewer GC pauses, compared to implementations based on slices or linked lists. By wrapping around the buffer, previously used space is reused, making allocation unnecessary until all buffer capacity is used. The ring buffer implementation performs best when resizes are infrequest, as is the case when items moving in and out of the Deque are balanced or when the base capacity is large enough to rarely require a resize. For maximum speed, this deque implementation leaves concurrency safety up to the application to provide, however the application chooses, if needed at all. ## Reading Empty Deque -Since it is OK for the deque to contain a `nil` value, it is necessary to either panic or return a second boolean value to indicate the deque is empty, when reading or removing an element. This deque panics when reading from an empty deque. This is a run-time check to help catch programming errors, which may be missed if a second return value is ignored. Simply check `Deque.Len()` before reading from the deque. +Since it is OK for the deque to contain a `nil` value, it is necessary to either panic or return a second boolean value to indicate the deque is empty, when reading or removing an element. This deque panics when reading from an empty deque. This is a run-time check to help catch programming errors, which may be missed if a second return value is ignored. Simply check `Deque.Len()` before reading from the deque. ## Generics -Deque uses generics to create a Deque that contains items of the type specified. To create a Deque that holds a specific type, provide a type argument to New or with the variable declaration. For example: +Deque uses generics to create a Deque that contains items of the type specified. To create a Deque that holds a specific type, provide a type argument with the `Deque` variable declaration. For example: ```go - stringDeque := deque.New[string]() + stringDeque := new(deque.Deque[string]) var intDeque deque.Deque[int] ``` diff --git a/vendor/github.com/gammazero/deque/deque.go b/vendor/github.com/gammazero/deque/deque.go index a120ccf7..ff109962 100644 --- a/vendor/github.com/gammazero/deque/deque.go +++ b/vendor/github.com/gammazero/deque/deque.go @@ -1,11 +1,34 @@ package deque +import "fmt" + // minCapacity is the smallest capacity that deque may have. Must be power of 2 // for bitwise modulus: x % n == x & (n - 1). const minCapacity = 16 // Deque represents a single instance of the deque data structure. A Deque -// instance contains items of the type sepcified by the type argument. +// instance contains items of the type specified by the type argument. +// +// For example, to create a Deque that contains strings do one of the +// following: +// +// var stringDeque deque.Deque[string] +// stringDeque := new(deque.Deque[string]) +// stringDeque := &deque.Deque[string]{} +// +// To create a Deque that will never resize to have space for less than 64 +// items, specify a base capacity: +// +// var d deque.Deque[int] +// d.SetBaseCap(64) +// +// To ensure the Deque can store 1000 items without needing to resize while +// items are added: +// +// d.Grow(1000) +// +// Any values supplied to SetBaseCap and Grow are rounded up to the nearest +// power of 2, since the Deque grows by powers of 2. type Deque[T any] struct { buf []T head int @@ -14,51 +37,6 @@ type Deque[T any] struct { minCap int } -// New creates a new Deque, optionally setting the current and minimum capacity -// when non-zero values are given for these. The Deque instance returns -// operates on items of the type specified by the type argument. For example, -// to create a Deque that contains strings, -// -// stringDeque := deque.New[string]() -// -// To create a Deque with capacity to store 2048 ints without resizing, and -// that will not resize below space for 32 items when removing items: -// d := deque.New[int](2048, 32) -// -// To create a Deque that has not yet allocated memory, but after it does will -// never resize to have space for less than 64 items: -// d := deque.New[int](0, 64) -// -// Any size values supplied here are rounded up to the nearest power of 2. -func New[T any](size ...int) *Deque[T] { - var capacity, minimum int - if len(size) >= 1 { - capacity = size[0] - if len(size) >= 2 { - minimum = size[1] - } - } - - minCap := minCapacity - for minCap < minimum { - minCap <<= 1 - } - - var buf []T - if capacity != 0 { - bufSize := minCap - for bufSize < capacity { - bufSize <<= 1 - } - buf = make([]T, bufSize) - } - - return &Deque[T]{ - buf: buf, - minCap: minCap, - } -} - // Cap returns the current capacity of the Deque. If q is nil, q.Cap() is zero. func (q *Deque[T]) Cap() int { if q == nil { @@ -68,7 +46,7 @@ func (q *Deque[T]) Cap() int { } // Len returns the number of elements currently stored in the queue. If q is -// nil, q.Len() is zero. +// nil, q.Len() returns zero. func (q *Deque[T]) Len() int { if q == nil { return 0 @@ -77,8 +55,8 @@ func (q *Deque[T]) Len() int { } // PushBack appends an element to the back of the queue. Implements FIFO when -// elements are removed with PopFront(), and LIFO when elements are removed -// with PopBack(). +// elements are removed with PopFront, and LIFO when elements are removed with +// PopBack. func (q *Deque[T]) PushBack(elem T) { q.growIfFull() @@ -99,7 +77,7 @@ func (q *Deque[T]) PushFront(elem T) { } // PopFront removes and returns the element from the front of the queue. -// Implements FIFO when used with PushBack(). If the queue is empty, the call +// Implements FIFO when used with PushBack. If the queue is empty, the call // panics. func (q *Deque[T]) PopFront() T { if q.count <= 0 { @@ -117,7 +95,7 @@ func (q *Deque[T]) PopFront() T { } // PopBack removes and returns the element from the back of the queue. -// Implements LIFO when used with PushBack(). If the queue is empty, the call +// Implements LIFO when used with PushBack. If the queue is empty, the call // panics. func (q *Deque[T]) PopBack() T { if q.count <= 0 { @@ -138,8 +116,7 @@ func (q *Deque[T]) PopBack() T { } // Front returns the element at the front of the queue. This is the element -// that would be returned by PopFront(). This call panics if the queue is -// empty. +// that would be returned by PopFront. This call panics if the queue is empty. func (q *Deque[T]) Front() T { if q.count <= 0 { panic("deque: Front() called when empty") @@ -148,7 +125,7 @@ func (q *Deque[T]) Front() T { } // Back returns the element at the back of the queue. This is the element that -// would be returned by PopBack(). This call panics if the queue is empty. +// would be returned by PopBack. This call panics if the queue is empty. func (q *Deque[T]) Back() T { if q.count <= 0 { panic("deque: Back() called when empty") @@ -169,22 +146,18 @@ func (q *Deque[T]) Back() T { // and when full the oldest is popped from the other end. All the log entries // in the buffer must be readable without altering the buffer contents. func (q *Deque[T]) At(i int) T { - if i < 0 || i >= q.count { - panic("deque: At() called with index out of range") - } + q.checkRange(i) // bitwise modulus return q.buf[(q.head+i)&(len(q.buf)-1)] } -// Set puts the element at index i in the queue. Set shares the same purpose -// than At() but perform the opposite operation. The index i is the same index -// defined by At(). If the index is invalid, the call panics. -func (q *Deque[T]) Set(i int, elem T) { - if i < 0 || i >= q.count { - panic("deque: Set() called with index out of range") - } +// Set assigns the item to index i in the queue. Set indexes the deque the same +// as At but perform the opposite operation. If the index is invalid, the call +// panics. +func (q *Deque[T]) Set(i int, item T) { + q.checkRange(i) // bitwise modulus - q.buf[(q.head+i)&(len(q.buf)-1)] = elem + q.buf[(q.head+i)&(len(q.buf)-1)] = item } // Clear removes all elements from the queue, but retains the current capacity. @@ -193,21 +166,52 @@ func (q *Deque[T]) Set(i int, elem T) { // only added. Only when items are removed is the queue subject to getting // resized smaller. func (q *Deque[T]) Clear() { - // bitwise modulus - modBits := len(q.buf) - 1 var zero T - for h := q.head; h != q.tail; h = (h + 1) & modBits { - q.buf[h] = zero + modBits := len(q.buf) - 1 + h := q.head + for i := 0; i < q.Len(); i++ { + q.buf[(h+i)&modBits] = zero } q.head = 0 q.tail = 0 q.count = 0 } +// Grow grows deque's capacity, if necessary, to guarantee space for another n +// items. After Grow(n), at least n items can be written to the deque without +// another allocation. If n is negative, Grow panics. +func (q *Deque[T]) Grow(n int) { + if n < 0 { + panic("deque.Grow: negative count") + } + c := q.Cap() + l := q.Len() + // If already big enough. + if n <= c-l { + return + } + + if c == 0 { + c = minCapacity + } + + newLen := l + n + for c < newLen { + c <<= 1 + } + if l == 0 { + q.buf = make([]T, c) + q.head = 0 + q.tail = 0 + } else { + q.resize(c) + } +} + // Rotate rotates the deque n steps front-to-back. If n is negative, rotates -// back-to-front. Having Deque provide Rotate() avoids resizing that could -// happen if implementing rotation using only Pop and Push methods. If q.Len() -// is one or less, or q is nil, then Rotate does nothing. +// back-to-front. Having Deque provide Rotate avoids resizing that could happen +// if implementing rotation using only Pop and Push methods. If q.Len() is one +// or less, or q is nil, then Rotate does nothing. func (q *Deque[T]) Rotate(n int) { if q.Len() <= 1 { return @@ -285,16 +289,21 @@ func (q *Deque[T]) RIndex(f func(T) bool) int { // Insert is used to insert an element into the middle of the queue, before the // element at the specified index. Insert(0,e) is the same as PushFront(e) and -// Insert(Len(),e) is the same as PushBack(e). Accepts only non-negative index -// values, and panics if index is out of range. +// Insert(Len(),e) is the same as PushBack(e). Out of range indexes result in +// pushing the item onto the front of back of the deque. // // Important: Deque is optimized for O(1) operations at the ends of the queue, // not for operations in the the middle. Complexity of this function is // constant plus linear in the lesser of the distances between the index and // either of the ends of the queue. func (q *Deque[T]) Insert(at int, item T) { - if at < 0 || at > q.count { - panic("deque: Insert() called with index out of range") + if at <= 0 { + q.PushFront(item) + return + } + if at >= q.Len() { + q.PushBack(item) + return } if at*2 < q.count { q.PushFront(item) @@ -326,10 +335,7 @@ func (q *Deque[T]) Insert(at int, item T) { // constant plus linear in the lesser of the distances between the index and // either of the ends of the queue. func (q *Deque[T]) Remove(at int) T { - if at < 0 || at >= q.Len() { - panic("deque: Remove() called with index out of range") - } - + q.checkRange(at) rm := (q.head + at) & (len(q.buf) - 1) if at*2 < q.count { for i := 0; i < at; i++ { @@ -348,18 +354,33 @@ func (q *Deque[T]) Remove(at int) T { return q.PopBack() } -// SetMinCapacity sets a minimum capacity of 2^minCapacityExp. If the value of -// the minimum capacity is less than or equal to the minimum allowed, then -// capacity is set to the minimum allowed. This may be called at anytime to set -// a new minimum capacity. -// -// Setting a larger minimum capacity may be used to prevent resizing when the -// number of stored items changes frequently across a wide range. -func (q *Deque[T]) SetMinCapacity(minCapacityExp uint) { - if 1< minCapacity { - q.minCap = 1 << minCapacityExp - } else { - q.minCap = minCapacity +// SetBaseCap sets a base capacity so that at least the specified number of +// items can always be stored without resizing. +func (q *Deque[T]) SetBaseCap(baseCap int) { + minCap := minCapacity + for minCap < baseCap { + minCap <<= 1 + } + q.minCap = minCap +} + +// Swap exchanges the two values at idxA and idxB. It panics if either index is +// out of range. +func (q *Deque[T]) Swap(idxA, idxB int) { + q.checkRange(idxA) + q.checkRange(idxB) + if idxA == idxB { + return + } + + realA := (q.head + idxA) & (len(q.buf) - 1) + realB := (q.head + idxB) & (len(q.buf) - 1) + q.buf[realA], q.buf[realB] = q.buf[realB], q.buf[realA] +} + +func (q *Deque[T]) checkRange(i int) { + if i < 0 || i >= q.count { + panic(fmt.Sprintf("deque: index out of range %d with length %d", i, q.Len())) } } @@ -385,21 +406,21 @@ func (q *Deque[T]) growIfFull() { q.buf = make([]T, q.minCap) return } - q.resize() + q.resize(q.count << 1) } // shrinkIfExcess resize down if the buffer 1/4 full. func (q *Deque[T]) shrinkIfExcess() { if len(q.buf) > q.minCap && (q.count<<2) == len(q.buf) { - q.resize() + q.resize(q.count << 1) } } // resize resizes the deque to fit exactly twice its current contents. This is // used to grow the queue when it is full, and also to shrink it when it is // only a quarter full. -func (q *Deque[T]) resize() { - newBuf := make([]T, q.count<<1) +func (q *Deque[T]) resize(newSize int) { + newBuf := make([]T, newSize) if q.tail > q.head { copy(newBuf, q.buf[q.head:q.tail]) } else { diff --git a/vendor/github.com/gammazero/deque/doc.go b/vendor/github.com/gammazero/deque/doc.go index 3e6832b9..dfff00ad 100644 --- a/vendor/github.com/gammazero/deque/doc.go +++ b/vendor/github.com/gammazero/deque/doc.go @@ -4,10 +4,10 @@ implementation. Deque generalizes a queue and a stack, to efficiently add and remove items at either end with O(1) performance. Queue (FIFO) operations are supported using -PushBack() and PopFront(). Stack (LIFO) operations are supported using -PushBack() and PopBack(). +PushBack and PopFront. Stack (LIFO) operations are supported using PushBack and +PopBack. -Ring-buffer Performance +# Ring-buffer Performance The ring-buffer automatically resizes by powers of two, growing when additional capacity is needed and shrinking when only a quarter of the capacity is used, @@ -20,7 +20,7 @@ and linked lists. For maximum speed, this deque implementation leaves concurrency safety up to the application to provide, however the application chooses, if needed at all. -Reading Empty Deque +# Reading Empty Deque Since it is OK for the deque to contain the zero-value of an item, it is necessary to either panic or return a second boolean value to indicate the @@ -29,11 +29,10 @@ reading from an empty deque. This is a run-time check to help catch programming errors, which may be missed if a second return value is ignored. Simply check Deque.Len() before reading from the deque. -Generics +# Generics Deque uses generics to create a Deque that contains items of the type specified. To create a Deque that holds a specific type, provide a type -argument to New or with the variable declaration. - +argument with the Deque variable declaration. */ package deque diff --git a/vendor/github.com/godbus/dbus/v5/CONTRIBUTING.md b/vendor/github.com/godbus/dbus/v5/CONTRIBUTING.md deleted file mode 100644 index c88f9b2b..00000000 --- a/vendor/github.com/godbus/dbus/v5/CONTRIBUTING.md +++ /dev/null @@ -1,50 +0,0 @@ -# How to Contribute - -## Getting Started - -- Fork the repository on GitHub -- Read the [README](README.markdown) for build and test instructions -- Play with the project, submit bugs, submit patches! - -## Contribution Flow - -This is a rough outline of what a contributor's workflow looks like: - -- Create a topic branch from where you want to base your work (usually master). -- Make commits of logical units. -- Make sure your commit messages are in the proper format (see below). -- Push your changes to a topic branch in your fork of the repository. -- Make sure the tests pass, and add any new tests as appropriate. -- Submit a pull request to the original repository. - -Thanks for your contributions! - -### Format of the Commit Message - -We follow a rough convention for commit messages that is designed to answer two -questions: what changed and why. The subject line should feature the what and -the body of the commit should describe the why. - -``` -scripts: add the test-cluster command - -this uses tmux to setup a test cluster that you can easily kill and -start for debugging. - -Fixes #38 -``` - -The format can be described more formally as follows: - -``` -: - - - -