From 6e6f22db433818ed597f0e95625f41f662334796 Mon Sep 17 00:00:00 2001 From: Gyuho Lee Date: Sun, 7 Jun 2020 22:25:55 -0700 Subject: [PATCH] vendor: add missing failpoint Signed-off-by: Gyuho Lee --- functional/cmd/etcd-tester/main.go | 2 +- go.mod | 1 + go.sum | 2 + vendor/github.com/etcd-io/gofail/LICENSE | 202 ++++++++++ vendor/github.com/etcd-io/gofail/NOTICE | 5 + .../etcd-io/gofail/runtime/failpoint.go | 83 +++++ .../github.com/etcd-io/gofail/runtime/http.go | 95 +++++ .../etcd-io/gofail/runtime/runtime.go | 151 ++++++++ .../etcd-io/gofail/runtime/terms.go | 350 ++++++++++++++++++ vendor/modules.txt | 3 + 10 files changed, 893 insertions(+), 1 deletion(-) create mode 100644 vendor/github.com/etcd-io/gofail/LICENSE create mode 100644 vendor/github.com/etcd-io/gofail/NOTICE create mode 100644 vendor/github.com/etcd-io/gofail/runtime/failpoint.go create mode 100644 vendor/github.com/etcd-io/gofail/runtime/http.go create mode 100644 vendor/github.com/etcd-io/gofail/runtime/runtime.go create mode 100644 vendor/github.com/etcd-io/gofail/runtime/terms.go diff --git a/functional/cmd/etcd-tester/main.go b/functional/cmd/etcd-tester/main.go index 0d3116e37..8f646adb4 100644 --- a/functional/cmd/etcd-tester/main.go +++ b/functional/cmd/etcd-tester/main.go @@ -18,8 +18,8 @@ package main import ( "flag" + _ "github.com/etcd-io/gofail/runtime" "go.etcd.io/etcd/v3/functional/tester" - "go.uber.org/zap" ) diff --git a/go.mod b/go.mod index 4b3c15e76..024f67972 100644 --- a/go.mod +++ b/go.mod @@ -10,6 +10,7 @@ require ( github.com/creack/pty v1.1.7 github.com/dgrijalva/jwt-go v3.2.0+incompatible github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4 + github.com/etcd-io/gofail v0.0.0-20190801230047-ad7f989257ca github.com/fatih/color v1.7.0 // indirect github.com/gogo/protobuf v1.2.1 github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903 diff --git a/go.sum b/go.sum index 013b06ed3..932c38750 100644 --- a/go.sum +++ b/go.sum @@ -28,6 +28,8 @@ github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4 h1:qk/FSDDxo05w github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/etcd-io/gofail v0.0.0-20190801230047-ad7f989257ca h1:Y2I0lxOttdUKz+hNaIdG3FtjuQrTmwXun1opRV65IZc= +github.com/etcd-io/gofail v0.0.0-20190801230047-ad7f989257ca/go.mod h1:49H/RkXP8pKaZy4h0d+NW16rSLhyVBt4o6VLJbmOqDE= github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= diff --git a/vendor/github.com/etcd-io/gofail/LICENSE b/vendor/github.com/etcd-io/gofail/LICENSE new file mode 100644 index 000000000..d64569567 --- /dev/null +++ b/vendor/github.com/etcd-io/gofail/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/vendor/github.com/etcd-io/gofail/NOTICE b/vendor/github.com/etcd-io/gofail/NOTICE new file mode 100644 index 000000000..23a0ada2f --- /dev/null +++ b/vendor/github.com/etcd-io/gofail/NOTICE @@ -0,0 +1,5 @@ +CoreOS Project +Copyright 2018 CoreOS, Inc + +This product includes software developed at CoreOS, Inc. +(http://www.coreos.com/). diff --git a/vendor/github.com/etcd-io/gofail/runtime/failpoint.go b/vendor/github.com/etcd-io/gofail/runtime/failpoint.go new file mode 100644 index 000000000..5a973b35d --- /dev/null +++ b/vendor/github.com/etcd-io/gofail/runtime/failpoint.go @@ -0,0 +1,83 @@ +// Copyright 2016 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 runtime + +import ( + "context" + "fmt" + "sync" +) + +type Failpoint struct { + cmu sync.RWMutex + ctx context.Context + cancel context.CancelFunc + donec chan struct{} + released bool + + mu sync.RWMutex + t *terms +} + +func NewFailpoint(pkg, name string, goFailGo bool) *Failpoint { + fp := &Failpoint{} + if goFailGo { + fp.ctx, fp.cancel = context.WithCancel(context.Background()) + fp.donec = make(chan struct{}) + } + register(pkg+"/"+name, fp) + return fp +} + +// Acquire gets evalutes the failpoint terms; if the failpoint +// is active, it will return a value. Otherwise, returns a non-nil error. +func (fp *Failpoint) Acquire() (interface{}, error) { + fp.mu.RLock() + if fp.t == nil { + fp.mu.RUnlock() + return nil, ErrDisabled + } + v, err := fp.t.eval() + if v == nil { + err = ErrDisabled + } + if err != nil { + fp.mu.RUnlock() + } + return v, err +} + +// Release is called when the failpoint exists. +func (fp *Failpoint) Release() { + fp.cmu.RLock() + ctx := fp.ctx + donec := fp.donec + fp.cmu.RUnlock() + if ctx != nil && !fp.released { + <-ctx.Done() + select { + case <-donec: + default: + close(donec) + } + } + + fp.mu.RUnlock() +} + +// BadType is called when the failpoint evaluates to the wrong type. +func (fp *Failpoint) BadType(v interface{}, t string) { + fmt.Printf("failpoint: %q got value %v of type \"%T\" but expected type %q\n", fp.t.fpath, v, v, t) +} diff --git a/vendor/github.com/etcd-io/gofail/runtime/http.go b/vendor/github.com/etcd-io/gofail/runtime/http.go new file mode 100644 index 000000000..674a7d7c7 --- /dev/null +++ b/vendor/github.com/etcd-io/gofail/runtime/http.go @@ -0,0 +1,95 @@ +// Copyright 2016 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 runtime + +import ( + "io/ioutil" + "net" + "net/http" + "sort" + "strings" +) + +type httpHandler struct{} + +func serve(host string) error { + ln, err := net.Listen("tcp", host) + if err != nil { + return err + } + go http.Serve(ln, &httpHandler{}) + return nil +} + +func (*httpHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { + key := r.RequestURI + if len(key) == 0 || key[0] != '/' { + http.Error(w, "malformed request URI", http.StatusBadRequest) + return + } + key = key[1:] + + switch { + // sets the failpoint + case r.Method == "PUT": + v, err := ioutil.ReadAll(r.Body) + if err != nil { + http.Error(w, "failed ReadAll in PUT", http.StatusBadRequest) + return + } + unlock, eerr := enableAndLock(key, string(v)) + if eerr != nil { + http.Error(w, "failed to set failpoint "+string(key), http.StatusBadRequest) + return + } + w.WriteHeader(http.StatusNoContent) + if f, ok := w.(http.Flusher); ok { + // flush before unlocking so a panic failpoint won't + // take down the http server before it sends the response + f.Flush() + } + unlock() + // gets status of the failpoint + case r.Method == "GET": + if len(key) == 0 { + fps := List() + sort.Strings(fps) + lines := make([]string, len(fps)) + for i := range lines { + s, _ := Status(fps[i]) + lines[i] = fps[i] + "=" + s + } + w.Write([]byte(strings.Join(lines, "\n") + "\n")) + } else { + status, err := Status(key) + if err != nil { + http.Error(w, "failed to GET: "+err.Error(), http.StatusNotFound) + } + w.Write([]byte(status + "\n")) + } + // deactivates a failpoint + case r.Method == "DELETE": + if err := Disable(key); err != nil { + http.Error(w, "failed to delete failpoint "+err.Error(), http.StatusBadRequest) + return + } + w.WriteHeader(http.StatusNoContent) + default: + w.Header().Add("Allow", "DELETE") + w.Header().Add("Allow", "GET") + w.Header().Set("Allow", "PUT") + http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) + } +} diff --git a/vendor/github.com/etcd-io/gofail/runtime/runtime.go b/vendor/github.com/etcd-io/gofail/runtime/runtime.go new file mode 100644 index 000000000..b7db36aa7 --- /dev/null +++ b/vendor/github.com/etcd-io/gofail/runtime/runtime.go @@ -0,0 +1,151 @@ +// Copyright 2016 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 runtime + +import ( + "context" + "fmt" + "os" + "strings" + "sync" +) + +var ( + ErrNoExist = fmt.Errorf("failpoint: failpoint does not exist") + ErrDisabled = fmt.Errorf("failpoint: failpoint is disabled") + + failpoints map[string]*Failpoint + failpointsMu sync.RWMutex + envTerms map[string]string +) + +func init() { + failpoints = make(map[string]*Failpoint) + envTerms = make(map[string]string) + if s := os.Getenv("GOFAIL_FAILPOINTS"); len(s) > 0 { + // format is =[;=;...] + for _, fp := range strings.Split(s, ";") { + fp_term := strings.Split(fp, "=") + if len(fp_term) != 2 { + fmt.Printf("bad failpoint %q\n", fp) + os.Exit(1) + } + envTerms[fp_term[0]] = fp_term[1] + } + } + if s := os.Getenv("GOFAIL_HTTP"); len(s) > 0 { + if err := serve(s); err != nil { + fmt.Println(err) + os.Exit(1) + } + } +} + +// Enable sets a failpoint to a given failpoint description. +func Enable(failpath, inTerms string) error { + unlock, err := enableAndLock(failpath, inTerms) + if unlock != nil { + unlock() + } + return err +} + +// enableAndLock enables a failpoint and returns a function to unlock it +func enableAndLock(failpath, inTerms string) (func(), error) { + t, err := newTerms(failpath, inTerms) + if err != nil { + fmt.Printf("failed to enable \"%s=%s\" (%v)\n", failpath, inTerms, err) + return nil, err + } + failpointsMu.RLock() + fp := failpoints[failpath] + failpointsMu.RUnlock() + if fp == nil { + return nil, ErrNoExist + } + fp.mu.Lock() + fp.t = t + fp.released = false + return func() { fp.mu.Unlock() }, nil +} + +// Disable stops a failpoint from firing. +func Disable(failpath string) error { + failpointsMu.RLock() + fp := failpoints[failpath] + failpointsMu.RUnlock() + if fp == nil { + return ErrNoExist + } + + fp.cmu.RLock() + cancel := fp.cancel + donec := fp.donec + fp.cmu.RUnlock() + if cancel != nil && donec != nil { + cancel() + <-donec + + fp.cmu.Lock() + fp.ctx, fp.cancel = context.WithCancel(context.Background()) + fp.donec = make(chan struct{}) + fp.released = true + fp.cmu.Unlock() + } + + fp.mu.Lock() + defer fp.mu.Unlock() + if fp.t == nil { + return ErrDisabled + } + fp.t = nil + return nil +} + +// Status gives the current setting for the failpoint +func Status(failpath string) (string, error) { + failpointsMu.RLock() + fp := failpoints[failpath] + failpointsMu.RUnlock() + if fp == nil { + return "", ErrNoExist + } + fp.mu.RLock() + t := fp.t + fp.mu.RUnlock() + if t == nil { + return "", ErrDisabled + } + return t.desc, nil +} + +func List() []string { + failpointsMu.RLock() + ret := make([]string, 0, len(failpoints)) + for fp := range failpoints { + ret = append(ret, fp) + } + failpointsMu.RUnlock() + return ret +} + +func register(failpath string, fp *Failpoint) { + failpointsMu.Lock() + failpoints[failpath] = fp + failpointsMu.Unlock() + if t, ok := envTerms[failpath]; ok { + Enable(failpath, t) + } +} diff --git a/vendor/github.com/etcd-io/gofail/runtime/terms.go b/vendor/github.com/etcd-io/gofail/runtime/terms.go new file mode 100644 index 000000000..d81477d5e --- /dev/null +++ b/vendor/github.com/etcd-io/gofail/runtime/terms.go @@ -0,0 +1,350 @@ +// Copyright 2016 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 runtime + +import ( + "fmt" + "math/rand" + "os" + "os/exec" + "strings" + "sync" + "time" +) + +var ( + ErrExhausted = fmt.Errorf("failpoint: terms exhausted") + ErrBadParse = fmt.Errorf("failpoint: could not parse terms") +) + +// terms encodes the state for a failpoint term string (see fail(9) for examples) +// :: ( "->" )* +type terms struct { + // chain is a slice of all the terms from desc + chain []*term + // desc is the full term given for the failpoint + desc string + // fpath is the failpoint path for these terms + fpath string + + // mu protects the state of the terms chain + mu sync.Mutex +} + +// term is an executable unit of the failpoint terms chain +type term struct { + desc string + + mods mod + act actFunc + val interface{} + + parent *terms +} + +type mod interface { + allow() bool +} + +type modCount struct{ c int } + +func (mc *modCount) allow() bool { + if mc.c > 0 { + mc.c-- + return true + } + return false +} + +type modProb struct{ p float64 } + +func (mp *modProb) allow() bool { return rand.Float64() <= mp.p } + +type modList struct{ l []mod } + +func (ml *modList) allow() bool { + for _, m := range ml.l { + if !m.allow() { + return false + } + } + return true +} + +func newTerms(fpath, desc string) (*terms, error) { + chain := parse(desc) + if len(chain) == 0 { + return nil, ErrBadParse + } + t := &terms{chain: chain, desc: desc, fpath: fpath} + for _, c := range chain { + c.parent = t + } + return t, nil +} + +func (t *terms) String() string { return t.desc } + +func (t *terms) eval() (interface{}, error) { + t.mu.Lock() + defer t.mu.Unlock() + for _, term := range t.chain { + if term.mods.allow() { + return term.do(), nil + } + } + return nil, nil +} + +// split terms from a -> b -> ... into [a, b, ...] +func parse(desc string) (chain []*term) { + origDesc := desc + for len(desc) != 0 { + t := parseTerm(desc) + if t == nil { + fmt.Printf("failed to parse %q past %q\n", origDesc, desc) + return nil + } + desc = desc[len(t.desc):] + chain = append(chain, t) + if len(desc) >= 2 { + if !strings.HasPrefix(desc, "->") { + fmt.Printf("failed to parse %q past %q, expected \"->\"\n", origDesc, desc) + return nil + } + desc = desc[2:] + } + } + return chain +} + +// :: [ "(" ")" ] +func parseTerm(desc string) *term { + t := &term{} + modStr, mods := parseMod(desc) + t.mods = &modList{mods} + actStr, act := parseAct(desc[len(modStr):]) + t.act = act + valStr, val := parseVal(desc[len(modStr)+len(actStr):]) + t.val = val + t.desc = desc[:len(modStr)+len(actStr)+len(valStr)] + if len(t.desc) == 0 { + return nil + } + return t +} + +// :: (( "%")|( "*" ))* +func parseMod(desc string) (ret string, mods []mod) { + for { + s, v := parseIntFloat(desc) + if len(s) == 0 { + break + } + if len(s) == len(desc) { + return "", nil + } + switch v := v.(type) { + case float64: + if desc[len(s)] != '%' { + return "", nil + } + ret = ret + desc[:len(s)+1] + mods = append(mods, &modProb{v / 100.0}) + desc = desc[len(s)+1:] + case int: + if desc[len(s)] != '*' { + return "", nil + } + ret = ret + desc[:len(s)+1] + mods = append(mods, &modCount{v}) + desc = desc[len(s)+1:] + default: + panic("???") + } + } + return ret, mods +} + +// parseIntFloat parses an int or float from a string and returns the string +// it parsed it from (unlike scanf). +func parseIntFloat(desc string) (string, interface{}) { + // parse for ints + i := 0 + for i < len(desc) { + if desc[i] < '0' || desc[i] > '9' { + break + } + i++ + } + if i == 0 { + return "", nil + } + + intVal := int(0) + _, err := fmt.Sscanf(desc[:i], "%d", &intVal) + if err != nil { + return "", nil + } + if len(desc) == i { + return desc[:i], intVal + } + if desc[i] != '.' { + return desc[:i], intVal + } + + // parse for floats + i++ + if i == len(desc) { + return desc[:i], float64(intVal) + } + + j := i + for i < len(desc) { + if desc[i] < '0' || desc[i] > '9' { + break + } + i++ + } + if j == i { + return desc[:i], float64(intVal) + } + + f := float64(0) + if _, err = fmt.Sscanf(desc[:i], "%f", &f); err != nil { + return "", nil + } + return desc[:i], f +} + +// parseAct parses an action +// :: "off" | "return" | "sleep" | "panic" | "break" | "print" +func parseAct(desc string) (string, actFunc) { + for k, v := range actMap { + if strings.HasPrefix(desc, k) { + return k, v + } + } + return "", nil +} + +// :: | | | +func parseVal(desc string) (string, interface{}) { + // return => struct{} + if len(desc) == 0 { + return "", struct{}{} + } + // malformed + if len(desc) == 1 || desc[0] != '(' { + return "", nil + } + // return() => struct{} + if desc[1] == ')' { + return "()", struct{}{} + } + // return("s") => string + s := "" + n, err := fmt.Sscanf(desc[1:], "%q", &s) + if n == 1 && err == nil { + return desc[:len(s)+4], s + } + // return(1) => int + v := 0 + n, err = fmt.Sscanf(desc[1:], "%d", &v) + if n == 1 && err == nil { + return desc[:len(fmt.Sprintf("%d", v))+2], v + } + // return(true) => bool + b := false + n, err = fmt.Sscanf(desc[1:], "%t", &b) + if n == 1 && err == nil { + return desc[:len(fmt.Sprintf("%t", b))+2], b + } + // unknown type; malformed input? + return "", nil +} + +type actFunc func(*term) interface{} + +var actMap = map[string]actFunc{ + "off": actOff, + "return": actReturn, + "sleep": actSleep, + "panic": actPanic, + "break": actBreak, + "print": actPrint, +} + +func (t *term) do() interface{} { return t.act(t) } + +func actOff(t *term) interface{} { return nil } + +func actReturn(t *term) interface{} { return t.val } + +func actSleep(t *term) interface{} { + var dur time.Duration + switch v := t.val.(type) { + case int: + dur = time.Duration(v) * time.Millisecond + case string: + vDur, err := time.ParseDuration(v) + if err != nil { + fmt.Printf("failpoint: could not parse sleep(%v) on %s\n", v, t.parent.fpath) + return nil + } + dur = vDur + default: + fmt.Printf("failpoint: ignoring sleep(%v) on %s\n", v, t.parent.fpath) + return nil + } + time.Sleep(dur) + return nil +} + +func actPanic(t *term) interface{} { + if t.val != nil { + panic(fmt.Sprintf("failpoint panic: %v", t.val)) + } + panic("failpoint panic: " + t.parent.fpath) +} + +func actBreak(t *term) interface{} { + p, perr := exec.LookPath(os.Args[0]) + if perr != nil { + panic(perr) + } + cmd := exec.Command("gdb", p, fmt.Sprintf("%d", os.Getpid())) + cmd.Stdin = os.Stdin + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + if err := cmd.Start(); err != nil { + panic(err) + } + + // wait for gdb prompt + // XXX: tried doing this by piping stdout here and waiting on "(gdb) " + // but the the output won't appear since the process is STOPed and + // can't copy it back to the actual stdout + time.Sleep(3 * time.Second) + + // don't zombie gdb + go cmd.Wait() + return nil +} + +func actPrint(t *term) interface{} { + fmt.Println("failpoint print:", t.parent.fpath) + return nil +} diff --git a/vendor/modules.txt b/vendor/modules.txt index 04f4a498a..301a24c56 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -22,6 +22,9 @@ github.com/dgrijalva/jwt-go # github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4 ## explicit github.com/dustin/go-humanize +# github.com/etcd-io/gofail v0.0.0-20190801230047-ad7f989257ca +## explicit +github.com/etcd-io/gofail/runtime # github.com/fatih/color v1.7.0 ## explicit # github.com/gogo/protobuf v1.2.1