diff --git a/cmd/kg/main.go b/cmd/kg/main.go index ee85fdb..7e84fd8 100644 --- a/cmd/kg/main.go +++ b/cmd/kg/main.go @@ -27,10 +27,10 @@ import ( "github.com/go-kit/kit/log" "github.com/go-kit/kit/log/level" + "github.com/metalmatze/signal/internalserver" "github.com/oklog/run" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/collectors" - "github.com/prometheus/client_golang/prometheus/promhttp" "github.com/spf13/cobra" apiextensions "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset" "k8s.io/client-go/kubernetes" @@ -251,18 +251,21 @@ func runRoot(_ *cobra.Command, _ []string) error { var g run.Group { + h := internalserver.NewHandler( + internalserver.WithName("Internal Kilo API"), + internalserver.WithPrometheusRegistry(registry), + internalserver.WithPProf(), + ) + h.AddEndpoint("/health", "Exposes health checks", healthHandler) + h.AddEndpoint("/graph", "Exposes Kilo mesh topology graph", (&graphHandler{m, gr, &hostname, s}).ServeHTTP) // Run the HTTP server. - mux := http.NewServeMux() - mux.HandleFunc("/health", healthHandler) - mux.Handle("/graph", &graphHandler{m, gr, &hostname, s}) - mux.Handle("/metrics", promhttp.HandlerFor(registry, promhttp.HandlerOpts{})) l, err := net.Listen("tcp", listen) if err != nil { return fmt.Errorf("failed to listen on %s: %v", listen, err) } g.Add(func() error { - if err := http.Serve(l, mux); err != nil && err != http.ErrServerClosed { + if err := http.Serve(l, h); err != nil && err != http.ErrServerClosed { return fmt.Errorf("error: server exited unexpectedly: %v", err) } return nil diff --git a/go.mod b/go.mod index 25a65e6..3a9ab0d 100644 --- a/go.mod +++ b/go.mod @@ -10,6 +10,7 @@ require ( github.com/coreos/go-iptables v0.6.0 github.com/go-kit/kit v0.9.0 github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348 + github.com/metalmatze/signal v0.0.0-20210307161603-1c9aa721a97a github.com/oklog/run v1.1.0 github.com/prometheus/client_golang v1.11.0 github.com/spf13/cobra v1.1.3 diff --git a/go.sum b/go.sum index 3b85298..b4f8040 100644 --- a/go.sum +++ b/go.sum @@ -32,6 +32,8 @@ github.com/Azure/go-autorest/logger v0.2.0/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZ github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/DATA-DOG/go-sqlmock v1.4.1 h1:ThlnYciV1iM/V0OSF/dtkqWb6xo5qITT1TJBG1MRDJM= +github.com/DATA-DOG/go-sqlmock v1.4.1/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= @@ -253,6 +255,7 @@ github.com/jsimonetti/rtnetlink v0.0.0-20210525051524-4cc836578190 h1:iycCSDo8EK github.com/jsimonetti/rtnetlink v0.0.0-20210525051524-4cc836578190/go.mod h1:NmKSdU4VGSiv1bMsdqNALI4RSvvjtz65tTMCnD05qLo= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.11 h1:uVUAXhF2To8cbw/3xN3pxj6kk7TYKs98NIrTqPlMWAQ= github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= @@ -310,6 +313,8 @@ github.com/mdlayher/netlink v1.4.1/go.mod h1:e4/KuJ+s8UhfUpO9z00/fDZZmhSrs+oxyqA github.com/mdlayher/socket v0.0.0-20210307095302-262dc9984e00/go.mod h1:GAFlyu4/XV68LkQKYzKhIo/WW7j3Zi0YRAz/BOoanUc= github.com/mdlayher/socket v0.0.0-20211102153432-57e3fa563ecb h1:2dC7L10LmTqlyMVzFJ00qM25lqESg9Z4u3GuEXN5iHY= github.com/mdlayher/socket v0.0.0-20211102153432-57e3fa563ecb/go.mod h1:nFZ1EtZYK8Gi/k6QNu7z7CgO20i/4ExeQswwWuPmG/g= +github.com/metalmatze/signal v0.0.0-20210307161603-1c9aa721a97a h1:0usWxe5SGXKQovz3p+BiQ81Jy845xSMu2CWKuXsXuUM= +github.com/metalmatze/signal v0.0.0-20210307161603-1c9aa721a97a/go.mod h1:3OETvrxfELvGsU2RoGGWercfeZ4bCL3+SOwzIWtJH/Q= github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/mikioh/ipaddr v0.0.0-20190404000644-d465c8ab6721 h1:RlZweED6sbSArvlE924+mUcZuXKLBHA35U7LN621Bws= github.com/mikioh/ipaddr v0.0.0-20190404000644-d465c8ab6721/go.mod h1:Ickgr2WtCLZ2MDGd4Gr0geeCH5HybhRJbonOgQpvSxc= @@ -371,6 +376,7 @@ github.com/pquerna/cachecontrol v0.0.0-20171018203845-0dec1b30a021/go.mod h1:prY github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= +github.com/prometheus/client_golang v1.5.1/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU= github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= github.com/prometheus/client_golang v1.11.0 h1:HNkLOAEQMIDv/K+04rukrLx6ch7msSRwf3/SASFAGtQ= github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= @@ -382,12 +388,14 @@ github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6T github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4= github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= github.com/prometheus/common v0.26.0 h1:iMAkS2TDoNWnKM+Kopnx/8tnEStIfpYA0ur0xQzzhMQ= github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= github.com/prometheus/procfs v0.2.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= github.com/prometheus/procfs v0.6.0 h1:mxy4L2jP6qMonqmq+aTtOx1ifVWUgG/TAmntgbh3xv4= diff --git a/vendor/github.com/metalmatze/signal/LICENSE b/vendor/github.com/metalmatze/signal/LICENSE new file mode 100644 index 0000000..e7c9c22 --- /dev/null +++ b/vendor/github.com/metalmatze/signal/LICENSE @@ -0,0 +1,201 @@ + 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 2020 Signal Authors + + 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/metalmatze/signal/healthcheck/checks.go b/vendor/github.com/metalmatze/signal/healthcheck/checks.go new file mode 100644 index 0000000..da776eb --- /dev/null +++ b/vendor/github.com/metalmatze/signal/healthcheck/checks.go @@ -0,0 +1,129 @@ +// Copyright 2020 by the contributors. +// +// 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 healthcheck + +import ( + "context" + "database/sql" + "fmt" + "net" + "net/http" + "runtime" + "time" +) + +// TCPDialCheck returns a Check that checks TCP connectivity to the provided +// endpoint. +func TCPDialCheck(addr string, timeout time.Duration) Check { + return func() error { + conn, err := net.DialTimeout("tcp", addr, timeout) + if err != nil { + return err + } + return conn.Close() + } +} + +// HTTPGetCheck returns a Check that performs an HTTP GET request against the +// specified URL. The check fails if the response times out or returns a non-200 +// status code. +func HTTPGetCheck(url string, timeout time.Duration) Check { + return func() error { + return HTTPCheck(url, http.MethodGet, http.StatusOK, timeout)() + } +} + +// HTTPCheck returns a Check that performs a HTTP request against the specified URL. +// The Check fails if the response times out or returns an unexpected status code. +func HTTPCheck(url string, method string, status int, timeout time.Duration) Check { + client := &http.Client{ + Timeout: timeout, + CheckRedirect: func(*http.Request, []*http.Request) error { + return http.ErrUseLastResponse + }, + } + + return HTTPCheckClient(client, url, method, status, timeout) +} + +// HTTPCheckClient returns a Check that performs a HTTP request against the specified URL. +// The Check fails if the response times out or returns an unexpected status code. +// On top of that it uses a custom client specified by the caller. +func HTTPCheckClient(client *http.Client, url string, method string, status int, timeout time.Duration) Check { + return func() error { + ctx, cancel := context.WithTimeout(context.Background(), timeout) + defer cancel() + + req, err := http.NewRequest(method, url, nil) + if err != nil { + return err + } + req = req.WithContext(ctx) + + resp, err := client.Do(req) + if err != nil { + return err + } + defer resp.Body.Close() + + if resp.StatusCode != status { + return fmt.Errorf("returned status %d, expected %d", resp.StatusCode, status) + } + return nil + } +} + +// DatabasePingCheck returns a Check that validates connectivity to a +// database/sql.DB using Ping(). +func DatabasePingCheck(database *sql.DB, timeout time.Duration) Check { + return func() error { + ctx, cancel := context.WithTimeout(context.Background(), timeout) + defer cancel() + if database == nil { + return fmt.Errorf("database is nil") + } + return database.PingContext(ctx) + } +} + +// DNSResolveCheck returns a Check that makes sure the provided host can resolve +// to at least one IP address within the specified timeout. +func DNSResolveCheck(host string, timeout time.Duration) Check { + resolver := net.Resolver{} + return func() error { + ctx, cancel := context.WithTimeout(context.Background(), timeout) + defer cancel() + addrs, err := resolver.LookupHost(ctx, host) + if err != nil { + return err + } + if len(addrs) < 1 { + return fmt.Errorf("could not resolve host") + } + return nil + } +} + +// GoroutineCountCheck returns a Check that fails if too many goroutines are +// running (which could indicate a resource leak). +func GoroutineCountCheck(threshold int) Check { + return func() error { + count := runtime.NumGoroutine() + if count > threshold { + return fmt.Errorf("too many goroutines (%d > %d)", count, threshold) + } + return nil + } +} diff --git a/vendor/github.com/metalmatze/signal/healthcheck/doc.go b/vendor/github.com/metalmatze/signal/healthcheck/doc.go new file mode 100644 index 0000000..1152d90 --- /dev/null +++ b/vendor/github.com/metalmatze/signal/healthcheck/doc.go @@ -0,0 +1,24 @@ +// Copyright 2020 by the contributors. +// +// 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 healthcheck helps you implement liveness and readiness checks +for your application. It supports synchronous and asynchronous (background) +checks. It can optionally report each check's status as a set of Prometheus +gauge metrics for cluster-wide monitoring and alerting. + +It also includes a small library of generic checks for DNS, TCP, and HTTP +reachability as well as Goroutine usage. +*/ +package healthcheck diff --git a/vendor/github.com/metalmatze/signal/healthcheck/handler.go b/vendor/github.com/metalmatze/signal/healthcheck/handler.go new file mode 100644 index 0000000..83e8f34 --- /dev/null +++ b/vendor/github.com/metalmatze/signal/healthcheck/handler.go @@ -0,0 +1,103 @@ +// Copyright 2020 by the contributors. +// +// 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 healthcheck + +import ( + "encoding/json" + "net/http" + "sync" +) + +// basicHandler is a basic Handler implementation. +type basicHandler struct { + http.ServeMux + checksMutex sync.RWMutex + livenessChecks map[string]Check + readinessChecks map[string]Check +} + +// NewHandler creates a new basic Handler +func NewHandler() Handler { + h := &basicHandler{ + livenessChecks: make(map[string]Check), + readinessChecks: make(map[string]Check), + } + h.Handle("/live", http.HandlerFunc(h.LiveEndpoint)) + h.Handle("/ready", http.HandlerFunc(h.ReadyEndpoint)) + return h +} + +func (s *basicHandler) LiveEndpoint(w http.ResponseWriter, r *http.Request) { + s.handle(w, r, s.livenessChecks) +} + +func (s *basicHandler) ReadyEndpoint(w http.ResponseWriter, r *http.Request) { + s.handle(w, r, s.readinessChecks, s.livenessChecks) +} + +func (s *basicHandler) AddLivenessCheck(name string, check Check) { + s.checksMutex.Lock() + defer s.checksMutex.Unlock() + s.livenessChecks[name] = check +} + +func (s *basicHandler) AddReadinessCheck(name string, check Check) { + s.checksMutex.Lock() + defer s.checksMutex.Unlock() + s.readinessChecks[name] = check +} + +func (s *basicHandler) collectChecks(checks map[string]Check, resultsOut map[string]string, statusOut *int) { + s.checksMutex.RLock() + defer s.checksMutex.RUnlock() + for name, check := range checks { + if err := check(); err != nil { + *statusOut = http.StatusServiceUnavailable + resultsOut[name] = err.Error() + } else { + resultsOut[name] = "OK" + } + } +} + +func (s *basicHandler) handle(w http.ResponseWriter, r *http.Request, checks ...map[string]Check) { + if r.Method != http.MethodGet { + http.Error(w, "method not allowed", http.StatusMethodNotAllowed) + return + } + + checkResults := make(map[string]string) + status := http.StatusOK + for _, checks := range checks { + s.collectChecks(checks, checkResults, &status) + } + + // write out the response code and content type header + w.Header().Set("Content-Type", "application/json; charset=utf-8") + w.WriteHeader(status) + + // if ?hide=1, return an empty body. Kubernetes only cares about the + // HTTP status code, so we won't waste bytes on the full body. + if r.URL.Query().Get("hide") == "1" { + _, _ = w.Write([]byte("{}\n")) + return + } + + // otherwise, write the JSON body ignoring any encoding errors (which + // shouldn't really be possible since we're encoding a map[string]string). + encoder := json.NewEncoder(w) + encoder.SetIndent("", " ") + _ = encoder.Encode(checkResults) +} diff --git a/vendor/github.com/metalmatze/signal/healthcheck/metrics_handler.go b/vendor/github.com/metalmatze/signal/healthcheck/metrics_handler.go new file mode 100644 index 0000000..5055d56 --- /dev/null +++ b/vendor/github.com/metalmatze/signal/healthcheck/metrics_handler.go @@ -0,0 +1,72 @@ +// Copyright 2020 by the contributors. +// +// 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 healthcheck + +import ( + "net/http" + + "github.com/prometheus/client_golang/prometheus" +) + +type metricsHandler struct { + handler Handler + registry prometheus.Registerer +} + +// NewMetricsHandler returns a healthy Handler that writes the current check status +// into the provided Prometheus registry. +func NewMetricsHandler(handler Handler, registry prometheus.Registerer) Handler { + return &metricsHandler{ + handler: handler, + registry: registry, + } +} + +func (h *metricsHandler) AddLivenessCheck(name string, check Check) { + h.handler.AddLivenessCheck(name, h.wrap(prometheus.Labels{"name": name, "check": "live"}, check)) +} + +func (h *metricsHandler) AddReadinessCheck(name string, check Check) { + h.handler.AddReadinessCheck(name, h.wrap(prometheus.Labels{"name": name, "check": "ready"}, check)) +} + +func (h *metricsHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { + h.handler.ServeHTTP(w, r) +} + +func (h *metricsHandler) LiveEndpoint(w http.ResponseWriter, r *http.Request) { + h.handler.LiveEndpoint(w, r) +} + +func (h *metricsHandler) ReadyEndpoint(w http.ResponseWriter, r *http.Request) { + h.handler.ReadyEndpoint(w, r) +} + +func (h *metricsHandler) wrap(labels prometheus.Labels, check Check) Check { + h.registry.MustRegister(prometheus.NewGaugeFunc( + prometheus.GaugeOpts{ + Name: "healthcheck", + Help: "Indicates if check is healthy (1 is healthy, 0 is unhealthy)", + ConstLabels: labels, + }, + func() float64 { + if check() != nil { + return 0 + } + return 1 + }, + )) + return check +} diff --git a/vendor/github.com/metalmatze/signal/healthcheck/timeout.go b/vendor/github.com/metalmatze/signal/healthcheck/timeout.go new file mode 100644 index 0000000..983fce6 --- /dev/null +++ b/vendor/github.com/metalmatze/signal/healthcheck/timeout.go @@ -0,0 +1,52 @@ +// Copyright 2020 by the contributors. +// +// 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 healthcheck + +import ( + "fmt" + "time" +) + +// TimeoutError is the error returned when a Timeout-wrapped Check takes too long +type timeoutError time.Duration + +func (e timeoutError) Error() string { + return fmt.Sprintf("timed out after %s", time.Duration(e).String()) +} + +// Timeout returns whether this error is a timeout (always true for timeoutError) +func (e timeoutError) Timeout() bool { + return true +} + +// Temporary returns whether this error is temporary (always true for timeoutError) +func (e timeoutError) Temporary() bool { + return true +} + +// Timeout adds a timeout to a Check. If the underlying check takes longer than +// the timeout, it returns an error. +func Timeout(check Check, timeout time.Duration) Check { + return func() error { + c := make(chan error, 1) + go func() { c <- check() }() + select { + case err := <-c: + return err + case <-time.After(timeout): + return timeoutError(timeout) + } + } +} diff --git a/vendor/github.com/metalmatze/signal/healthcheck/types.go b/vendor/github.com/metalmatze/signal/healthcheck/types.go new file mode 100644 index 0000000..8a88c27 --- /dev/null +++ b/vendor/github.com/metalmatze/signal/healthcheck/types.go @@ -0,0 +1,52 @@ +// Copyright 2020 by the contributors. +// +// 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 healthcheck + +import ( + "net/http" +) + +// Check is a health/readiness check. +type Check func() error + +// Handler is an http.Handler with additional methods that register health and +// readiness checks. It handles handle "/live" and "/ready" HTTP +// endpoints. +type Handler interface { + // The Handler is an http.Handler, so it can be exposed directly and handle + // /live and /ready endpoints. + http.Handler + + // AddLivenessCheck adds a check that indicates that this instance of the + // application should be destroyed or restarted. A failed liveness check + // indicates that this instance is unhealthy, not some upstream dependency. + // Every liveness check is also included as a readiness check. + AddLivenessCheck(name string, check Check) + + // AddReadinessCheck adds a check that indicates that this instance of the + // application is currently unable to serve requests because of an upstream + // or some transient failure. If a readiness check fails, this instance + // should no longer receiver requests, but should not be restarted or + // destroyed. + AddReadinessCheck(name string, check Check) + + // LiveEndpoint is the HTTP handler for just the /live endpoint, which is + // useful if you need to attach it into your own HTTP handler tree. + LiveEndpoint(http.ResponseWriter, *http.Request) + + // ReadyEndpoint is the HTTP handler for just the /ready endpoint, which is + // useful if you need to attach it into your own HTTP handler tree. + ReadyEndpoint(http.ResponseWriter, *http.Request) +} diff --git a/vendor/github.com/metalmatze/signal/internalserver/handler.go b/vendor/github.com/metalmatze/signal/internalserver/handler.go new file mode 100644 index 0000000..c155e95 --- /dev/null +++ b/vendor/github.com/metalmatze/signal/internalserver/handler.go @@ -0,0 +1,138 @@ +// Copyright 2021 by the contributors. +// +// 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 internalserver + +import ( + "fmt" + "net/http" + "net/http/pprof" + "sort" + + "github.com/metalmatze/signal/healthcheck" + "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/client_golang/prometheus/promhttp" +) + +// Handler is a http.ServeMux that knows about all endpoints to render a nice index page. +type Handler struct { + http.ServeMux + endpoints []endpoint + name string +} + +// endpoint has a description to a pattern. +type endpoint struct { + Pattern string + Description string +} + +// NewHandler creates a new internalserver Handler. +func NewHandler(options ...Option) *Handler { + h := &Handler{name: "Internal"} + + for _, option := range options { + option(h) + } + + h.HandleFunc("/", h.index) + + return h +} + +// AddEndpoint wraps HandleFunc for adding http handlers to add a meaningful description to the index page. +func (h *Handler) AddEndpoint(pattern string, description string, handler http.HandlerFunc) { + h.endpoints = append(h.endpoints, endpoint{ + Pattern: pattern, + Description: description, + }) + + // Sort endpoints by pattern after adding a new one, to always show them in the same order. + sort.Slice(h.endpoints, func(i, j int) bool { + return h.endpoints[i].Pattern < h.endpoints[j].Pattern + }) + + h.HandleFunc(pattern, handler) +} + +func (h *Handler) index(w http.ResponseWriter, r *http.Request) { + html := fmt.Sprintf("%s\n", h.name) + html += fmt.Sprintf("

%s

\n", h.name) + + for _, e := range h.endpoints { + html += fmt.Sprintf("

%s - %s

\n", e.Pattern, e.Pattern, e.Description) + } + html += `` + + w.Header().Set("Content-Type", "text/html; charset=utf-8") + _, _ = w.Write([]byte(html)) +} + +// Option is a func that modifies the configuration for the internalserver handler. +type Option func(h *Handler) + +// WithName allows to set an application name for the internalserver handler. +func WithName(name string) Option { + return func(h *Handler) { + h.name = name + } +} + +// WithHealthchecks adds the healthchecks endpoints /live and /ready to the internalserver. +func WithHealthchecks(healthchecks healthcheck.Handler) Option { + return func(h *Handler) { + h.AddEndpoint( + "/live", + "Exposes liveness checks", + healthchecks.LiveEndpoint, + ) + h.AddEndpoint( + "/ready", + "Exposes readiness checks", + healthchecks.ReadyEndpoint, + ) + } +} + +// WithPrometheusRegistry adds a /metrics endpoint to the internalserver. +func WithPrometheusRegistry(registry *prometheus.Registry) Option { + return func(h *Handler) { + h.AddEndpoint( + "/metrics", + "Exposes Prometheus metrics", + promhttp.HandlerFor(registry, promhttp.HandlerOpts{}).ServeHTTP, + ) + } +} + +// WithPProf adds all pprof endpoints under /debug to the internalserver. +func WithPProf() Option { + return func(h *Handler) { + m := http.NewServeMux() + m.HandleFunc("/debug/pprof/", pprof.Index) + m.HandleFunc("/debug/pprof/cmdline", pprof.Cmdline) + m.HandleFunc("/debug/pprof/profile", pprof.Profile) + m.HandleFunc("/debug/pprof/symbol", pprof.Symbol) + m.HandleFunc("/debug/pprof/trace", pprof.Trace) + m.HandleFunc("/debug/", func(w http.ResponseWriter, r *http.Request) { + http.Redirect(w, r, "/debug/pprof/", http.StatusMovedPermanently) + }) + + h.AddEndpoint( + "/debug/", + "Exposes pprof endpoints to consume via HTTP", + m.ServeHTTP, + ) + } +} diff --git a/vendor/modules.txt b/vendor/modules.txt index 151fd04..d6e4811 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -127,6 +127,10 @@ github.com/mdlayher/netlink/nlenc # github.com/mdlayher/socket v0.0.0-20211102153432-57e3fa563ecb ## explicit; go 1.17 github.com/mdlayher/socket +# github.com/metalmatze/signal v0.0.0-20210307161603-1c9aa721a97a +## explicit; go 1.14 +github.com/metalmatze/signal/healthcheck +github.com/metalmatze/signal/internalserver # github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd ## explicit github.com/modern-go/concurrent