From 3eb22b073a22104a11e99eb30892118707013e8f Mon Sep 17 00:00:00 2001 From: Todd Date: Thu, 31 Mar 2022 08:48:50 -0700 Subject: [PATCH] Defines the controller api metrics and registers them at controller startup (#1917) Co-authored-by: Hugo Vieira --- globals/fields.go | 1 + go.mod | 10 +- go.sum | 36 ++- internal/cmd/base/servers.go | 20 +- internal/cmd/commands/server/server_test.go | 8 + internal/cmd/ops/server.go | 2 + .../gen/testing/protooptions/service.pb.go | 227 ++++++++++++++ .../gen/testing/protooptions/service.pb.gw.go | 277 ++++++++++++++++++ .../testing/protooptions/service_grpc.pb.go | 101 +++++++ .../local/testing/options/v1/service.proto | 31 ++ internal/servers/controller/controller.go | 2 + internal/servers/controller/handler.go | 7 +- .../servers/controller/internal/metric/api.go | 271 +++++++++++++++++ .../controller/internal/metric/api_test.go | 135 +++++++++ internal/servers/controller/testing.go | 9 +- internal/servers/controller/testing_test.go | 7 + 16 files changed, 1128 insertions(+), 16 deletions(-) create mode 100644 internal/gen/testing/protooptions/service.pb.go create mode 100644 internal/gen/testing/protooptions/service.pb.gw.go create mode 100644 internal/gen/testing/protooptions/service_grpc.pb.go create mode 100644 internal/proto/local/testing/options/v1/service.proto create mode 100644 internal/servers/controller/internal/metric/api.go create mode 100644 internal/servers/controller/internal/metric/api_test.go diff --git a/globals/fields.go b/globals/fields.go index be429bc48..00589c64e 100644 --- a/globals/fields.go +++ b/globals/fields.go @@ -72,4 +72,5 @@ const ( ConnectionsField = "connections" CredentialTypeField = "credential_type" CredentialMappingOverridesField = "credential_mapping_overrides" + MetricNamespace = "boundary" ) diff --git a/go.mod b/go.mod index c4a0c4a28..441d88cfc 100644 --- a/go.mod +++ b/go.mod @@ -7,7 +7,7 @@ replace github.com/hashicorp/boundary/api => ./api replace github.com/hashicorp/boundary/sdk => ./sdk require ( - github.com/armon/go-metrics v0.3.9 + github.com/armon/go-metrics v0.3.9 // indirect github.com/bufbuild/buf v0.56.0 github.com/fatih/color v1.13.0 github.com/fatih/structs v1.1.0 @@ -86,6 +86,8 @@ require ( require github.com/hashicorp/go-dbw v0.0.0-20211215222256-2ff0d37184ff // this is a branch and should be updated before merging +require github.com/prometheus/client_golang v1.12.1 + require ( github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4 // indirect github.com/AlecAivazis/survey/v2 v2.2.9 // indirect @@ -99,10 +101,12 @@ require ( github.com/alessio/shellescape v1.4.1 // indirect github.com/apex/log v1.9.0 // indirect github.com/armon/go-radix v1.0.0 // indirect + github.com/beorn7/perks v1.0.1 // indirect github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d // indirect github.com/bgentry/speakeasy v0.1.0 // indirect github.com/cenkalti/backoff/v3 v3.0.0 // indirect github.com/cenkalti/backoff/v4 v4.1.0 // indirect + github.com/cespare/xxhash/v2 v2.1.2 // indirect github.com/containerd/continuity v0.0.0-20200709052629-daa8e1ccc0bc // indirect github.com/coreos/go-oidc/v3 v3.0.0 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.0 // indirect @@ -153,6 +157,7 @@ require ( github.com/lib/pq v1.10.2 // indirect github.com/mattn/go-isatty v0.0.14 // indirect github.com/mattn/go-sqlite3 v2.0.1+incompatible // indirect + github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect github.com/mitchellh/go-testing-interface v1.0.0 // indirect @@ -166,6 +171,9 @@ require ( github.com/pierrec/lz4 v2.5.2+incompatible // indirect github.com/pkg/profile v1.6.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/prometheus/client_model v0.2.0 // indirect + github.com/prometheus/common v0.32.1 // indirect + github.com/prometheus/procfs v0.7.3 // indirect github.com/rogpeppe/go-internal v1.8.1 // indirect github.com/russross/blackfriday/v2 v2.0.1 // indirect github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect diff --git a/go.sum b/go.sum index 45f29115e..d1d920014 100644 --- a/go.sum +++ b/go.sum @@ -74,6 +74,7 @@ github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuy github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= github.com/alessio/shellescape v1.4.1 h1:V7yhSDDn8LP4lc4jS8pFkt0zCnzVJlG5JXy9BVKJUX0= github.com/alessio/shellescape v1.4.1/go.mod h1:PZAiSCk0LJaZkiCSkPv8qIobYglO3FPpyFjDCtHLS30= github.com/alexbrainman/sspi v0.0.0-20180613141037-e580b900e9f5/go.mod h1:976q2ETgjT2snVCf2ZaBnyBbVoPERGjUz+0sofzEfro= @@ -98,6 +99,7 @@ github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLj github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= +github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d h1:xDfNPAt8lFiC1UJrqV3uuy861HCTo708pDMbjHHdCas= github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d/go.mod h1:6QX/PXZ00z/TKoufEY6K/a0k6AhaJrQKdFe6OfVXsa4= @@ -115,8 +117,11 @@ github.com/cenkalti/backoff/v4 v4.0.2/go.mod h1:eEew/i+1Q6OrCDZh3WiXYv3+nJwBASZ8 github.com/cenkalti/backoff/v4 v4.1.0 h1:c8LkOFQTzuO0WBM/ae5HdGQuZPfPxp7lqBRwQRm4fSc= github.com/cenkalti/backoff/v4 v4.1.0/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE= +github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= @@ -583,14 +588,18 @@ github.com/jinzhu/now v1.1.3/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/ github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= github.com/jmoiron/sqlx v1.2.0/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks= github.com/jpillora/backoff v0.0.0-20180909062703-3050d21c67d7/go.mod h1:2iMrUgbbvHEiQClaW2NsSzMyGHqN+rDFqY705q49KG0= +github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -github.com/json-iterator/go v1.1.11 h1:uVUAXhF2To8cbw/3xN3pxj6kk7TYKs98NIrTqPlMWAQ= +github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88/go.mod h1:3w7q1U84EfirKl04SVQ/s7nPm1ZPhiXd34z40TNz36k= github.com/k0kubun/pp v2.3.0+incompatible/go.mod h1:GWse8YhT0p8pT4ir3ZgBbfZild3tgzSScAn6HmfYukg= github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0/go.mod h1:1NbS8ALrpOvjt0rHPNLyCIeMtbizbir8U//inJ+zuB8= @@ -659,6 +668,7 @@ github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsO github.com/mattn/go-sqlite3 v1.14.9/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= github.com/mattn/go-sqlite3 v2.0.1+incompatible h1:xQ15muvnzGBHpIpdrNi1DA5x0+TcBZzsIDwmw9uTHzw= github.com/mattn/go-sqlite3 v2.0.1+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= +github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b h1:j7+1HpAFS1zy5+Q4qx1fWh90gTKwiN4QCGoY9TWyyO4= github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= @@ -697,8 +707,9 @@ github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= -github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= github.com/mr-tron/base58 v1.2.0 h1:T/HDJBh4ZCPbU39/+c3rRvE0uKBQlU27+QI8LJ4t64o= @@ -707,6 +718,7 @@ github.com/mtibben/percent v0.2.1 h1:5gssi8Nqo8QU/r2pynCm+hBQHpkB/uNK7BJCFogWdzs github.com/mtibben/percent v0.2.1/go.mod h1:KG9uO+SZkUp+VkRHsCdYQV3XSZrrSpR3O9ibNBTZrns= github.com/mutecomm/go-sqlcipher/v4 v4.4.0/go.mod h1:PyN04SaWalavxRGH9E8ZftG6Ju7rsPrGmQRjrEaVpiY= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/nakagami/firebirdsql v0.0.0-20190310045651-3c02a58cfed8/go.mod h1:86wM1zFnC6/uDBfZGNwB65O+pR2OFi5q/YQaEUid1qA= github.com/neo4j/neo4j-go-driver v1.8.1-0.20200803113522-b626aa943eba/go.mod h1:ncO5VaFWh0Nrt+4KT4mOZboaczBZcLuHrG+/sUeP8gI= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= @@ -762,15 +774,28 @@ github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSg github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= github.com/prometheus/client_golang v1.4.0/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/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= +github.com/prometheus/client_golang v1.12.1 h1:ZiaPsmm9uiBeaSMRznKsCDNtPCS0T3JVDGF+06gjBzk= +github.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M= github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 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/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= +github.com/prometheus/common v0.32.1 h1:hWIdL3N2HoUx3B8j3YN9mWor0qhY/NlEKZEaXxuIRh4= +github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= 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.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= +github.com/prometheus/procfs v0.7.3 h1:4jVXhlkAyzOScmCkXBTOLRLTz8EeU+eyjrwB/EPq0VU= +github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= github.com/remyoudompheng/bigfft v0.0.0-20190728182440-6a916e37a237/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/rogpeppe/fastuuid v1.1.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= @@ -1028,6 +1053,7 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210510120150-4163338589ed/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210907225631-ff17edfbf26d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= @@ -1047,6 +1073,7 @@ golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210402161424-2e8d93401602/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 h1:RerP+noqYHUQ8CMRcPlC2nvTa4dcBIjegkuWdcUDuqg= golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= @@ -1091,6 +1118,7 @@ golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200121082415-34d275377bf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -1106,6 +1134,8 @@ golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200602225109-6fdc65e7d980/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200826173525-f9321e4c35a6/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200831180312-196b9ba8737a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -1126,6 +1156,7 @@ golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210819135213-f52c844e1c1c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -1134,6 +1165,7 @@ golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211102192858-4dd72447c267/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220204135822-1c1b9b1eba6a h1:ppl5mZgokTT8uPkmYOyEUmPTr3ypaKkg5eFOGrAmxxE= golang.org/x/sys v0.0.0-20220204135822-1c1b9b1eba6a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= diff --git a/internal/cmd/base/servers.go b/internal/cmd/base/servers.go index 738fd92c7..4a5c29770 100644 --- a/internal/cmd/base/servers.go +++ b/internal/cmd/base/servers.go @@ -17,7 +17,6 @@ import ( "syscall" "time" - "github.com/armon/go-metrics" "github.com/hashicorp/boundary/globals" "github.com/hashicorp/boundary/internal/cmd/base/logging" "github.com/hashicorp/boundary/internal/cmd/config" @@ -42,6 +41,7 @@ import ( "github.com/hashicorp/go-secure-stdlib/reloadutil" "github.com/hashicorp/go-secure-stdlib/strutil" "github.com/mitchellh/cli" + "github.com/prometheus/client_golang/prometheus" "google.golang.org/grpc/grpclog" ) @@ -83,8 +83,7 @@ type Server struct { Kms *kms.Kms SecureRandomReader io.Reader - InmemSink *metrics.InmemSink - PrometheusEnabled bool + PrometheusRegisterer prometheus.Registerer ReloadFuncsLock *sync.RWMutex ReloadFuncs map[string][]reloadutil.ReloadFunc @@ -136,13 +135,14 @@ type Server struct { func NewServer(cmd *Command) *Server { return &Server{ - Command: cmd, - InfoKeys: make([]string, 0, 20), - Info: make(map[string]string), - SecureRandomReader: rand.Reader, - ReloadFuncsLock: new(sync.RWMutex), - ReloadFuncs: make(map[string][]reloadutil.ReloadFunc), - StderrLock: new(sync.Mutex), + Command: cmd, + InfoKeys: make([]string, 0, 20), + Info: make(map[string]string), + SecureRandomReader: rand.Reader, + ReloadFuncsLock: new(sync.RWMutex), + ReloadFuncs: make(map[string][]reloadutil.ReloadFunc), + StderrLock: new(sync.Mutex), + PrometheusRegisterer: prometheus.DefaultRegisterer, } } diff --git a/internal/cmd/commands/server/server_test.go b/internal/cmd/commands/server/server_test.go index eb2d47ed7..1ff4aa0ed 100644 --- a/internal/cmd/commands/server/server_test.go +++ b/internal/cmd/commands/server/server_test.go @@ -36,6 +36,10 @@ type testServerCommandOpts struct { // Use the well-known dev mode target method id (1234567890) UseDevTarget bool + + // Whether or not to enable metric collection. If enable metrics will use + // prometheus' default registerer. + EnableMetrics bool } func testServerCommand(t *testing.T, opts testServerCommandOpts) *Command { @@ -51,6 +55,10 @@ func testServerCommand(t *testing.T, opts testServerCommandOpts) *Command { require.NoError(cmd.SetupLogging("trace", "", "", "")) require.NoError(cmd.SetupEventing(cmd.Logger, cmd.StderrLock, "test-server-command")) + if !opts.EnableMetrics { + cmd.PrometheusRegisterer = nil + } + if opts.CreateDevDatabase { kmsHcl := fmt.Sprintf(rootKmsConfig, opts.ControllerKey) parsedKmsConfig, err := config.Parse(kmsHcl) diff --git a/internal/cmd/ops/server.go b/internal/cmd/ops/server.go index 8ecfd8d8e..3ae537bdb 100644 --- a/internal/cmd/ops/server.go +++ b/internal/cmd/ops/server.go @@ -19,6 +19,7 @@ import ( "github.com/hashicorp/go-secure-stdlib/listenerutil" "github.com/mitchellh/cli" "github.com/pkg/errors" + "github.com/prometheus/client_golang/prometheus/promhttp" ) // Server is a collection of all state required to serve @@ -139,6 +140,7 @@ func createOpsHandler(lncfg *listenerutil.ListenerConfig, c *controller.Controll mux.Handle("/health", h) } + mux.Handle("/metrics", promhttp.Handler()) return cleanhttp.PrintablePathCheckHandler(mux, nil), nil } diff --git a/internal/gen/testing/protooptions/service.pb.go b/internal/gen/testing/protooptions/service.pb.go new file mode 100644 index 000000000..b02728cd8 --- /dev/null +++ b/internal/gen/testing/protooptions/service.pb.go @@ -0,0 +1,227 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.27.1 +// protoc v3.17.3 +// source: testing/options/v1/service.proto + +package protooptions + +import ( + _ "google.golang.org/genproto/googleapis/api/annotations" + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +// The request message. +type TestMethodRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` +} + +func (x *TestMethodRequest) Reset() { + *x = TestMethodRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_testing_options_v1_service_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *TestMethodRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TestMethodRequest) ProtoMessage() {} + +func (x *TestMethodRequest) ProtoReflect() protoreflect.Message { + mi := &file_testing_options_v1_service_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use TestMethodRequest.ProtoReflect.Descriptor instead. +func (*TestMethodRequest) Descriptor() ([]byte, []int) { + return file_testing_options_v1_service_proto_rawDescGZIP(), []int{0} +} + +func (x *TestMethodRequest) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +// The response message. +type TestMethodResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Item string `protobuf:"bytes,1,opt,name=item,proto3" json:"item,omitempty"` +} + +func (x *TestMethodResponse) Reset() { + *x = TestMethodResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_testing_options_v1_service_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *TestMethodResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TestMethodResponse) ProtoMessage() {} + +func (x *TestMethodResponse) ProtoReflect() protoreflect.Message { + mi := &file_testing_options_v1_service_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use TestMethodResponse.ProtoReflect.Descriptor instead. +func (*TestMethodResponse) Descriptor() ([]byte, []int) { + return file_testing_options_v1_service_proto_rawDescGZIP(), []int{1} +} + +func (x *TestMethodResponse) GetItem() string { + if x != nil { + return x.Item + } + return "" +} + +var File_testing_options_v1_service_proto protoreflect.FileDescriptor + +var file_testing_options_v1_service_proto_rawDesc = []byte{ + 0x0a, 0x20, 0x74, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x67, 0x2f, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, + 0x73, 0x2f, 0x76, 0x31, 0x2f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x2e, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x12, 0x12, 0x74, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x67, 0x2e, 0x6f, 0x70, 0x74, 0x69, + 0x6f, 0x6e, 0x73, 0x2e, 0x76, 0x31, 0x1a, 0x1c, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x61, + 0x70, 0x69, 0x2f, 0x61, 0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x23, 0x0a, 0x11, 0x54, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x68, + 0x6f, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x22, 0x28, 0x0a, 0x12, 0x54, 0x65, 0x73, + 0x74, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, + 0x12, 0x0a, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x69, + 0x74, 0x65, 0x6d, 0x32, 0x94, 0x01, 0x0a, 0x0b, 0x54, 0x65, 0x73, 0x74, 0x53, 0x65, 0x72, 0x76, + 0x69, 0x63, 0x65, 0x12, 0x84, 0x01, 0x0a, 0x0a, 0x54, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x68, + 0x6f, 0x64, 0x12, 0x25, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x67, 0x2e, 0x6f, 0x70, 0x74, + 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x68, + 0x6f, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x26, 0x2e, 0x74, 0x65, 0x73, 0x74, + 0x69, 0x6e, 0x67, 0x2e, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x54, + 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x22, 0x27, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x21, 0x12, 0x0d, 0x2f, 0x76, 0x31, 0x2f, 0x74, + 0x65, 0x73, 0x74, 0x2f, 0x7b, 0x69, 0x64, 0x7d, 0x5a, 0x0a, 0x12, 0x08, 0x2f, 0x76, 0x32, 0x2f, + 0x74, 0x65, 0x73, 0x74, 0x62, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x42, 0x4e, 0x5a, 0x4c, 0x67, 0x69, + 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, + 0x72, 0x70, 0x2f, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x61, 0x72, 0x79, 0x2f, 0x69, 0x6e, 0x74, 0x65, + 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x67, 0x65, 0x6e, 0x2f, 0x74, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x67, + 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x3b, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x33, +} + +var ( + file_testing_options_v1_service_proto_rawDescOnce sync.Once + file_testing_options_v1_service_proto_rawDescData = file_testing_options_v1_service_proto_rawDesc +) + +func file_testing_options_v1_service_proto_rawDescGZIP() []byte { + file_testing_options_v1_service_proto_rawDescOnce.Do(func() { + file_testing_options_v1_service_proto_rawDescData = protoimpl.X.CompressGZIP(file_testing_options_v1_service_proto_rawDescData) + }) + return file_testing_options_v1_service_proto_rawDescData +} + +var file_testing_options_v1_service_proto_msgTypes = make([]protoimpl.MessageInfo, 2) +var file_testing_options_v1_service_proto_goTypes = []interface{}{ + (*TestMethodRequest)(nil), // 0: testing.options.v1.TestMethodRequest + (*TestMethodResponse)(nil), // 1: testing.options.v1.TestMethodResponse +} +var file_testing_options_v1_service_proto_depIdxs = []int32{ + 0, // 0: testing.options.v1.TestService.TestMethod:input_type -> testing.options.v1.TestMethodRequest + 1, // 1: testing.options.v1.TestService.TestMethod:output_type -> testing.options.v1.TestMethodResponse + 1, // [1:2] is the sub-list for method output_type + 0, // [0:1] is the sub-list for method input_type + 0, // [0:0] is the sub-list for extension type_name + 0, // [0:0] is the sub-list for extension extendee + 0, // [0:0] is the sub-list for field type_name +} + +func init() { file_testing_options_v1_service_proto_init() } +func file_testing_options_v1_service_proto_init() { + if File_testing_options_v1_service_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_testing_options_v1_service_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*TestMethodRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_testing_options_v1_service_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*TestMethodResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_testing_options_v1_service_proto_rawDesc, + NumEnums: 0, + NumMessages: 2, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_testing_options_v1_service_proto_goTypes, + DependencyIndexes: file_testing_options_v1_service_proto_depIdxs, + MessageInfos: file_testing_options_v1_service_proto_msgTypes, + }.Build() + File_testing_options_v1_service_proto = out.File + file_testing_options_v1_service_proto_rawDesc = nil + file_testing_options_v1_service_proto_goTypes = nil + file_testing_options_v1_service_proto_depIdxs = nil +} diff --git a/internal/gen/testing/protooptions/service.pb.gw.go b/internal/gen/testing/protooptions/service.pb.gw.go new file mode 100644 index 000000000..738fc5d7f --- /dev/null +++ b/internal/gen/testing/protooptions/service.pb.gw.go @@ -0,0 +1,277 @@ +// Code generated by protoc-gen-grpc-gateway. DO NOT EDIT. +// source: testing/options/v1/service.proto + +/* +Package protooptions is a reverse proxy. + +It translates gRPC into RESTful JSON APIs. +*/ +package protooptions + +import ( + "context" + "io" + "net/http" + + "github.com/grpc-ecosystem/grpc-gateway/v2/runtime" + "github.com/grpc-ecosystem/grpc-gateway/v2/utilities" + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/grpclog" + "google.golang.org/grpc/metadata" + "google.golang.org/grpc/status" + "google.golang.org/protobuf/proto" +) + +// Suppress "imported and not used" errors +var _ codes.Code +var _ io.Reader +var _ status.Status +var _ = runtime.String +var _ = utilities.NewDoubleArray +var _ = metadata.Join + +func request_TestService_TestMethod_0(ctx context.Context, marshaler runtime.Marshaler, client TestServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq TestMethodRequest + var metadata runtime.ServerMetadata + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["id"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "id") + } + + protoReq.Id, err = runtime.String(val) + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "id", err) + } + + msg, err := client.TestMethod(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_TestService_TestMethod_0(ctx context.Context, marshaler runtime.Marshaler, server TestServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq TestMethodRequest + var metadata runtime.ServerMetadata + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["id"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "id") + } + + protoReq.Id, err = runtime.String(val) + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "id", err) + } + + msg, err := server.TestMethod(ctx, &protoReq) + return msg, metadata, err + +} + +var ( + filter_TestService_TestMethod_1 = &utilities.DoubleArray{Encoding: map[string]int{}, Base: []int(nil), Check: []int(nil)} +) + +func request_TestService_TestMethod_1(ctx context.Context, marshaler runtime.Marshaler, client TestServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq TestMethodRequest + var metadata runtime.ServerMetadata + + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_TestService_TestMethod_1); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := client.TestMethod(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_TestService_TestMethod_1(ctx context.Context, marshaler runtime.Marshaler, server TestServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq TestMethodRequest + var metadata runtime.ServerMetadata + + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_TestService_TestMethod_1); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := server.TestMethod(ctx, &protoReq) + return msg, metadata, err + +} + +// RegisterTestServiceHandlerServer registers the http handlers for service TestService to "mux". +// UnaryRPC :call TestServiceServer directly. +// StreamingRPC :currently unsupported pending https://github.com/grpc/grpc-go/issues/906. +// Note that using this registration option will cause many gRPC library features to stop working. Consider using RegisterTestServiceHandlerFromEndpoint instead. +func RegisterTestServiceHandlerServer(ctx context.Context, mux *runtime.ServeMux, server TestServiceServer) error { + + mux.Handle("GET", pattern_TestService_TestMethod_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req, "/testing.options.v1.TestService/TestMethod", runtime.WithHTTPPathPattern("/v1/test/{id}")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_TestService_TestMethod_0(rctx, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_TestService_TestMethod_0(ctx, mux, outboundMarshaler, w, req, response_TestService_TestMethod_0{resp}, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_TestService_TestMethod_1, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req, "/testing.options.v1.TestService/TestMethod", runtime.WithHTTPPathPattern("/v2/test")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_TestService_TestMethod_1(rctx, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_TestService_TestMethod_1(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + return nil +} + +// RegisterTestServiceHandlerFromEndpoint is same as RegisterTestServiceHandler but +// automatically dials to "endpoint" and closes the connection when "ctx" gets done. +func RegisterTestServiceHandlerFromEndpoint(ctx context.Context, mux *runtime.ServeMux, endpoint string, opts []grpc.DialOption) (err error) { + conn, err := grpc.Dial(endpoint, opts...) + if err != nil { + return err + } + defer func() { + if err != nil { + if cerr := conn.Close(); cerr != nil { + grpclog.Infof("Failed to close conn to %s: %v", endpoint, cerr) + } + return + } + go func() { + <-ctx.Done() + if cerr := conn.Close(); cerr != nil { + grpclog.Infof("Failed to close conn to %s: %v", endpoint, cerr) + } + }() + }() + + return RegisterTestServiceHandler(ctx, mux, conn) +} + +// RegisterTestServiceHandler registers the http handlers for service TestService to "mux". +// The handlers forward requests to the grpc endpoint over "conn". +func RegisterTestServiceHandler(ctx context.Context, mux *runtime.ServeMux, conn *grpc.ClientConn) error { + return RegisterTestServiceHandlerClient(ctx, mux, NewTestServiceClient(conn)) +} + +// RegisterTestServiceHandlerClient registers the http handlers for service TestService +// to "mux". The handlers forward requests to the grpc endpoint over the given implementation of "TestServiceClient". +// Note: the gRPC framework executes interceptors within the gRPC handler. If the passed in "TestServiceClient" +// doesn't go through the normal gRPC flow (creating a gRPC client etc.) then it will be up to the passed in +// "TestServiceClient" to call the correct interceptors. +func RegisterTestServiceHandlerClient(ctx context.Context, mux *runtime.ServeMux, client TestServiceClient) error { + + mux.Handle("GET", pattern_TestService_TestMethod_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req, "/testing.options.v1.TestService/TestMethod", runtime.WithHTTPPathPattern("/v1/test/{id}")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_TestService_TestMethod_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_TestService_TestMethod_0(ctx, mux, outboundMarshaler, w, req, response_TestService_TestMethod_0{resp}, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_TestService_TestMethod_1, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req, "/testing.options.v1.TestService/TestMethod", runtime.WithHTTPPathPattern("/v2/test")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_TestService_TestMethod_1(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_TestService_TestMethod_1(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + return nil +} + +type response_TestService_TestMethod_0 struct { + proto.Message +} + +func (m response_TestService_TestMethod_0) XXX_ResponseBody() interface{} { + response := m.Message.(*TestMethodResponse) + return response.Item +} + +var ( + pattern_TestService_TestMethod_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 1, 0, 4, 1, 5, 2}, []string{"v1", "test", "id"}, "")) + + pattern_TestService_TestMethod_1 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v2", "test"}, "")) +) + +var ( + forward_TestService_TestMethod_0 = runtime.ForwardResponseMessage + + forward_TestService_TestMethod_1 = runtime.ForwardResponseMessage +) diff --git a/internal/gen/testing/protooptions/service_grpc.pb.go b/internal/gen/testing/protooptions/service_grpc.pb.go new file mode 100644 index 000000000..bb22d31e2 --- /dev/null +++ b/internal/gen/testing/protooptions/service_grpc.pb.go @@ -0,0 +1,101 @@ +// Code generated by protoc-gen-go-grpc. DO NOT EDIT. + +package protooptions + +import ( + context "context" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" +) + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +// Requires gRPC-Go v1.32.0 or later. +const _ = grpc.SupportPackageIsVersion7 + +// TestServiceClient is the client API for TestService service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +type TestServiceClient interface { + TestMethod(ctx context.Context, in *TestMethodRequest, opts ...grpc.CallOption) (*TestMethodResponse, error) +} + +type testServiceClient struct { + cc grpc.ClientConnInterface +} + +func NewTestServiceClient(cc grpc.ClientConnInterface) TestServiceClient { + return &testServiceClient{cc} +} + +func (c *testServiceClient) TestMethod(ctx context.Context, in *TestMethodRequest, opts ...grpc.CallOption) (*TestMethodResponse, error) { + out := new(TestMethodResponse) + err := c.cc.Invoke(ctx, "/testing.options.v1.TestService/TestMethod", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// TestServiceServer is the server API for TestService service. +// All implementations must embed UnimplementedTestServiceServer +// for forward compatibility +type TestServiceServer interface { + TestMethod(context.Context, *TestMethodRequest) (*TestMethodResponse, error) + mustEmbedUnimplementedTestServiceServer() +} + +// UnimplementedTestServiceServer must be embedded to have forward compatible implementations. +type UnimplementedTestServiceServer struct { +} + +func (UnimplementedTestServiceServer) TestMethod(context.Context, *TestMethodRequest) (*TestMethodResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method TestMethod not implemented") +} +func (UnimplementedTestServiceServer) mustEmbedUnimplementedTestServiceServer() {} + +// UnsafeTestServiceServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to TestServiceServer will +// result in compilation errors. +type UnsafeTestServiceServer interface { + mustEmbedUnimplementedTestServiceServer() +} + +func RegisterTestServiceServer(s grpc.ServiceRegistrar, srv TestServiceServer) { + s.RegisterService(&TestService_ServiceDesc, srv) +} + +func _TestService_TestMethod_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(TestMethodRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(TestServiceServer).TestMethod(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/testing.options.v1.TestService/TestMethod", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(TestServiceServer).TestMethod(ctx, req.(*TestMethodRequest)) + } + return interceptor(ctx, in, info, handler) +} + +// TestService_ServiceDesc is the grpc.ServiceDesc for TestService service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var TestService_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "testing.options.v1.TestService", + HandlerType: (*TestServiceServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "TestMethod", + Handler: _TestService_TestMethod_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "testing/options/v1/service.proto", +} diff --git a/internal/proto/local/testing/options/v1/service.proto b/internal/proto/local/testing/options/v1/service.proto new file mode 100644 index 000000000..2943a4235 --- /dev/null +++ b/internal/proto/local/testing/options/v1/service.proto @@ -0,0 +1,31 @@ +syntax = "proto3"; + +package testing.options.v1; + +option go_package = "github.com/hashicorp/boundary/internal/gen/testing/protooptions;protooptions"; + +import "google/api/annotations.proto"; + +// The test service definition. This is just a service that can be used to +// test options applied to services. +service TestService { + rpc TestMethod(TestMethodRequest) returns (TestMethodResponse) { + option (google.api.http) = { + get: "/v1/test/{id}" + response_body: "item" + additional_bindings { + get: "/v2/test" + } + }; + } +} + +// The request message. +message TestMethodRequest { + string id = 1; +} + +// The response message. +message TestMethodResponse { + string item = 1; +} \ No newline at end of file diff --git a/internal/servers/controller/controller.go b/internal/servers/controller/controller.go index 6e1e60ddd..3e4bce496 100644 --- a/internal/servers/controller/controller.go +++ b/internal/servers/controller/controller.go @@ -26,6 +26,7 @@ import ( "github.com/hashicorp/boundary/internal/servers" "github.com/hashicorp/boundary/internal/servers/controller/common" "github.com/hashicorp/boundary/internal/servers/controller/handlers/health" + "github.com/hashicorp/boundary/internal/servers/controller/internal/metric" "github.com/hashicorp/boundary/internal/session" "github.com/hashicorp/boundary/internal/target" "github.com/hashicorp/boundary/internal/types/scope" @@ -88,6 +89,7 @@ type Controller struct { } func New(ctx context.Context, conf *Config) (*Controller, error) { + metric.InitializeApiCollectors(conf.PrometheusRegisterer) c := &Controller{ conf: conf, logger: conf.Logger.Named("controller"), diff --git a/internal/servers/controller/handler.go b/internal/servers/controller/handler.go index f0df293b3..7d75d3e4c 100644 --- a/internal/servers/controller/handler.go +++ b/internal/servers/controller/handler.go @@ -38,6 +38,7 @@ import ( "github.com/hashicorp/boundary/internal/servers/controller/handlers/sessions" "github.com/hashicorp/boundary/internal/servers/controller/handlers/targets" "github.com/hashicorp/boundary/internal/servers/controller/handlers/users" + "github.com/hashicorp/boundary/internal/servers/controller/internal/metric" "github.com/hashicorp/go-cleanhttp" "github.com/hashicorp/go-secure-stdlib/listenerutil" "github.com/hashicorp/go-secure-stdlib/strutil" @@ -72,8 +73,12 @@ func (c *Controller) apiHandler(props HandlerProperties) (http.Handler, error) { callbackInterceptingHandler := wrapHandlerWithCallbackInterceptor(commonWrappedHandler, c) printablePathCheckHandler := cleanhttp.PrintablePathCheckHandler(callbackInterceptingHandler, nil) eventsHandler, err := common.WrapWithEventsHandler(printablePathCheckHandler, c.conf.Eventer, c.kms, props.ListenerConfig) + if err != nil { + return nil, err + } + metricsHandler := metric.InstrumentApiHandler(eventsHandler) - return eventsHandler, err + return metricsHandler, nil } // GetHealthHandler returns a gRPC Gateway mux that is registered against the diff --git a/internal/servers/controller/internal/metric/api.go b/internal/servers/controller/internal/metric/api.go new file mode 100644 index 000000000..8952c9391 --- /dev/null +++ b/internal/servers/controller/internal/metric/api.go @@ -0,0 +1,271 @@ +// Package metric provides functions to initialize the controller specific +// collectors and hooks to measure metrics and update the relevant collectors. +package metric + +import ( + "fmt" + "net/http" + "path" + "regexp" + "strconv" + "strings" + + "github.com/hashicorp/boundary/globals" + "github.com/hashicorp/boundary/internal/gen/controller/api/services" + "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/client_golang/prometheus/promhttp" + grpcpb "google.golang.org/genproto/googleapis/api/annotations" + "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/reflect/protoreflect" + "google.golang.org/protobuf/reflect/protoregistry" + "google.golang.org/protobuf/types/descriptorpb" +) + +var ( + pathRegex map[*regexp.Regexp]string + expectedPathsToMethods map[string][]string +) + +func init() { + expectedPathsToMethods = apiPathsAndMethods() + pathRegex = make(map[*regexp.Regexp]string) + for p := range expectedPathsToMethods { + pathRegex[buildRegexFromPath(p)] = p + } +} + +// gatherPathInfo populates the provided map with the paths associated with +// the provided HttpRule proto option as well as the path information contained +// in any embedded AdditionalBindings in that HttpRule. +func gatherPathInfo(rule *grpcpb.HttpRule, paths map[string][]string) { + switch r := rule.GetPattern().(type) { + case *grpcpb.HttpRule_Get: + paths[r.Get] = append(paths[r.Get], http.MethodGet) + case *grpcpb.HttpRule_Post: + paths[r.Post] = append(paths[r.Post], http.MethodPost) + case *grpcpb.HttpRule_Patch: + paths[r.Patch] = append(paths[r.Patch], http.MethodPatch) + case *grpcpb.HttpRule_Put: + paths[r.Put] = append(paths[r.Put], http.MethodPut) + case *grpcpb.HttpRule_Delete: + paths[r.Delete] = append(paths[r.Delete], http.MethodDelete) + default: + panic("unknown rule of ") + } + for _, additional := range rule.AdditionalBindings { + gatherPathInfo(additional, paths) + } +} + +func gatherServicePathsAndMethods(fd protoreflect.FileDescriptor, paths map[string][]string) error { + for j := 0; j < fd.Services().Len(); j++ { + sd := fd.Services().Get(j) + for i := 0; i < sd.Methods().Len(); i++ { + r := sd.Methods().Get(i) + opts := r.Options().(*descriptorpb.MethodOptions) + httpRule := proto.GetExtension(opts, grpcpb.E_Http).(*grpcpb.HttpRule) + if proto.Equal(httpRule, &grpcpb.HttpRule{}) || httpRule == nil { + return fmt.Errorf("empty or no http rule found on service method %q", r.FullName()) + } + gatherPathInfo(httpRule, paths) + } + } + return nil +} + +// apiPathsAndMethods provides the pathing information for all services +// registered to the controller API. This does not include any paths that are +// defined outside of the protobufs such as anything using the OPTION method +// or any other paths registered like the dev UI passthrough path. +func apiPathsAndMethods() map[string][]string { + paths := make(map[string][]string) + protoregistry.GlobalFiles.RangeFilesByPackage( + services.File_controller_api_services_v1_user_service_proto.Package(), + func(f protoreflect.FileDescriptor) bool { + if err := gatherServicePathsAndMethods(f, paths); err != nil { + panic(err) + } + return true + }) + return paths +} + +func buildRegexFromPath(p string) *regexp.Regexp { + // a public id in boundary consists of a some prefix, an underscore and + // at least 10 alphanumerical characters like "h_1234567890". + const idRegexp = "[[:alnum:]]{1,}_[[:alnum:]]{10,}" + + // Replace any tag in the form of {id} or {auth_method_id} with the above + // regex so we can match paths to that when measuring requests. + pWithId := string(regexp.MustCompile("\\{[^\\}]*id\\}").ReplaceAll([]byte(p), []byte(idRegexp))) + + // Escape everything except for our id regexp. + var seg []string + for _, s := range strings.Split(pWithId, idRegexp) { + seg = append(seg, regexp.QuoteMeta(s)) + } + escapedPathRegex := strings.Join(seg, idRegexp) + return regexp.MustCompile(fmt.Sprintf("^%s$", escapedPathRegex)) +} + +const ( + invalidPathValue = "invalid" + + labelHttpCode = "code" + labelHttpPath = "path" + labelHttpMethod = "method" + apiSubSystem = "controller_api" +) + +var ( + // 100 bytes, 1kb, 10kb, 100kb, 1mb, 10mb, 100mb, 1gb + msgSizeBuckets = prometheus.ExponentialBuckets(100, 10, 8) + + // httpRequestLatency collects measurements of how long it takes + // the boundary system to reply to a request to the controller api + // from the time that boundary received the request. + httpRequestLatency prometheus.ObserverVec = prometheus.NewHistogramVec( + prometheus.HistogramOpts{ + Namespace: globals.MetricNamespace, + Subsystem: apiSubSystem, + Name: "http_request_duration_seconds", + Help: "Histogram of latencies for HTTP requests.", + Buckets: prometheus.DefBuckets, + }, + []string{labelHttpCode, labelHttpPath, labelHttpMethod}, + ) + + // httpRequestSize collections measurements of how large each request + // to the boundary controller api is. + httpRequestSize prometheus.ObserverVec = prometheus.NewHistogramVec( + prometheus.HistogramOpts{ + Namespace: globals.MetricNamespace, + Subsystem: apiSubSystem, + Name: "http_request_size_bytes", + Help: "Histogram of request sizes for HTTP requests.", + Buckets: msgSizeBuckets, + }, + []string{labelHttpCode, labelHttpPath, labelHttpMethod}, + ) + + // httpRequestSize collections measurements of how large each response + // from the boundary controller api is. + httpResponseSize prometheus.ObserverVec = prometheus.NewHistogramVec( + prometheus.HistogramOpts{ + Namespace: globals.MetricNamespace, + Subsystem: apiSubSystem, + Name: "http_response_size_bytes", + Help: "Histogram of response sizes for HTTP responses.", + Buckets: msgSizeBuckets, + }, + []string{labelHttpCode, labelHttpPath, labelHttpMethod}, + ) +) + +var universalStatusCodes = []int{ + http.StatusUnauthorized, + http.StatusForbidden, + http.StatusNotFound, + http.StatusMethodNotAllowed, + http.StatusBadRequest, + + http.StatusInternalServerError, + http.StatusGatewayTimeout, +} + +// Codes which are only currently used in the authentication flow +var authenticationStatusCodes = []int{ + http.StatusAccepted, + http.StatusFound, +} + +var expectedStatusCodesPerMethod = map[string][]int{ + http.MethodGet: append(universalStatusCodes, + http.StatusOK), + http.MethodPost: append(universalStatusCodes, + append(authenticationStatusCodes, http.StatusOK)...), + http.MethodPatch: append(universalStatusCodes, + http.StatusOK), + + // delete methods always returns no content instead of a StatusOK + http.MethodDelete: append(universalStatusCodes, + http.StatusNoContent), + + http.MethodOptions: { + http.StatusNoContent, + http.StatusForbidden, + http.StatusMethodNotAllowed, + }, +} + +// pathLabel maps the requested path the the label value recorded for metrics +func pathLabel(incomingPath string) string { + if incomingPath == "" || incomingPath[0] != '/' { + incomingPath = fmt.Sprintf("/%s", incomingPath) + } + incomingPath = path.Clean(incomingPath) + + for r, ep := range pathRegex { + if r.Match([]byte(incomingPath)) { + return ep + } + } + return invalidPathValue +} + +// InstrumentApiHandler provides a handler which measures api +// 1. The response size +// 2. The request size +// 3. The request latency +// and attaches status code, method, and path labels for each of these +// measurements. +func InstrumentApiHandler(wrapped http.Handler) http.Handler { + return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { + l := prometheus.Labels{ + labelHttpPath: pathLabel(req.URL.Path), + } + promhttp.InstrumentHandlerDuration( + httpRequestLatency.MustCurryWith(l), + promhttp.InstrumentHandlerRequestSize( + httpResponseSize.MustCurryWith(l), + promhttp.InstrumentHandlerResponseSize( + httpResponseSize.MustCurryWith(l), + wrapped, + ), + ), + ).ServeHTTP(rw, req) + }) +} + +// InitializeApiCollectors registers the api collectors to the default +// prometheus register and initializes them to 0 for all possible label +// combinations. +func InitializeApiCollectors(r prometheus.Registerer) { + if r == nil { + return + } + r.MustRegister(httpResponseSize, httpRequestSize, httpRequestLatency) + + for p, methods := range expectedPathsToMethods { + for _, m := range methods { + for _, sc := range expectedStatusCodesPerMethod[m] { + l := prometheus.Labels{labelHttpCode: strconv.Itoa(sc), labelHttpPath: p, labelHttpMethod: strings.ToLower(m)} + httpResponseSize.With(l) + httpRequestSize.With(l) + httpRequestLatency.With(l) + } + } + } + + // When an invalid path is found, any method is possible both we expect + // an error response. + p := invalidPathValue + for m := range expectedStatusCodesPerMethod { + for _, sc := range []int{http.StatusNotFound, http.StatusMethodNotAllowed} { + l := prometheus.Labels{labelHttpCode: strconv.Itoa(sc), labelHttpPath: p, labelHttpMethod: strings.ToLower(m)} + httpResponseSize.With(l) + httpRequestSize.With(l) + httpRequestLatency.With(l) + } + } +} diff --git a/internal/servers/controller/internal/metric/api_test.go b/internal/servers/controller/internal/metric/api_test.go new file mode 100644 index 000000000..84ca2a9a0 --- /dev/null +++ b/internal/servers/controller/internal/metric/api_test.go @@ -0,0 +1,135 @@ +package metric + +import ( + "net/http" + "testing" + + "github.com/hashicorp/boundary/internal/gen/testing/protooptions" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestBuildRegexFromPath(t *testing.T) { + cases := []struct { + path string + match []string + dont []string + }{ + { + path: "/v1/pathsomething/{id}:test-thing", + match: []string{ + "/v1/pathsomething/a4s_aKsdFh723018djsa:test-thing", + "/v1/pathsomething/h_1234567890:test-thing", + }, + dont: []string{ + "/v1/pathsomething/{id}:test-thing", + "/v1/pathsomething/{any_old_id}:test-thing", + "/v1/pathsomething:test-thing", + "/v1/pathsomething/not-an-id:test-thing", + "/v1/pathsomething/a4s_aKsdFh723018djsa:other-thing", + }, + }, + { + path: "/v1/pathsomething/{auth_method_id}:authenticate", + match: []string{ + "/v1/pathsomething/a4s_aKsdFh723018djsa:authenticate", + "/v1/pathsomething/am_1234567890:authenticate", + }, + dont: []string{ + "/v1/pathsomething/{id}:authenticate", + "/v1/pathsomething/{auth_method}:authenticate", + }, + }, + { + path: "/v1/pathsomething/{non_id_tag}:test", + match: []string{ + "/v1/pathsomething/{non_id_tag}:test", + }, + dont: []string{ + "/v1/pathsomething/h_1234567890:test", + "/v1/pathsomething/.*:test", + }, + }, + { + path: "/v1/pathsomething/a?", + match: []string{ + "/v1/pathsomething/a?", + }, + dont: []string{ + "/v1/pathsomething/", + "/v1/pathsomething/a", + "/v1/pathsomething/aa", + }, + }, + } + for _, tc := range cases { + t.Run(tc.path, func(t *testing.T) { + r := buildRegexFromPath(tc.path) + for _, m := range tc.match { + assert.True(t, r.Match([]byte(m))) + } + for _, d := range tc.dont { + assert.False(t, r.Match([]byte(d))) + } + }) + } +} + +func TestPathLabel(t *testing.T) { + cases := []struct { + in string + want string + }{ + { + in: "/v1/accounts", + want: "/v1/accounts", + }, + { + in: "v1/accounts", + want: "/v1/accounts", + }, + { + in: "/v1/hosts", + want: "/v1/hosts", + }, + { + in: "/v2/accounts", + want: "invalid", + }, + { + in: "/v1/accounts/a_1234567890", + want: "/v1/accounts/{id}", + }, + { + in: "v1/accounts/a_1234567890", + want: "/v1/accounts/{id}", + }, + { + in: "/v1/accounts/a_1234567890:set-password", + want: "/v1/accounts/{id}:set-password", + }, + { + in: "v1/accounts/a_1234567890:set-password", + want: "/v1/accounts/{id}:set-password", + }, + { + // mistype the custom action + in: "/v1/accounts/a_1234567890:set-passwords", + want: "invalid", + }, + } + for _, tc := range cases { + t.Run(tc.in, func(t *testing.T) { + assert.Equal(t, tc.want, pathLabel(tc.in)) + }) + } +} + +func TestServicePathsAndMethods(t *testing.T) { + paths := make(map[string][]string) + require.NoError(t, gatherServicePathsAndMethods(protooptions.File_testing_options_v1_service_proto, paths)) + assert.Equal(t, map[string][]string{ + "/v1/test/{id}": {http.MethodGet}, + "/v2/test": {http.MethodGet}, + }, paths) +} diff --git a/internal/servers/controller/testing.go b/internal/servers/controller/testing.go index b433e6972..9037c6ba0 100644 --- a/internal/servers/controller/testing.go +++ b/internal/servers/controller/testing.go @@ -10,8 +10,6 @@ import ( "testing" "time" - "github.com/hashicorp/boundary/internal/session" - grpc_middleware "github.com/grpc-ecosystem/go-grpc-middleware" "github.com/hashicorp/boundary/api" "github.com/hashicorp/boundary/api/authmethods" @@ -27,10 +25,12 @@ import ( "github.com/hashicorp/boundary/internal/kms" "github.com/hashicorp/boundary/internal/observability/event" "github.com/hashicorp/boundary/internal/servers" + "github.com/hashicorp/boundary/internal/session" "github.com/hashicorp/go-hclog" wrapping "github.com/hashicorp/go-kms-wrapping/v2" "github.com/hashicorp/go-secure-stdlib/base62" "github.com/hashicorp/go-secure-stdlib/strutil" + "github.com/prometheus/client_golang/prometheus" "github.com/stretchr/testify/require" "google.golang.org/grpc" "google.golang.org/grpc/test/bufconn" @@ -393,6 +393,10 @@ type TestControllerOpts struct { // The logger to use, or one will be created Logger hclog.Logger + // The registerer to use for registering all the collectors. Nil means + // no metrics are registered. + PrometheusRegisterer prometheus.Registerer + // A cluster address for overriding the advertised controller listener // (overrides address provided in config, if any) PublicClusterAddr string @@ -519,6 +523,7 @@ func TestControllerConfig(t *testing.T, ctx context.Context, tc *TestController, }) } + tc.b.PrometheusRegisterer = opts.PrometheusRegisterer if opts.Config.Controller == nil { opts.Config.Controller = new(config.Controller) } diff --git a/internal/servers/controller/testing_test.go b/internal/servers/controller/testing_test.go index a85c31903..e0f0104ed 100644 --- a/internal/servers/controller/testing_test.go +++ b/internal/servers/controller/testing_test.go @@ -10,4 +10,11 @@ func Test_TestController(t *testing.T) { tc := NewTestController(t, nil) defer tc.Shutdown() }) + t.Run("start 2 controllers", func(t *testing.T) { + t.Parallel() + tc1 := NewTestController(t, nil) + tc2 := NewTestController(t, nil) + defer tc1.Shutdown() + defer tc2.Shutdown() + }) }