From a8d083efe1a356e16f8aaeef17c61a4c33ad6435 Mon Sep 17 00:00:00 2001 From: Enrique Llorente Pastora Date: Tue, 27 Apr 2021 08:45:09 +0200 Subject: [PATCH] Use github.com/gofrs/flock to lock handler (#731) * Use github.com/gofrs/flock to lock handler The nmstate-handler has a lock that make it wait if another handler is already running in the system, but the library used for that does not work well with containers. This change replace that library with different one that uses golang syscall.Flock behind the scine [1] and that works fine with containers sharing node volume. [1] github.com/nightlyone/lockfile. Signed-off-by: Quique Llorente * Replace handler readiness probe The nmstate handler is marked as ready even if it was not able to take ownership of the lock, this change runs nmstatectl and touchs a file after gaining lock ownership so the readiness probe can check both things. Signed-off-by: Quique Llorente * Test nmstate handler lock mechanism The nmstate handler don't get ready if another handler is running at the same node. This change add an e2e tests to check that this is working. Signed-off-by: Quique Llorente --- deploy/handler/operator.yaml | 4 +- go.mod | 3 +- go.sum | 1 + main.go | 38 ++- pkg/file/touch.go | 24 ++ test/e2e/daemonset/daemonset.go | 8 + test/e2e/operator/main_test.go | 77 +++-- test/e2e/operator/nmstate_install_test.go | 82 ++++-- .../lockfile => gofrs/flock}/.gitignore | 7 +- vendor/github.com/gofrs/flock/.travis.yml | 10 + vendor/github.com/gofrs/flock/LICENSE | 27 ++ vendor/github.com/gofrs/flock/README.md | 41 +++ vendor/github.com/gofrs/flock/appveyor.yml | 25 ++ vendor/github.com/gofrs/flock/flock.go | 144 ++++++++++ vendor/github.com/gofrs/flock/flock_aix.go | 271 ++++++++++++++++++ vendor/github.com/gofrs/flock/flock_unix.go | 197 +++++++++++++ vendor/github.com/gofrs/flock/flock_winapi.go | 76 +++++ .../github.com/gofrs/flock/flock_windows.go | 142 +++++++++ .../nightlyone/lockfile/.gitmodules | 3 - .../nightlyone/lockfile/.travis.yml | 14 - vendor/github.com/nightlyone/lockfile/LICENSE | 19 -- .../github.com/nightlyone/lockfile/README.md | 52 ---- .../nightlyone/lockfile/appveyor.yml | 12 - vendor/github.com/nightlyone/lockfile/go.mod | 3 - .../nightlyone/lockfile/lockfile.go | 222 -------------- .../nightlyone/lockfile/lockfile_unix.go | 21 -- .../nightlyone/lockfile/lockfile_windows.go | 30 -- .../pkg/webhook/server/server.go | 162 ----------- vendor/modules.txt | 5 +- 29 files changed, 1106 insertions(+), 614 deletions(-) create mode 100644 pkg/file/touch.go rename vendor/github.com/{nightlyone/lockfile => gofrs/flock}/.gitignore (86%) create mode 100644 vendor/github.com/gofrs/flock/.travis.yml create mode 100644 vendor/github.com/gofrs/flock/LICENSE create mode 100644 vendor/github.com/gofrs/flock/README.md create mode 100644 vendor/github.com/gofrs/flock/appveyor.yml create mode 100644 vendor/github.com/gofrs/flock/flock.go create mode 100644 vendor/github.com/gofrs/flock/flock_aix.go create mode 100644 vendor/github.com/gofrs/flock/flock_unix.go create mode 100644 vendor/github.com/gofrs/flock/flock_winapi.go create mode 100644 vendor/github.com/gofrs/flock/flock_windows.go delete mode 100644 vendor/github.com/nightlyone/lockfile/.gitmodules delete mode 100644 vendor/github.com/nightlyone/lockfile/.travis.yml delete mode 100644 vendor/github.com/nightlyone/lockfile/LICENSE delete mode 100644 vendor/github.com/nightlyone/lockfile/README.md delete mode 100644 vendor/github.com/nightlyone/lockfile/appveyor.yml delete mode 100644 vendor/github.com/nightlyone/lockfile/go.mod delete mode 100644 vendor/github.com/nightlyone/lockfile/lockfile.go delete mode 100644 vendor/github.com/nightlyone/lockfile/lockfile_unix.go delete mode 100644 vendor/github.com/nightlyone/lockfile/lockfile_windows.go delete mode 100644 vendor/github.com/qinqon/kube-admission-webhook/pkg/webhook/server/server.go diff --git a/deploy/handler/operator.yaml b/deploy/handler/operator.yaml index f79ab92ace..40713877a7 100644 --- a/deploy/handler/operator.yaml +++ b/deploy/handler/operator.yaml @@ -219,8 +219,8 @@ spec: readinessProbe: exec: command: - - nmstatectl - - show + - cat + - /tmp/healthy initialDelaySeconds: 5 periodSeconds: 5 timeoutSeconds: 1 diff --git a/go.mod b/go.mod index f45cc909a9..3ad02296fd 100644 --- a/go.mod +++ b/go.mod @@ -8,8 +8,9 @@ require ( github.com/github-release/github-release v0.10.0 github.com/go-logr/logr v0.3.0 github.com/gobwas/glob v0.2.3 + github.com/gofrs/flock v0.8.0 github.com/kelseyhightower/envconfig v1.4.0 - github.com/nightlyone/lockfile v1.0.0 + github.com/nightlyone/lockfile v1.0.0 // indirect github.com/onsi/ginkgo v1.15.0 github.com/onsi/gomega v1.10.5 github.com/openshift/cluster-network-operator v0.0.0-20200922032245-f47200e8dbc0 diff --git a/go.sum b/go.sum index 4250221424..80bd823c26 100644 --- a/go.sum +++ b/go.sum @@ -667,6 +667,7 @@ github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5x github.com/godror/godror v0.13.3/go.mod h1:2ouUT4kdhUBk7TAkHWD4SN0CdI0pgEQbo8FVHhbSKWg= github.com/gofrs/flock v0.0.0-20190320160742-5135e617513b/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU= github.com/gofrs/flock v0.7.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU= +github.com/gofrs/flock v0.8.0 h1:MSdYClljsF3PbENUUEx85nkWfJSGfzYI9yEBZOJz6CY= github.com/gofrs/flock v0.8.0/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU= github.com/gofrs/uuid v3.3.0+incompatible h1:8K4tyRfvU1CYPgJsveYFQMhpFd/wXNM7iK6rR7UHz84= github.com/gofrs/uuid v3.3.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= diff --git a/main.go b/main.go index af77f83c8e..4f5f61216c 100644 --- a/main.go +++ b/main.go @@ -32,8 +32,8 @@ import ( "sigs.k8s.io/controller-runtime/pkg/log/zap" // +kubebuilder:scaffold:imports + "github.com/gofrs/flock" "github.com/kelseyhightower/envconfig" - "github.com/nightlyone/lockfile" "github.com/pkg/errors" "github.com/qinqon/kube-admission-webhook/pkg/certificate" "k8s.io/apimachinery/pkg/util/wait" @@ -42,6 +42,8 @@ import ( nmstatev1beta1 "github.com/nmstate/kubernetes-nmstate/api/v1beta1" "github.com/nmstate/kubernetes-nmstate/controllers" "github.com/nmstate/kubernetes-nmstate/pkg/environment" + "github.com/nmstate/kubernetes-nmstate/pkg/file" + "github.com/nmstate/kubernetes-nmstate/pkg/nmstatectl" "github.com/nmstate/kubernetes-nmstate/pkg/webhook" ) @@ -186,6 +188,25 @@ func main() { setupLog.Error(err, "unable to create NodeNetworkState controller", "controller", "NMState") os.Exit(1) } + + // Check that nmstatectl is working + _, err := nmstatectl.Show() + if err != nil { + os.Exit(1) + setupLog.Error(err, "failed checking nmstatectl health") + } + + // Handler runs with host networking so opening ports is problematic + // they will collide with node ports so to ensure that we reach this + // point (we have the handler lock and nmstatectl show is working) a + // file is touched and the file is checked at readinessProbe field. + healthyFile := "/tmp/healthy" + setupLog.Info("Marking handler as healthy touching healthy file", "healthyFile", healthyFile) + err = file.Touch(healthyFile) + if err != nil { + os.Exit(1) + setupLog.Error(err, "failed marking handler as healthy") + } } setProfiler() @@ -214,23 +235,20 @@ func setProfiler() { } } -func lockHandler() (lockfile.Lockfile, error) { +func lockHandler() (*flock.Flock, error) { lockFilePath, ok := os.LookupEnv("NMSTATE_INSTANCE_NODE_LOCK_FILE") if !ok { - return "", errors.New("Failed to find NMSTATE_INSTANCE_NODE_LOCK_FILE ENV var") + return nil, errors.New("Failed to find NMSTATE_INSTANCE_NODE_LOCK_FILE ENV var") } setupLog.Info(fmt.Sprintf("Try to take exclusive lock on file: %s", lockFilePath)) - handlerLock, err := lockfile.New(lockFilePath) - if err != nil { - return handlerLock, errors.Wrapf(err, "failed to create lockFile for %s", lockFilePath) - } - err = wait.PollImmediateInfinite(5*time.Second, func() (done bool, err error) { - err = handlerLock.TryLock() + handlerLock := flock.New(lockFilePath) + err := wait.PollImmediateInfinite(5*time.Second, func() (done bool, err error) { + locked, err := handlerLock.TryLock() if err != nil { setupLog.Error(err, "retrying to lock handler") return false, nil // Don't return the error here, it will not re-poll if we do } - return true, nil + return locked, nil }) return handlerLock, err } diff --git a/pkg/file/touch.go b/pkg/file/touch.go new file mode 100644 index 0000000000..485df2a11e --- /dev/null +++ b/pkg/file/touch.go @@ -0,0 +1,24 @@ +package file + +import ( + "os" + "time" +) + +func Touch(fileName string) error { + _, err := os.Stat(fileName) + if os.IsNotExist(err) { + file, err := os.Create(fileName) + if err != nil { + return err + } + defer file.Close() + } else { + currentTime := time.Now().Local() + err = os.Chtimes(fileName, currentTime, currentTime) + if err != nil { + return err + } + } + return nil +} diff --git a/test/e2e/daemonset/daemonset.go b/test/e2e/daemonset/daemonset.go index 2720e6b403..2508bc1209 100644 --- a/test/e2e/daemonset/daemonset.go +++ b/test/e2e/daemonset/daemonset.go @@ -21,6 +21,14 @@ func GetEventually(daemonSetKey types.NamespacedName) AsyncAssertion { }, 180*time.Second, 1*time.Second) } +func GetConsistently(daemonSetKey types.NamespacedName) AsyncAssertion { + return Consistently(func() (appsv1.DaemonSet, error) { + daemonSet := appsv1.DaemonSet{} + err := testenv.Client.Get(context.TODO(), daemonSetKey, &daemonSet) + return daemonSet, err + }, 15*time.Second, 1*time.Second) +} + // GetDaemonSetList returns a DaemonSetList matching the labels passed func GetList(filteringLabels map[string]string) (appsv1.DaemonSetList, error) { ds := appsv1.DaemonSetList{} diff --git a/test/e2e/operator/main_test.go b/test/e2e/operator/main_test.go index 40bdeb336f..b8c5f35aa8 100644 --- a/test/e2e/operator/main_test.go +++ b/test/e2e/operator/main_test.go @@ -2,6 +2,8 @@ package operator import ( "context" + "fmt" + "os" "testing" "time" @@ -11,44 +13,39 @@ import ( ginkgoreporters "kubevirt.io/qe-tools/pkg/ginkgo-reporters" corev1 "k8s.io/api/core/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/client" dynclient "sigs.k8s.io/controller-runtime/pkg/client" logf "sigs.k8s.io/controller-runtime/pkg/log" "sigs.k8s.io/controller-runtime/pkg/log/zap" + nmstatev1beta1 "github.com/nmstate/kubernetes-nmstate/api/v1beta1" testenv "github.com/nmstate/kubernetes-nmstate/test/env" knmstatereporter "github.com/nmstate/kubernetes-nmstate/test/reporter" ) var ( - t *testing.T - nodes []string - startTime time.Time + t *testing.T + nodes []string + startTime time.Time + defaultNMState = nmstatev1beta1.NMState{ + ObjectMeta: metav1.ObjectMeta{ + Name: "nmstate", + Namespace: "nmstate", + }, + } + webhookKey = types.NamespacedName{Namespace: "nmstate", Name: "nmstate-webhook"} + handlerKey = types.NamespacedName{Namespace: "nmstate", Name: "nmstate-handler"} + handlerLabels = map[string]string{"component": "kubernetes-nmstate-handler"} ) -var _ = BeforeSuite(func() { - - logf.SetLogger(zap.New(zap.WriteTo(GinkgoWriter), zap.UseDevMode(true))) - - testenv.Start() - -}) - -func TestMain(m *testing.M) { +func TestE2E(t *testing.T) { testenv.TestMain() -} -func TestE2E(tapi *testing.T) { - t = tapi RegisterFailHandler(Fail) - By("Getting node list from cluster") - nodeList := corev1.NodeList{} - err := testenv.Client.List(context.TODO(), &nodeList, &dynclient.ListOptions{}) - Expect(err).ToNot(HaveOccurred()) - for _, node := range nodeList.Items { - nodes = append(nodes, node.Name) - } - reporters := make([]Reporter, 0) reporters = append(reporters, knmstatereporter.New("test_logs/e2e/operator", testenv.OperatorNamespace, nodes)) if ginkgoreporters.Polarion.Run { @@ -61,8 +58,38 @@ func TestE2E(tapi *testing.T) { RunSpecsWithDefaultAndCustomReporters(t, "Operator E2E Test Suite", reporters) } -var _ = BeforeEach(func() { +var _ = BeforeSuite(func() { + + // Change to root directory some test expect that + os.Chdir("../../../") + + logf.SetLogger(zap.New(zap.WriteTo(GinkgoWriter), zap.UseDevMode(true))) + + testenv.Start() + + By("Getting node list from cluster") + nodeList := corev1.NodeList{} + err := testenv.Client.List(context.TODO(), &nodeList, &dynclient.ListOptions{}) + Expect(err).ToNot(HaveOccurred()) + for _, node := range nodeList.Items { + nodes = append(nodes, node.Name) + } }) -var _ = AfterEach(func() { +var _ = AfterSuite(func() { + uninstallNMState(defaultNMState) }) + +func installNMState(nmstate nmstatev1beta1.NMState) { + By(fmt.Sprintf("Creating NMState CR '%s'", nmstate.Name)) + err := testenv.Client.Create(context.TODO(), &nmstate) + ExpectWithOffset(1, err).ToNot(HaveOccurred(), "NMState CR created without error") +} + +func uninstallNMState(nmstate nmstatev1beta1.NMState) { + By(fmt.Sprintf("Deleting NMState CR '%s'", nmstate.Name)) + err := testenv.Client.Delete(context.TODO(), &nmstate, &client.DeleteOptions{}) + if !apierrors.IsNotFound(err) { + Expect(err).ToNot(HaveOccurred(), "NMState CR successfully removed") + } +} diff --git a/test/e2e/operator/nmstate_install_test.go b/test/e2e/operator/nmstate_install_test.go index 0d3b0ea869..99e3c1239e 100644 --- a/test/e2e/operator/nmstate_install_test.go +++ b/test/e2e/operator/nmstate_install_test.go @@ -7,46 +7,35 @@ import ( . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" + corev1 "k8s.io/api/core/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" "sigs.k8s.io/controller-runtime/pkg/client" - nmstatev1beta1 "github.com/nmstate/kubernetes-nmstate/api/v1beta1" "github.com/nmstate/kubernetes-nmstate/test/e2e/daemonset" "github.com/nmstate/kubernetes-nmstate/test/e2e/deployment" + "github.com/nmstate/kubernetes-nmstate/test/cmd" testenv "github.com/nmstate/kubernetes-nmstate/test/env" ) -var ( - defaultNMState = nmstatev1beta1.NMState{ - ObjectMeta: metav1.ObjectMeta{ - Name: "nmstate", - Namespace: "nmstate", - }, - } - webhookKey = types.NamespacedName{Namespace: "nmstate", Name: "nmstate-webhook"} - handlerKey = types.NamespacedName{Namespace: "nmstate", Name: "nmstate-handler"} - handlerLabels = map[string]string{"component": "kubernetes-nmstate-handler"} -) - var _ = Describe("NMState operator", func() { Context("when installed for the first time", func() { BeforeEach(func() { - installDefaultNMState() + installNMState(defaultNMState) }) It("should deploy daemonset and webhook deployment", func() { daemonset.GetEventually(handlerKey).Should(daemonset.BeReady()) deployment.GetEventually(webhookKey).Should(deployment.BeReady()) }) AfterEach(func() { - uninstallDefaultNMState() + uninstallNMState(defaultNMState) }) }) Context("when NMState is installed", func() { It("should list one NMState CR", func() { - installDefaultNMState() + installNMState(defaultNMState) daemonset.GetEventually(handlerKey).Should(daemonset.BeReady()) ds, err := daemonset.GetList(handlerLabels) Expect(err).ToNot(HaveOccurred(), "List daemon sets in namespace nmstate succeeds") @@ -71,7 +60,7 @@ var _ = Describe("NMState operator", func() { }) Context("and uninstalled", func() { BeforeEach(func() { - uninstallDefaultNMState() + uninstallNMState(defaultNMState) }) It("should uninstall handler and webhook", func() { Eventually(func() bool { @@ -85,24 +74,55 @@ var _ = Describe("NMState operator", func() { }) }) }) + Context("when another handler is installed with different namespace", func() { + var ( + operatorNamespace = "nmstate-alt" + ) + BeforeEach(func() { + installNMState(defaultNMState) + daemonset.GetEventually(handlerKey).Should(daemonset.BeReady()) + installOperator(operatorNamespace) + }) + AfterEach(func() { + uninstallNMState(defaultNMState) + uninstallOperator(operatorNamespace) + installOperator("nmstate") + }) + It("should wait on the old one to be deleted", func() { + By("Checking handler is locked") + daemonset.GetConsistently(types.NamespacedName{Namespace: operatorNamespace, Name: "nmstate-handler"}).ShouldNot(daemonset.BeReady()) + uninstallOperator("nmstate") + By("Checking handler is unlocked after deleting old one") + daemonset.GetEventually(types.NamespacedName{Namespace: operatorNamespace, Name: "nmstate-handler"}).Should(daemonset.BeReady()) + }) + }) }) -func installNMState(nmstate nmstatev1beta1.NMState) { - err := testenv.Client.Create(context.TODO(), &nmstate) - Expect(err).ToNot(HaveOccurred(), "NMState CR created without error") -} - -func installDefaultNMState() { - installNMState(defaultNMState) -} +func installOperator(namespace string) error { + By(fmt.Sprintf("Creating NMState operator with namespace '%s'", namespace)) + _, err := cmd.Run("make", false, fmt.Sprintf("OPERATOR_NAMESPACE=%s", namespace), fmt.Sprintf("HANDLER_NAMESPACE=%s", namespace), "IMAGE_REGISTRY=registry:5000", "manifests") + Expect(err).ToNot(HaveOccurred()) -func uninstallNMState(nmstate nmstatev1beta1.NMState) { - err := testenv.Client.Delete(context.TODO(), &nmstate, &client.DeleteOptions{}) - if !apierrors.IsNotFound(err) { - Expect(err).ToNot(HaveOccurred(), "NMState CR successfully removed") + manifestsDir := "build/_output/manifests/" + manifests := []string{"namespace.yaml", "service_account.yaml", "operator.yaml", "role.yaml", "role_binding.yaml"} + for _, manifest := range manifests { + _, err = cmd.Kubectl("apply", "-f", manifestsDir+manifest) + Expect(err).ToNot(HaveOccurred()) } + deployment.GetEventually(types.NamespacedName{Namespace: namespace, Name: "nmstate-operator"}).Should(deployment.BeReady()) + + return nil } -func uninstallDefaultNMState() { - uninstallNMState(defaultNMState) +func uninstallOperator(namespace string) { + By(fmt.Sprintf("Deleting namespace '%s'", namespace)) + ns := corev1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: namespace, + }, + } + Expect(testenv.Client.Delete(context.TODO(), &ns)).To(SatisfyAny(Succeed(), WithTransform(apierrors.IsNotFound, BeTrue()))) + Eventually(func() error { + return testenv.Client.Get(context.TODO(), types.NamespacedName{Name: namespace}, &ns) + }, 2*time.Minute, 5*time.Second).Should(SatisfyAll(HaveOccurred(), WithTransform(apierrors.IsNotFound, BeTrue()))) } diff --git a/vendor/github.com/nightlyone/lockfile/.gitignore b/vendor/github.com/gofrs/flock/.gitignore similarity index 86% rename from vendor/github.com/nightlyone/lockfile/.gitignore rename to vendor/github.com/gofrs/flock/.gitignore index 5a05665dee..daf913b1b3 100644 --- a/vendor/github.com/nightlyone/lockfile/.gitignore +++ b/vendor/github.com/gofrs/flock/.gitignore @@ -7,11 +7,6 @@ _obj _test -# popular temporaries -.err -.out -.diff - # Architecture specific extensions/prefixes *.[568vq] [568vq].out @@ -25,3 +20,5 @@ _cgo_export.* _testmain.go *.exe +*.test +*.prof diff --git a/vendor/github.com/gofrs/flock/.travis.yml b/vendor/github.com/gofrs/flock/.travis.yml new file mode 100644 index 0000000000..b16d040fa8 --- /dev/null +++ b/vendor/github.com/gofrs/flock/.travis.yml @@ -0,0 +1,10 @@ +language: go +go: + - 1.14.x + - 1.15.x +script: go test -v -check.vv -race ./... +sudo: false +notifications: + email: + on_success: never + on_failure: always diff --git a/vendor/github.com/gofrs/flock/LICENSE b/vendor/github.com/gofrs/flock/LICENSE new file mode 100644 index 0000000000..8b8ff36fe4 --- /dev/null +++ b/vendor/github.com/gofrs/flock/LICENSE @@ -0,0 +1,27 @@ +Copyright (c) 2015-2020, Tim Heckman +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +* Neither the name of gofrs nor the names of its contributors may be used + to endorse or promote products derived from this software without + specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/github.com/gofrs/flock/README.md b/vendor/github.com/gofrs/flock/README.md new file mode 100644 index 0000000000..71ce63692e --- /dev/null +++ b/vendor/github.com/gofrs/flock/README.md @@ -0,0 +1,41 @@ +# flock +[![TravisCI Build Status](https://img.shields.io/travis/gofrs/flock/master.svg?style=flat)](https://travis-ci.org/gofrs/flock) +[![GoDoc](https://img.shields.io/badge/godoc-flock-blue.svg?style=flat)](https://godoc.org/github.com/gofrs/flock) +[![License](https://img.shields.io/badge/license-BSD_3--Clause-brightgreen.svg?style=flat)](https://github.com/gofrs/flock/blob/master/LICENSE) +[![Go Report Card](https://goreportcard.com/badge/github.com/gofrs/flock)](https://goreportcard.com/report/github.com/gofrs/flock) + +`flock` implements a thread-safe sync.Locker interface for file locking. It also +includes a non-blocking TryLock() function to allow locking without blocking execution. + +## License +`flock` is released under the BSD 3-Clause License. See the `LICENSE` file for more details. + +## Go Compatibility +This package makes use of the `context` package that was introduced in Go 1.7. As such, this +package has an implicit dependency on Go 1.7+. + +## Installation +``` +go get -u github.com/gofrs/flock +``` + +## Usage +```Go +import "github.com/gofrs/flock" + +fileLock := flock.New("/var/lock/go-lock.lock") + +locked, err := fileLock.TryLock() + +if err != nil { + // handle locking error +} + +if locked { + // do work + fileLock.Unlock() +} +``` + +For more detailed usage information take a look at the package API docs on +[GoDoc](https://godoc.org/github.com/gofrs/flock). diff --git a/vendor/github.com/gofrs/flock/appveyor.yml b/vendor/github.com/gofrs/flock/appveyor.yml new file mode 100644 index 0000000000..909b4bf7cb --- /dev/null +++ b/vendor/github.com/gofrs/flock/appveyor.yml @@ -0,0 +1,25 @@ +version: '{build}' + +build: false +deploy: false + +clone_folder: 'c:\gopath\src\github.com\gofrs\flock' + +environment: + GOPATH: 'c:\gopath' + GOVERSION: '1.15' + +init: + - git config --global core.autocrlf input + +install: + - rmdir c:\go /s /q + - appveyor DownloadFile https://storage.googleapis.com/golang/go%GOVERSION%.windows-amd64.msi + - msiexec /i go%GOVERSION%.windows-amd64.msi /q + - set Path=c:\go\bin;c:\gopath\bin;%Path% + - go version + - go env + +test_script: + - go get -t ./... + - go test -race -v ./... diff --git a/vendor/github.com/gofrs/flock/flock.go b/vendor/github.com/gofrs/flock/flock.go new file mode 100644 index 0000000000..95c784ca50 --- /dev/null +++ b/vendor/github.com/gofrs/flock/flock.go @@ -0,0 +1,144 @@ +// Copyright 2015 Tim Heckman. All rights reserved. +// Use of this source code is governed by the BSD 3-Clause +// license that can be found in the LICENSE file. + +// Package flock implements a thread-safe interface for file locking. +// It also includes a non-blocking TryLock() function to allow locking +// without blocking execution. +// +// Package flock is released under the BSD 3-Clause License. See the LICENSE file +// for more details. +// +// While using this library, remember that the locking behaviors are not +// guaranteed to be the same on each platform. For example, some UNIX-like +// operating systems will transparently convert a shared lock to an exclusive +// lock. If you Unlock() the flock from a location where you believe that you +// have the shared lock, you may accidentally drop the exclusive lock. +package flock + +import ( + "context" + "os" + "runtime" + "sync" + "time" +) + +// Flock is the struct type to handle file locking. All fields are unexported, +// with access to some of the fields provided by getter methods (Path() and Locked()). +type Flock struct { + path string + m sync.RWMutex + fh *os.File + l bool + r bool +} + +// New returns a new instance of *Flock. The only parameter +// it takes is the path to the desired lockfile. +func New(path string) *Flock { + return &Flock{path: path} +} + +// NewFlock returns a new instance of *Flock. The only parameter +// it takes is the path to the desired lockfile. +// +// Deprecated: Use New instead. +func NewFlock(path string) *Flock { + return New(path) +} + +// Close is equivalent to calling Unlock. +// +// This will release the lock and close the underlying file descriptor. +// It will not remove the file from disk, that's up to your application. +func (f *Flock) Close() error { + return f.Unlock() +} + +// Path returns the path as provided in NewFlock(). +func (f *Flock) Path() string { + return f.path +} + +// Locked returns the lock state (locked: true, unlocked: false). +// +// Warning: by the time you use the returned value, the state may have changed. +func (f *Flock) Locked() bool { + f.m.RLock() + defer f.m.RUnlock() + return f.l +} + +// RLocked returns the read lock state (locked: true, unlocked: false). +// +// Warning: by the time you use the returned value, the state may have changed. +func (f *Flock) RLocked() bool { + f.m.RLock() + defer f.m.RUnlock() + return f.r +} + +func (f *Flock) String() string { + return f.path +} + +// TryLockContext repeatedly tries to take an exclusive lock until one of the +// conditions is met: TryLock succeeds, TryLock fails with error, or Context +// Done channel is closed. +func (f *Flock) TryLockContext(ctx context.Context, retryDelay time.Duration) (bool, error) { + return tryCtx(ctx, f.TryLock, retryDelay) +} + +// TryRLockContext repeatedly tries to take a shared lock until one of the +// conditions is met: TryRLock succeeds, TryRLock fails with error, or Context +// Done channel is closed. +func (f *Flock) TryRLockContext(ctx context.Context, retryDelay time.Duration) (bool, error) { + return tryCtx(ctx, f.TryRLock, retryDelay) +} + +func tryCtx(ctx context.Context, fn func() (bool, error), retryDelay time.Duration) (bool, error) { + if ctx.Err() != nil { + return false, ctx.Err() + } + for { + if ok, err := fn(); ok || err != nil { + return ok, err + } + select { + case <-ctx.Done(): + return false, ctx.Err() + case <-time.After(retryDelay): + // try again + } + } +} + +func (f *Flock) setFh() error { + // open a new os.File instance + // create it if it doesn't exist, and open the file read-only. + flags := os.O_CREATE + if runtime.GOOS == "aix" { + // AIX cannot preform write-lock (ie exclusive) on a + // read-only file. + flags |= os.O_RDWR + } else { + flags |= os.O_RDONLY + } + fh, err := os.OpenFile(f.path, flags, os.FileMode(0600)) + if err != nil { + return err + } + + // set the filehandle on the struct + f.fh = fh + return nil +} + +// ensure the file handle is closed if no lock is held +func (f *Flock) ensureFhState() { + if !f.l && !f.r && f.fh != nil { + f.fh.Close() + f.fh = nil + } +} diff --git a/vendor/github.com/gofrs/flock/flock_aix.go b/vendor/github.com/gofrs/flock/flock_aix.go new file mode 100644 index 0000000000..2a1607b2ff --- /dev/null +++ b/vendor/github.com/gofrs/flock/flock_aix.go @@ -0,0 +1,271 @@ +// Copyright 2019 Tim Heckman. All rights reserved. Use of this source code is +// governed by the BSD 3-Clause license that can be found in the LICENSE file. + +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// This code implements the filelock API using POSIX 'fcntl' locks, which attach +// to an (inode, process) pair rather than a file descriptor. To avoid unlocking +// files prematurely when the same file is opened through different descriptors, +// we allow only one read-lock at a time. +// +// This code is adapted from the Go package: +// cmd/go/internal/lockedfile/internal/filelock + +//+build aix + +package flock + +import ( + "errors" + "io" + "os" + "sync" + "syscall" + + "golang.org/x/sys/unix" +) + +type lockType int16 + +const ( + readLock lockType = unix.F_RDLCK + writeLock lockType = unix.F_WRLCK +) + +type inode = uint64 + +type inodeLock struct { + owner *Flock + queue []<-chan *Flock +} + +var ( + mu sync.Mutex + inodes = map[*Flock]inode{} + locks = map[inode]inodeLock{} +) + +// Lock is a blocking call to try and take an exclusive file lock. It will wait +// until it is able to obtain the exclusive file lock. It's recommended that +// TryLock() be used over this function. This function may block the ability to +// query the current Locked() or RLocked() status due to a RW-mutex lock. +// +// If we are already exclusive-locked, this function short-circuits and returns +// immediately assuming it can take the mutex lock. +// +// If the *Flock has a shared lock (RLock), this may transparently replace the +// shared lock with an exclusive lock on some UNIX-like operating systems. Be +// careful when using exclusive locks in conjunction with shared locks +// (RLock()), because calling Unlock() may accidentally release the exclusive +// lock that was once a shared lock. +func (f *Flock) Lock() error { + return f.lock(&f.l, writeLock) +} + +// RLock is a blocking call to try and take a shared file lock. It will wait +// until it is able to obtain the shared file lock. It's recommended that +// TryRLock() be used over this function. This function may block the ability to +// query the current Locked() or RLocked() status due to a RW-mutex lock. +// +// If we are already shared-locked, this function short-circuits and returns +// immediately assuming it can take the mutex lock. +func (f *Flock) RLock() error { + return f.lock(&f.r, readLock) +} + +func (f *Flock) lock(locked *bool, flag lockType) error { + f.m.Lock() + defer f.m.Unlock() + + if *locked { + return nil + } + + if f.fh == nil { + if err := f.setFh(); err != nil { + return err + } + defer f.ensureFhState() + } + + if _, err := f.doLock(flag, true); err != nil { + return err + } + + *locked = true + return nil +} + +func (f *Flock) doLock(lt lockType, blocking bool) (bool, error) { + // POSIX locks apply per inode and process, and the lock for an inode is + // released when *any* descriptor for that inode is closed. So we need to + // synchronize access to each inode internally, and must serialize lock and + // unlock calls that refer to the same inode through different descriptors. + fi, err := f.fh.Stat() + if err != nil { + return false, err + } + ino := inode(fi.Sys().(*syscall.Stat_t).Ino) + + mu.Lock() + if i, dup := inodes[f]; dup && i != ino { + mu.Unlock() + return false, &os.PathError{ + Path: f.Path(), + Err: errors.New("inode for file changed since last Lock or RLock"), + } + } + + inodes[f] = ino + + var wait chan *Flock + l := locks[ino] + if l.owner == f { + // This file already owns the lock, but the call may change its lock type. + } else if l.owner == nil { + // No owner: it's ours now. + l.owner = f + } else if !blocking { + // Already owned: cannot take the lock. + mu.Unlock() + return false, nil + } else { + // Already owned: add a channel to wait on. + wait = make(chan *Flock) + l.queue = append(l.queue, wait) + } + locks[ino] = l + mu.Unlock() + + if wait != nil { + wait <- f + } + + err = setlkw(f.fh.Fd(), lt) + + if err != nil { + f.doUnlock() + return false, err + } + + return true, nil +} + +func (f *Flock) Unlock() error { + f.m.Lock() + defer f.m.Unlock() + + // if we aren't locked or if the lockfile instance is nil + // just return a nil error because we are unlocked + if (!f.l && !f.r) || f.fh == nil { + return nil + } + + if err := f.doUnlock(); err != nil { + return err + } + + f.fh.Close() + + f.l = false + f.r = false + f.fh = nil + + return nil +} + +func (f *Flock) doUnlock() (err error) { + var owner *Flock + mu.Lock() + ino, ok := inodes[f] + if ok { + owner = locks[ino].owner + } + mu.Unlock() + + if owner == f { + err = setlkw(f.fh.Fd(), unix.F_UNLCK) + } + + mu.Lock() + l := locks[ino] + if len(l.queue) == 0 { + // No waiters: remove the map entry. + delete(locks, ino) + } else { + // The first waiter is sending us their file now. + // Receive it and update the queue. + l.owner = <-l.queue[0] + l.queue = l.queue[1:] + locks[ino] = l + } + delete(inodes, f) + mu.Unlock() + + return err +} + +// TryLock is the preferred function for taking an exclusive file lock. This +// function takes an RW-mutex lock before it tries to lock the file, so there is +// the possibility that this function may block for a short time if another +// goroutine is trying to take any action. +// +// The actual file lock is non-blocking. If we are unable to get the exclusive +// file lock, the function will return false instead of waiting for the lock. If +// we get the lock, we also set the *Flock instance as being exclusive-locked. +func (f *Flock) TryLock() (bool, error) { + return f.try(&f.l, writeLock) +} + +// TryRLock is the preferred function for taking a shared file lock. This +// function takes an RW-mutex lock before it tries to lock the file, so there is +// the possibility that this function may block for a short time if another +// goroutine is trying to take any action. +// +// The actual file lock is non-blocking. If we are unable to get the shared file +// lock, the function will return false instead of waiting for the lock. If we +// get the lock, we also set the *Flock instance as being share-locked. +func (f *Flock) TryRLock() (bool, error) { + return f.try(&f.r, readLock) +} + +func (f *Flock) try(locked *bool, flag lockType) (bool, error) { + f.m.Lock() + defer f.m.Unlock() + + if *locked { + return true, nil + } + + if f.fh == nil { + if err := f.setFh(); err != nil { + return false, err + } + defer f.ensureFhState() + } + + haslock, err := f.doLock(flag, false) + if err != nil { + return false, err + } + + *locked = haslock + return haslock, nil +} + +// setlkw calls FcntlFlock with F_SETLKW for the entire file indicated by fd. +func setlkw(fd uintptr, lt lockType) error { + for { + err := unix.FcntlFlock(fd, unix.F_SETLKW, &unix.Flock_t{ + Type: int16(lt), + Whence: io.SeekStart, + Start: 0, + Len: 0, // All bytes. + }) + if err != unix.EINTR { + return err + } + } +} diff --git a/vendor/github.com/gofrs/flock/flock_unix.go b/vendor/github.com/gofrs/flock/flock_unix.go new file mode 100644 index 0000000000..c315a3e290 --- /dev/null +++ b/vendor/github.com/gofrs/flock/flock_unix.go @@ -0,0 +1,197 @@ +// Copyright 2015 Tim Heckman. All rights reserved. +// Use of this source code is governed by the BSD 3-Clause +// license that can be found in the LICENSE file. + +// +build !aix,!windows + +package flock + +import ( + "os" + "syscall" +) + +// Lock is a blocking call to try and take an exclusive file lock. It will wait +// until it is able to obtain the exclusive file lock. It's recommended that +// TryLock() be used over this function. This function may block the ability to +// query the current Locked() or RLocked() status due to a RW-mutex lock. +// +// If we are already exclusive-locked, this function short-circuits and returns +// immediately assuming it can take the mutex lock. +// +// If the *Flock has a shared lock (RLock), this may transparently replace the +// shared lock with an exclusive lock on some UNIX-like operating systems. Be +// careful when using exclusive locks in conjunction with shared locks +// (RLock()), because calling Unlock() may accidentally release the exclusive +// lock that was once a shared lock. +func (f *Flock) Lock() error { + return f.lock(&f.l, syscall.LOCK_EX) +} + +// RLock is a blocking call to try and take a shared file lock. It will wait +// until it is able to obtain the shared file lock. It's recommended that +// TryRLock() be used over this function. This function may block the ability to +// query the current Locked() or RLocked() status due to a RW-mutex lock. +// +// If we are already shared-locked, this function short-circuits and returns +// immediately assuming it can take the mutex lock. +func (f *Flock) RLock() error { + return f.lock(&f.r, syscall.LOCK_SH) +} + +func (f *Flock) lock(locked *bool, flag int) error { + f.m.Lock() + defer f.m.Unlock() + + if *locked { + return nil + } + + if f.fh == nil { + if err := f.setFh(); err != nil { + return err + } + defer f.ensureFhState() + } + + if err := syscall.Flock(int(f.fh.Fd()), flag); err != nil { + shouldRetry, reopenErr := f.reopenFDOnError(err) + if reopenErr != nil { + return reopenErr + } + + if !shouldRetry { + return err + } + + if err = syscall.Flock(int(f.fh.Fd()), flag); err != nil { + return err + } + } + + *locked = true + return nil +} + +// Unlock is a function to unlock the file. This file takes a RW-mutex lock, so +// while it is running the Locked() and RLocked() functions will be blocked. +// +// This function short-circuits if we are unlocked already. If not, it calls +// syscall.LOCK_UN on the file and closes the file descriptor. It does not +// remove the file from disk. It's up to your application to do. +// +// Please note, if your shared lock became an exclusive lock this may +// unintentionally drop the exclusive lock if called by the consumer that +// believes they have a shared lock. Please see Lock() for more details. +func (f *Flock) Unlock() error { + f.m.Lock() + defer f.m.Unlock() + + // if we aren't locked or if the lockfile instance is nil + // just return a nil error because we are unlocked + if (!f.l && !f.r) || f.fh == nil { + return nil + } + + // mark the file as unlocked + if err := syscall.Flock(int(f.fh.Fd()), syscall.LOCK_UN); err != nil { + return err + } + + f.fh.Close() + + f.l = false + f.r = false + f.fh = nil + + return nil +} + +// TryLock is the preferred function for taking an exclusive file lock. This +// function takes an RW-mutex lock before it tries to lock the file, so there is +// the possibility that this function may block for a short time if another +// goroutine is trying to take any action. +// +// The actual file lock is non-blocking. If we are unable to get the exclusive +// file lock, the function will return false instead of waiting for the lock. If +// we get the lock, we also set the *Flock instance as being exclusive-locked. +func (f *Flock) TryLock() (bool, error) { + return f.try(&f.l, syscall.LOCK_EX) +} + +// TryRLock is the preferred function for taking a shared file lock. This +// function takes an RW-mutex lock before it tries to lock the file, so there is +// the possibility that this function may block for a short time if another +// goroutine is trying to take any action. +// +// The actual file lock is non-blocking. If we are unable to get the shared file +// lock, the function will return false instead of waiting for the lock. If we +// get the lock, we also set the *Flock instance as being share-locked. +func (f *Flock) TryRLock() (bool, error) { + return f.try(&f.r, syscall.LOCK_SH) +} + +func (f *Flock) try(locked *bool, flag int) (bool, error) { + f.m.Lock() + defer f.m.Unlock() + + if *locked { + return true, nil + } + + if f.fh == nil { + if err := f.setFh(); err != nil { + return false, err + } + defer f.ensureFhState() + } + + var retried bool +retry: + err := syscall.Flock(int(f.fh.Fd()), flag|syscall.LOCK_NB) + + switch err { + case syscall.EWOULDBLOCK: + return false, nil + case nil: + *locked = true + return true, nil + } + if !retried { + if shouldRetry, reopenErr := f.reopenFDOnError(err); reopenErr != nil { + return false, reopenErr + } else if shouldRetry { + retried = true + goto retry + } + } + + return false, err +} + +// reopenFDOnError determines whether we should reopen the file handle +// in readwrite mode and try again. This comes from util-linux/sys-utils/flock.c: +// Since Linux 3.4 (commit 55725513) +// Probably NFSv4 where flock() is emulated by fcntl(). +func (f *Flock) reopenFDOnError(err error) (bool, error) { + if err != syscall.EIO && err != syscall.EBADF { + return false, nil + } + if st, err := f.fh.Stat(); err == nil { + // if the file is able to be read and written + if st.Mode()&0600 == 0600 { + f.fh.Close() + f.fh = nil + + // reopen in read-write mode and set the filehandle + fh, err := os.OpenFile(f.path, os.O_CREATE|os.O_RDWR, os.FileMode(0600)) + if err != nil { + return false, err + } + f.fh = fh + return true, nil + } + } + + return false, nil +} diff --git a/vendor/github.com/gofrs/flock/flock_winapi.go b/vendor/github.com/gofrs/flock/flock_winapi.go new file mode 100644 index 0000000000..fe405a255a --- /dev/null +++ b/vendor/github.com/gofrs/flock/flock_winapi.go @@ -0,0 +1,76 @@ +// Copyright 2015 Tim Heckman. All rights reserved. +// Use of this source code is governed by the BSD 3-Clause +// license that can be found in the LICENSE file. + +// +build windows + +package flock + +import ( + "syscall" + "unsafe" +) + +var ( + kernel32, _ = syscall.LoadLibrary("kernel32.dll") + procLockFileEx, _ = syscall.GetProcAddress(kernel32, "LockFileEx") + procUnlockFileEx, _ = syscall.GetProcAddress(kernel32, "UnlockFileEx") +) + +const ( + winLockfileFailImmediately = 0x00000001 + winLockfileExclusiveLock = 0x00000002 + winLockfileSharedLock = 0x00000000 +) + +// Use of 0x00000000 for the shared lock is a guess based on some the MS Windows +// `LockFileEX` docs, which document the `LOCKFILE_EXCLUSIVE_LOCK` flag as: +// +// > The function requests an exclusive lock. Otherwise, it requests a shared +// > lock. +// +// https://msdn.microsoft.com/en-us/library/windows/desktop/aa365203(v=vs.85).aspx + +func lockFileEx(handle syscall.Handle, flags uint32, reserved uint32, numberOfBytesToLockLow uint32, numberOfBytesToLockHigh uint32, offset *syscall.Overlapped) (bool, syscall.Errno) { + r1, _, errNo := syscall.Syscall6( + uintptr(procLockFileEx), + 6, + uintptr(handle), + uintptr(flags), + uintptr(reserved), + uintptr(numberOfBytesToLockLow), + uintptr(numberOfBytesToLockHigh), + uintptr(unsafe.Pointer(offset))) + + if r1 != 1 { + if errNo == 0 { + return false, syscall.EINVAL + } + + return false, errNo + } + + return true, 0 +} + +func unlockFileEx(handle syscall.Handle, reserved uint32, numberOfBytesToLockLow uint32, numberOfBytesToLockHigh uint32, offset *syscall.Overlapped) (bool, syscall.Errno) { + r1, _, errNo := syscall.Syscall6( + uintptr(procUnlockFileEx), + 5, + uintptr(handle), + uintptr(reserved), + uintptr(numberOfBytesToLockLow), + uintptr(numberOfBytesToLockHigh), + uintptr(unsafe.Pointer(offset)), + 0) + + if r1 != 1 { + if errNo == 0 { + return false, syscall.EINVAL + } + + return false, errNo + } + + return true, 0 +} diff --git a/vendor/github.com/gofrs/flock/flock_windows.go b/vendor/github.com/gofrs/flock/flock_windows.go new file mode 100644 index 0000000000..ddb534ccef --- /dev/null +++ b/vendor/github.com/gofrs/flock/flock_windows.go @@ -0,0 +1,142 @@ +// Copyright 2015 Tim Heckman. All rights reserved. +// Use of this source code is governed by the BSD 3-Clause +// license that can be found in the LICENSE file. + +package flock + +import ( + "syscall" +) + +// ErrorLockViolation is the error code returned from the Windows syscall when a +// lock would block and you ask to fail immediately. +const ErrorLockViolation syscall.Errno = 0x21 // 33 + +// Lock is a blocking call to try and take an exclusive file lock. It will wait +// until it is able to obtain the exclusive file lock. It's recommended that +// TryLock() be used over this function. This function may block the ability to +// query the current Locked() or RLocked() status due to a RW-mutex lock. +// +// If we are already locked, this function short-circuits and returns +// immediately assuming it can take the mutex lock. +func (f *Flock) Lock() error { + return f.lock(&f.l, winLockfileExclusiveLock) +} + +// RLock is a blocking call to try and take a shared file lock. It will wait +// until it is able to obtain the shared file lock. It's recommended that +// TryRLock() be used over this function. This function may block the ability to +// query the current Locked() or RLocked() status due to a RW-mutex lock. +// +// If we are already locked, this function short-circuits and returns +// immediately assuming it can take the mutex lock. +func (f *Flock) RLock() error { + return f.lock(&f.r, winLockfileSharedLock) +} + +func (f *Flock) lock(locked *bool, flag uint32) error { + f.m.Lock() + defer f.m.Unlock() + + if *locked { + return nil + } + + if f.fh == nil { + if err := f.setFh(); err != nil { + return err + } + defer f.ensureFhState() + } + + if _, errNo := lockFileEx(syscall.Handle(f.fh.Fd()), flag, 0, 1, 0, &syscall.Overlapped{}); errNo > 0 { + return errNo + } + + *locked = true + return nil +} + +// Unlock is a function to unlock the file. This file takes a RW-mutex lock, so +// while it is running the Locked() and RLocked() functions will be blocked. +// +// This function short-circuits if we are unlocked already. If not, it calls +// UnlockFileEx() on the file and closes the file descriptor. It does not remove +// the file from disk. It's up to your application to do. +func (f *Flock) Unlock() error { + f.m.Lock() + defer f.m.Unlock() + + // if we aren't locked or if the lockfile instance is nil + // just return a nil error because we are unlocked + if (!f.l && !f.r) || f.fh == nil { + return nil + } + + // mark the file as unlocked + if _, errNo := unlockFileEx(syscall.Handle(f.fh.Fd()), 0, 1, 0, &syscall.Overlapped{}); errNo > 0 { + return errNo + } + + f.fh.Close() + + f.l = false + f.r = false + f.fh = nil + + return nil +} + +// TryLock is the preferred function for taking an exclusive file lock. This +// function does take a RW-mutex lock before it tries to lock the file, so there +// is the possibility that this function may block for a short time if another +// goroutine is trying to take any action. +// +// The actual file lock is non-blocking. If we are unable to get the exclusive +// file lock, the function will return false instead of waiting for the lock. If +// we get the lock, we also set the *Flock instance as being exclusive-locked. +func (f *Flock) TryLock() (bool, error) { + return f.try(&f.l, winLockfileExclusiveLock) +} + +// TryRLock is the preferred function for taking a shared file lock. This +// function does take a RW-mutex lock before it tries to lock the file, so there +// is the possibility that this function may block for a short time if another +// goroutine is trying to take any action. +// +// The actual file lock is non-blocking. If we are unable to get the shared file +// lock, the function will return false instead of waiting for the lock. If we +// get the lock, we also set the *Flock instance as being shared-locked. +func (f *Flock) TryRLock() (bool, error) { + return f.try(&f.r, winLockfileSharedLock) +} + +func (f *Flock) try(locked *bool, flag uint32) (bool, error) { + f.m.Lock() + defer f.m.Unlock() + + if *locked { + return true, nil + } + + if f.fh == nil { + if err := f.setFh(); err != nil { + return false, err + } + defer f.ensureFhState() + } + + _, errNo := lockFileEx(syscall.Handle(f.fh.Fd()), flag|winLockfileFailImmediately, 0, 1, 0, &syscall.Overlapped{}) + + if errNo > 0 { + if errNo == ErrorLockViolation || errNo == syscall.ERROR_IO_PENDING { + return false, nil + } + + return false, errNo + } + + *locked = true + + return true, nil +} diff --git a/vendor/github.com/nightlyone/lockfile/.gitmodules b/vendor/github.com/nightlyone/lockfile/.gitmodules deleted file mode 100644 index 6faa9e3469..0000000000 --- a/vendor/github.com/nightlyone/lockfile/.gitmodules +++ /dev/null @@ -1,3 +0,0 @@ -[submodule "git-hooks"] - path = git-hooks - url = https://github.com/nightlyone/git-hooks diff --git a/vendor/github.com/nightlyone/lockfile/.travis.yml b/vendor/github.com/nightlyone/lockfile/.travis.yml deleted file mode 100644 index 4f0af474a5..0000000000 --- a/vendor/github.com/nightlyone/lockfile/.travis.yml +++ /dev/null @@ -1,14 +0,0 @@ -language: go -go: - - 1.13 - - 1.14 - - tip - -# Only test commits to production branch and all pull requests -branches: - only: - - master - -matrix: - allow_failures: - - go: tip diff --git a/vendor/github.com/nightlyone/lockfile/LICENSE b/vendor/github.com/nightlyone/lockfile/LICENSE deleted file mode 100644 index eb5b804685..0000000000 --- a/vendor/github.com/nightlyone/lockfile/LICENSE +++ /dev/null @@ -1,19 +0,0 @@ -Copyright (c) 2012 Ingo Oeser - -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. diff --git a/vendor/github.com/nightlyone/lockfile/README.md b/vendor/github.com/nightlyone/lockfile/README.md deleted file mode 100644 index c35235cdae..0000000000 --- a/vendor/github.com/nightlyone/lockfile/README.md +++ /dev/null @@ -1,52 +0,0 @@ -lockfile -========= -Handle locking via pid files. - -[![Build Status Unix][1]][2] -[![Build status Windows][3]][4] - -[1]: https://secure.travis-ci.org/nightlyone/lockfile.png -[2]: https://travis-ci.org/nightlyone/lockfile -[3]: https://ci.appveyor.com/api/projects/status/7mojkmauj81uvp8u/branch/master?svg=true -[4]: https://ci.appveyor.com/project/nightlyone/lockfile/branch/master - - - -install -------- -Install [Go 1][5], either [from source][6] or [with a prepackaged binary][7]. -For Windows suport, Go 1.4 or newer is required. - -Then run - - go get github.com/nightlyone/lockfile - -[5]: http://golang.org -[6]: http://golang.org/doc/install/source -[7]: http://golang.org/doc/install - -LICENSE -------- -MIT - -documentation -------------- -[package documentation at godoc.org](http://godoc.org/github.com/nightlyone/lockfile) - -install -------------------- - go get github.com/nightlyone/lockfile - - -contributing -============ - -Contributions are welcome. Please open an issue or send me a pull request for a dedicated branch. -Make sure the git commit hooks show it works. - -git commit hooks ------------------------ -enable commit hooks via - - cd .git ; rm -rf hooks; ln -s ../git-hooks hooks ; cd .. - diff --git a/vendor/github.com/nightlyone/lockfile/appveyor.yml b/vendor/github.com/nightlyone/lockfile/appveyor.yml deleted file mode 100644 index cf72a58b13..0000000000 --- a/vendor/github.com/nightlyone/lockfile/appveyor.yml +++ /dev/null @@ -1,12 +0,0 @@ -clone_folder: c:\gopath\src\github.com\nightlyone\lockfile - -environment: - GOPATH: c:\gopath - -install: - - go version - - go env - - go get -v -t ./... - -build_script: - - go test -v ./... diff --git a/vendor/github.com/nightlyone/lockfile/go.mod b/vendor/github.com/nightlyone/lockfile/go.mod deleted file mode 100644 index 5133ff7153..0000000000 --- a/vendor/github.com/nightlyone/lockfile/go.mod +++ /dev/null @@ -1,3 +0,0 @@ -module github.com/nightlyone/lockfile - -go 1.11 diff --git a/vendor/github.com/nightlyone/lockfile/lockfile.go b/vendor/github.com/nightlyone/lockfile/lockfile.go deleted file mode 100644 index 76bdfbe095..0000000000 --- a/vendor/github.com/nightlyone/lockfile/lockfile.go +++ /dev/null @@ -1,222 +0,0 @@ -// Package lockfile handles pid file based locking. -// While a sync.Mutex helps against concurrency issues within a single process, -// this package is designed to help against concurrency issues between cooperating processes -// or serializing multiple invocations of the same process. You can also combine sync.Mutex -// with Lockfile in order to serialize an action between different goroutines in a single program -// and also multiple invocations of this program. -package lockfile - -import ( - "errors" - "fmt" - "io" - "io/ioutil" - "os" - "path/filepath" -) - -// Lockfile is a pid file which can be locked -type Lockfile string - -// TemporaryError is a type of error where a retry after a random amount of sleep should help to mitigate it. -type TemporaryError string - -func (t TemporaryError) Error() string { return string(t) } - -// Temporary returns always true. -// It exists, so you can detect it via -// if te, ok := err.(interface{ Temporary() bool }); ok { -// fmt.Println("I am a temporary error situation, so wait and retry") -// } -func (t TemporaryError) Temporary() bool { return true } - -// Various errors returned by this package -var ( - ErrBusy = TemporaryError("Locked by other process") // If you get this, retry after a short sleep might help - ErrNotExist = TemporaryError("Lockfile created, but doesn't exist") // If you get this, retry after a short sleep might help - ErrNeedAbsPath = errors.New("Lockfiles must be given as absolute path names") - ErrInvalidPid = errors.New("Lockfile contains invalid pid for system") - ErrDeadOwner = errors.New("Lockfile contains pid of process not existent on this system anymore") - ErrRogueDeletion = errors.New("Lockfile owned by me has been removed unexpectedly") -) - -// New describes a new filename located at the given absolute path. -func New(path string) (Lockfile, error) { - if !filepath.IsAbs(path) { - return Lockfile(""), ErrNeedAbsPath - } - - return Lockfile(path), nil -} - -// GetOwner returns who owns the lockfile. -func (l Lockfile) GetOwner() (*os.Process, error) { - name := string(l) - - // Ok, see, if we have a stale lockfile here - content, err := ioutil.ReadFile(name) - if err != nil { - return nil, err - } - - // try hard for pids. If no pid, the lockfile is junk anyway and we delete it. - pid, err := scanPidLine(content) - if err != nil { - return nil, err - } - - running, err := isRunning(pid) - if err != nil { - return nil, err - } - - if running { - proc, err := os.FindProcess(pid) - if err != nil { - return nil, err - } - - return proc, nil - } - - return nil, ErrDeadOwner -} - -// TryLock tries to own the lock. -// It Returns nil, if successful and and error describing the reason, it didn't work out. -// Please note, that existing lockfiles containing pids of dead processes -// and lockfiles containing no pid at all are simply deleted. -func (l Lockfile) TryLock() error { - name := string(l) - - // This has been checked by New already. If we trigger here, - // the caller didn't use New and re-implemented it's functionality badly. - // So panic, that he might find this easily during testing. - if !filepath.IsAbs(name) { - panic(ErrNeedAbsPath) - } - - tmplock, cleanup, err := makePidFile(name, os.Getpid()) - if err != nil { - return err - } - - defer cleanup() - - // EEXIST and similar error codes, caught by os.IsExist, are intentionally ignored, - // as it means that someone was faster creating this link - // and ignoring this kind of error is part of the algorithm. - // Then we will probably fail the pid owner check later, if this process is still alive. - // We cannot ignore ALL errors, since failure to support hard links, disk full - // as well as many other errors can happen to a filesystem operation - // and we really want to abort on those. - if err := os.Link(tmplock, name); err != nil { - if !os.IsExist(err) { - return err - } - } - - fiTmp, err := os.Lstat(tmplock) - if err != nil { - return err - } - - fiLock, err := os.Lstat(name) - if err != nil { - // tell user that a retry would be a good idea - if os.IsNotExist(err) { - return ErrNotExist - } - - return err - } - - // Success - if os.SameFile(fiTmp, fiLock) { - return nil - } - - proc, err := l.GetOwner() - switch err { - default: - // Other errors -> defensively fail and let caller handle this - return err - case nil: - if proc.Pid != os.Getpid() { - return ErrBusy - } - case ErrDeadOwner, ErrInvalidPid: // cases we can fix below - } - - // clean stale/invalid lockfile - err = os.Remove(name) - if err != nil { - // If it doesn't exist, then it doesn't matter who removed it. - if !os.IsNotExist(err) { - return err - } - } - - // now that the stale lockfile is gone, let's recurse - return l.TryLock() -} - -// Unlock a lock again, if we owned it. Returns any error that happened during release of lock. -func (l Lockfile) Unlock() error { - proc, err := l.GetOwner() - switch err { - case ErrInvalidPid, ErrDeadOwner: - return ErrRogueDeletion - case nil: - if proc.Pid == os.Getpid() { - // we really own it, so let's remove it. - return os.Remove(string(l)) - } - // Not owned by me, so don't delete it. - return ErrRogueDeletion - default: - // This is an application error or system error. - // So give a better error for logging here. - if os.IsNotExist(err) { - return ErrRogueDeletion - } - // Other errors -> defensively fail and let caller handle this - return err - } -} - -func scanPidLine(content []byte) (int, error) { - if len(content) == 0 { - return 0, ErrInvalidPid - } - - var pid int - if _, err := fmt.Sscanln(string(content), &pid); err != nil { - return 0, ErrInvalidPid - } - - if pid <= 0 { - return 0, ErrInvalidPid - } - - return pid, nil -} - -func makePidFile(name string, pid int) (tmpname string, cleanup func(), err error) { - tmplock, err := ioutil.TempFile(filepath.Dir(name), filepath.Base(name)+".") - if err != nil { - return "", nil, err - } - - cleanup = func() { - _ = tmplock.Close() - _ = os.Remove(tmplock.Name()) - } - - if _, err := io.WriteString(tmplock, fmt.Sprintf("%d\n", pid)); err != nil { - cleanup() // Do cleanup here, so call doesn't have to. - return "", nil, err - } - - return tmplock.Name(), cleanup, nil -} diff --git a/vendor/github.com/nightlyone/lockfile/lockfile_unix.go b/vendor/github.com/nightlyone/lockfile/lockfile_unix.go deleted file mode 100644 index d43e9b5ee5..0000000000 --- a/vendor/github.com/nightlyone/lockfile/lockfile_unix.go +++ /dev/null @@ -1,21 +0,0 @@ -// +build darwin dragonfly freebsd linux nacl netbsd openbsd solaris aix - -package lockfile - -import ( - "os" - "syscall" -) - -func isRunning(pid int) (bool, error) { - proc, err := os.FindProcess(pid) - if err != nil { - return false, err - } - - if err := proc.Signal(syscall.Signal(0)); err != nil { - return false, nil - } - - return true, nil -} diff --git a/vendor/github.com/nightlyone/lockfile/lockfile_windows.go b/vendor/github.com/nightlyone/lockfile/lockfile_windows.go deleted file mode 100644 index 482bd91d7b..0000000000 --- a/vendor/github.com/nightlyone/lockfile/lockfile_windows.go +++ /dev/null @@ -1,30 +0,0 @@ -package lockfile - -import ( - "syscall" -) - -//For some reason these consts don't exist in syscall. -const ( - error_invalid_parameter = 87 - code_still_active = 259 -) - -func isRunning(pid int) (bool, error) { - procHnd, err := syscall.OpenProcess(syscall.PROCESS_QUERY_INFORMATION, true, uint32(pid)) - if err != nil { - if scerr, ok := err.(syscall.Errno); ok { - if uintptr(scerr) == error_invalid_parameter { - return false, nil - } - } - } - - var code uint32 - err = syscall.GetExitCodeProcess(procHnd, &code) - if err != nil { - return false, err - } - - return code == code_still_active, nil -} diff --git a/vendor/github.com/qinqon/kube-admission-webhook/pkg/webhook/server/server.go b/vendor/github.com/qinqon/kube-admission-webhook/pkg/webhook/server/server.go deleted file mode 100644 index 33f46a2dea..0000000000 --- a/vendor/github.com/qinqon/kube-admission-webhook/pkg/webhook/server/server.go +++ /dev/null @@ -1,162 +0,0 @@ -package server - -import ( - "context" - "io/ioutil" - "os" - "path" - "time" - - "github.com/go-logr/logr" - "github.com/pkg/errors" - - corev1 "k8s.io/api/core/v1" - utilruntime "k8s.io/apimachinery/pkg/util/runtime" - "k8s.io/apimachinery/pkg/util/wait" - "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/healthz" - logf "sigs.k8s.io/controller-runtime/pkg/log" - "sigs.k8s.io/controller-runtime/pkg/manager" - "sigs.k8s.io/controller-runtime/pkg/runtime/inject" - "sigs.k8s.io/controller-runtime/pkg/webhook" - - "github.com/qinqon/kube-admission-webhook/pkg/certificate" - "github.com/qinqon/kube-admission-webhook/pkg/certificate/triple" -) - -type Server struct { - webhookServer *webhook.Server - certManager *certificate.Manager - log logr.Logger -} - -type ServerModifier func(w *webhook.Server) - -// Add creates a new Conditions Mutating Webhook and adds it to the Manager. The Manager will set fields on the Webhook -// and Start it when the Manager is Started. -func New(client client.Client, certificateOpts certificate.Options, serverOpts ...ServerModifier) (*Server, error) { - certManager, err := certificate.NewManager(client, certificateOpts) - if err != nil { - return nil, errors.Wrap(err, "failed constructing certificate manager") - } - - s := &Server{ - webhookServer: &webhook.Server{ - Port: 8443, - CertDir: "/etc/webhook/certs/", - }, - certManager: certManager, - log: logf.Log.WithName("webhook/server"), - } - s.UpdateOpts(serverOpts...) - s.webhookServer.Register("/readyz", healthz.CheckHandler{Checker: healthz.Ping}) - return s, nil -} - -func WithHook(path string, hook *webhook.Admission) ServerModifier { - return func(s *webhook.Server) { - s.Register(path, hook) - } -} - -func WithPort(port int) ServerModifier { - return func(s *webhook.Server) { - s.Port = port - } -} - -func WithCertDir(certDir string) ServerModifier { - return func(s *webhook.Server) { - s.CertDir = certDir - } -} - -//updates Server parameters using ServerModifier functions. Once the manager is started these parameters cannot be updated -func (s *Server) UpdateOpts(serverOpts ...ServerModifier) { - for _, serverOpt := range serverOpts { - serverOpt(s.webhookServer) - } -} - -func (s *Server) Add(mgr manager.Manager) error { - err := s.certManager.Add(mgr) - if err != nil { - return errors.Wrap(err, "failed adding certificate manager to controller-runtime manager") - } - err = mgr.Add(s) - if err != nil { - return errors.Wrap(err, "failed adding webhook server to controller-runtime manager") - } - return nil -} - -func (s *Server) checkTLS() error { - - keyPath := path.Join(s.webhookServer.CertDir, corev1.TLSPrivateKeyKey) - _, err := os.Stat(keyPath) - if err != nil { - return errors.Wrap(err, "failed checking TLS key file stats") - } - - certsPath := path.Join(s.webhookServer.CertDir, corev1.TLSCertKey) - _, err = os.Stat(certsPath) - if err != nil { - return errors.Wrap(err, "failed checking TLS cert file stats") - } - - key, err := ioutil.ReadFile(path.Join(s.webhookServer.CertDir, corev1.TLSPrivateKeyKey)) - if err != nil { - return errors.Wrap(err, "failed reading for TLS key") - } - - certPEM, err := ioutil.ReadFile(path.Join(s.webhookServer.CertDir, corev1.TLSCertKey)) - if err != nil { - return errors.Wrap(err, "failed reading for TLS cert") - } - - caPEM, err := s.certManager.CABundle() - if err != nil { - return errors.Wrap(err, "failed to retrieve CA cert") - } - - err = triple.VerifyTLS(certPEM, key, caPEM) - if err != nil { - return errors.Wrapf(err, "failed verifying %s/%s", certsPath, keyPath) - } - - return nil -} - -func (s *Server) waitForTLSReadiness() error { - return wait.PollImmediate(5*time.Second, 5*time.Minute, func() (bool, error) { - err := s.checkTLS() - if err != nil { - utilruntime.HandleError(err) - return false, nil - } - return true, nil - }) -} - -func (s *Server) Start(ctx context.Context) error { - s.log.Info("Starting nodenetworkconfigurationpolicy webhook server") - - err := s.waitForTLSReadiness() - if err != nil { - return errors.Wrap(err, "failed watting for ready TLS key/cert") - } - - err = s.webhookServer.Start(ctx) - if err != nil { - return errors.Wrap(err, "failed starting webhook server") - } - return nil -} - -func (s *Server) InjectFunc(f inject.Func) error { - return s.webhookServer.InjectFunc(f) -} - -func (s *Server) NeedLeaderElection() bool { - return s.webhookServer.NeedLeaderElection() -} diff --git a/vendor/modules.txt b/vendor/modules.txt index eaaeef5e22..d4b6035665 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -311,6 +311,9 @@ github.com/gobwas/glob/util/runes github.com/gobwas/glob/util/strings # github.com/godbus/dbus v4.1.0+incompatible github.com/godbus/dbus +# github.com/gofrs/flock v0.8.0 +## explicit +github.com/gofrs/flock # github.com/gogo/protobuf v1.3.1 github.com/gogo/protobuf/gogoproto github.com/gogo/protobuf/proto @@ -466,7 +469,6 @@ github.com/modern-go/reflect2 github.com/morikuni/aec # github.com/nightlyone/lockfile v1.0.0 ## explicit -github.com/nightlyone/lockfile # github.com/nozzle/throttler v0.0.0-20180817012639-2ea982251481 github.com/nozzle/throttler # github.com/nxadm/tail v1.4.4 @@ -691,7 +693,6 @@ github.com/prometheus/procfs/internal/util ## explicit github.com/qinqon/kube-admission-webhook/pkg/certificate github.com/qinqon/kube-admission-webhook/pkg/certificate/triple -github.com/qinqon/kube-admission-webhook/pkg/webhook/server # github.com/rogpeppe/go-internal v1.5.0 github.com/rogpeppe/go-internal/modfile github.com/rogpeppe/go-internal/module