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..caa469bf90 --- /dev/null +++ b/pkg/daemon/apply.go @@ -0,0 +1,209 @@ +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{ + "/etc/containers/registries.conf": { + actions: []ConfigUpdateAction{ + ServicePostAction{ + Reason: "Change to /etc/containers/registries.conf", + ServiceName: "crio.service", + ServiceAction: "restart", + }, + }, + }, + } + + 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..3604218289 --- /dev/null +++ b/pkg/daemon/apply_test.go @@ -0,0 +1,295 @@ +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.Nil(t, err) + + _, err = lookupStrategy(aPrefix, filepath.Join(aPrefix, "/etc/containers/registries.conf")) + assert.Nil(t, err) +} + +// 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{ + ServicePostAction{ + Reason: "Change to /etc/containers/registries.conf", + ServiceName: "crio.service", + ServiceAction: "restart", + }, + }, + }, { + 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/sysctl.d/inotify.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..fe8ed652ef 100644 --- a/test/e2e/mcd_test.go +++ b/test/e2e/mcd_test.go @@ -3,13 +3,16 @@ package e2e_test import ( "context" "fmt" + "strconv" "strings" "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" @@ -17,11 +20,14 @@ import ( "k8s.io/apimachinery/pkg/util/uuid" "k8s.io/apimachinery/pkg/util/wait" + apioperatorsv1alpha1 "github.com/openshift/api/operator/v1alpha1" 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/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 @@ -523,3 +529,86 @@ func TestIgn3Cfg(t *testing.T) { require.Nil(t, err) } + +func TestUpdateICSP(t *testing.T) { + upField := 3 + cs := framework.NewClientSet("") + + // create a dummy MC with an sshKey for user Core + icspName := fmt.Sprintf("00-icsp-worker-%s", uuid.NewUUID()) + icspRule := &apioperatorsv1alpha1.ImageContentSourcePolicy{ + ObjectMeta: metav1.ObjectMeta{ + Name: icspName, + Labels: mcLabelForWorkers(), + }, + Spec: apioperatorsv1alpha1.ImageContentSourcePolicySpec{ + RepositoryDigestMirrors: []apioperatorsv1alpha1.RepositoryDigestMirrors{ + {Source: "other.com/ns-o3", Mirrors: []string{fmt.Sprintf("insecure.com/%s", icspName), "blocked.com/ns-b/ns3-b"}}, + }, + }, + } + + nodes, err := getNodesByRole(cs, "worker") + require.Nil(t, err) + + uptimes := map[string]string{} + for _, node := range nodes { + uptime := execCmdOnNode(t, cs, node, "uptime") + fields := strings.Split(uptime, " ") + uptimes[node.Name] = fields[upField] + t.Logf("Node %s had uptime: %s (%s)", node.Name, uptime, fields[upField]) + } + _, err = cs.ImageContentSourcePolicies().Create(context.TODO(), icspRule, metav1.CreateOptions{}) + require.Nil(t, err, "failed to create ICSP") + t.Logf("Created %s", icspRule.Name) + + + // Create and apply an empty config with the same name + // Once it has been applied, we can also assue the ICSP changes have taken effect + mcadd := createMC(icspName, "worker") + ignConfig := ctrlcommon.NewIgnConfig() + rawIgnConfig := helpers.MarshalOrDie(ignConfig) + mcadd.Spec.Config.Raw = rawIgnConfig + + _, err = cs.MachineConfigs().Create(context.TODO(), mcadd, metav1.CreateOptions{}) + require.Nil(t, err, "failed to create MC") + t.Logf("Created %s", mcadd.Name) + + // grab the latest worker- MC + renderedConfig, err := waitForRenderedConfig(t, cs, "worker", mcadd.Name) + require.Nil(t, err) + err = waitForPoolComplete(t, cs, "worker", renderedConfig) + require.Nil(t, err) + nodes, err = getNodesByRole(cs, "worker") + require.Nil(t, err) + + for _, node := range nodes { + assert.Equal(t, node.Annotations[constants.CurrentMachineConfigAnnotationKey], renderedConfig) + assert.Equal(t, node.Annotations[constants.MachineConfigDaemonStateAnnotationKey], constants.MachineConfigDaemonStateDone) + uptime := execCmdOnNode(t, cs, node, "uptime") + fields := strings.Split(uptime, " ") + t.Logf("Node %s has uptime: %s (%s)", node.Name, uptime, fields[upField]) + + // now rsh into that daemon and grep the registries file to check if {icspName} was written + // must do both commands in same shell, combine commands into one exec.Command() + found := execCmdOnNode(t, cs, node, "grep", "location", "/rootfs/etc/containers/registries.conf") + if !strings.Contains(found, icspName) { + t.Errorf("updated registry not found in registries.conf, got %s", found) + } else { + t.Logf("Node %s has the updated registries.conf with %s", node.Name, icspName) + } + + // Test for decreased uptime + oldUp, err := strconv.Atoi(uptimes[node.Name]) + require.Nil(t, err) + + newUp, err := strconv.Atoi(fields[upField]) + require.Nil(t, err) + + if oldUp > newUp { + t.Errorf("Node %s uptime reduced from %v to %v", node.Name, oldUp, newUp) + } + } +} + + 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: + +``` +: + + + +