From dd770983ea1654e41b131842346705c4b443b9ed Mon Sep 17 00:00:00 2001 From: Andrew Beekhof Date: Wed, 15 Jul 2020 16:33:03 +1000 Subject: [PATCH] Make rebooting an optional part of applying updates Drain and cordon is always performed Currently the only exception is /etc/containers/registry.conf Treat skipped systemd actions as errors and remove support for unused actions Switch to more generic config arrangement Fix formatting and remove dead code Draining is unconditional, no need to do it twice Add basic unit tests Improved unit tests Convert actionResult to a public interface so that we can compare them Add additional unit tests for applying file based changes Removed unneeded test inputs Reduce the cyclomatic complexity of update() in pkg/daemon Remove unnecessary use of Daemon from ActionResult Log each action description in ActionResult tests Prevent use-of-nil when finalizing MachineConfig updates Add an e2e test for registry changes Rename the interface for config update actions Refactor MCD bootstrap finalize so that it can be reused for the non-reboots flow Remove all strategies that do not invole reboots Signed-off-by: Andrew Beekhof --- go.mod | 3 +- go.sum | 4 + pkg/daemon/apply.go | 199 ++++ pkg/daemon/apply_test.go | 291 ++++++ pkg/daemon/daemon.go | 42 +- .../testdata/machine-configs/test-base.yaml | 255 +++++ pkg/daemon/update.go | 50 +- test/e2e/framework/clientset.go | 3 + test/e2e/mcd_test.go | 4 + .../github.com/coreos/go-systemd/dbus/dbus.go | 240 +++++ .../coreos/go-systemd/dbus/methods.go | 600 +++++++++++ .../coreos/go-systemd/dbus/properties.go | 237 +++++ .../github.com/coreos/go-systemd/dbus/set.go | 47 + .../coreos/go-systemd/dbus/subscription.go | 333 ++++++ .../go-systemd/dbus/subscription_set.go | 57 + .../github.com/deckarep/golang-set/.gitignore | 22 + .../deckarep/golang-set/.travis.yml | 11 + vendor/github.com/deckarep/golang-set/LICENSE | 22 + .../github.com/deckarep/golang-set/README.md | 95 ++ .../deckarep/golang-set/iterator.go | 58 ++ vendor/github.com/deckarep/golang-set/set.go | 217 ++++ .../deckarep/golang-set/threadsafe.go | 283 +++++ .../deckarep/golang-set/threadunsafe.go | 337 ++++++ .../go-toolsmith/astcast/astcast_generate.go | 163 +++ .../typep/simplePredicates_generate.go | 140 +++ vendor/github.com/godbus/dbus/.travis.yml | 46 + vendor/github.com/godbus/dbus/CONTRIBUTING.md | 50 + vendor/github.com/godbus/dbus/LICENSE | 25 + vendor/github.com/godbus/dbus/MAINTAINERS | 3 + vendor/github.com/godbus/dbus/README.markdown | 44 + vendor/github.com/godbus/dbus/auth.go | 252 +++++ .../github.com/godbus/dbus/auth_anonymous.go | 16 + .../github.com/godbus/dbus/auth_external.go | 26 + vendor/github.com/godbus/dbus/auth_sha1.go | 102 ++ vendor/github.com/godbus/dbus/call.go | 60 ++ vendor/github.com/godbus/dbus/conn.go | 868 +++++++++++++++ vendor/github.com/godbus/dbus/conn_darwin.go | 37 + vendor/github.com/godbus/dbus/conn_other.go | 93 ++ vendor/github.com/godbus/dbus/conn_unix.go | 18 + vendor/github.com/godbus/dbus/conn_windows.go | 15 + vendor/github.com/godbus/dbus/dbus.go | 427 ++++++++ vendor/github.com/godbus/dbus/decoder.go | 286 +++++ .../github.com/godbus/dbus/default_handler.go | 328 ++++++ vendor/github.com/godbus/dbus/doc.go | 69 ++ vendor/github.com/godbus/dbus/encoder.go | 210 ++++ vendor/github.com/godbus/dbus/export.go | 412 ++++++++ vendor/github.com/godbus/dbus/go.mod | 3 + vendor/github.com/godbus/dbus/homedir.go | 28 + .../github.com/godbus/dbus/homedir_dynamic.go | 15 + .../github.com/godbus/dbus/homedir_static.go | 45 + vendor/github.com/godbus/dbus/match.go | 62 ++ vendor/github.com/godbus/dbus/message.go | 353 +++++++ vendor/github.com/godbus/dbus/object.go | 211 ++++ .../godbus/dbus/server_interfaces.go | 107 ++ vendor/github.com/godbus/dbus/sig.go | 259 +++++ .../godbus/dbus/transport_darwin.go | 6 + .../godbus/dbus/transport_generic.go | 50 + .../godbus/dbus/transport_nonce_tcp.go | 39 + .../github.com/godbus/dbus/transport_tcp.go | 41 + .../github.com/godbus/dbus/transport_unix.go | 214 ++++ .../dbus/transport_unixcred_dragonfly.go | 95 ++ .../godbus/dbus/transport_unixcred_freebsd.go | 91 ++ .../godbus/dbus/transport_unixcred_linux.go | 25 + .../godbus/dbus/transport_unixcred_openbsd.go | 14 + vendor/github.com/godbus/dbus/variant.go | 144 +++ .../github.com/godbus/dbus/variant_lexer.go | 284 +++++ .../github.com/godbus/dbus/variant_parser.go | 817 +++++++++++++++ vendor/github.com/google/btree/btree_mem.go | 76 ++ vendor/golang.org/x/sys/unix/mkasm_darwin.go | 78 ++ vendor/golang.org/x/sys/unix/mkpost.go | 122 +++ vendor/golang.org/x/sys/unix/mksyscall.go | 402 +++++++ .../x/sys/unix/mksyscall_aix_ppc.go | 415 ++++++++ .../x/sys/unix/mksyscall_aix_ppc64.go | 614 +++++++++++ .../x/sys/unix/mksyscall_solaris.go | 335 ++++++ .../golang.org/x/sys/unix/mksysctl_openbsd.go | 355 +++++++ vendor/golang.org/x/sys/unix/mksysnum.go | 190 ++++ vendor/golang.org/x/sys/unix/types_aix.go | 237 +++++ vendor/golang.org/x/sys/unix/types_darwin.go | 283 +++++ .../golang.org/x/sys/unix/types_dragonfly.go | 263 +++++ vendor/golang.org/x/sys/unix/types_freebsd.go | 400 +++++++ vendor/golang.org/x/sys/unix/types_netbsd.go | 290 ++++++ vendor/golang.org/x/sys/unix/types_openbsd.go | 283 +++++ vendor/golang.org/x/sys/unix/types_solaris.go | 266 +++++ .../text/encoding/internal/identifier/gen.go | 142 +++ vendor/golang.org/x/text/unicode/bidi/gen.go | 133 +++ .../x/text/unicode/bidi/gen_ranges.go | 57 + .../x/text/unicode/bidi/gen_trieval.go | 64 ++ .../x/text/unicode/norm/maketables.go | 986 ++++++++++++++++++ .../golang.org/x/text/unicode/norm/triegen.go | 117 +++ vendor/golang.org/x/text/width/gen.go | 115 ++ vendor/golang.org/x/text/width/gen_common.go | 96 ++ vendor/golang.org/x/text/width/gen_trieval.go | 34 + .../x/tools/internal/imports/mkindex.go | 173 +++ .../x/tools/internal/imports/mkstdlib.go | 128 +++ vendor/modules.txt | 684 ++++++------ 95 files changed, 16987 insertions(+), 354 deletions(-) create mode 100644 pkg/daemon/apply.go create mode 100644 pkg/daemon/apply_test.go create mode 100644 pkg/daemon/testdata/machine-configs/test-base.yaml create mode 100644 vendor/github.com/coreos/go-systemd/dbus/dbus.go create mode 100644 vendor/github.com/coreos/go-systemd/dbus/methods.go create mode 100644 vendor/github.com/coreos/go-systemd/dbus/properties.go create mode 100644 vendor/github.com/coreos/go-systemd/dbus/set.go create mode 100644 vendor/github.com/coreos/go-systemd/dbus/subscription.go create mode 100644 vendor/github.com/coreos/go-systemd/dbus/subscription_set.go create mode 100644 vendor/github.com/deckarep/golang-set/.gitignore create mode 100644 vendor/github.com/deckarep/golang-set/.travis.yml create mode 100644 vendor/github.com/deckarep/golang-set/LICENSE create mode 100644 vendor/github.com/deckarep/golang-set/README.md create mode 100644 vendor/github.com/deckarep/golang-set/iterator.go create mode 100644 vendor/github.com/deckarep/golang-set/set.go create mode 100644 vendor/github.com/deckarep/golang-set/threadsafe.go create mode 100644 vendor/github.com/deckarep/golang-set/threadunsafe.go create mode 100644 vendor/github.com/go-toolsmith/astcast/astcast_generate.go create mode 100644 vendor/github.com/go-toolsmith/typep/simplePredicates_generate.go create mode 100644 vendor/github.com/godbus/dbus/.travis.yml create mode 100644 vendor/github.com/godbus/dbus/CONTRIBUTING.md create mode 100644 vendor/github.com/godbus/dbus/LICENSE create mode 100644 vendor/github.com/godbus/dbus/MAINTAINERS create mode 100644 vendor/github.com/godbus/dbus/README.markdown create mode 100644 vendor/github.com/godbus/dbus/auth.go create mode 100644 vendor/github.com/godbus/dbus/auth_anonymous.go create mode 100644 vendor/github.com/godbus/dbus/auth_external.go create mode 100644 vendor/github.com/godbus/dbus/auth_sha1.go create mode 100644 vendor/github.com/godbus/dbus/call.go create mode 100644 vendor/github.com/godbus/dbus/conn.go create mode 100644 vendor/github.com/godbus/dbus/conn_darwin.go create mode 100644 vendor/github.com/godbus/dbus/conn_other.go create mode 100644 vendor/github.com/godbus/dbus/conn_unix.go create mode 100644 vendor/github.com/godbus/dbus/conn_windows.go create mode 100644 vendor/github.com/godbus/dbus/dbus.go create mode 100644 vendor/github.com/godbus/dbus/decoder.go create mode 100644 vendor/github.com/godbus/dbus/default_handler.go create mode 100644 vendor/github.com/godbus/dbus/doc.go create mode 100644 vendor/github.com/godbus/dbus/encoder.go create mode 100644 vendor/github.com/godbus/dbus/export.go create mode 100644 vendor/github.com/godbus/dbus/go.mod create mode 100644 vendor/github.com/godbus/dbus/homedir.go create mode 100644 vendor/github.com/godbus/dbus/homedir_dynamic.go create mode 100644 vendor/github.com/godbus/dbus/homedir_static.go create mode 100644 vendor/github.com/godbus/dbus/match.go create mode 100644 vendor/github.com/godbus/dbus/message.go create mode 100644 vendor/github.com/godbus/dbus/object.go create mode 100644 vendor/github.com/godbus/dbus/server_interfaces.go create mode 100644 vendor/github.com/godbus/dbus/sig.go create mode 100644 vendor/github.com/godbus/dbus/transport_darwin.go create mode 100644 vendor/github.com/godbus/dbus/transport_generic.go create mode 100644 vendor/github.com/godbus/dbus/transport_nonce_tcp.go create mode 100644 vendor/github.com/godbus/dbus/transport_tcp.go create mode 100644 vendor/github.com/godbus/dbus/transport_unix.go create mode 100644 vendor/github.com/godbus/dbus/transport_unixcred_dragonfly.go create mode 100644 vendor/github.com/godbus/dbus/transport_unixcred_freebsd.go create mode 100644 vendor/github.com/godbus/dbus/transport_unixcred_linux.go create mode 100644 vendor/github.com/godbus/dbus/transport_unixcred_openbsd.go create mode 100644 vendor/github.com/godbus/dbus/variant.go create mode 100644 vendor/github.com/godbus/dbus/variant_lexer.go create mode 100644 vendor/github.com/godbus/dbus/variant_parser.go create mode 100644 vendor/github.com/google/btree/btree_mem.go create mode 100644 vendor/golang.org/x/sys/unix/mkasm_darwin.go create mode 100644 vendor/golang.org/x/sys/unix/mkpost.go create mode 100644 vendor/golang.org/x/sys/unix/mksyscall.go create mode 100644 vendor/golang.org/x/sys/unix/mksyscall_aix_ppc.go create mode 100644 vendor/golang.org/x/sys/unix/mksyscall_aix_ppc64.go create mode 100644 vendor/golang.org/x/sys/unix/mksyscall_solaris.go create mode 100644 vendor/golang.org/x/sys/unix/mksysctl_openbsd.go create mode 100644 vendor/golang.org/x/sys/unix/mksysnum.go create mode 100644 vendor/golang.org/x/sys/unix/types_aix.go create mode 100644 vendor/golang.org/x/sys/unix/types_darwin.go create mode 100644 vendor/golang.org/x/sys/unix/types_dragonfly.go create mode 100644 vendor/golang.org/x/sys/unix/types_freebsd.go create mode 100644 vendor/golang.org/x/sys/unix/types_netbsd.go create mode 100644 vendor/golang.org/x/sys/unix/types_openbsd.go create mode 100644 vendor/golang.org/x/sys/unix/types_solaris.go create mode 100644 vendor/golang.org/x/text/encoding/internal/identifier/gen.go create mode 100644 vendor/golang.org/x/text/unicode/bidi/gen.go create mode 100644 vendor/golang.org/x/text/unicode/bidi/gen_ranges.go create mode 100644 vendor/golang.org/x/text/unicode/bidi/gen_trieval.go create mode 100644 vendor/golang.org/x/text/unicode/norm/maketables.go create mode 100644 vendor/golang.org/x/text/unicode/norm/triegen.go create mode 100644 vendor/golang.org/x/text/width/gen.go create mode 100644 vendor/golang.org/x/text/width/gen_common.go create mode 100644 vendor/golang.org/x/text/width/gen_trieval.go create mode 100644 vendor/golang.org/x/tools/internal/imports/mkindex.go create mode 100644 vendor/golang.org/x/tools/internal/imports/mkstdlib.go diff --git a/go.mod b/go.mod index 8b251ecb76..95f4b4aa8c 100644 --- a/go.mod +++ b/go.mod @@ -17,11 +17,12 @@ require ( github.com/containers/storage v1.13.5 github.com/coreos/fcct v0.5.0 github.com/coreos/go-semver v0.3.0 - github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f // indirect + github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f github.com/coreos/ign-converter v0.0.0-20200629171308-e40a44f244c5 github.com/coreos/ignition v0.35.0 github.com/coreos/ignition/v2 v2.3.0 github.com/davecgh/go-spew v1.1.1 + github.com/deckarep/golang-set v1.7.1 github.com/docker/docker v1.4.2-0.20190927142053-ada3c14355ce // indirect github.com/docker/docker-credential-helpers v0.6.3 // indirect github.com/docker/go-connections v0.4.0 // indirect diff --git a/go.sum b/go.sum index 6ed2abc3f7..4af80da3a2 100644 --- a/go.sum +++ b/go.sum @@ -138,6 +138,8 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/daviddengcn/go-colortext v0.0.0-20160507010035-511bcaf42ccd/go.mod h1:dv4zxwHi5C/8AeI+4gX4dCWOIvNi7I6JCSX0HvlKPgE= +github.com/deckarep/golang-set v1.7.1 h1:SCQV0S6gTtp6itiFrTqI+pfmJ4LN85S1YzhDf9rTHJQ= +github.com/deckarep/golang-set v1.7.1/go.mod h1:93vsz/8Wt4joVM7c2AVqh+YRMiUSc14yDtF28KmMOgQ= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/docker/distribution v0.0.0-20180920194744-16128bbac47f/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= github.com/docker/distribution v2.7.1+incompatible h1:a5mlkVzth6W5A4fOsS3D2EO5BUmsJpcB+cRlLU7cSug= @@ -290,6 +292,7 @@ github.com/go-toolsmith/typep v1.0.0 h1:zKymWyA1TRYvqYrYDrfEMZULyrhcnGY3x7LDKU2X github.com/go-toolsmith/typep v1.0.0/go.mod h1:JSQCQMUPdRlMZFswiq3TGpNp1GMktqkR2Ns5AIQkATU= github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= +github.com/godbus/dbus v0.0.0-20190623212516-8a1682060722 h1:NNKZiuNXd6lpZRyoFM/uhssj5W9Ps1DbhGHxT49Pm9I= github.com/godbus/dbus v0.0.0-20190623212516-8a1682060722/go.mod h1:bBOAhwG1umN6/6ZUMtDFBMQR8jRg9O75tm9K00oMsK4= github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= @@ -554,6 +557,7 @@ github.com/openshift/build-machinery-go v0.0.0-20200424080330-082bf86082cc/go.mo github.com/openshift/client-go v0.0.0-20190617165122-8892c0adc000/go.mod h1:6rzn+JTr7+WYS2E1TExP4gByoABxMznR6y2SnUIkmxk= github.com/openshift/client-go v0.0.0-20200320150128-a906f3d8e723 h1:FfrELmZ9N9NtVE15qmTRkJIETX75QHdr65xiuTKvNYo= github.com/openshift/client-go v0.0.0-20200320150128-a906f3d8e723/go.mod h1:wNBSSt4RZTHhUWyhBE3gxTR32QpF9DB2SfS14u2IxuE= +github.com/openshift/client-go v3.9.0+incompatible h1:13k3Ok0B7TA2hA3bQW2aFqn6y04JaJWdk7ITTyg+Ek0= github.com/openshift/library-go v0.0.0-20190619114638-6b58b672ee58/go.mod h1:NBttNjZpWwup/nthuLbPAPSYC8Qyo+BBK5bCtFoyYjo= github.com/openshift/library-go v0.0.0-20191003152030-97c62d8a2901 h1:UQkY3zDJG4MMVzS7VFsyACxs/haMJ2aHNR/jXzWhScs= github.com/openshift/library-go v0.0.0-20191003152030-97c62d8a2901/go.mod h1:NBttNjZpWwup/nthuLbPAPSYC8Qyo+BBK5bCtFoyYjo= diff --git a/pkg/daemon/apply.go b/pkg/daemon/apply.go new file mode 100644 index 0000000000..f10d8bf723 --- /dev/null +++ b/pkg/daemon/apply.go @@ -0,0 +1,199 @@ +package daemon + +import ( + "fmt" + "reflect" + "strings" + + "k8s.io/client-go/tools/record" + + "github.com/coreos/go-systemd/dbus" + igntypes "github.com/coreos/ignition/v2/config/v3_1/types" + mapset "github.com/deckarep/golang-set" + "github.com/golang/glog" + mcfgv1 "github.com/openshift/machine-config-operator/pkg/apis/machineconfiguration.openshift.io/v1" + ctrlcommon "github.com/openshift/machine-config-operator/pkg/controller/common" +) + +type ConfigUpdateAction interface { + Describe() string + Execute(dn *Daemon, newConfig *mcfgv1.MachineConfig) error +} + +type RebootPostAction struct { + ConfigUpdateAction + + Reason string +} + +func (a RebootPostAction) Describe() string { + return fmt.Sprintf("Rebooting node: %v", a.Reason) +} + +func (a RebootPostAction) Execute(dn *Daemon, newConfig *mcfgv1.MachineConfig) error { + return dn.finalizeAndReboot(newConfig) +} + +type ServicePostAction struct { + ConfigUpdateAction + + Reason string + + ServiceName string + ServiceAction string +} + +func (a ServicePostAction) Describe() string { + return fmt.Sprintf("Restarting service %v", a.Reason) +} + +func (a ServicePostAction) Execute(dn *Daemon, newConfig *mcfgv1.MachineConfig) error { + // TODO: add support for stop and reload operations if necessary + // For now only restart operation is supported + + eventBroadcaster := record.NewBroadcaster() + eventBroadcaster.StartLogging(glog.V(2).Infof) + + systemdConnection, dbusConnErr := dbus.NewSystemConnection() + if dbusConnErr != nil { + glog.Warningf("Unable to establish systemd dbus connection: %s", dbusConnErr) + return dbusConnErr + } + + defer systemdConnection.Close() + + var err error + outputChannel := make(chan string) + switch a.ServiceAction { + case "restart": + glog.Infof("Restarting unit %q", a.ServiceName) + _, err = systemdConnection.RestartUnit(a.ServiceName, "replace", outputChannel) + default: + return fmt.Errorf("Unhandled systemd action %q for %q", a.ServiceAction, a.ServiceName) + } + + if err != nil { + return fmt.Errorf("Running systemd action failed: %s", err) + } + + // If the provided channel is non-nil, a result string will be sent to it upon + // job completion + output := <-outputChannel + switch output { + // one of: done, canceled, timeout, failed, dependency, skipped. + case "done": + glog.Infof("Systemd action %q for %q completed successful: %s", a.ServiceAction, a.ServiceName, output) + case "skipped": + // The code suggests that 'skipped indicates that a job was + // skipped because it didn't apply to the units current state' + // + // This should only apply to stop and start actions which we + // don't support yet so treat it like an error for now + return fmt.Errorf("Systemd action %q for %q was skipped: %s", a.ServiceAction, a.ServiceName, output) + default: + return fmt.Errorf("Systemd action %q for %q failed: %s", a.ServiceAction, a.ServiceName, output) + } + return nil +} + +func getFileNames(files []igntypes.File) []interface{} { + names := make([]interface{}, len(files)) + for i, file := range files { + names[i] = file.Path + } + return names +} + +func filesToMap(files []igntypes.File) map[string]igntypes.File { + fileMap := make(map[string]igntypes.File, len(files)) + for _, file := range files { + fileMap[file.Path] = file + } + return fileMap +} + +type ChangeStrategy struct { + actions []ConfigUpdateAction +} + +func lookupStrategy(stripPrefix, filePath string) ([]ConfigUpdateAction, error) { + + strategies := map[string]ChangeStrategy{} + + key := filePath + if len(stripPrefix) > 0 { + key = strings.TrimPrefix(filePath, stripPrefix) + } + + if strategy, ok := strategies[key]; ok { + return strategy.actions, nil + } + return []ConfigUpdateAction{}, fmt.Errorf("Default strategy for applying changes to %q", key) +} + +func getFileChanges(stripPrefix string, oldIgnConfig, newIgnConfig igntypes.Config) []ConfigUpdateAction { + actions := []ConfigUpdateAction{} + + oldFiles := mapset.NewSetFromSlice(getFileNames(oldIgnConfig.Storage.Files)) + newFiles := mapset.NewSetFromSlice(getFileNames(newIgnConfig.Storage.Files)) + + for filename := range newFiles.Difference(oldFiles).Iter() { + return []ConfigUpdateAction{RebootPostAction{Reason: fmt.Sprintf("File %q was added", filename.(string))}} + } + + for filename := range oldFiles.Difference(newFiles).Iter() { + return []ConfigUpdateAction{RebootPostAction{Reason: fmt.Sprintf("File %q was removed", filename.(string))}} + } + + newFilesMap := filesToMap(newIgnConfig.Storage.Files) + for file := range newFiles.Intersect(oldFiles).Iter() { + candidate := newFilesMap[file.(string)] + if err := checkV3Files([]igntypes.File{candidate}); err != nil { + strategyActions, err := lookupStrategy(stripPrefix, candidate.Node.Path) + if err == nil { + for _, a := range strategyActions { + actions = append(actions, a) + } + } else { + return []ConfigUpdateAction{RebootPostAction{Reason: err.Error()}} + } + } + } + + return actions +} + +func calculateActions(stripPrefix string, oldConfig, newConfig *mcfgv1.MachineConfig, diff *machineConfigDiff) []ConfigUpdateAction { + + if diff.osUpdate || diff.kargs || diff.fips || diff.kernelType { + return []ConfigUpdateAction{RebootPostAction{Reason: "OS/Kernel changed"}} + } + + oldIgnConfig, err := ctrlcommon.ParseAndConvertConfig(oldConfig.Spec.Config.Raw) + if err != nil { + return []ConfigUpdateAction{RebootPostAction{ + Reason: fmt.Sprintf("parsing old Ignition config failed with error: %v", err)}} + } + newIgnConfig, err := ctrlcommon.ParseAndConvertConfig(newConfig.Spec.Config.Raw) + if err != nil { + return []ConfigUpdateAction{RebootPostAction{ + Reason: fmt.Sprintf("parsing new Ignition config failed with error: %v", err)}} + } + + // Check for any changes not already excluded by Reconcilable() + // Alternatively, fold this code into that function + if !reflect.DeepEqual(oldIgnConfig.Ignition, newIgnConfig.Ignition) { + return []ConfigUpdateAction{RebootPostAction{Reason: "Ignition changed"}} + } + if !reflect.DeepEqual(oldIgnConfig.Passwd, newIgnConfig.Passwd) { + return []ConfigUpdateAction{RebootPostAction{Reason: "Passwords changed"}} + } + if !reflect.DeepEqual(oldIgnConfig.Systemd, newIgnConfig.Systemd) { + return []ConfigUpdateAction{RebootPostAction{Reason: "Systemd configuration changed"}} + } + if !reflect.DeepEqual(oldIgnConfig.Storage.Files, newIgnConfig.Storage.Files) { + return getFileChanges(stripPrefix, oldIgnConfig, newIgnConfig) + } + + return []ConfigUpdateAction{} +} diff --git a/pkg/daemon/apply_test.go b/pkg/daemon/apply_test.go new file mode 100644 index 0000000000..b685c63d9a --- /dev/null +++ b/pkg/daemon/apply_test.go @@ -0,0 +1,291 @@ +package daemon + +import ( + "bytes" + "fmt" + "io/ioutil" + "os" + "path/filepath" + "testing" + + "github.com/BurntSushi/toml" + "github.com/clarketm/json" + "github.com/containers/image/pkg/sysregistriesv2" + yaml "github.com/ghodss/yaml" + "github.com/stretchr/testify/assert" + "github.com/vincent-petithory/dataurl" + + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/util/diff" + + igntypes "github.com/coreos/ignition/v2/config/v3_1/types" + mcfgv1 "github.com/openshift/machine-config-operator/pkg/apis/machineconfiguration.openshift.io/v1" + ctrlcommon "github.com/openshift/machine-config-operator/pkg/controller/common" + + "github.com/google/go-cmp/cmp" +) + +const ( + testDir = "./testdata" +) + +// Test prefix handling, required to facilitate unit testing of +// calculateActions() +func TestStrategyLookup(t *testing.T) { + aPrefix := "/somewhere/else" + _, err := lookupStrategy("", "/etc/containers/registries.conf") + assert.Equal(t, err, fmt.Errorf("Default strategy for applying changes to \"/etc/containers/registries.conf\"")) + + _, err = lookupStrategy(aPrefix, filepath.Join(aPrefix, "/etc/containers/registries.conf")) + assert.Equal(t, err, fmt.Errorf("Default strategy for applying changes to \"/etc/containers/registries.conf\"")) +} + +// TestCalculateActions attempts to verify the actions needed to apply the +// changes of reconcilable config changes. +func TestCalculateActions(t *testing.T) { + tests := []struct { + name string + mConfig string + modifyConfig func(t *testing.T, prefix string, ignCfg igntypes.Config) igntypes.Config + expectedActions []ConfigUpdateAction + }{{ + name: "no-op", + mConfig: "test-base", + modifyConfig: func(t *testing.T, prefix string, ignCfg igntypes.Config) igntypes.Config { + return ignCfg + }, + expectedActions: []ConfigUpdateAction{}, + }, { + name: "modified unit", + mConfig: "test-base", + modifyConfig: func(t *testing.T, prefix string, ignCfg igntypes.Config) igntypes.Config { + newValue := false + ignCfg.Systemd.Units[0].Enabled = &newValue + return ignCfg + }, + expectedActions: []ConfigUpdateAction{ + RebootPostAction{Reason: "Systemd configuration changed"}, + }, + }, { + mConfig: "test-base", + name: "inotify change", + modifyConfig: func(t *testing.T, prefix string, ignCfg igntypes.Config) igntypes.Config { + for i := range ignCfg.Storage.Files { + f := &ignCfg.Storage.Files[i] + if f.Path == filepath.Join(prefix, "/etc/sysctl.d/inotify.conf") { + newData := []byte("fs.inotify.max_user_watches = 65530\nfs.inotify.max_user_instances = 8192") + configdu := dataurl.New(newData, "text/plain") + configdu.Encoding = dataurl.EncodingASCII + data := configdu.String() + f.Contents.Source = &data + } + } + + return ignCfg + }, + expectedActions: []ConfigUpdateAction{ + RebootPostAction{Reason: "Default strategy for applying changes to \"/etc/sysctl.d/inotify.conf\""}, + }, + }, { + mConfig: "test-base", + name: "icsp url change", + modifyConfig: func(t *testing.T, prefix string, ignCfg igntypes.Config) igntypes.Config { + for i := range ignCfg.Storage.Files { + f := &ignCfg.Storage.Files[i] + if f.Path == filepath.Join(prefix, "/etc/containers/registries.conf") { + tomlConf := sysregistriesv2.V2RegistriesConf{ + UnqualifiedSearchRegistries: []string{"registry.access.redhat.com", "docker.io"}, + Registries: []sysregistriesv2.Registry{ + { + Endpoint: sysregistriesv2.Endpoint{ + Location: "registry.product.example.org/ocp/4.2-DATE-VERSION", + }, + MirrorByDigestOnly: true, + Mirrors: []sysregistriesv2.Endpoint{ + {Location: "registry.mirror.example.com/ocp"}, + }, + }, + { + Endpoint: sysregistriesv2.Endpoint{ + Location: "registry.product.example.org/ocp/release", + }, + MirrorByDigestOnly: true, + Mirrors: []sysregistriesv2.Endpoint{ + {Location: "registry.mirror.example.com/ocp4"}, + }, + }, + }, + } + + data := encodeRegistries(t, tomlConf) + f.Contents.Source = &data + } + } + + return ignCfg + }, + expectedActions: []ConfigUpdateAction{ + RebootPostAction{Reason: "Default strategy for applying changes to \"/etc/containers/registries.conf\""}, + }, + }, { + mConfig: "test-base", + name: "icsp and systemd change", + modifyConfig: func(t *testing.T, prefix string, ignCfg igntypes.Config) igntypes.Config { + newValue := false + ignCfg.Systemd.Units[0].Enabled = &newValue + for i := range ignCfg.Storage.Files { + f := &ignCfg.Storage.Files[i] + if f.Path == filepath.Join(prefix, "/etc/containers/registries.conf") { + tomlConf := sysregistriesv2.V2RegistriesConf{} + + data := encodeRegistries(t, tomlConf) + f.Contents.Source = &data + } + + } + + return ignCfg + }, + expectedActions: []ConfigUpdateAction{ + RebootPostAction{Reason: "Systemd configuration changed"}, + }, + }, { + mConfig: "test-base", + name: "icsp and inotify change", + modifyConfig: func(t *testing.T, prefix string, ignCfg igntypes.Config) igntypes.Config { + for i := range ignCfg.Storage.Files { + f := &ignCfg.Storage.Files[i] + if f.Path == filepath.Join(prefix, "/etc/containers/registries.conf") { + tomlConf := sysregistriesv2.V2RegistriesConf{ + UnqualifiedSearchRegistries: []string{"registry.access.redhat.com", "docker.io"}, + Registries: []sysregistriesv2.Registry{}, + } + + data := encodeRegistries(t, tomlConf) + f.Contents.Source = &data + + } else if f.Path == filepath.Join(prefix, "/etc/sysctl.d/inotify.conf") { + newData := []byte("fs.inotify.max_user_watches = 65530\nfs.inotify.max_user_instances = 8192") + configdu := dataurl.New(newData, "text/plain") + configdu.Encoding = dataurl.EncodingASCII + data := configdu.String() + f.Contents.Source = &data + } + + } + + return ignCfg + }, + expectedActions: []ConfigUpdateAction{ + RebootPostAction{Reason: "Default strategy for applying changes to \"/etc/containers/registries.conf\""}, + }, + }} + + for idx, test := range tests { + t.Run(fmt.Sprintf("test#%d_%s", idx, test.name), func(t *testing.T) { + tempDir, err := ioutil.TempDir(os.TempDir(), "mco-apply-") + assert.Nil(t, err) + + oldMC := readMachineConfig(t, test.mConfig) + fixCfg, err := ctrlcommon.ParseAndConvertConfig(oldMC.Spec.Config.Raw) + assert.Nil(t, err) + + // Re-write all File paths with a prefix + // + // Part of identifying actions that need to be performed + // involves CheckV3Files which looks at the disk. We + // don't want to write to the real '/', and relative + // paths are not legal Ignition entries, so append a + // prefix to a temporary directory. + // + for i := range fixCfg.Storage.Files { + f := &fixCfg.Storage.Files[i] + f.Path = filepath.Join(tempDir, f.Path) + } + + // Cannot use writeFiles as it wants to write to + // /etc/machine-config-daemon when populating an empty + // filesystem + err = populateFiles(fixCfg.Storage.Files) + assert.Nil(t, err) + + fixedIgnCfg, err := json.Marshal(fixCfg) + assert.Nil(t, err) + + oldMC.Spec.Config = runtime.RawExtension{ + Raw: fixedIgnCfg, + } + + newMC := oldMC.DeepCopy() + + ignCfg, err := ctrlcommon.ParseAndConvertConfig(newMC.Spec.Config.Raw) + assert.Nil(t, err) + + newIgnCfg := test.modifyConfig(t, tempDir, ignCfg) + rawIgnCfg, err := json.Marshal(newIgnCfg) + assert.Nil(t, err) + + newMC.Spec.Config = runtime.RawExtension{ + Raw: rawIgnCfg, + } + + failed := false + configDiff, _ := reconcilable(oldMC, newMC) + actions := calculateActions(tempDir, oldMC, newMC, configDiff) + if len(actions) != len(test.expectedActions) { + failed = true + + } else if ! cmp.Equal(test.expectedActions, actions) { + failed = true + } + if failed { + t.Error(diff.ObjectDiff(test.expectedActions, actions)) + } else { + for _, action := range actions { + fmt.Printf("Executing action [%v]\n", action.Describe()) + } + } + os.Remove(tempDir) + }) + } + +} + +func readMachineConfig(t *testing.T, file string) *mcfgv1.MachineConfig { + mcPath := filepath.Join(testDir, "machine-configs", file+".yaml") + mcData, err := ioutil.ReadFile(mcPath) + assert.Nil(t, err) + mc := new(mcfgv1.MachineConfig) + err = yaml.Unmarshal([]byte(mcData), mc) + assert.Nil(t, err) + return mc +} + +func encodeRegistries(t *testing.T, rConf sysregistriesv2.V2RegistriesConf) string { + var newData bytes.Buffer + encoder := toml.NewEncoder(&newData) + err := encoder.Encode(rConf) + assert.Nil(t, err) + + configdu := dataurl.New(newData.Bytes(), "text/plain") + configdu.Encoding = dataurl.EncodingASCII + return configdu.String() +} + +func populateFiles(files []igntypes.File) error { + for _, file := range files { + contents, err := dataurl.DecodeString(*file.Contents.Source) + if err != nil { + return err + } + mode := defaultFilePermissions + if file.Mode != nil { + mode = os.FileMode(*file.Mode) + } + + if err := writeFileAtomically(file.Path, contents.Data, defaultDirectoryPermissions, mode, -1, -1); err != nil { + return err + } + } + return nil +} diff --git a/pkg/daemon/daemon.go b/pkg/daemon/daemon.go index 15a8bb10ee..ef768b8a8f 100644 --- a/pkg/daemon/daemon.go +++ b/pkg/daemon/daemon.go @@ -1072,6 +1072,26 @@ func (dn *Daemon) checkStateOnFirstRun() error { return dn.triggerUpdateWithMachineConfig(state.currentConfig, state.desiredConfig) } + if state.bootstrapping { + if err := dn.storeCurrentConfigOnDisk(state.currentConfig); err != nil { + return err + } + } + + inDesiredConfig, err := dn.confirmConfigState(state) + if inDesiredConfig { + return err + } + + if dn.recorder != nil { + dn.recorder.Eventf(getNodeRef(dn.node), corev1.EventTypeNormal, "BootResync", fmt.Sprintf("Booting node %s, currentConfig %s, desiredConfig %s", dn.node.Name, state.currentConfig.GetName(), state.desiredConfig.GetName())) + } + // currentConfig != desiredConfig, and we're not booting up into the desiredConfig. + // Kick off an update. + return dn.triggerUpdateWithMachineConfig(state.currentConfig, state.desiredConfig) +} + +func (dn *Daemon) confirmConfigState(state *stateAndConfigs) (bool, error) { // We've validated our state. In the case where we had a pendingConfig, // make that now currentConfig. We update the node annotation, delete the // state file, etc. @@ -1084,21 +1104,15 @@ func (dn *Daemon) checkStateOnFirstRun() error { dn.recorder.Eventf(getNodeRef(dn.node), corev1.EventTypeNormal, "NodeDone", fmt.Sprintf("Setting node %s, currentConfig %s to Done", dn.node.Name, state.pendingConfig.GetName())) } if err := dn.nodeWriter.SetDone(dn.kubeClient.CoreV1().Nodes(), dn.nodeLister, dn.name, state.pendingConfig.GetName()); err != nil { - return errors.Wrap(err, "error setting node's state to Done") + return true, errors.Wrap(err, "error setting node's state to Done") } if out, err := dn.storePendingState(state.pendingConfig, 0); err != nil { - return errors.Wrapf(err, "failed to reset pending config: %s", string(out)) + return true, errors.Wrapf(err, "failed to reset pending config: %s", string(out)) } state.currentConfig = state.pendingConfig } - if state.bootstrapping { - if err := dn.storeCurrentConfigOnDisk(state.currentConfig); err != nil { - return err - } - } - inDesiredConfig := state.currentConfig.GetName() == state.desiredConfig.GetName() if inDesiredConfig { if state.pendingConfig != nil { @@ -1107,7 +1121,7 @@ func (dn *Daemon) checkStateOnFirstRun() error { glog.Infof("Completing pending config %s", state.pendingConfig.GetName()) if err := dn.completeUpdate(dn.node, state.pendingConfig.GetName()); err != nil { MCDUpdateState.WithLabelValues("", err.Error()).SetToCurrentTime() - return err + return inDesiredConfig, err } } // If we're degraded here, it means we got an error likely on startup and we retried. @@ -1116,7 +1130,7 @@ func (dn *Daemon) checkStateOnFirstRun() error { if err := dn.nodeWriter.SetDone(dn.kubeClient.CoreV1().Nodes(), dn.nodeLister, dn.name, state.currentConfig.GetName()); err != nil { errLabelStr := fmt.Sprintf("error setting node's state to Done: %v", err) MCDUpdateState.WithLabelValues("", errLabelStr).SetToCurrentTime() - return errors.Wrap(err, "error setting node's state to Done") + return inDesiredConfig, errors.Wrap(err, "error setting node's state to Done") } } @@ -1124,14 +1138,8 @@ func (dn *Daemon) checkStateOnFirstRun() error { MCDUpdateState.WithLabelValues(state.currentConfig.GetName(), "").SetToCurrentTime() // All good! - return nil - } - if dn.recorder != nil { - dn.recorder.Eventf(getNodeRef(dn.node), corev1.EventTypeNormal, "BootResync", fmt.Sprintf("Booting node %s, currentConfig %s, desiredConfig %s", dn.node.Name, state.currentConfig.GetName(), state.desiredConfig.GetName())) } - // currentConfig != desiredConfig, and we're not booting up into the desiredConfig. - // Kick off an update. - return dn.triggerUpdateWithMachineConfig(state.currentConfig, state.desiredConfig) + return inDesiredConfig, nil } // runOnceFromMachineConfig utilizes a parsed machineConfig and executes in onceFrom diff --git a/pkg/daemon/testdata/machine-configs/test-base.yaml b/pkg/daemon/testdata/machine-configs/test-base.yaml new file mode 100644 index 0000000000..01f85ba5f6 --- /dev/null +++ b/pkg/daemon/testdata/machine-configs/test-base.yaml @@ -0,0 +1,255 @@ +apiVersion: machineconfiguration.openshift.io/v1 +kind: MachineConfig +metadata: + annotations: + machineconfiguration.openshift.io/generated-by-controller-version: was-not-built-properly + creationTimestamp: null + name: rendered-worker-f65bca0639c20840c47f73c0cf202a7c + ownerReferences: + - apiVersion: machineconfiguration.openshift.io/v1 + blockOwnerDeletion: true + controller: true + kind: MachineConfigPool + name: worker + uid: "" +spec: + config: + ignition: + version: 3.1.0 + passwd: + users: + - name: core + sshAuthorizedKeys: + - ssh-rsa SSH-AUTHORIZED-KEY + storage: + files: + - contents: + source: data:, + mode: 384 + overwrite: true + path: /etc/pki/ca-trust/source/anchors/openshift-config-user-ca-bundle.crt + - contents: + source: data:,r%20%2Fetc%2Fkubernetes%2Fcni%2Fnet.d%2F80-openshift-network.conf%0Ar%20%2Fetc%2Fkubernetes%2Fcni%2Fnet.d%2F10-ovn-kubernetes.conf%0Ad%20%2Frun%2Fmultus%2Fcni%2Fnet.d%2F%200755%20root%20root%20-%20-%0AD%20%2Fvar%2Flib%2Fcni%2Fnetworks%2Fopenshift-sdn%2F%200755%20root%20root%20-%20-%0A + mode: 420 + overwrite: true + path: /etc/tmpfiles.d/cleanup-cni.conf + - contents: + source: data:, + mode: 420 + overwrite: true + path: /etc/kubernetes/static-pod-resources/configmaps/cloud-config/ca-bundle.pem + - contents: + source: data:,%23%20This%20file%20is%20generated%20by%20the%20Machine%20Config%20Operator's%20containerruntimeconfig%20controller.%0A%23%0A%23%20storage.conf%20is%20the%20configuration%20file%20for%20all%20tools%0A%23%20that%20share%20the%20containers%2Fstorage%20libraries%0A%23%20See%20man%205%20containers-storage.conf%20for%20more%20information%0A%23%20The%20%22container%20storage%22%20table%20contains%20all%20of%20the%20server%20options.%0A%5Bstorage%5D%0A%0A%23%20Default%20Storage%20Driver%0Adriver%20%3D%20%22overlay%22%0A%0A%23%20Temporary%20storage%20location%0Arunroot%20%3D%20%22%2Fvar%2Frun%2Fcontainers%2Fstorage%22%0A%0A%23%20Primary%20Read%2FWrite%20location%20of%20container%20storage%0Agraphroot%20%3D%20%22%2Fvar%2Flib%2Fcontainers%2Fstorage%22%0A%0A%5Bstorage.options%5D%0A%23%20Storage%20options%20to%20be%20passed%20to%20underlying%20storage%20drivers%0A%0A%23%20AdditionalImageStores%20is%20used%20to%20pass%20paths%20to%20additional%20Read%2FOnly%20image%20stores%0A%23%20Must%20be%20comma%20separated%20list.%0Aadditionalimagestores%20%3D%20%5B%0A%5D%0A%0A%23%20Size%20is%20used%20to%20set%20a%20maximum%20size%20of%20the%20container%20image.%20%20Only%20supported%20by%0A%23%20certain%20container%20storage%20drivers.%0Asize%20%3D%20%22%22%0A%0A%23%20OverrideKernelCheck%20tells%20the%20driver%20to%20ignore%20kernel%20checks%20based%20on%20kernel%20version%0Aoverride_kernel_check%20%3D%20%22true%22%0A%0A%23%20Remap-UIDs%2FGIDs%20is%20the%20mapping%20from%20UIDs%2FGIDs%20as%20they%20should%20appear%20inside%20of%0A%23%20a%20container%2C%20to%20UIDs%2FGIDs%20as%20they%20should%20appear%20outside%20of%20the%20container%2C%20and%0A%23%20the%20length%20of%20the%20range%20of%20UIDs%2FGIDs.%20%20Additional%20mapped%20sets%20can%20be%20listed%0A%23%20and%20will%20be%20heeded%20by%20libraries%2C%20but%20there%20are%20limits%20to%20the%20number%20of%0A%23%20mappings%20which%20the%20kernel%20will%20allow%20when%20you%20later%20attempt%20to%20run%20a%0A%23%20container.%0A%23%0A%23%20remap-uids%20%3D%200%3A1668442479%3A65536%0A%23%20remap-gids%20%3D%200%3A1668442479%3A65536%0A%0A%23%20Remap-User%2FGroup%20is%20a%20name%20which%20can%20be%20used%20to%20look%20up%20one%20or%20more%20UID%2FGID%0A%23%20ranges%20in%20the%20%2Fetc%2Fsubuid%20or%20%2Fetc%2Fsubgid%20file.%20%20Mappings%20are%20set%20up%20starting%0A%23%20with%20an%20in-container%20ID%20of%200%20and%20the%20a%20host-level%20ID%20taken%20from%20the%20lowest%0A%23%20range%20that%20matches%20the%20specified%20name%2C%20and%20using%20the%20length%20of%20that%20range.%0A%23%20Additional%20ranges%20are%20then%20assigned%2C%20using%20the%20ranges%20which%20specify%20the%0A%23%20lowest%20host-level%20IDs%20first%2C%20to%20the%20lowest%20not-yet-mapped%20container-level%20ID%2C%0A%23%20until%20all%20of%20the%20entries%20have%20been%20used%20for%20maps.%0A%23%0A%23%20remap-user%20%3D%20%22storage%22%0A%23%20remap-group%20%3D%20%22storage%22%0A%0A%5Bstorage.options.thinpool%5D%0A%23%20Storage%20Options%20for%20thinpool%0A%0A%23%20autoextend_percent%20determines%20the%20amount%20by%20which%20pool%20needs%20to%20be%0A%23%20grown.%20This%20is%20specified%20in%20terms%20of%20%25%20of%20pool%20size.%20So%20a%20value%20of%2020%20means%0A%23%20that%20when%20threshold%20is%20hit%2C%20pool%20will%20be%20grown%20by%2020%25%20of%20existing%0A%23%20pool%20size.%0A%23%20autoextend_percent%20%3D%20%2220%22%0A%0A%23%20autoextend_threshold%20determines%20the%20pool%20extension%20threshold%20in%20terms%0A%23%20of%20percentage%20of%20pool%20size.%20For%20example%2C%20if%20threshold%20is%2060%2C%20that%20means%20when%0A%23%20pool%20is%2060%25%20full%2C%20threshold%20has%20been%20hit.%0A%23%20autoextend_threshold%20%3D%20%2280%22%0A%0A%23%20basesize%20specifies%20the%20size%20to%20use%20when%20creating%20the%20base%20device%2C%20which%0A%23%20limits%20the%20size%20of%20images%20and%20containers.%0A%23%20basesize%20%3D%20%2210G%22%0A%0A%23%20blocksize%20specifies%20a%20custom%20blocksize%20to%20use%20for%20the%20thin%20pool.%0A%23%20blocksize%3D%2264k%22%0A%0A%23%20directlvm_device%20specifies%20a%20custom%20block%20storage%20device%20to%20use%20for%20the%0A%23%20thin%20pool.%20Required%20if%20you%20setup%20devicemapper%0A%23%20directlvm_device%20%3D%20%22%22%0A%0A%23%20directlvm_device_force%20wipes%20device%20even%20if%20device%20already%20has%20a%20filesystem%0A%23%20directlvm_device_force%20%3D%20%22True%22%0A%0A%23%20fs%20specifies%20the%20filesystem%20type%20to%20use%20for%20the%20base%20device.%0A%23%20fs%3D%22xfs%22%0A%0A%23%20log_level%20sets%20the%20log%20level%20of%20devicemapper.%0A%23%200%3A%20LogLevelSuppress%200%20(Default)%0A%23%202%3A%20LogLevelFatal%0A%23%203%3A%20LogLevelErr%0A%23%204%3A%20LogLevelWarn%0A%23%205%3A%20LogLevelNotice%0A%23%206%3A%20LogLevelInfo%0A%23%207%3A%20LogLevelDebug%0A%23%20log_level%20%3D%20%227%22%0A%0A%23%20min_free_space%20specifies%20the%20min%20free%20space%20percent%20in%20a%20thin%20pool%20require%20for%0A%23%20new%20device%20creation%20to%20succeed.%20Valid%20values%20are%20from%200%25%20-%2099%25.%0A%23%20Value%200%25%20disables%0A%23%20min_free_space%20%3D%20%2210%25%22%0A%0A%23%20mkfsarg%20specifies%20extra%20mkfs%20arguments%20to%20be%20used%20when%20creating%20the%20base%0A%23%20device.%0A%23%20mkfsarg%20%3D%20%22%22%0A%0A%23%20mountopt%20specifies%20extra%20mount%20options%20used%20when%20mounting%20the%20thin%20devices.%0A%23%20mountopt%20%3D%20%22%22%0A%0A%23%20use_deferred_removal%20Marking%20device%20for%20deferred%20removal%0A%23%20use_deferred_removal%20%3D%20%22True%22%0A%0A%23%20use_deferred_deletion%20Marking%20device%20for%20deferred%20deletion%0A%23%20use_deferred_deletion%20%3D%20%22True%22%0A%0A%23%20xfs_nospace_max_retries%20specifies%20the%20maximum%20number%20of%20retries%20XFS%20should%0A%23%20attempt%20to%20complete%20IO%20when%20ENOSPC%20(no%20space)%20error%20is%20returned%20by%0A%23%20underlying%20storage%20device.%0A%23%20xfs_nospace_max_retries%20%3D%20%220%22%0A + mode: 420 + overwrite: true + path: /etc/containers/storage.conf + - contents: + source: data:,%23!%2Fbin%2Fbash%0A%23%0A%23%2090-long-hostname%20is%20a%20wrapper%20around%20%2Fusr%2Flocal%2Fsbin%2Fset-valid-hostname.sh%2C%0A%23%20which%20ensures%20that%20a%20node%20has%20a%20valid%20hostname.%0AIF%3D%241%0ASTATUS%3D%242%0A%0Alog()%20%7B%20logger%20--tag%20%22network-manager%2F%24(basename%20%240)%22%20%22%24%7B%40%7D%22%3B%20%7D%0A%0Aif%20%5B%5B%20!%20%22%24STATUS%22%20%3D~%20(up%7Chostname%7Cdhcp4-change%7Cdhcp6-change)%20%5D%5D%3B%20then%0A%20%20%20%20exit%200%0Afi%0A%0Aif%20%5B%5B%20!%20%22%24(%3C%20%2Fproc%2Fsys%2Fkernel%2Fhostname)%22%20%3D~%20(localhost%7Clocalhost.localdomain)%20%5D%5D%3B%20then%0A%20%20%20%20log%20%22hostname%20is%20already%20set%22%0A%20%20%20%20exit%200%0Afi%0A%0A%23%20source%20the%20script%20since%20NetworkManager%20execution%20rules%20do%0A%23%20allow%20sourcing%20from%20%2Fusr%2Flocal.%20RHCOS%20has%20an%20read-only%20rootfs%0A%23%20which%20limits%20where%20this%20can%20be%20stashed.%0Asource%20%2Fusr%2Flocal%2Fsbin%2Fset-valid-hostname.sh%0Ahost_name%3D%22%24%7BDHCP4_HOST_NAME%3A-%24DHCP6_HOST_NAME%7D%22%0A%0Aif%20%5B%20-n%20%22%24%7Bhost_name%7D%22%20%5D%3B%20then%0A%20%20%20%20set_valid_hostname%20%22%24%7Bhost_name%7D%22%0Afi%0A + mode: 493 + overwrite: true + path: /etc/NetworkManager/dispatcher.d/90-long-hostname + - contents: + source: data:,%23%20Force-load%20legacy%20iptables%20so%20it%20is%20usable%20from%20pod%20network%20namespaces%0Aip_tables%0A + mode: 420 + overwrite: true + path: /etc/modules-load.d/iptables.conf + - contents: + source: data:,-----BEGIN%20CERTIFICATE-----%0AKUBE%20API%20SERVER%20SERVING%20CA%20DATA%0A-----END%20CERTIFICATE-----%0A + mode: 420 + overwrite: true + path: /etc/kubernetes/kubelet-ca.crt + - contents: + source: data:,%23%20Turning%20on%20Accounting%20helps%20track%20down%20performance%20issues.%0A%5BManager%5D%0ADefaultCPUAccounting%3Dyes%0ADefaultMemoryAccounting%3Dyes%0ADefaultBlockIOAccounting%3Dyes%0A + mode: 420 + overwrite: true + path: /etc/systemd/system.conf.d/kubelet-cgroups.conf + - contents: + source: data:,%23%20ignore%20known%20SDN-managed%20devices%0A%5Bdevice%5D%0Amatch-device%3Dinterface-name%3Abr-int%3Binterface-name%3Abr-local%3Binterface-name%3Abr-nexthop%2Cinterface-name%3Aovn-k8s-*%2Cinterface-name%3Ak8s-*%3Binterface-name%3Atun0%3Binterface-name%3Abr0%3Bdriver%3Aveth%0Amanaged%3D0%0A + mode: 420 + overwrite: true + path: /etc/NetworkManager/conf.d/sdn.conf + - contents: + source: data:,%7B%22auths%22%3A%7B%22example.com%22%3A%7B%22auth%22%3A%22MCC-PULL-SECRET%22%7D%7D%7D%0A + mode: 384 + overwrite: true + path: /var/lib/kubelet/config.json + - contents: + source: data:,-----BEGIN%20CERTIFICATE-----%0AROOT%20CA%20DATA%0A-----END%20CERTIFICATE-----%0A + mode: 420 + overwrite: true + path: /etc/kubernetes/ca.crt + - contents: + source: data:,net.ipv4.ip_forward%20%3D%201%0Anet.ipv6.conf.all.forwarding%20%3D%201%0A + mode: 420 + overwrite: true + path: /etc/sysctl.d/forward.conf + - contents: + source: data:,%0Afs.inotify.max_user_watches%20%3D%2065536%0Afs.inotify.max_user_instances%20%3D%208192%0A + mode: 420 + overwrite: true + path: /etc/sysctl.d/inotify.conf + - contents: + source: data:,%23!%2Fbin%2Fbash%0A%23%20On%20some%20platforms%20the%20hostname%20may%20be%20too%20long%20(%3E63%20chars).%0A%23%20%20-%20On%20firstboot%20the%20hostname%20is%20set%20in%20the%20initramfs%20before%20NetworkManager%0A%23%20%20%20%20And%20it%20may%20be%20truncated%20at%2064%20characters%20(too%20long)%0A%23%20%20-%20On%20reboot%20affect%20nodes%20use%20'localhost'.%0A%23%0A%23%20This%20script%20is%20a%20simple%20workaround%20for%20hostname%20woes%2C%20including%0A%23%20%20-%20NOT%20a%20localhost%20name%0A%23%20%20-%20NOT%20longer%20than%2063%20characters.%20Names%20will%20be%20truncated%20at%20the%0A%23%20%20%20%20first%20dot%2C%20and%20then%20capped%20at%2063%20char%20(which%20ever%20is%20less).%0A%23%20%20-%20Race%20conditions%20between%20truncated%20hostnames%20by%20the%20dhclient%0A%23%20%20%20%20and%20NetworkManager.%0A%23%0A%23%20Finally%2C%20this%20script%20is%20invoked%20via%3A%0A%23%20%20-%20%2Fetc%2FNetworkManager%2Fdispatcher.d%2F90-long-hostnames%0A%23%20%20-%20on%20boot%20via%20node-valid-hostname.service%0A%0Aexport%20PATH%3D%22%2Fusr%2Fbin%3A%2Fusr%2Flocal%2Fbin%3A%2Fsbin%3A%2Fusr%2Flocal%2Fsbin%3A%2Fbin%3A%24%7BPATH%7D%22%0Alog()%20%7B%20logger%20--tag%20%22%24(basename%20%240)%22%20%22%24%7B%40%7D%22%3B%20%7D%0A%0A%23%20wait_localhost%20waits%20until%20the%20host%20gets%20a%20real%20hostname.%0A%23%20This%20will%20wait%20indefinately.%20node-valid-hostname.service%20will%20terminate%0A%23%20this%20after%205m.%0Await_localhost()%20%7B%0A%20%20%20%20log%20%22waiting%20for%20non-localhost%20hostname%20to%20be%20assigned%22%0A%20%20%20%20while%20%5B%5B%20%22%24(%3C%20%2Fproc%2Fsys%2Fkernel%2Fhostname)%22%20%3D~%20(localhost%7Clocalhost.localdomain)%20%5D%5D%3B%0A%20%20%20%20do%0A%20%20%20%20%20%20%20%20sleep%201%0A%20%20%20%20done%0A%20%20%20%20log%20%22node%20identified%20as%20%24(%3C%2Fproc%2Fsys%2Fkernel%2Fhostname)%22%0A%20%20%20%20exit%200%0A%7D%0A%0Aset_valid_hostname()%20%7B%0A%20%20%20%20local%20host_name%3D%24%7B1%7D%0A%20%20%20%20local%20type_arg%3D%22transient%22%0A%0A%20%20%20%20%23%20%2Fetc%2Fhostname%20is%20used%20for%20static%20hostnames%20and%20is%20authorative.%0A%20%20%20%20%23%20This%20will%20check%20to%20make%20sure%20that%20the%20static%20hostname%20is%20the%0A%20%20%20%20%23%20less%20than%20or%20equal%20to%2063%20characters%20in%20length.%0A%20%20%20%20if%20%5B%20-f%20%2Fetc%2Fhostname%20%5D%20%26%26%20%5B%20%22%24(cat%20%2Fetc%2Fhostname%20%7C%20wc%20-m)%22%20-gt%200%20%5D%3B%20then%0A%20%20%20%20%20%20%20%20etc_name%3D%22%24(%3C%20%2Fetc%2Fhostname)%22%0A%20%20%20%20%20%20%20%20type_arg%3D%22static%22%0A%20%20%20%20%20%20%20%20if%20%5B%20%22%24%7Betc_name%7D%22%20!%3D%20%22%24%7Bhost_name%7D%22%20%5D%3B%20then%0A%20%20%20%20%20%20%20%20%20%20%20%20log%20%22%2Fetc%2Fhostname%20is%20set%20to%20%24%7Betc_name%7D%20but%20does%20not%20match%20%24%7Bhost_name%7D%22%0A%20%20%20%20%20%20%20%20%20%20%20%20log%20%22using%20%2Fetc%2Fhostname%20as%20the%20authorative%20name%22%0A%20%20%20%20%20%20%20%20%20%20%20%20host_name%3D%22%24%7Betc_name%7D%22%0A%20%20%20%20%20%20%20%20fi%0A%20%20%20%20fi%0A%0A%20%20%20%20%23%20Only%20mutate%20the%20hostname%20if%20the%20length%20is%20longer%20than%2063%20characters.%20The%0A%20%20%20%20%23%20hostname%20will%20be%20the%20lesser%20of%2063%20characters%20after%20the%20first%20dot%20in%20the%0A%20%20%20%20%23%20FQDN.%0A%20%20%20%20if%20%5B%20%22%24%7B%23host_name%7D%22%20-gt%2063%20%5D%3B%20then%0A%20%20%20%20%20%20%20%20alt_name%3D%24(printf%20%22%24%7Bhost_name%7D%22%20%7C%20cut%20-f1%20-d'.'%20%7C%20cut%20-c%20-63)%0A%20%20%20%20%20%20%20%20log%20%22%24%7Bhost_name%7D%20is%20longer%20than%2063%20characters%2C%20using%20trunacated%20hostname%22%0A%20%20%20%20%20%20%20%20host_name%3D%22%24%7Balt_name%7D%22%0A%20%20%20%20fi%0A%20%20%20%20log%20%22setting%20%24%7Btype_arg%7D%20hostname%20to%20%24%7Bhost_name%7D%22%0A%20%20%20%20%2Fbin%2Fhostnamectl%20%22--%24%7Btype_arg%7D%22%20set-hostname%20%22%24%7Bhost_name%7D%22%0A%20%20%20%20exit%200%0A%7D%0A%0Acli_run()%20%7B%0A%20%20%20%20mode%3D%22%24%7B1%3A%3Fmode%20must%20be%20the%20first%20argument%7D%22%3B%20shift%3B%0A%20%20%20%20case%20%22%24%7Bmode%7D%22%20in%0A%20%20%20%20%20%20%20%20%20%20%20%20wait_localhost)%20wait_localhost%3B%3B%0A%20%20%20%20%20%20%20%20set_valid_hostname)%20hname%3D%22%24%7B1%3A%3Fhostname%20is%20a%20required%20last%20argument%7D%22%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20set_valid_hostname%20%22%24%7Bhname%7D%22%3B%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20*)%20log%20%22unknown%20mode%20%24%7Bmode%7D%22%3B%20exit%201%3B%3B%0A%20%20%20%20esac%0A%7D%0A%0A%23%20Allow%20the%20functions%20to%20be%20sourced.%20This%20can%20be%20run%20either%20as%20a%0A%23%20standalone%20command%20or%20in%20systemd%20or%20part%20of%20NetworkManager.%0Aif%20%5B%5B%20%22%24%7BBASH_SOURCE%5B0%5D%7D%22%20%3D%3D%20%22%24%7B0%7D%22%20%5D%5D%3B%20then%0A%20%20%20%20cli_run%20%24%7B%40%7D%0Afi%0A + mode: 493 + overwrite: true + path: /usr/local/sbin/set-valid-hostname.sh + - contents: + source: data:, + mode: 493 + overwrite: true + path: /etc/kubernetes/kubelet-plugins/volume/exec/.dummy + - contents: + source: data:text/plain,unqualified-search-registries%20%3D%20%5B%22registry.access.redhat.com%22%2C%20%22docker.io%22%5D%0A%0A%5B%5Bregistry%5D%5D%0A%20%20prefix%20%3D%20%22%22%0A%20%20location%20%3D%20%22registry.product.example.org%2Focp%2F4.2-DATE-VERSION%22%0A%20%20mirror-by-digest-only%20%3D%20true%0A%0A%20%20%5B%5Bregistry.mirror%5D%5D%0A%20%20%20%20location%20%3D%20%22registry.mirror.example.com%2Focp%22%0A%0A%5B%5Bregistry%5D%5D%0A%20%20prefix%20%3D%20%22%22%0A%20%20location%20%3D%20%22registry.product.example.org%2Focp%2Frelease%22%0A%20%20mirror-by-digest-only%20%3D%20true%0A%0A%20%20%5B%5Bregistry.mirror%5D%5D%0A%20%20%20%20location%20%3D%20%22registry.mirror.example.com%2Focp%22%0A + mode: 420 + overwrite: true + path: /etc/containers/registries.conf + - contents: + source: data:,%5Bcrio.api%5D%0Astream_address%20%3D%20%22%22%0Astream_port%20%3D%20%2210010%22%0A%0A%5Bcrio.runtime%5D%0Aconmon%20%3D%20%22%2Fusr%2Flibexec%2Fcrio%2Fconmon%22%0Aconmon_cgroup%20%3D%20%22pod%22%0Adefault_env%20%3D%20%5B%0A%20%20%20%20%22NSS_SDB_USE_CACHE%3Dno%22%2C%0A%5D%0Alog_level%20%3D%20%22info%22%0Acgroup_manager%20%3D%20%22systemd%22%0Adefault_capabilities%20%3D%20%5B%0A%20%20%20%20%22CHOWN%22%2C%0A%20%20%20%20%22DAC_OVERRIDE%22%2C%0A%20%20%20%20%22FSETID%22%2C%0A%20%20%20%20%22FOWNER%22%2C%0A%20%20%20%20%22NET_RAW%22%2C%0A%20%20%20%20%22SETGID%22%2C%0A%20%20%20%20%22SETUID%22%2C%0A%20%20%20%20%22SETPCAP%22%2C%0A%20%20%20%20%22NET_BIND_SERVICE%22%2C%0A%20%20%20%20%22SYS_CHROOT%22%2C%0A%20%20%20%20%22KILL%22%2C%0A%5D%0Ahooks_dir%20%3D%20%5B%0A%20%20%20%20%22%2Fetc%2Fcontainers%2Foci%2Fhooks.d%22%2C%0A%5D%0Amanage_ns_lifecycle%20%3D%20true%0A%0A%5Bcrio.image%5D%0Aglobal_auth_file%20%3D%20%22%2Fvar%2Flib%2Fkubelet%2Fconfig.json%22%0Apause_image%20%3D%20%22registry.product.example.org%2Focp%2F4.2-DATE-VERSION%40sha256%3Abbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb%22%0Apause_image_auth_file%20%3D%20%22%2Fvar%2Flib%2Fkubelet%2Fconfig.json%22%0Apause_command%20%3D%20%22%2Fusr%2Fbin%2Fpod%22%0A%0A%5Bcrio.network%5D%0Anetwork_dir%20%3D%20%22%2Fetc%2Fkubernetes%2Fcni%2Fnet.d%2F%22%0Aplugin_dirs%20%3D%20%5B%0A%20%20%20%20%22%2Fvar%2Flib%2Fcni%2Fbin%22%2C%0A%20%20%20%20%22%2Fusr%2Flibexec%2Fcni%22%2C%0A%5D%0A%0A%5Bcrio.metrics%5D%0Aenable_metrics%20%3D%20true%0Ametrics_port%20%3D%209537%0A + mode: 420 + overwrite: true + path: /etc/crio/crio.conf.d/00-default + - contents: + source: data:,%7B%0A%20%20%20%20%22default%22%3A%20%5B%0A%20%20%20%20%20%20%20%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%22type%22%3A%20%22insecureAcceptAnything%22%0A%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%5D%2C%0A%20%20%20%20%22transports%22%3A%0A%20%20%20%20%20%20%20%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%22docker-daemon%22%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22%22%3A%20%5B%7B%22type%22%3A%22insecureAcceptAnything%22%7D%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20%7D%0A%7D + mode: 420 + overwrite: true + path: /etc/containers/policy.json + - contents: + source: data:, + mode: 420 + overwrite: true + path: /etc/kubernetes/cloud.conf + - contents: + source: data:,kind%3A%20KubeletConfiguration%0AapiVersion%3A%20kubelet.config.k8s.io%2Fv1beta1%0Aauthentication%3A%0A%20%20x509%3A%0A%20%20%20%20clientCAFile%3A%20%2Fetc%2Fkubernetes%2Fkubelet-ca.crt%0A%20%20anonymous%3A%0A%20%20%20%20enabled%3A%20false%0AcgroupDriver%3A%20systemd%0AcgroupRoot%3A%20%2F%0AclusterDNS%3A%0A%20%20-%20172.30.0.10%0AclusterDomain%3A%20cluster.local%0AcontainerLogMaxSize%3A%2050Mi%0AmaxPods%3A%20250%0AkubeAPIQPS%3A%2050%0AkubeAPIBurst%3A%20100%0ArotateCertificates%3A%20true%0AserializeImagePulls%3A%20false%0AstaticPodPath%3A%20%2Fetc%2Fkubernetes%2Fmanifests%0AsystemCgroups%3A%20%2Fsystem.slice%0AsystemReserved%3A%0A%20%20cpu%3A%20500m%0A%20%20memory%3A%201Gi%0A%20%20ephemeral-storage%3A%201Gi%0AfeatureGates%3A%0A%20%20APIPriorityAndFairness%3A%20true%0A%20%20LegacyNodeRoleBehavior%3A%20false%0A%20%20NodeDisruptionExclusion%3A%20true%0A%20%20RotateKubeletServerCertificate%3A%20true%0A%20%20SCTPSupport%3A%20true%0A%20%20ServiceNodeExclusion%3A%20true%0A%20%20SupportPodPidsLimit%3A%20true%0AserverTLSBootstrap%3A%20true%0A + mode: 420 + overwrite: true + path: /etc/kubernetes/kubelet.conf + systemd: + units: + - dropins: + - contents: | + [Unit] + name: 10-mco-default-env.conf + name: crio.service + - contents: | + [Unit] + Description=Kubernetes Kubelet + Wants=rpc-statd.service network-online.target crio.service + After=network-online.target crio.service + + [Service] + Type=notify + ExecStartPre=/bin/mkdir --parents /etc/kubernetes/manifests + ExecStartPre=/bin/rm -f /var/lib/kubelet/cpu_manager_state + Environment="KUBELET_LOG_LEVEL=4" + EnvironmentFile=/etc/os-release + EnvironmentFile=-/etc/kubernetes/kubelet-workaround + EnvironmentFile=-/etc/kubernetes/kubelet-env + + ExecStart=/usr/bin/hyperkube \ + kubelet \ + --config=/etc/kubernetes/kubelet.conf \ + --bootstrap-kubeconfig=/etc/kubernetes/kubeconfig \ + --kubeconfig=/var/lib/kubelet/kubeconfig \ + --container-runtime=remote \ + --container-runtime-endpoint=/var/run/crio/crio.sock \ + --runtime-cgroups=/system.slice/crio.service \ + --node-labels=node-role.kubernetes.io/worker,node.openshift.io/os_id=${ID} \ + --minimum-container-ttl-duration=6m0s \ + --volume-plugin-dir=/etc/kubernetes/kubelet-plugins/volume/exec \ + --cloud-provider= \ + \ + --pod-infra-container-image=registry.product.example.org/ocp/4.2-DATE-VERSION@sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb \ + --v=${KUBELET_LOG_LEVEL} + + Restart=always + RestartSec=10 + + [Install] + WantedBy=multi-user.target + dropins: + - contents: | + [Unit] + name: 10-mco-default-env.conf + enabled: true + name: kubelet.service + - contents: | + [Unit] + Description=Machine Config Daemon Firstboot + # Make sure it runs only on OSTree booted system + ConditionPathExists=/run/ostree-booted + # Removal of this file signals firstboot completion + ConditionPathExists=/etc/ignition-machine-config-encapsulated.json + After=machine-config-daemon-pull.service + Before=crio.service crio-wipe.service + Before=kubelet.service + + [Service] + Type=oneshot + RemainAfterExit=yes + ExecStart=/run/bin/machine-config-daemon firstboot-complete-machineconfig + + [Install] + WantedBy=multi-user.target + RequiredBy=crio.service kubelet.service + enabled: true + name: machine-config-daemon-firstboot.service + - contents: | + [Unit] + Description=Machine Config Daemon Pull + # Make sure it runs only on OSTree booted system + ConditionPathExists=/run/ostree-booted + # This "stamp file" is unlinked when we complete + # machine-config-daemon-firstboot.service + ConditionPathExists=/etc/ignition-machine-config-encapsulated.json + Wants=network-online.target + After=network-online.target + + [Service] + Type=oneshot + RemainAfterExit=yes + # See https://github.com/coreos/fedora-coreos-tracker/issues/354 + ExecStart=/bin/sh -c '/bin/mkdir -p /run/bin && chcon --reference=/usr/bin /run/bin' + ExecStart=/bin/sh -c "/usr/bin/podman pull --authfile=/var/lib/kubelet/config.json --quiet 'registry.product.example.org/ocp/4.2-DATE-VERSION@sha256:dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd'" + ExecStart=/bin/sh -c "/usr/bin/podman run --rm --quiet --net=host --entrypoint=cat 'registry.product.example.org/ocp/4.2-DATE-VERSION@sha256:dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd' /usr/bin/machine-config-daemon > /run/bin/machine-config-daemon.tmp" + ExecStart=/bin/sh -c '/usr/bin/chmod a+x /run/bin/machine-config-daemon.tmp && mv /run/bin/machine-config-daemon.tmp /run/bin/machine-config-daemon' + + [Install] + RequiredBy=machine-config-daemon-firstboot.service + enabled: true + name: machine-config-daemon-pull.service + - contents: | + [Unit] + Description=Ensure the node hostname is valid for the cluster + Before=network-online.target + + [Service] + Type=oneshot + RemainAfterExit=yes + User=root + + # SystemD prevents direct execution of the script in /usr/local/sbin, + # so it is sourced. See the script for functionality. + ExecStart=/bin/bash -c "source /usr/local/sbin/set-valid-hostname.sh; wait_localhost; set_valid_hostname `hostname`" + + # Wait up to 5min for the node to get a real hostname. + TimeoutSec=300 + + [Install] + WantedBy=multi-user.target + # Ensure that network-online.target will not complete until the node has a real hostname. + RequiredBy=network-online.target + enabled: true + name: node-valid-hostname.service + - enabled: false + name: openvswitch.service + - enabled: false + name: ovsdb-server.service + - dropins: + - contents: | + [Unit] + name: 10-mco-default-env.conf + name: pivot.service + fips: false + kernelArguments: [] + kernelType: default + osImageURL: registry.product.example.org/ocp/4.2-DATE-VERSION@sha256:eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee diff --git a/pkg/daemon/update.go b/pkg/daemon/update.go index c052e099ea..a55dad3e3e 100644 --- a/pkg/daemon/update.go +++ b/pkg/daemon/update.go @@ -305,6 +305,10 @@ func (dn *Daemon) update(oldConfig, newConfig *mcfgv1.MachineConfig) (retErr err return err } + // calculateActions() must be called prior to updating the disk so as to + // compare with the existing file contents + actions := calculateActions("", oldConfig, newConfig, diff) + // update files on disk that need updating if err := dn.updateFiles(oldConfig, newConfig); err != nil { return err @@ -380,7 +384,51 @@ func (dn *Daemon) update(oldConfig, newConfig *mcfgv1.MachineConfig) (retErr err } }() - return dn.updateOSAndReboot(newConfig) + return dn.finalizeUpdate(newConfig, actions) +} + +func (dn *Daemon) finalizeUpdate(newConfig *mcfgv1.MachineConfig, actions []ConfigUpdateAction) (retErr error) { + if err := dn.updateOS(newConfig); err != nil { + return err + } + + for _, action := range actions { + dn.logSystem("Performing %v", action.Describe()) + if err := action.Execute(dn, newConfig); err != nil { + dn.logSystem("Applying machine config failed, node will reboot: %v", err) + return dn.finalizeAndReboot(newConfig) + } + } + + if dn.kubeClient == nil { + // Match drain(): + // + // If we are not cluster-driven, skip draining of the node + // and there is no need to replcate the SetDone() logic + return dn.finalizeAndReboot(newConfig) + } + + if err := drain.RunCordonOrUncordon(dn.drainer, dn.node, false); err != nil { + glog.Errorf("Could not un-cordon machine, node will reboot: %v", err) + return dn.finalizeAndReboot(newConfig) + } + + // If we are not rebooting, we need to replicate the + // checkStateOnFirstRun() functionality to indicate that the node has + // been successfully updated + + state, err := dn.getStateAndConfigs(newConfig.GetName()) + if err != nil { + glog.Errorf("Error processing state and configs, node will reboot: %v", err) + return dn.finalizeAndReboot(newConfig) + } + + _, err = dn.confirmConfigState(state) + if err != nil { + glog.Errorf("Setting node's state to Done failed, node will reboot: %v", err) + return dn.finalizeAndReboot(newConfig) + } + return nil } // machineConfigDiff represents an ad-hoc difference between two MachineConfig objects. diff --git a/test/e2e/framework/clientset.go b/test/e2e/framework/clientset.go index 8a0975f24c..e7d2a8168f 100644 --- a/test/e2e/framework/clientset.go +++ b/test/e2e/framework/clientset.go @@ -5,6 +5,7 @@ import ( "github.com/golang/glog" clientconfigv1 "github.com/openshift/client-go/config/clientset/versioned/typed/config/v1" + icspclient "github.com/openshift/client-go/operator/clientset/versioned/typed/operator/v1alpha1" clientmachineconfigv1 "github.com/openshift/machine-config-operator/pkg/generated/clientset/versioned/typed/machineconfiguration.openshift.io/v1" clientapiextensionsv1beta1 "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/typed/apiextensions/v1beta1" appsv1client "k8s.io/client-go/kubernetes/typed/apps/v1" @@ -19,6 +20,7 @@ type ClientSet struct { clientconfigv1.ConfigV1Interface clientmachineconfigv1.MachineconfigurationV1Interface clientapiextensionsv1beta1.ApiextensionsV1beta1Interface + icspclient.OperatorV1alpha1Interface } // NewClientSet returns a *ClientBuilder with the given kubeconfig. @@ -47,6 +49,7 @@ func NewClientSet(kubeconfig string) *ClientSet { clientSet.MachineconfigurationV1Interface = clientmachineconfigv1.NewForConfigOrDie(config) clientSet.ApiextensionsV1beta1Interface = clientapiextensionsv1beta1.NewForConfigOrDie(config) clientSet.AppsV1Interface = appsv1client.NewForConfigOrDie(config) + clientSet.OperatorV1alpha1Interface = icspclient.NewForConfigOrDie(config) return clientSet } diff --git a/test/e2e/mcd_test.go b/test/e2e/mcd_test.go index 01d7af2b25..cc34381256 100644 --- a/test/e2e/mcd_test.go +++ b/test/e2e/mcd_test.go @@ -7,9 +7,11 @@ import ( "testing" "time" + ign3types "github.com/coreos/ignition/v2/config/v3_1/types" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" @@ -22,6 +24,8 @@ import ( "github.com/openshift/machine-config-operator/pkg/daemon/constants" "github.com/openshift/machine-config-operator/test/e2e/framework" "github.com/openshift/machine-config-operator/test/helpers" + + ) // Test case for https://github.com/openshift/machine-config-operator/issues/358 diff --git a/vendor/github.com/coreos/go-systemd/dbus/dbus.go b/vendor/github.com/coreos/go-systemd/dbus/dbus.go new file mode 100644 index 0000000000..f652582e65 --- /dev/null +++ b/vendor/github.com/coreos/go-systemd/dbus/dbus.go @@ -0,0 +1,240 @@ +// 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 ( + "encoding/hex" + "fmt" + "os" + "strconv" + "strings" + "sync" + + "github.com/godbus/dbus" +) + +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 + } +} + +// New establishes a connection to any available bus and authenticates. +// Callers should call Close() when done with the connection. +func New() (*Conn, error) { + conn, err := NewSystemConnection() + if err != nil && os.Geteuid() == 0 { + return NewSystemdConnection() + } + return conn, err +} + +// NewSystemConnection establishes a connection to the system bus and authenticates. +// Callers should call Close() when done with the connection +func NewSystemConnection() (*Conn, error) { + return NewConnection(func() (*dbus.Conn, error) { + return dbusAuthHelloConnection(dbus.SystemBusPrivate) + }) +} + +// NewUserConnection 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 NewUserConnection() (*Conn, error) { + return NewConnection(func() (*dbus.Conn, error) { + return dbusAuthHelloConnection(dbus.SessionBusPrivate) + }) +} + +// NewSystemdConnection 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 NewSystemdConnection() (*Conn, error) { + return NewConnection(func() (*dbus.Conn, error) { + // We skip Hello when talking directly to systemd. + return dbusAuthConnection(func(opts ...dbus.ConnOption) (*dbus.Conn, error) { + return dbus.Dial("unix:path=/run/systemd/private") + }) + }) +} + +// Close closes an established connection +func (c *Conn) Close() { + c.sysconn.Close() + c.sigconn.Close() +} + +// 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(createBus func(opts ...dbus.ConnOption) (*dbus.Conn, error)) (*dbus.Conn, error) { + conn, err := createBus() + 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(createBus func(opts ...dbus.ConnOption) (*dbus.Conn, error)) (*dbus.Conn, error) { + conn, err := dbusAuthConnection(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/dbus/methods.go b/vendor/github.com/coreos/go-systemd/dbus/methods.go new file mode 100644 index 0000000000..5859583eb2 --- /dev/null +++ b/vendor/github.com/coreos/go-systemd/dbus/methods.go @@ -0,0 +1,600 @@ +// 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 ( + "errors" + "fmt" + "path" + "strconv" + + "github.com/godbus/dbus" +) + +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(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.Call(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 +} + +// StartUnit 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) StartUnit(name string, mode string, ch chan<- string) (int, error) { + return c.startJob(ch, "org.freedesktop.systemd1.Manager.StartUnit", name, mode) +} + +// StopUnit is similar to StartUnit but stops the specified unit rather +// than starting it. +func (c *Conn) StopUnit(name string, mode string, ch chan<- string) (int, error) { + return c.startJob(ch, "org.freedesktop.systemd1.Manager.StopUnit", name, mode) +} + +// ReloadUnit reloads a unit. Reloading is done only if the unit is already running and fails otherwise. +func (c *Conn) ReloadUnit(name string, mode string, ch chan<- string) (int, error) { + return c.startJob(ch, "org.freedesktop.systemd1.Manager.ReloadUnit", name, mode) +} + +// RestartUnit restarts a service. If a service is restarted that isn't +// running it will be started. +func (c *Conn) RestartUnit(name string, mode string, ch chan<- string) (int, error) { + return c.startJob(ch, "org.freedesktop.systemd1.Manager.RestartUnit", name, mode) +} + +// TryRestartUnit is like RestartUnit, except that a service that isn't running +// is not affected by the restart. +func (c *Conn) TryRestartUnit(name string, mode string, ch chan<- string) (int, error) { + return c.startJob(ch, "org.freedesktop.systemd1.Manager.TryRestartUnit", name, mode) +} + +// ReloadOrRestartUnit attempts a reload if the unit supports it and use a restart +// otherwise. +func (c *Conn) ReloadOrRestartUnit(name string, mode string, ch chan<- string) (int, error) { + return c.startJob(ch, "org.freedesktop.systemd1.Manager.ReloadOrRestartUnit", name, mode) +} + +// ReloadOrTryRestartUnit attempts a reload if the unit supports it and use a "Try" +// flavored restart otherwise. +func (c *Conn) ReloadOrTryRestartUnit(name string, mode string, ch chan<- string) (int, error) { + return c.startJob(ch, "org.freedesktop.systemd1.Manager.ReloadOrTryRestartUnit", name, mode) +} + +// StartTransientUnit() 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 StartUnit(), properties contains properties +// of the unit. +func (c *Conn) StartTransientUnit(name string, mode string, properties []Property, ch chan<- string) (int, error) { + return c.startJob(ch, "org.freedesktop.systemd1.Manager.StartTransientUnit", name, mode, properties, make([]PropertyCollection, 0)) +} + +// KillUnit takes the unit name and a UNIX signal number to send. All of the unit's +// processes are killed. +func (c *Conn) KillUnit(name string, signal int32) { + c.sysobj.Call("org.freedesktop.systemd1.Manager.KillUnit", 0, name, "all", signal).Store() +} + +// ResetFailedUnit resets the "failed" state of a specific unit. +func (c *Conn) ResetFailedUnit(name string) error { + return c.sysobj.Call("org.freedesktop.systemd1.Manager.ResetFailedUnit", 0, name).Store() +} + +// SystemState returns the systemd state. Equivalent to `systemctl is-system-running`. +func (c *Conn) SystemState() (*Property, error) { + var err error + var prop dbus.Variant + + obj := c.sysconn.Object("org.freedesktop.systemd1", "/org/freedesktop/systemd1") + err = obj.Call("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(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.Call("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 +} + +// GetUnitProperties takes the (unescaped) unit name and returns all of its dbus object properties. +func (c *Conn) GetUnitProperties(unit string) (map[string]interface{}, error) { + path := unitPath(unit) + return c.getProperties(path, "org.freedesktop.systemd1.Unit") +} + +// GetUnitPathProperties takes the (escaped) unit path and returns all of its dbus object properties. +func (c *Conn) GetUnitPathProperties(path dbus.ObjectPath) (map[string]interface{}, error) { + return c.getProperties(path, "org.freedesktop.systemd1.Unit") +} + +// GetAllProperties takes the (unescaped) unit name and returns all of its dbus object properties. +func (c *Conn) GetAllProperties(unit string) (map[string]interface{}, error) { + path := unitPath(unit) + return c.getProperties(path, "") +} + +func (c *Conn) getProperty(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.Call("org.freedesktop.DBus.Properties.Get", 0, dbusInterface, propertyName).Store(&prop) + if err != nil { + return nil, err + } + + return &Property{Name: propertyName, Value: prop}, nil +} + +func (c *Conn) GetUnitProperty(unit string, propertyName string) (*Property, error) { + return c.getProperty(unit, "org.freedesktop.systemd1.Unit", propertyName) +} + +// GetServiceProperty returns property for given service name and property name +func (c *Conn) GetServiceProperty(service string, propertyName string) (*Property, error) { + return c.getProperty(service, "org.freedesktop.systemd1.Service", propertyName) +} + +// GetUnitTypeProperties 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 +// return "dbus.Error: Unknown interface" if the unitType is not the correct type of the unit +func (c *Conn) GetUnitTypeProperties(unit string, unitType string) (map[string]interface{}, error) { + path := unitPath(unit) + return c.getProperties(path, "org.freedesktop.systemd1."+unitType) +} + +// SetUnitProperties() 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) SetUnitProperties(name string, runtime bool, properties ...Property) error { + return c.sysobj.Call("org.freedesktop.systemd1.Manager.SetUnitProperties", 0, name, runtime, properties).Store() +} + +func (c *Conn) GetUnitTypeProperty(unit string, unitType string, propertyName string) (*Property, error) { + return c.getProperty(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 +} + +// ListUnits 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) ListUnits() ([]UnitStatus, error) { + return c.listUnitsInternal(c.sysobj.Call("org.freedesktop.systemd1.Manager.ListUnits", 0).Store) +} + +// ListUnitsFiltered returns an array with units filtered by state. +// It takes a list of units' statuses to filter. +func (c *Conn) ListUnitsFiltered(states []string) ([]UnitStatus, error) { + return c.listUnitsInternal(c.sysobj.Call("org.freedesktop.systemd1.Manager.ListUnitsFiltered", 0, states).Store) +} + +// ListUnitsByPatterns 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) ListUnitsByPatterns(states []string, patterns []string) ([]UnitStatus, error) { + return c.listUnitsInternal(c.sysobj.Call("org.freedesktop.systemd1.Manager.ListUnitsByPatterns", 0, states, patterns).Store) +} + +// ListUnitsByNames returns an array with units. It takes a list of units' +// names and returns an UnitStatus array. Comparing to ListUnitsByPatterns +// method, this method returns statuses even for inactive or non-existing +// units. Input array should contain exact unit names, but not patterns. +// Note: Requires systemd v230 or higher +func (c *Conn) ListUnitsByNames(units []string) ([]UnitStatus, error) { + return c.listUnitsInternal(c.sysobj.Call("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 +} + +// ListUnitFiles returns an array of all available units on disk. +func (c *Conn) ListUnitFiles() ([]UnitFile, error) { + return c.listUnitFilesInternal(c.sysobj.Call("org.freedesktop.systemd1.Manager.ListUnitFiles", 0).Store) +} + +// ListUnitFilesByPatterns returns an array of all available units on disk matched the patterns. +func (c *Conn) ListUnitFilesByPatterns(states []string, patterns []string) ([]UnitFile, error) { + return c.listUnitFilesInternal(c.sysobj.Call("org.freedesktop.systemd1.Manager.ListUnitFilesByPatterns", 0, states, patterns).Store) +} + +type LinkUnitFileChange EnableUnitFileChange + +// LinkUnitFiles() 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) LinkUnitFiles(files []string, runtime bool, force bool) ([]LinkUnitFileChange, error) { + result := make([][]interface{}, 0) + err := c.sysobj.Call("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 +} + +// EnableUnitFiles() 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) EnableUnitFiles(files []string, runtime bool, force bool) (bool, []EnableUnitFileChange, error) { + var carries_install_info bool + + result := make([][]interface{}, 0) + err := c.sysobj.Call("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 +} + +// DisableUnitFiles() 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) DisableUnitFiles(files []string, runtime bool) ([]DisableUnitFileChange, error) { + result := make([][]interface{}, 0) + err := c.sysobj.Call("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 +} + +// MaskUnitFiles masks one or more units in the system +// +// It takes three arguments: +// * 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) +// * runtime to specify whether the unit was enabled for runtime +// only (true, /run/systemd/..), or persistently (false, /etc/systemd/..) +// * force flag +func (c *Conn) MaskUnitFiles(files []string, runtime bool, force bool) ([]MaskUnitFileChange, error) { + result := make([][]interface{}, 0) + err := c.sysobj.Call("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 +} + +// UnmaskUnitFiles unmasks one or more units in the system +// +// It takes two arguments: +// * 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) +// * runtime to specify whether the unit was enabled for runtime +// only (true, /run/systemd/..), or persistently (false, /etc/systemd/..) +func (c *Conn) UnmaskUnitFiles(files []string, runtime bool) ([]UnmaskUnitFileChange, error) { + result := make([][]interface{}, 0) + err := c.sysobj.Call("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 +} + +// Reload instructs systemd to scan for and reload unit files. This is +// equivalent to a 'systemctl daemon-reload'. +func (c *Conn) Reload() error { + return c.sysobj.Call("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))) +} diff --git a/vendor/github.com/coreos/go-systemd/dbus/properties.go b/vendor/github.com/coreos/go-systemd/dbus/properties.go new file mode 100644 index 0000000000..6c81895876 --- /dev/null +++ b/vendor/github.com/coreos/go-systemd/dbus/properties.go @@ -0,0 +1,237 @@ +// 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" +) + +// 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{ + 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/dbus/set.go b/vendor/github.com/coreos/go-systemd/dbus/set.go new file mode 100644 index 0000000000..17c5d48565 --- /dev/null +++ b/vendor/github.com/coreos/go-systemd/dbus/set.go @@ -0,0 +1,47 @@ +// 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/dbus/subscription.go b/vendor/github.com/coreos/go-systemd/dbus/subscription.go new file mode 100644 index 0000000000..f6d7a08a10 --- /dev/null +++ b/vendor/github.com/coreos/go-systemd/dbus/subscription.go @@ -0,0 +1,333 @@ +// 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" +) + +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/dbus/subscription_set.go b/vendor/github.com/coreos/go-systemd/dbus/subscription_set.go new file mode 100644 index 0000000000..5b408d5847 --- /dev/null +++ b/vendor/github.com/coreos/go-systemd/dbus/subscription_set.go @@ -0,0 +1,57 @@ +// 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/deckarep/golang-set/.gitignore b/vendor/github.com/deckarep/golang-set/.gitignore new file mode 100644 index 0000000000..00268614f0 --- /dev/null +++ b/vendor/github.com/deckarep/golang-set/.gitignore @@ -0,0 +1,22 @@ +# Compiled Object files, Static and Dynamic libs (Shared Objects) +*.o +*.a +*.so + +# Folders +_obj +_test + +# Architecture specific extensions/prefixes +*.[568vq] +[568vq].out + +*.cgo1.go +*.cgo2.c +_cgo_defun.c +_cgo_gotypes.go +_cgo_export.* + +_testmain.go + +*.exe diff --git a/vendor/github.com/deckarep/golang-set/.travis.yml b/vendor/github.com/deckarep/golang-set/.travis.yml new file mode 100644 index 0000000000..c760d24d1e --- /dev/null +++ b/vendor/github.com/deckarep/golang-set/.travis.yml @@ -0,0 +1,11 @@ +language: go + +go: + - 1.8 + - 1.9 + - tip + +script: + - go test -race ./... + - go test -bench=. + diff --git a/vendor/github.com/deckarep/golang-set/LICENSE b/vendor/github.com/deckarep/golang-set/LICENSE new file mode 100644 index 0000000000..b5768f89cf --- /dev/null +++ b/vendor/github.com/deckarep/golang-set/LICENSE @@ -0,0 +1,22 @@ +Open Source Initiative OSI - The MIT License (MIT):Licensing + +The MIT License (MIT) +Copyright (c) 2013 Ralph Caraveo (deckarep@gmail.com) + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/vendor/github.com/deckarep/golang-set/README.md b/vendor/github.com/deckarep/golang-set/README.md new file mode 100644 index 0000000000..c3b50b2c5c --- /dev/null +++ b/vendor/github.com/deckarep/golang-set/README.md @@ -0,0 +1,95 @@ +[![Build Status](https://travis-ci.org/deckarep/golang-set.svg?branch=master)](https://travis-ci.org/deckarep/golang-set) +[![Go Report Card](https://goreportcard.com/badge/github.com/deckarep/golang-set)](https://goreportcard.com/report/github.com/deckarep/golang-set) +[![GoDoc](https://godoc.org/github.com/deckarep/golang-set?status.svg)](http://godoc.org/github.com/deckarep/golang-set) + +## golang-set + + +The missing set collection for the Go language. Until Go has sets built-in...use this. + +Coming from Python one of the things I miss is the superbly wonderful set collection. This is my attempt to mimic the primary features of the set from Python. +You can of course argue that there is no need for a set in Go, otherwise the creators would have added one to the standard library. To those I say simply ignore this repository +and carry-on and to the rest that find this useful please contribute in helping me make it better by: + +* Helping to make more idiomatic improvements to the code. +* Helping to increase the performance of it. ~~(So far, no attempt has been made, but since it uses a map internally, I expect it to be mostly performant.)~~ +* Helping to make the unit-tests more robust and kick-ass. +* Helping to fill in the [documentation.](http://godoc.org/github.com/deckarep/golang-set) +* Simply offering feedback and suggestions. (Positive, constructive feedback is appreciated.) + +I have to give some credit for helping seed the idea with this post on [stackoverflow.](http://programmers.stackexchange.com/questions/177428/sets-data-structure-in-golang) + +*Update* - as of 3/9/2014, you can use a compile-time generic version of this package in the [gen](http://clipperhouse.github.io/gen/) framework. This framework allows you to use the golang-set in a completely generic and type-safe way by allowing you to generate a supporting .go file based on your custom types. + +## Features (as of 9/22/2014) + +* a CartesianProduct() method has been added with unit-tests: [Read more about the cartesian product](http://en.wikipedia.org/wiki/Cartesian_product) + +## Features (as of 9/15/2014) + +* a PowerSet() method has been added with unit-tests: [Read more about the Power set](http://en.wikipedia.org/wiki/Power_set) + +## Features (as of 4/22/2014) + +* One common interface to both implementations +* Two set implementations to choose from + * a thread-safe implementation designed for concurrent use + * a non-thread-safe implementation designed for performance +* 75 benchmarks for both implementations +* 35 unit tests for both implementations +* 14 concurrent tests for the thread-safe implementation + + + +Please see the unit test file for additional usage examples. The Python set documentation will also do a better job than I can of explaining how a set typically [works.](http://docs.python.org/2/library/sets.html) Please keep in mind +however that the Python set is a built-in type and supports additional features and syntax that make it awesome. + +## Examples but not exhaustive: + +```go +requiredClasses := mapset.NewSet() +requiredClasses.Add("Cooking") +requiredClasses.Add("English") +requiredClasses.Add("Math") +requiredClasses.Add("Biology") + +scienceSlice := []interface{}{"Biology", "Chemistry"} +scienceClasses := mapset.NewSetFromSlice(scienceSlice) + +electiveClasses := mapset.NewSet() +electiveClasses.Add("Welding") +electiveClasses.Add("Music") +electiveClasses.Add("Automotive") + +bonusClasses := mapset.NewSet() +bonusClasses.Add("Go Programming") +bonusClasses.Add("Python Programming") + +//Show me all the available classes I can take +allClasses := requiredClasses.Union(scienceClasses).Union(electiveClasses).Union(bonusClasses) +fmt.Println(allClasses) //Set{Cooking, English, Math, Chemistry, Welding, Biology, Music, Automotive, Go Programming, Python Programming} + + +//Is cooking considered a science class? +fmt.Println(scienceClasses.Contains("Cooking")) //false + +//Show me all classes that are not science classes, since I hate science. +fmt.Println(allClasses.Difference(scienceClasses)) //Set{Music, Automotive, Go Programming, Python Programming, Cooking, English, Math, Welding} + +//Which science classes are also required classes? +fmt.Println(scienceClasses.Intersect(requiredClasses)) //Set{Biology} + +//How many bonus classes do you offer? +fmt.Println(bonusClasses.Cardinality()) //2 + +//Do you have the following classes? Welding, Automotive and English? +fmt.Println(allClasses.IsSuperset(mapset.NewSetFromSlice([]interface{}{"Welding", "Automotive", "English"}))) //true +``` + +Thanks! + +-Ralph + +[![Bitdeli Badge](https://d2weczhvl823v0.cloudfront.net/deckarep/golang-set/trend.png)](https://bitdeli.com/free "Bitdeli Badge") + +[![Analytics](https://ga-beacon.appspot.com/UA-42584447-2/deckarep/golang-set)](https://github.com/igrigorik/ga-beacon) diff --git a/vendor/github.com/deckarep/golang-set/iterator.go b/vendor/github.com/deckarep/golang-set/iterator.go new file mode 100644 index 0000000000..9dfecade42 --- /dev/null +++ b/vendor/github.com/deckarep/golang-set/iterator.go @@ -0,0 +1,58 @@ +/* +Open Source Initiative OSI - The MIT License (MIT):Licensing + +The MIT License (MIT) +Copyright (c) 2013 Ralph Caraveo (deckarep@gmail.com) + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +package mapset + +// Iterator defines an iterator over a Set, its C channel can be used to range over the Set's +// elements. +type Iterator struct { + C <-chan interface{} + stop chan struct{} +} + +// Stop stops the Iterator, no further elements will be received on C, C will be closed. +func (i *Iterator) Stop() { + // Allows for Stop() to be called multiple times + // (close() panics when called on already closed channel) + defer func() { + recover() + }() + + close(i.stop) + + // Exhaust any remaining elements. + for range i.C { + } +} + +// newIterator returns a new Iterator instance together with its item and stop channels. +func newIterator() (*Iterator, chan<- interface{}, <-chan struct{}) { + itemChan := make(chan interface{}) + stopChan := make(chan struct{}) + return &Iterator{ + C: itemChan, + stop: stopChan, + }, itemChan, stopChan +} diff --git a/vendor/github.com/deckarep/golang-set/set.go b/vendor/github.com/deckarep/golang-set/set.go new file mode 100644 index 0000000000..29eb2e5a22 --- /dev/null +++ b/vendor/github.com/deckarep/golang-set/set.go @@ -0,0 +1,217 @@ +/* +Open Source Initiative OSI - The MIT License (MIT):Licensing + +The MIT License (MIT) +Copyright (c) 2013 Ralph Caraveo (deckarep@gmail.com) + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +// Package mapset implements a simple and generic set collection. +// Items stored within it are unordered and unique. It supports +// typical set operations: membership testing, intersection, union, +// difference, symmetric difference and cloning. +// +// Package mapset provides two implementations of the Set +// interface. The default implementation is safe for concurrent +// access, but a non-thread-safe implementation is also provided for +// programs that can benefit from the slight speed improvement and +// that can enforce mutual exclusion through other means. +package mapset + +// Set is the primary interface provided by the mapset package. It +// represents an unordered set of data and a large number of +// operations that can be applied to that set. +type Set interface { + // Adds an element to the set. Returns whether + // the item was added. + Add(i interface{}) bool + + // Returns the number of elements in the set. + Cardinality() int + + // Removes all elements from the set, leaving + // the empty set. + Clear() + + // Returns a clone of the set using the same + // implementation, duplicating all keys. + Clone() Set + + // Returns whether the given items + // are all in the set. + Contains(i ...interface{}) bool + + // Returns the difference between this set + // and other. The returned set will contain + // all elements of this set that are not also + // elements of other. + // + // Note that the argument to Difference + // must be of the same type as the receiver + // of the method. Otherwise, Difference will + // panic. + Difference(other Set) Set + + // Determines if two sets are equal to each + // other. If they have the same cardinality + // and contain the same elements, they are + // considered equal. The order in which + // the elements were added is irrelevant. + // + // Note that the argument to Equal must be + // of the same type as the receiver of the + // method. Otherwise, Equal will panic. + Equal(other Set) bool + + // Returns a new set containing only the elements + // that exist only in both sets. + // + // Note that the argument to Intersect + // must be of the same type as the receiver + // of the method. Otherwise, Intersect will + // panic. + Intersect(other Set) Set + + // Determines if every element in this set is in + // the other set but the two sets are not equal. + // + // Note that the argument to IsProperSubset + // must be of the same type as the receiver + // of the method. Otherwise, IsProperSubset + // will panic. + IsProperSubset(other Set) bool + + // Determines if every element in the other set + // is in this set but the two sets are not + // equal. + // + // Note that the argument to IsSuperset + // must be of the same type as the receiver + // of the method. Otherwise, IsSuperset will + // panic. + IsProperSuperset(other Set) bool + + // Determines if every element in this set is in + // the other set. + // + // Note that the argument to IsSubset + // must be of the same type as the receiver + // of the method. Otherwise, IsSubset will + // panic. + IsSubset(other Set) bool + + // Determines if every element in the other set + // is in this set. + // + // Note that the argument to IsSuperset + // must be of the same type as the receiver + // of the method. Otherwise, IsSuperset will + // panic. + IsSuperset(other Set) bool + + // Iterates over elements and executes the passed func against each element. + // If passed func returns true, stop iteration at the time. + Each(func(interface{}) bool) + + // Returns a channel of elements that you can + // range over. + Iter() <-chan interface{} + + // Returns an Iterator object that you can + // use to range over the set. + Iterator() *Iterator + + // Remove a single element from the set. + Remove(i interface{}) + + // Provides a convenient string representation + // of the current state of the set. + String() string + + // Returns a new set with all elements which are + // in either this set or the other set but not in both. + // + // Note that the argument to SymmetricDifference + // must be of the same type as the receiver + // of the method. Otherwise, SymmetricDifference + // will panic. + SymmetricDifference(other Set) Set + + // Returns a new set with all elements in both sets. + // + // Note that the argument to Union must be of the + + // same type as the receiver of the method. + // Otherwise, IsSuperset will panic. + Union(other Set) Set + + // Pop removes and returns an arbitrary item from the set. + Pop() interface{} + + // Returns all subsets of a given set (Power Set). + PowerSet() Set + + // Returns the Cartesian Product of two sets. + CartesianProduct(other Set) Set + + // Returns the members of the set as a slice. + ToSlice() []interface{} +} + +// NewSet creates and returns a reference to an empty set. Operations +// on the resulting set are thread-safe. +func NewSet(s ...interface{}) Set { + set := newThreadSafeSet() + for _, item := range s { + set.Add(item) + } + return &set +} + +// NewSetWith creates and returns a new set with the given elements. +// Operations on the resulting set are thread-safe. +func NewSetWith(elts ...interface{}) Set { + return NewSetFromSlice(elts) +} + +// NewSetFromSlice creates and returns a reference to a set from an +// existing slice. Operations on the resulting set are thread-safe. +func NewSetFromSlice(s []interface{}) Set { + a := NewSet(s...) + return a +} + +// NewThreadUnsafeSet creates and returns a reference to an empty set. +// Operations on the resulting set are not thread-safe. +func NewThreadUnsafeSet() Set { + set := newThreadUnsafeSet() + return &set +} + +// NewThreadUnsafeSetFromSlice creates and returns a reference to a +// set from an existing slice. Operations on the resulting set are +// not thread-safe. +func NewThreadUnsafeSetFromSlice(s []interface{}) Set { + a := NewThreadUnsafeSet() + for _, item := range s { + a.Add(item) + } + return a +} diff --git a/vendor/github.com/deckarep/golang-set/threadsafe.go b/vendor/github.com/deckarep/golang-set/threadsafe.go new file mode 100644 index 0000000000..269b4ab0cb --- /dev/null +++ b/vendor/github.com/deckarep/golang-set/threadsafe.go @@ -0,0 +1,283 @@ +/* +Open Source Initiative OSI - The MIT License (MIT):Licensing + +The MIT License (MIT) +Copyright (c) 2013 Ralph Caraveo (deckarep@gmail.com) + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +package mapset + +import "sync" + +type threadSafeSet struct { + s threadUnsafeSet + sync.RWMutex +} + +func newThreadSafeSet() threadSafeSet { + return threadSafeSet{s: newThreadUnsafeSet()} +} + +func (set *threadSafeSet) Add(i interface{}) bool { + set.Lock() + ret := set.s.Add(i) + set.Unlock() + return ret +} + +func (set *threadSafeSet) Contains(i ...interface{}) bool { + set.RLock() + ret := set.s.Contains(i...) + set.RUnlock() + return ret +} + +func (set *threadSafeSet) IsSubset(other Set) bool { + o := other.(*threadSafeSet) + + set.RLock() + o.RLock() + + ret := set.s.IsSubset(&o.s) + set.RUnlock() + o.RUnlock() + return ret +} + +func (set *threadSafeSet) IsProperSubset(other Set) bool { + o := other.(*threadSafeSet) + + set.RLock() + defer set.RUnlock() + o.RLock() + defer o.RUnlock() + + return set.s.IsProperSubset(&o.s) +} + +func (set *threadSafeSet) IsSuperset(other Set) bool { + return other.IsSubset(set) +} + +func (set *threadSafeSet) IsProperSuperset(other Set) bool { + return other.IsProperSubset(set) +} + +func (set *threadSafeSet) Union(other Set) Set { + o := other.(*threadSafeSet) + + set.RLock() + o.RLock() + + unsafeUnion := set.s.Union(&o.s).(*threadUnsafeSet) + ret := &threadSafeSet{s: *unsafeUnion} + set.RUnlock() + o.RUnlock() + return ret +} + +func (set *threadSafeSet) Intersect(other Set) Set { + o := other.(*threadSafeSet) + + set.RLock() + o.RLock() + + unsafeIntersection := set.s.Intersect(&o.s).(*threadUnsafeSet) + ret := &threadSafeSet{s: *unsafeIntersection} + set.RUnlock() + o.RUnlock() + return ret +} + +func (set *threadSafeSet) Difference(other Set) Set { + o := other.(*threadSafeSet) + + set.RLock() + o.RLock() + + unsafeDifference := set.s.Difference(&o.s).(*threadUnsafeSet) + ret := &threadSafeSet{s: *unsafeDifference} + set.RUnlock() + o.RUnlock() + return ret +} + +func (set *threadSafeSet) SymmetricDifference(other Set) Set { + o := other.(*threadSafeSet) + + set.RLock() + o.RLock() + + unsafeDifference := set.s.SymmetricDifference(&o.s).(*threadUnsafeSet) + ret := &threadSafeSet{s: *unsafeDifference} + set.RUnlock() + o.RUnlock() + return ret +} + +func (set *threadSafeSet) Clear() { + set.Lock() + set.s = newThreadUnsafeSet() + set.Unlock() +} + +func (set *threadSafeSet) Remove(i interface{}) { + set.Lock() + delete(set.s, i) + set.Unlock() +} + +func (set *threadSafeSet) Cardinality() int { + set.RLock() + defer set.RUnlock() + return len(set.s) +} + +func (set *threadSafeSet) Each(cb func(interface{}) bool) { + set.RLock() + for elem := range set.s { + if cb(elem) { + break + } + } + set.RUnlock() +} + +func (set *threadSafeSet) Iter() <-chan interface{} { + ch := make(chan interface{}) + go func() { + set.RLock() + + for elem := range set.s { + ch <- elem + } + close(ch) + set.RUnlock() + }() + + return ch +} + +func (set *threadSafeSet) Iterator() *Iterator { + iterator, ch, stopCh := newIterator() + + go func() { + set.RLock() + L: + for elem := range set.s { + select { + case <-stopCh: + break L + case ch <- elem: + } + } + close(ch) + set.RUnlock() + }() + + return iterator +} + +func (set *threadSafeSet) Equal(other Set) bool { + o := other.(*threadSafeSet) + + set.RLock() + o.RLock() + + ret := set.s.Equal(&o.s) + set.RUnlock() + o.RUnlock() + return ret +} + +func (set *threadSafeSet) Clone() Set { + set.RLock() + + unsafeClone := set.s.Clone().(*threadUnsafeSet) + ret := &threadSafeSet{s: *unsafeClone} + set.RUnlock() + return ret +} + +func (set *threadSafeSet) String() string { + set.RLock() + ret := set.s.String() + set.RUnlock() + return ret +} + +func (set *threadSafeSet) PowerSet() Set { + set.RLock() + unsafePowerSet := set.s.PowerSet().(*threadUnsafeSet) + set.RUnlock() + + ret := &threadSafeSet{s: newThreadUnsafeSet()} + for subset := range unsafePowerSet.Iter() { + unsafeSubset := subset.(*threadUnsafeSet) + ret.Add(&threadSafeSet{s: *unsafeSubset}) + } + return ret +} + +func (set *threadSafeSet) Pop() interface{} { + set.Lock() + defer set.Unlock() + return set.s.Pop() +} + +func (set *threadSafeSet) CartesianProduct(other Set) Set { + o := other.(*threadSafeSet) + + set.RLock() + o.RLock() + + unsafeCartProduct := set.s.CartesianProduct(&o.s).(*threadUnsafeSet) + ret := &threadSafeSet{s: *unsafeCartProduct} + set.RUnlock() + o.RUnlock() + return ret +} + +func (set *threadSafeSet) ToSlice() []interface{} { + keys := make([]interface{}, 0, set.Cardinality()) + set.RLock() + for elem := range set.s { + keys = append(keys, elem) + } + set.RUnlock() + return keys +} + +func (set *threadSafeSet) MarshalJSON() ([]byte, error) { + set.RLock() + b, err := set.s.MarshalJSON() + set.RUnlock() + + return b, err +} + +func (set *threadSafeSet) UnmarshalJSON(p []byte) error { + set.RLock() + err := set.s.UnmarshalJSON(p) + set.RUnlock() + + return err +} diff --git a/vendor/github.com/deckarep/golang-set/threadunsafe.go b/vendor/github.com/deckarep/golang-set/threadunsafe.go new file mode 100644 index 0000000000..10bdd46f15 --- /dev/null +++ b/vendor/github.com/deckarep/golang-set/threadunsafe.go @@ -0,0 +1,337 @@ +/* +Open Source Initiative OSI - The MIT License (MIT):Licensing + +The MIT License (MIT) +Copyright (c) 2013 Ralph Caraveo (deckarep@gmail.com) + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +package mapset + +import ( + "bytes" + "encoding/json" + "fmt" + "reflect" + "strings" +) + +type threadUnsafeSet map[interface{}]struct{} + +// An OrderedPair represents a 2-tuple of values. +type OrderedPair struct { + First interface{} + Second interface{} +} + +func newThreadUnsafeSet() threadUnsafeSet { + return make(threadUnsafeSet) +} + +// Equal says whether two 2-tuples contain the same values in the same order. +func (pair *OrderedPair) Equal(other OrderedPair) bool { + if pair.First == other.First && + pair.Second == other.Second { + return true + } + + return false +} + +func (set *threadUnsafeSet) Add(i interface{}) bool { + _, found := (*set)[i] + if found { + return false //False if it existed already + } + + (*set)[i] = struct{}{} + return true +} + +func (set *threadUnsafeSet) Contains(i ...interface{}) bool { + for _, val := range i { + if _, ok := (*set)[val]; !ok { + return false + } + } + return true +} + +func (set *threadUnsafeSet) IsSubset(other Set) bool { + _ = other.(*threadUnsafeSet) + for elem := range *set { + if !other.Contains(elem) { + return false + } + } + return true +} + +func (set *threadUnsafeSet) IsProperSubset(other Set) bool { + return set.IsSubset(other) && !set.Equal(other) +} + +func (set *threadUnsafeSet) IsSuperset(other Set) bool { + return other.IsSubset(set) +} + +func (set *threadUnsafeSet) IsProperSuperset(other Set) bool { + return set.IsSuperset(other) && !set.Equal(other) +} + +func (set *threadUnsafeSet) Union(other Set) Set { + o := other.(*threadUnsafeSet) + + unionedSet := newThreadUnsafeSet() + + for elem := range *set { + unionedSet.Add(elem) + } + for elem := range *o { + unionedSet.Add(elem) + } + return &unionedSet +} + +func (set *threadUnsafeSet) Intersect(other Set) Set { + o := other.(*threadUnsafeSet) + + intersection := newThreadUnsafeSet() + // loop over smaller set + if set.Cardinality() < other.Cardinality() { + for elem := range *set { + if other.Contains(elem) { + intersection.Add(elem) + } + } + } else { + for elem := range *o { + if set.Contains(elem) { + intersection.Add(elem) + } + } + } + return &intersection +} + +func (set *threadUnsafeSet) Difference(other Set) Set { + _ = other.(*threadUnsafeSet) + + difference := newThreadUnsafeSet() + for elem := range *set { + if !other.Contains(elem) { + difference.Add(elem) + } + } + return &difference +} + +func (set *threadUnsafeSet) SymmetricDifference(other Set) Set { + _ = other.(*threadUnsafeSet) + + aDiff := set.Difference(other) + bDiff := other.Difference(set) + return aDiff.Union(bDiff) +} + +func (set *threadUnsafeSet) Clear() { + *set = newThreadUnsafeSet() +} + +func (set *threadUnsafeSet) Remove(i interface{}) { + delete(*set, i) +} + +func (set *threadUnsafeSet) Cardinality() int { + return len(*set) +} + +func (set *threadUnsafeSet) Each(cb func(interface{}) bool) { + for elem := range *set { + if cb(elem) { + break + } + } +} + +func (set *threadUnsafeSet) Iter() <-chan interface{} { + ch := make(chan interface{}) + go func() { + for elem := range *set { + ch <- elem + } + close(ch) + }() + + return ch +} + +func (set *threadUnsafeSet) Iterator() *Iterator { + iterator, ch, stopCh := newIterator() + + go func() { + L: + for elem := range *set { + select { + case <-stopCh: + break L + case ch <- elem: + } + } + close(ch) + }() + + return iterator +} + +func (set *threadUnsafeSet) Equal(other Set) bool { + _ = other.(*threadUnsafeSet) + + if set.Cardinality() != other.Cardinality() { + return false + } + for elem := range *set { + if !other.Contains(elem) { + return false + } + } + return true +} + +func (set *threadUnsafeSet) Clone() Set { + clonedSet := newThreadUnsafeSet() + for elem := range *set { + clonedSet.Add(elem) + } + return &clonedSet +} + +func (set *threadUnsafeSet) String() string { + items := make([]string, 0, len(*set)) + + for elem := range *set { + items = append(items, fmt.Sprintf("%v", elem)) + } + return fmt.Sprintf("Set{%s}", strings.Join(items, ", ")) +} + +// String outputs a 2-tuple in the form "(A, B)". +func (pair OrderedPair) String() string { + return fmt.Sprintf("(%v, %v)", pair.First, pair.Second) +} + +func (set *threadUnsafeSet) Pop() interface{} { + for item := range *set { + delete(*set, item) + return item + } + return nil +} + +func (set *threadUnsafeSet) PowerSet() Set { + powSet := NewThreadUnsafeSet() + nullset := newThreadUnsafeSet() + powSet.Add(&nullset) + + for es := range *set { + u := newThreadUnsafeSet() + j := powSet.Iter() + for er := range j { + p := newThreadUnsafeSet() + if reflect.TypeOf(er).Name() == "" { + k := er.(*threadUnsafeSet) + for ek := range *(k) { + p.Add(ek) + } + } else { + p.Add(er) + } + p.Add(es) + u.Add(&p) + } + + powSet = powSet.Union(&u) + } + + return powSet +} + +func (set *threadUnsafeSet) CartesianProduct(other Set) Set { + o := other.(*threadUnsafeSet) + cartProduct := NewThreadUnsafeSet() + + for i := range *set { + for j := range *o { + elem := OrderedPair{First: i, Second: j} + cartProduct.Add(elem) + } + } + + return cartProduct +} + +func (set *threadUnsafeSet) ToSlice() []interface{} { + keys := make([]interface{}, 0, set.Cardinality()) + for elem := range *set { + keys = append(keys, elem) + } + + return keys +} + +// MarshalJSON creates a JSON array from the set, it marshals all elements +func (set *threadUnsafeSet) MarshalJSON() ([]byte, error) { + items := make([]string, 0, set.Cardinality()) + + for elem := range *set { + b, err := json.Marshal(elem) + if err != nil { + return nil, err + } + + items = append(items, string(b)) + } + + return []byte(fmt.Sprintf("[%s]", strings.Join(items, ","))), nil +} + +// UnmarshalJSON recreates a set from a JSON array, it only decodes +// primitive types. Numbers are decoded as json.Number. +func (set *threadUnsafeSet) UnmarshalJSON(b []byte) error { + var i []interface{} + + d := json.NewDecoder(bytes.NewReader(b)) + d.UseNumber() + err := d.Decode(&i) + if err != nil { + return err + } + + for _, v := range i { + switch t := v.(type) { + case []interface{}, map[string]interface{}: + continue + default: + set.Add(t) + } + } + + return nil +} diff --git a/vendor/github.com/go-toolsmith/astcast/astcast_generate.go b/vendor/github.com/go-toolsmith/astcast/astcast_generate.go new file mode 100644 index 0000000000..e942f13136 --- /dev/null +++ b/vendor/github.com/go-toolsmith/astcast/astcast_generate.go @@ -0,0 +1,163 @@ +// +build ignore + +package main + +import ( + "bytes" + "go/format" + "io" + "log" + "os" + "text/template" +) + +func main() { + typeList := []string{ + // Expressions: + "ArrayType", + "BadExpr", + "BasicLit", + "BinaryExpr", + "CallExpr", + "ChanType", + "CompositeLit", + "Ellipsis", + "FuncLit", + "FuncType", + "Ident", + "IndexExpr", + "InterfaceType", + "KeyValueExpr", + "MapType", + "ParenExpr", + "SelectorExpr", + "SliceExpr", + "StarExpr", + "StructType", + "TypeAssertExpr", + "UnaryExpr", + + // Statements: + "AssignStmt", + "BadStmt", + "BlockStmt", + "BranchStmt", + "CaseClause", + "CommClause", + "DeclStmt", + "DeferStmt", + "EmptyStmt", + "ExprStmt", + "ForStmt", + "GoStmt", + "IfStmt", + "IncDecStmt", + "LabeledStmt", + "RangeStmt", + "ReturnStmt", + "SelectStmt", + "SendStmt", + "SwitchStmt", + "TypeSwitchStmt", + + // Others: + "Comment", + "CommentGroup", + "FieldList", + "File", + "Package", + } + + astcastFile, err := os.Create("astcast.go") + if err != nil { + log.Fatal(err) + } + writeCode(astcastFile, typeList) + astcastTestFile, err := os.Create("astcast_test.go") + if err != nil { + log.Fatal(err) + } + writeTests(astcastTestFile, typeList) +} + +func generateCode(tmplText string, typeList []string) []byte { + tmpl := template.Must(template.New("code").Parse(tmplText)) + var code bytes.Buffer + tmpl.Execute(&code, typeList) + prettyCode, err := format.Source(code.Bytes()) + if err != nil { + panic(err) + } + return prettyCode +} + +func writeCode(output io.Writer, typeList []string) { + code := generateCode(`// Code generated by astcast_generate.go; DO NOT EDIT + +// Package astcast wraps type assertion operations in such way that you don't have +// to worry about nil pointer results anymore. +package astcast + +import ( + "go/ast" +) + +// A set of sentinel nil-like values that are returned +// by all "casting" functions in case of failed type assertion. +var ( +{{ range . }} +Nil{{.}} = &ast.{{.}}{} +{{- end }} +) + +{{ range . }} +// To{{.}} returns x as a non-nil *ast.{{.}}. +// If ast.Node actually has such dynamic type, the result is +// identical to normal type assertion. In case if it has +// different type, the returned value is Nil{{.}}. +func To{{.}}(x ast.Node) *ast.{{.}} { + if x, ok := x.(*ast.{{.}}); ok { + return x + } + return Nil{{.}} +} +{{ end }} +`, typeList) + output.Write(code) +} + +func writeTests(output io.Writer, typeList []string) { + code := generateCode(`// Code generated by astcast_generate.go; DO NOT EDIT + +package astcast + +import ( + "go/ast" + "testing" +) + +{{ range . }} +func TestTo{{.}}(t *testing.T) { + // Test successfull cast. + if x := To{{.}}(&ast.{{.}}{}); x == Nil{{.}} || x == nil { + t.Error("expected successfull cast, got nil") + } + // Test nil cast. + if x := To{{.}}(nil); x != Nil{{.}} { + t.Error("nil node didn't resulted in a sentinel value return") + } + // Test unsuccessfull cast. + {{- if (eq . "Ident") }} + if x := To{{.}}(&ast.CallExpr{}); x != Nil{{.}} || x == nil { + t.Errorf("expected unsuccessfull cast to return nil sentinel") + } + {{- else }} + if x := To{{.}}(&ast.Ident{}); x != Nil{{.}} || x == nil { + t.Errorf("expected unsuccessfull cast to return nil sentinel") + } + {{- end }} +} +{{ end }} +`, typeList) + output.Write(code) +} diff --git a/vendor/github.com/go-toolsmith/typep/simplePredicates_generate.go b/vendor/github.com/go-toolsmith/typep/simplePredicates_generate.go new file mode 100644 index 0000000000..c0408378e6 --- /dev/null +++ b/vendor/github.com/go-toolsmith/typep/simplePredicates_generate.go @@ -0,0 +1,140 @@ +// +build ignore + +package main + +import ( + "bytes" + "go/format" + "io" + "log" + "os" + "text/template" +) + +type types struct { + BasicKinds []string + BasicProps []string + Types []string +} + +func main() { + typesList := types{ + BasicKinds: []string{ + "Bool", + "Int", + "Int8", + "Int16", + "Int32", + "Int64", + "Uint", + "Uint8", + "Uint16", + "Uint32", + "Uint64", + "Uintptr", + "Float32", + "Float64", + "Complex64", + "Complex128", + "String", + "UnsafePointer", + "UntypedBool", + "UntypedInt", + "UntypedRune", + "UntypedFloat", + "UntypedComplex", + "UntypedString", + "UntypedNil", + }, + + BasicProps: []string{ + "Boolean", + "Integer", + "Unsigned", + "Float", + "Complex", + "String", + "Untyped", + "Ordered", + "Numeric", + "ConstType", + }, + + Types: []string{ + "Basic", + "Array", + "Slice", + "Struct", + "Pointer", + "Tuple", + "Signature", + "Interface", + "Map", + "Chan", + "Named", + }, + } + + simplePredicateFile, err := os.Create("simplePredicates.go") + if err != nil { + log.Fatal(err) + } + writeCode(simplePredicateFile, typesList) +} + +func generateCode(tmplText string, typeList types) []byte { + tmpl := template.Must(template.New("code").Parse(tmplText)) + var code bytes.Buffer + tmpl.Execute(&code, typeList) + prettyCode, err := format.Source(code.Bytes()) + if err != nil { + panic(err) + } + return prettyCode +} + +func writeCode(output io.Writer, typeList types) { + code := generateCode(`// Code generated by simplePredicates_generate.go; DO NOT EDIT + +package typep + +import ( + "go/types" +) + +// Simple 1-to-1 type predicates via type assertion. + +{{ range .Types }} +// Is{{.}} reports whether a given type has *types.{{.}} type. +func Is{{.}}(typ types.Type) bool { + _, ok := typ.(*types.{{.}}) + return ok +} +{{ end }} + +// *types.Basic predicates for the info field. + +{{ range .BasicProps }} +// Has{{.}}Prop reports whether typ is a *types.Basic has Is{{.}} property. +func Has{{.}}Prop(typ types.Type) bool { + if typ, ok := typ.(*types.Basic); ok { + return typ.Info()&types.Is{{.}} != 0 + } + return false +} +{{ end }} + +// *types.Basic predicates for the kind field. + +{{ range .BasicKinds }} +// Has{{.}}Kind reports whether typ is a *types.Basic with its kind set to types.{{.}}. +func Has{{.}}Kind(typ types.Type) bool { + if typ, ok := typ.(*types.Basic); ok { + return typ.Kind() == types.{{.}} + } + return false +} +{{ end }} +`, typeList) + output.Write(code) +} diff --git a/vendor/github.com/godbus/dbus/.travis.yml b/vendor/github.com/godbus/dbus/.travis.yml new file mode 100644 index 0000000000..9cd57f432b --- /dev/null +++ b/vendor/github.com/godbus/dbus/.travis.yml @@ -0,0 +1,46 @@ +dist: precise +language: go +go_import_path: github.com/godbus/dbus +sudo: true + +go: + - 1.7.3 + - 1.8.7 + - 1.9.5 + - 1.10.1 + - tip + +env: + global: + matrix: + - TARGET=amd64 + - TARGET=arm64 + - TARGET=arm + - TARGET=386 + - TARGET=ppc64le + +matrix: + fast_finish: true + allow_failures: + - go: tip + exclude: + - go: tip + env: TARGET=arm + - go: tip + env: TARGET=arm64 + - go: tip + env: TARGET=386 + - go: tip + env: TARGET=ppc64le + +addons: + apt: + packages: + - dbus + - dbus-x11 + +before_install: + +script: + - go test -v -race ./... # Run all the tests with the race detector enabled + - go vet ./... # go vet is the official Go static analyzer diff --git a/vendor/github.com/godbus/dbus/CONTRIBUTING.md b/vendor/github.com/godbus/dbus/CONTRIBUTING.md new file mode 100644 index 0000000000..c88f9b2bdd --- /dev/null +++ b/vendor/github.com/godbus/dbus/CONTRIBUTING.md @@ -0,0 +1,50 @@ +# 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: + +``` +: + + + +