feat(inputs.openstack): Allow collection without admin privileges (#15468)

Co-authored-by: Roman Plevka <rplevka@redhat.com>
This commit is contained in:
Sven Rebhan 2024-06-11 15:35:10 -04:00 committed by GitHub
parent 7dbe28d144
commit 0d12cc5c4c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 372 additions and 317 deletions

13
go.mod
View File

@ -96,7 +96,7 @@ require (
github.com/google/uuid v1.6.0 github.com/google/uuid v1.6.0
github.com/gopacket/gopacket v1.2.0 github.com/gopacket/gopacket v1.2.0
github.com/gopcua/opcua v0.5.3 github.com/gopcua/opcua v0.5.3
github.com/gophercloud/gophercloud v1.11.0 github.com/gophercloud/gophercloud/v2 v2.0.0-rc.3
github.com/gorcon/rcon v1.3.5 github.com/gorcon/rcon v1.3.5
github.com/gorilla/mux v1.8.1 github.com/gorilla/mux v1.8.1
github.com/gorilla/websocket v1.5.1 github.com/gorilla/websocket v1.5.1
@ -199,14 +199,14 @@ require (
go.opentelemetry.io/otel/sdk/metric v1.27.0 go.opentelemetry.io/otel/sdk/metric v1.27.0
go.starlark.net v0.0.0-20240520160348-046347dcd104 go.starlark.net v0.0.0-20240520160348-046347dcd104
go.step.sm/crypto v0.44.1 go.step.sm/crypto v0.44.1
golang.org/x/crypto v0.23.0 golang.org/x/crypto v0.24.0
golang.org/x/mod v0.17.0 golang.org/x/mod v0.17.0
golang.org/x/net v0.25.0 golang.org/x/net v0.25.0
golang.org/x/oauth2 v0.20.0 golang.org/x/oauth2 v0.20.0
golang.org/x/sync v0.7.0 golang.org/x/sync v0.7.0
golang.org/x/sys v0.20.0 golang.org/x/sys v0.21.0
golang.org/x/term v0.20.0 golang.org/x/term v0.21.0
golang.org/x/text v0.15.0 golang.org/x/text v0.16.0
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20211230205640-daad0b7ba671 golang.zx2c4.com/wireguard/wgctrl v0.0.0-20211230205640-daad0b7ba671
gonum.org/v1/gonum v0.15.0 gonum.org/v1/gonum v0.15.0
google.golang.org/api v0.178.0 google.golang.org/api v0.178.0
@ -344,6 +344,7 @@ require (
github.com/google/s2a-go v0.1.7 // indirect github.com/google/s2a-go v0.1.7 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect
github.com/googleapis/gax-go/v2 v2.12.4 // indirect github.com/googleapis/gax-go/v2 v2.12.4 // indirect
github.com/gophercloud/gophercloud v1.12.0 // indirect
github.com/gorilla/securecookie v1.1.2 // indirect github.com/gorilla/securecookie v1.1.2 // indirect
github.com/grafana/regexp v0.0.0-20221122212121-6b5c0a4cb7fd // indirect github.com/grafana/regexp v0.0.0-20221122212121-6b5c0a4cb7fd // indirect
github.com/grid-x/serial v0.0.0-20211107191517-583c7356b3aa // indirect github.com/grid-x/serial v0.0.0-20211107191517-583c7356b3aa // indirect
@ -483,7 +484,7 @@ require (
go.uber.org/zap v1.24.0 // indirect go.uber.org/zap v1.24.0 // indirect
golang.org/x/exp v0.0.0-20240529005216-23cca8864a10 // indirect golang.org/x/exp v0.0.0-20240529005216-23cca8864a10 // indirect
golang.org/x/time v0.5.0 // indirect golang.org/x/time v0.5.0 // indirect
golang.org/x/tools v0.21.0 // indirect golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect
golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect
golang.zx2c4.com/wireguard v0.0.0-20211209221555-9c9e7e272434 // indirect golang.zx2c4.com/wireguard v0.0.0-20211209221555-9c9e7e272434 // indirect
google.golang.org/genproto v0.0.0-20240401170217-c3f982113cda // indirect google.golang.org/genproto v0.0.0-20240401170217-c3f982113cda // indirect

26
go.sum
View File

@ -1416,8 +1416,10 @@ github.com/gopacket/gopacket v1.2.0 h1:eXbzFad7f73P1n2EJHQlsKuvIMJjVXK5tXoSca78I
github.com/gopacket/gopacket v1.2.0/go.mod h1:BrAKEy5EOGQ76LSqh7DMAr7z0NNPdczWm2GxCG7+I8M= github.com/gopacket/gopacket v1.2.0/go.mod h1:BrAKEy5EOGQ76LSqh7DMAr7z0NNPdczWm2GxCG7+I8M=
github.com/gopcua/opcua v0.5.3 h1:K5QQhjK9KQxQW8doHL/Cd8oljUeXWnJJsNgP7mOGIhw= github.com/gopcua/opcua v0.5.3 h1:K5QQhjK9KQxQW8doHL/Cd8oljUeXWnJJsNgP7mOGIhw=
github.com/gopcua/opcua v0.5.3/go.mod h1:nrVl4/Rs3SDQRhNQ50EbAiI5JSpDrTG6Frx3s4HLnw4= github.com/gopcua/opcua v0.5.3/go.mod h1:nrVl4/Rs3SDQRhNQ50EbAiI5JSpDrTG6Frx3s4HLnw4=
github.com/gophercloud/gophercloud v1.11.0 h1:ls0O747DIq1D8SUHc7r2vI8BFbMLeLFuENaAIfEx7OM= github.com/gophercloud/gophercloud v1.12.0 h1:Jrz16vPAL93l80q16fp8NplrTCp93y7rZh2P3Q4Yq7g=
github.com/gophercloud/gophercloud v1.11.0/go.mod h1:aAVqcocTSXh2vYFZ1JTvx4EQmfgzxRcNupUfxZbBNDM= github.com/gophercloud/gophercloud v1.12.0/go.mod h1:aAVqcocTSXh2vYFZ1JTvx4EQmfgzxRcNupUfxZbBNDM=
github.com/gophercloud/gophercloud/v2 v2.0.0-rc.3 h1:Gc1oFIROarJoIcvg63BsXrK6G+DPwYVs5gKlmvV2UC8=
github.com/gophercloud/gophercloud/v2 v2.0.0-rc.3/go.mod h1:ZKbcGNjxFTSaP5wlvtLDdsppllD/UGGvXBPqcjeqA8Y=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gopherjs/gopherjs v0.0.0-20200217142428-fce0ec30dd00 h1:l5lAOZEym3oK3SQ2HBHWsJUfbNBiTXJDeW2QDxw9AQ0= github.com/gopherjs/gopherjs v0.0.0-20200217142428-fce0ec30dd00 h1:l5lAOZEym3oK3SQ2HBHWsJUfbNBiTXJDeW2QDxw9AQ0=
github.com/gopherjs/gopherjs v0.0.0-20200217142428-fce0ec30dd00/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gopherjs/gopherjs v0.0.0-20200217142428-fce0ec30dd00/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
@ -2420,8 +2422,8 @@ golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf
golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg= golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg=
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
golang.org/x/crypto v0.20.0/go.mod h1:Xwo95rrVNIoSMx9wa1JroENMToLWn3RNVrTBpLHgZPQ= golang.org/x/crypto v0.20.0/go.mod h1:Xwo95rrVNIoSMx9wa1JroENMToLWn3RNVrTBpLHgZPQ=
golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI= golang.org/x/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI=
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM=
golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
@ -2762,8 +2764,8 @@ golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws=
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
@ -2780,8 +2782,8 @@ golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U=
golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY= golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY=
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
golang.org/x/term v0.20.0 h1:VnkxpohqXaOBYJtBmEppKUG6mXpi+4O6purfc2+sMhw= golang.org/x/term v0.21.0 h1:WVXCp+/EBEHOj53Rvu+7KiT/iElMrO8ACK16SMZ3jaA=
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= golang.org/x/term v0.21.0/go.mod h1:ooXLefLobQVslOqselCNF4SxFAaoS6KujMbsGzSDmX0=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@ -2801,8 +2803,8 @@ golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk= golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
@ -2887,8 +2889,8 @@ golang.org/x/tools v0.2.0/go.mod h1:y4OqIKeOV/fWJetJ8bXPU1sEVniLMIyDAZWeHdV+NTA=
golang.org/x/tools v0.3.0/go.mod h1:/rWhSS2+zyEVwoJf8YAX6L2f0ntZ7Kn/mGgAWcipA5k= golang.org/x/tools v0.3.0/go.mod h1:/rWhSS2+zyEVwoJf8YAX6L2f0ntZ7Kn/mGgAWcipA5k=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s= golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s=
golang.org/x/tools v0.21.0 h1:qc0xYgIbsSDt9EyWz05J5wfa7LOVW0YTLOXrqdLAWIw= golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg=
golang.org/x/tools v0.21.0/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=

View File

@ -83,13 +83,15 @@ See the [CONFIGURATION.md][CONFIGURATION.md] for more details.
password = "password" password = "password"
## Available services are: ## Available services are:
## "agents", "aggregates", "cinder_services", "flavors", "hypervisors", "networks", ## "agents", "aggregates", "cinder_services", "flavors", "hypervisors",
## "nova_services", "ports", "projects", "servers", "services", "stacks", "storage_pools", ## "networks", "nova_services", "ports", "projects", "servers",
## "subnets", "volumes" ## "serverdiagnostics", "services", "stacks", "storage_pools", "subnets",
## "volumes"
# enabled_services = ["services", "projects", "hypervisors", "flavors", "networks", "volumes"] # enabled_services = ["services", "projects", "hypervisors", "flavors", "networks", "volumes"]
## Collect Server Diagnostics ## Query all instances of all tenants for the volumes and server services
# server_diagnotics = false ## NOTE: Usually this is only permitted for administrators!
# query_all_tenants = true
## output secrets (such as adminPass(for server) and UserID(for volume)). ## output secrets (such as adminPass(for server) and UserID(for volume)).
# output_secrets = false # output_secrets = false

View File

@ -17,31 +17,31 @@ import (
"fmt" "fmt"
"net/http" "net/http"
"regexp" "regexp"
"slices"
"sort" "sort"
"strconv" "strconv"
"strings" "strings"
"time" "time"
"github.com/gophercloud/gophercloud" "github.com/gophercloud/gophercloud/v2"
"github.com/gophercloud/gophercloud/openstack" "github.com/gophercloud/gophercloud/v2/openstack"
"github.com/gophercloud/gophercloud/openstack/blockstorage/extensions/schedulerstats" "github.com/gophercloud/gophercloud/v2/openstack/blockstorage/v3/schedulerstats"
cinder_services "github.com/gophercloud/gophercloud/openstack/blockstorage/extensions/services" cinder_services "github.com/gophercloud/gophercloud/v2/openstack/blockstorage/v3/services"
"github.com/gophercloud/gophercloud/openstack/blockstorage/extensions/volumetenants" "github.com/gophercloud/gophercloud/v2/openstack/blockstorage/v3/volumes"
"github.com/gophercloud/gophercloud/openstack/blockstorage/v3/volumes" "github.com/gophercloud/gophercloud/v2/openstack/compute/v2/aggregates"
"github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/aggregates" "github.com/gophercloud/gophercloud/v2/openstack/compute/v2/diagnostics"
"github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/diagnostics" "github.com/gophercloud/gophercloud/v2/openstack/compute/v2/flavors"
"github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/hypervisors" "github.com/gophercloud/gophercloud/v2/openstack/compute/v2/hypervisors"
nova_services "github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/services" "github.com/gophercloud/gophercloud/v2/openstack/compute/v2/servers"
"github.com/gophercloud/gophercloud/openstack/compute/v2/flavors" nova_services "github.com/gophercloud/gophercloud/v2/openstack/compute/v2/services"
"github.com/gophercloud/gophercloud/openstack/compute/v2/servers" "github.com/gophercloud/gophercloud/v2/openstack/identity/v3/projects"
"github.com/gophercloud/gophercloud/openstack/identity/v3/projects" "github.com/gophercloud/gophercloud/v2/openstack/identity/v3/services"
"github.com/gophercloud/gophercloud/openstack/identity/v3/services" "github.com/gophercloud/gophercloud/v2/openstack/identity/v3/tokens"
"github.com/gophercloud/gophercloud/openstack/identity/v3/tokens" "github.com/gophercloud/gophercloud/v2/openstack/networking/v2/extensions/agents"
"github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/agents" "github.com/gophercloud/gophercloud/v2/openstack/networking/v2/networks"
"github.com/gophercloud/gophercloud/openstack/networking/v2/networks" "github.com/gophercloud/gophercloud/v2/openstack/networking/v2/ports"
"github.com/gophercloud/gophercloud/openstack/networking/v2/ports" "github.com/gophercloud/gophercloud/v2/openstack/networking/v2/subnets"
"github.com/gophercloud/gophercloud/openstack/networking/v2/subnets" "github.com/gophercloud/gophercloud/v2/openstack/orchestration/v1/stacks"
"github.com/gophercloud/gophercloud/openstack/orchestration/v1/stacks"
"github.com/influxdata/telegraf" "github.com/influxdata/telegraf"
httpconfig "github.com/influxdata/telegraf/plugins/common/http" httpconfig "github.com/influxdata/telegraf/plugins/common/http"
@ -57,12 +57,6 @@ var (
typeStorage = regexp.MustCompile(`_errors$|_read$|_read_req$|_write$|_write_req$`) typeStorage = regexp.MustCompile(`_errors$|_read$|_read_req$|_write$|_write_req$`)
) )
// volume is a structure used to unmarshal raw JSON from the API into.
type volume struct {
volumes.Volume
volumetenants.VolumeTenantExt
}
// OpenStack is the main structure associated with a collection instance. // OpenStack is the main structure associated with a collection instance.
type OpenStack struct { type OpenStack struct {
// Configuration variables // Configuration variables
@ -72,12 +66,13 @@ type OpenStack struct {
Username string `toml:"username"` Username string `toml:"username"`
Password string `toml:"password"` Password string `toml:"password"`
EnabledServices []string `toml:"enabled_services"` EnabledServices []string `toml:"enabled_services"`
ServerDiagnotics bool `toml:"server_diagnotics"` ServerDiagnotics bool `toml:"server_diagnotics" deprecated:"1.32.0;1.40.0;add 'serverdiagnostics' to 'enabled_services' instead"`
OutputSecrets bool `toml:"output_secrets"` OutputSecrets bool `toml:"output_secrets"`
TagPrefix string `toml:"tag_prefix"` TagPrefix string `toml:"tag_prefix"`
TagValue string `toml:"tag_value"` TagValue string `toml:"tag_value"`
HumanReadableTS bool `toml:"human_readable_timestamps"` HumanReadableTS bool `toml:"human_readable_timestamps"`
MeasureRequest bool `toml:"measure_openstack_requests"` MeasureRequest bool `toml:"measure_openstack_requests"`
AllTenants bool `toml:"query_all_tenants"`
Log telegraf.Logger `toml:"-"` Log telegraf.Logger `toml:"-"`
httpconfig.HTTPClientConfig httpconfig.HTTPClientConfig
@ -92,7 +87,6 @@ type OpenStack struct {
// Locally cached resources // Locally cached resources
openstackFlavors map[string]flavors.Flavor openstackFlavors map[string]flavors.Flavor
openstackHypervisors []hypervisors.Hypervisor
openstackProjects map[string]projects.Project openstackProjects map[string]projects.Project
openstackServices map[string]services.Service openstackServices map[string]services.Service
@ -124,26 +118,29 @@ func (o *OpenStack) Init() error {
return errors.New("tag_value option can not be empty string") return errors.New("tag_value option can not be empty string")
} }
// For backward compatibility
if o.ServerDiagnotics && !slices.Contains(o.EnabledServices, "serverdiagnostics") {
o.EnabledServices = append(o.EnabledServices, "serverdiagnostics")
}
// Check the enabled services // Check the enabled services
o.services = make(map[string]bool, len(o.EnabledServices)) o.services = make(map[string]bool, len(o.EnabledServices))
for _, service := range o.EnabledServices { for _, service := range o.EnabledServices {
switch service { switch service {
case "agents", "aggregates", "cinder_services", "flavors", "hypervisors", case "agents", "aggregates", "cinder_services", "flavors", "hypervisors",
"networks", "nova_services", "ports", "projects", "servers", "services", "networks", "nova_services", "ports", "projects", "servers",
"stacks", "storage_pools", "subnets", "volumes": "serverdiagnostics", "services", "stacks", "storage_pools",
"subnets", "volumes":
o.services[service] = true o.services[service] = true
default: default:
return fmt.Errorf("invalid service %q", service) return fmt.Errorf("invalid service %q", service)
} }
} }
return nil return nil
} }
func (o *OpenStack) Start(_ telegraf.Accumulator) error { func (o *OpenStack) Start(telegraf.Accumulator) error {
o.openstackFlavors = map[string]flavors.Flavor{}
o.openstackHypervisors = []hypervisors.Hypervisor{}
o.openstackProjects = map[string]projects.Project{}
// Authenticate against Keystone and get a token provider // Authenticate against Keystone and get a token provider
provider, err := openstack.NewClient(o.IdentityEndpoint) provider, err := openstack.NewClient(o.IdentityEndpoint)
if err != nil { if err != nil {
@ -168,7 +165,7 @@ func (o *OpenStack) Start(_ telegraf.Accumulator) error {
Password: o.Password, Password: o.Password,
AllowReauth: true, AllowReauth: true,
} }
if err := openstack.Authenticate(provider, authOption); err != nil { if err := openstack.Authenticate(ctx, provider, authOption); err != nil {
return fmt.Errorf("unable to authenticate OpenStack user: %w", err) return fmt.Errorf("unable to authenticate OpenStack user: %w", err)
} }
@ -194,7 +191,7 @@ func (o *OpenStack) Start(_ telegraf.Accumulator) error {
o.Log.Warnf("failed to get services from v3 authentication: %v; falling back to services API", err) o.Log.Warnf("failed to get services from v3 authentication: %v; falling back to services API", err)
} }
// Determine the services available at the endpoint // Determine the services available at the endpoint
if err := o.availableServices(); err != nil { if err := o.availableServices(ctx); err != nil {
return fmt.Errorf("failed to get resource openstack services: %w", err) return fmt.Errorf("failed to get resource openstack services: %w", err)
} }
} }
@ -235,6 +232,37 @@ func (o *OpenStack) Start(_ telegraf.Accumulator) error {
} }
} }
// Prepare cross-dependency information
o.openstackFlavors = map[string]flavors.Flavor{}
o.openstackProjects = map[string]projects.Project{}
if slices.Contains(o.EnabledServices, "servers") {
// We need the flavors to output machine details for servers
page, err := flavors.ListDetail(o.compute, nil).AllPages(ctx)
if err != nil {
return fmt.Errorf("unable to list flavors: %w", err)
}
extractedflavors, err := flavors.ExtractFlavors(page)
if err != nil {
return fmt.Errorf("unable to extract flavors: %w", err)
}
for _, flavor := range extractedflavors {
o.openstackFlavors[flavor.ID] = flavor
}
// We need the project to deliver a human readable name in servers
page, err = projects.ListAvailable(o.identity).AllPages(ctx)
if err != nil {
return fmt.Errorf("unable to list projects: %w", err)
}
extractedProjects, err := projects.ExtractProjects(page)
if err != nil {
return fmt.Errorf("unable to extract projects: %w", err)
}
for _, project := range extractedProjects {
o.openstackProjects[project.ID] = project
}
}
return nil return nil
} }
@ -247,27 +275,9 @@ func (o *OpenStack) Stop() {
// Gather gathers resources from the OpenStack API and accumulates metrics. This // Gather gathers resources from the OpenStack API and accumulates metrics. This
// implements the Input interface. // implements the Input interface.
func (o *OpenStack) Gather(acc telegraf.Accumulator) error { func (o *OpenStack) Gather(acc telegraf.Accumulator) error {
ctx := context.Background()
callDuration := make(map[string]interface{}, len(o.services)) callDuration := make(map[string]interface{}, len(o.services))
// Prepare the shared resources
if o.services["hypervisors"] || o.services["servers"] || o.ServerDiagnotics {
start := time.Now()
if err := o.gatherHypervisors(); err != nil {
acc.AddError(fmt.Errorf("failed to get resource \"hypervisors\": %w", err))
}
if o.services["hypervisors"] {
callDuration["hypervisors"] = time.Since(start).Nanoseconds()
}
}
// Servers were already queried, so use this information
if o.services["servers"] || o.ServerDiagnotics {
start := time.Now()
if err := o.gatherServers(acc); err != nil {
return fmt.Errorf("failed to get resource \"servers\": %w", err)
}
callDuration["servers"] = time.Since(start).Nanoseconds()
}
for service := range o.services { for service := range o.services {
var err error var err error
@ -275,38 +285,47 @@ func (o *OpenStack) Gather(acc telegraf.Accumulator) error {
switch service { switch service {
case "services": case "services":
// As Services are already gathered in Init(), using this to accumulate them. // As Services are already gathered in Init(), using this to accumulate them.
o.accumulateServices(acc) for _, service := range o.openstackServices {
tags := map[string]string{
"name": service.Type,
}
fields := map[string]interface{}{
"service_id": service.ID,
"service_enabled": service.Enabled,
}
acc.AddFields("openstack_service", fields, tags)
}
continue continue
case "projects": case "projects":
err = o.gatherProjects(acc) err = o.gatherProjects(ctx, acc)
case "hypervisors": case "hypervisors":
// Gathered as part of the shared resource err = o.gatherHypervisors(ctx, acc)
o.accumulateHypervisor(acc)
continue
case "flavors": case "flavors":
err = o.gatherFlavors(acc) err = o.gatherFlavors(ctx, acc)
case "servers":
// Gathered as part of the shared resource
case "volumes": case "volumes":
err = o.gatherVolumes(acc) err = o.gatherVolumes(ctx, acc)
case "storage_pools": case "storage_pools":
err = o.gatherStoragePools(acc) err = o.gatherStoragePools(ctx, acc)
case "subnets": case "subnets":
err = o.gatherSubnets(acc) err = o.gatherSubnets(ctx, acc)
case "ports": case "ports":
err = o.gatherPorts(acc) err = o.gatherPorts(ctx, acc)
case "networks": case "networks":
err = o.gatherNetworks(acc) err = o.gatherNetworks(ctx, acc)
case "aggregates": case "aggregates":
err = o.gatherAggregates(acc) err = o.gatherAggregates(ctx, acc)
case "nova_services": case "nova_services":
err = o.gatherNovaServices(acc) err = o.gatherNovaServices(ctx, acc)
case "cinder_services": case "cinder_services":
err = o.gatherCinderServices(acc) err = o.gatherCinderServices(ctx, acc)
case "agents": case "agents":
err = o.gatherAgents(acc) err = o.gatherAgents(ctx, acc)
case "servers":
err = o.gatherServers(ctx, acc)
case "serverdiagnostics":
err = o.gatherServerDiagnostics(ctx, acc)
case "stacks": case "stacks":
err = o.gatherStacks(acc) err = o.gatherStacks(ctx, acc)
default: default:
return fmt.Errorf("invalid service %q", service) return fmt.Errorf("invalid service %q", service)
} }
@ -357,8 +376,8 @@ func (o *OpenStack) availableServicesFromAuth(provider *gophercloud.ProviderClie
} }
// availableServices collects the available endpoint services via API // availableServices collects the available endpoint services via API
func (o *OpenStack) availableServices() error { func (o *OpenStack) availableServices(ctx context.Context) error {
page, err := services.List(o.identity, nil).AllPages() page, err := services.List(o.identity, nil).AllPages(ctx)
if err != nil { if err != nil {
return fmt.Errorf("unable to list services: %w", err) return fmt.Errorf("unable to list services: %w", err)
} }
@ -376,8 +395,8 @@ func (o *OpenStack) availableServices() error {
} }
// gatherStacks collects and accumulates stacks data from the OpenStack API. // gatherStacks collects and accumulates stacks data from the OpenStack API.
func (o *OpenStack) gatherStacks(acc telegraf.Accumulator) error { func (o *OpenStack) gatherStacks(ctx context.Context, acc telegraf.Accumulator) error {
page, err := stacks.List(o.stack, &stacks.ListOpts{}).AllPages() page, err := stacks.List(o.stack, nil).AllPages(ctx)
if err != nil { if err != nil {
return fmt.Errorf("unable to list stacks: %w", err) return fmt.Errorf("unable to list stacks: %w", err)
} }
@ -407,8 +426,8 @@ func (o *OpenStack) gatherStacks(acc telegraf.Accumulator) error {
} }
// gatherNovaServices collects and accumulates nova_services data from the OpenStack API. // gatherNovaServices collects and accumulates nova_services data from the OpenStack API.
func (o *OpenStack) gatherNovaServices(acc telegraf.Accumulator) error { func (o *OpenStack) gatherNovaServices(ctx context.Context, acc telegraf.Accumulator) error {
page, err := nova_services.List(o.compute, &nova_services.ListOpts{}).AllPages() page, err := nova_services.List(o.compute, nil).AllPages(ctx)
if err != nil { if err != nil {
return fmt.Errorf("unable to list nova_services: %w", err) return fmt.Errorf("unable to list nova_services: %w", err)
} }
@ -437,8 +456,8 @@ func (o *OpenStack) gatherNovaServices(acc telegraf.Accumulator) error {
} }
// gatherCinderServices collects and accumulates cinder_services data from the OpenStack API. // gatherCinderServices collects and accumulates cinder_services data from the OpenStack API.
func (o *OpenStack) gatherCinderServices(acc telegraf.Accumulator) error { func (o *OpenStack) gatherCinderServices(ctx context.Context, acc telegraf.Accumulator) error {
page, err := cinder_services.List(o.volume, &cinder_services.ListOpts{}).AllPages() page, err := cinder_services.List(o.volume, nil).AllPages(ctx)
if err != nil { if err != nil {
return fmt.Errorf("unable to list cinder_services: %w", err) return fmt.Errorf("unable to list cinder_services: %w", err)
} }
@ -469,8 +488,8 @@ func (o *OpenStack) gatherCinderServices(acc telegraf.Accumulator) error {
} }
// gatherSubnets collects and accumulates subnets data from the OpenStack API. // gatherSubnets collects and accumulates subnets data from the OpenStack API.
func (o *OpenStack) gatherSubnets(acc telegraf.Accumulator) error { func (o *OpenStack) gatherSubnets(ctx context.Context, acc telegraf.Accumulator) error {
page, err := subnets.List(o.network, &subnets.ListOpts{}).AllPages() page, err := subnets.List(o.network, nil).AllPages(ctx)
if err != nil { if err != nil {
return fmt.Errorf("unable to list subnets: %w", err) return fmt.Errorf("unable to list subnets: %w", err)
} }
@ -511,8 +530,8 @@ func (o *OpenStack) gatherSubnets(acc telegraf.Accumulator) error {
} }
// gatherPorts collects and accumulates ports data from the OpenStack API. // gatherPorts collects and accumulates ports data from the OpenStack API.
func (o *OpenStack) gatherPorts(acc telegraf.Accumulator) error { func (o *OpenStack) gatherPorts(ctx context.Context, acc telegraf.Accumulator) error {
page, err := ports.List(o.network, &ports.ListOpts{}).AllPages() page, err := ports.List(o.network, nil).AllPages(ctx)
if err != nil { if err != nil {
return fmt.Errorf("unable to list ports: %w", err) return fmt.Errorf("unable to list ports: %w", err)
} }
@ -556,8 +575,8 @@ func (o *OpenStack) gatherPorts(acc telegraf.Accumulator) error {
} }
// gatherNetworks collects and accumulates networks data from the OpenStack API. // gatherNetworks collects and accumulates networks data from the OpenStack API.
func (o *OpenStack) gatherNetworks(acc telegraf.Accumulator) error { func (o *OpenStack) gatherNetworks(ctx context.Context, acc telegraf.Accumulator) error {
page, err := networks.List(o.network, &networks.ListOpts{}).AllPages() page, err := networks.List(o.network, nil).AllPages(ctx)
if err != nil { if err != nil {
return fmt.Errorf("unable to list networks: %w", err) return fmt.Errorf("unable to list networks: %w", err)
} }
@ -598,8 +617,8 @@ func (o *OpenStack) gatherNetworks(acc telegraf.Accumulator) error {
} }
// gatherAgents collects and accumulates agents data from the OpenStack API. // gatherAgents collects and accumulates agents data from the OpenStack API.
func (o *OpenStack) gatherAgents(acc telegraf.Accumulator) error { func (o *OpenStack) gatherAgents(ctx context.Context, acc telegraf.Accumulator) error {
page, err := agents.List(o.network, &agents.ListOpts{}).AllPages() page, err := agents.List(o.network, nil).AllPages(ctx)
if err != nil { if err != nil {
return fmt.Errorf("unable to list neutron agents: %w", err) return fmt.Errorf("unable to list neutron agents: %w", err)
} }
@ -631,8 +650,8 @@ func (o *OpenStack) gatherAgents(acc telegraf.Accumulator) error {
} }
// gatherAggregates collects and accumulates aggregates data from the OpenStack API. // gatherAggregates collects and accumulates aggregates data from the OpenStack API.
func (o *OpenStack) gatherAggregates(acc telegraf.Accumulator) error { func (o *OpenStack) gatherAggregates(ctx context.Context, acc telegraf.Accumulator) error {
page, err := aggregates.List(o.compute).AllPages() page, err := aggregates.List(o.compute).AllPages(ctx)
if err != nil { if err != nil {
return fmt.Errorf("unable to list aggregates: %w", err) return fmt.Errorf("unable to list aggregates: %w", err)
} }
@ -666,8 +685,8 @@ func (o *OpenStack) gatherAggregates(acc telegraf.Accumulator) error {
} }
// gatherProjects collects and accumulates projects data from the OpenStack API. // gatherProjects collects and accumulates projects data from the OpenStack API.
func (o *OpenStack) gatherProjects(acc telegraf.Accumulator) error { func (o *OpenStack) gatherProjects(ctx context.Context, acc telegraf.Accumulator) error {
page, err := projects.List(o.identity, &projects.ListOpts{}).AllPages() page, err := projects.List(o.identity, nil).AllPages(ctx)
if err != nil { if err != nil {
return fmt.Errorf("unable to list projects: %w", err) return fmt.Errorf("unable to list projects: %w", err)
} }
@ -698,8 +717,8 @@ func (o *OpenStack) gatherProjects(acc telegraf.Accumulator) error {
} }
// gatherHypervisors collects and accumulates hypervisors data from the OpenStack API. // gatherHypervisors collects and accumulates hypervisors data from the OpenStack API.
func (o *OpenStack) gatherHypervisors() error { func (o *OpenStack) gatherHypervisors(ctx context.Context, acc telegraf.Accumulator) error {
page, err := hypervisors.List(o.compute, hypervisors.ListOpts{}).AllPages() page, err := hypervisors.List(o.compute, nil).AllPages(ctx)
if err != nil { if err != nil {
return fmt.Errorf("unable to list hypervisors: %w", err) return fmt.Errorf("unable to list hypervisors: %w", err)
} }
@ -707,14 +726,51 @@ func (o *OpenStack) gatherHypervisors() error {
if err != nil { if err != nil {
return fmt.Errorf("unable to extract hypervisors: %w", err) return fmt.Errorf("unable to extract hypervisors: %w", err)
} }
o.openstackHypervisors = extractedHypervisors
for _, hypervisor := range extractedHypervisors {
tags := map[string]string{
"cpu_vendor": hypervisor.CPUInfo.Vendor,
"cpu_arch": hypervisor.CPUInfo.Arch,
"cpu_model": hypervisor.CPUInfo.Model,
"status": strings.ToLower(hypervisor.Status),
"state": hypervisor.State,
"hypervisor_hostname": hypervisor.HypervisorHostname,
"hypervisor_type": hypervisor.HypervisorType,
"hypervisor_version": strconv.Itoa(hypervisor.HypervisorVersion),
"service_host": hypervisor.Service.Host,
"service_id": hypervisor.Service.ID,
"service_disabled_reason": hypervisor.Service.DisabledReason,
}
for _, cpuFeature := range hypervisor.CPUInfo.Features {
tags["cpu_feature_"+cpuFeature] = "true"
}
fields := map[string]interface{}{
"id": hypervisor.ID,
"host_ip": hypervisor.HostIP,
"cpu_topology_sockets": hypervisor.CPUInfo.Topology.Sockets,
"cpu_topology_cores": hypervisor.CPUInfo.Topology.Cores,
"cpu_topology_threads": hypervisor.CPUInfo.Topology.Threads,
"current_workload": hypervisor.CurrentWorkload,
"disk_available_least": hypervisor.DiskAvailableLeast,
"free_disk_gb": hypervisor.FreeDiskGB,
"free_ram_mb": hypervisor.FreeRamMB,
"local_gb": hypervisor.LocalGB,
"local_gb_used": hypervisor.LocalGBUsed,
"memory_mb": hypervisor.MemoryMB,
"memory_mb_used": hypervisor.MemoryMBUsed,
"running_vms": hypervisor.RunningVMs,
"vcpus": hypervisor.VCPUs,
"vcpus_used": hypervisor.VCPUsUsed,
}
acc.AddFields("openstack_hypervisor", fields, tags)
}
return nil return nil
} }
// gatherFlavors collects and accumulates flavors data from the OpenStack API. // gatherFlavors collects and accumulates flavors data from the OpenStack API.
func (o *OpenStack) gatherFlavors(acc telegraf.Accumulator) error { func (o *OpenStack) gatherFlavors(ctx context.Context, acc telegraf.Accumulator) error {
page, err := flavors.ListDetail(o.compute, &flavors.ListOpts{}).AllPages() page, err := flavors.ListDetail(o.compute, nil).AllPages(ctx)
if err != nil { if err != nil {
return fmt.Errorf("unable to list flavors: %w", err) return fmt.Errorf("unable to list flavors: %w", err)
} }
@ -743,16 +799,16 @@ func (o *OpenStack) gatherFlavors(acc telegraf.Accumulator) error {
} }
// gatherVolumes collects and accumulates volumes data from the OpenStack API. // gatherVolumes collects and accumulates volumes data from the OpenStack API.
func (o *OpenStack) gatherVolumes(acc telegraf.Accumulator) error { func (o *OpenStack) gatherVolumes(ctx context.Context, acc telegraf.Accumulator) error {
page, err := volumes.List(o.volume, &volumes.ListOpts{AllTenants: true}).AllPages() page, err := volumes.List(o.volume, &volumes.ListOpts{AllTenants: o.AllTenants}).AllPages(ctx)
if err != nil { if err != nil {
return fmt.Errorf("unable to list volumes: %w", err) return fmt.Errorf("unable to list volumes: %w", err)
} }
v := []volume{} extractedVolumes, err := volumes.ExtractVolumes(page)
if err := volumes.ExtractVolumesInto(page, &v); err != nil { if err != nil {
return fmt.Errorf("unable to extract volumes: %w", err) return fmt.Errorf("unable to extract volumes: %w", err)
} }
for _, volume := range v { for _, volume := range extractedVolumes {
tags := map[string]string{ tags := map[string]string{
"status": strings.ToLower(volume.Status), "status": strings.ToLower(volume.Status),
"availability_zone": volume.AvailabilityZone, "availability_zone": volume.AvailabilityZone,
@ -798,8 +854,8 @@ func (o *OpenStack) gatherVolumes(acc telegraf.Accumulator) error {
} }
// gatherStoragePools collects and accumulates storage pools data from the OpenStack API. // gatherStoragePools collects and accumulates storage pools data from the OpenStack API.
func (o *OpenStack) gatherStoragePools(acc telegraf.Accumulator) error { func (o *OpenStack) gatherStoragePools(ctx context.Context, acc telegraf.Accumulator) error {
results, err := schedulerstats.List(o.volume, &schedulerstats.ListOpts{Detail: true}).AllPages() results, err := schedulerstats.List(o.volume, &schedulerstats.ListOpts{Detail: true}).AllPages(ctx)
if err != nil { if err != nil {
return fmt.Errorf("unable to list storage pools: %w", err) return fmt.Errorf("unable to list storage pools: %w", err)
} }
@ -824,10 +880,8 @@ func (o *OpenStack) gatherStoragePools(acc telegraf.Accumulator) error {
return nil return nil
} }
// gatherServers collects servers from the OpenStack API. func (o *OpenStack) gatherServers(ctx context.Context, acc telegraf.Accumulator) error {
func (o *OpenStack) gatherServers(acc telegraf.Accumulator) error { page, err := servers.List(o.compute, &servers.ListOpts{AllTenants: o.AllTenants}).AllPages(ctx)
for _, hypervisor := range o.openstackHypervisors {
page, err := servers.List(o.compute, &servers.ListOpts{AllTenants: true, Host: hypervisor.HypervisorHostname}).AllPages()
if err != nil { if err != nil {
return fmt.Errorf("unable to list servers: %w", err) return fmt.Errorf("unable to list servers: %w", err)
} }
@ -835,83 +889,41 @@ func (o *OpenStack) gatherServers(acc telegraf.Accumulator) error {
if err != nil { if err != nil {
return fmt.Errorf("unable to extract servers: %w", err) return fmt.Errorf("unable to extract servers: %w", err)
} }
for _, server := range extractedServers {
if o.services["servers"] {
o.accumulateServer(acc, server, hypervisor.HypervisorHostname)
}
if o.ServerDiagnotics && server.Status == "ACTIVE" {
diagnostic, err := diagnostics.Get(o.compute, server.ID).Extract()
if err != nil {
acc.AddError(fmt.Errorf("unable to get diagnostics for server %q: %w", server.ID, err))
continue
}
o.accumulateServerDiagnostics(acc, hypervisor.HypervisorHostname, server.ID, diagnostic)
}
}
}
return nil
}
func (o *OpenStack) accumulateHypervisor(acc telegraf.Accumulator) { for i := range extractedServers {
for _, hypervisor := range o.openstackHypervisors { server := &extractedServers[i]
// Try derive the associated project
project := "unknown"
if p, ok := o.openstackProjects[server.TenantID]; ok {
project = p.Name
}
// Try to derive the hostname
var hostname string
if server.Host != "" {
hostname = server.Host
} else if server.Hostname != nil && *server.Hostname != "" {
hostname = *server.Hostname
} else if server.HypervisorHostname != "" {
hostname = server.HypervisorHostname
} else {
hostname = server.HostID
}
tags := map[string]string{ tags := map[string]string{
"cpu_vendor": hypervisor.CPUInfo.Vendor, "tenant_id": server.TenantID,
"cpu_arch": hypervisor.CPUInfo.Arch, "name": server.Name,
"cpu_model": hypervisor.CPUInfo.Model, "host_id": server.HostID,
"status": strings.ToLower(hypervisor.Status), "status": strings.ToLower(server.Status),
"state": hypervisor.State, "key_name": server.KeyName,
"hypervisor_hostname": hypervisor.HypervisorHostname, "host_name": hostname,
"hypervisor_type": hypervisor.HypervisorType, "project": project,
"hypervisor_version": strconv.Itoa(hypervisor.HypervisorVersion),
"service_host": hypervisor.Service.Host,
"service_id": hypervisor.Service.ID,
"service_disabled_reason": hypervisor.Service.DisabledReason,
} }
for _, cpuFeature := range hypervisor.CPUInfo.Features {
tags["cpu_feature_"+cpuFeature] = "true"
}
fields := map[string]interface{}{
"id": hypervisor.ID,
"host_ip": hypervisor.HostIP,
"cpu_topology_sockets": hypervisor.CPUInfo.Topology.Sockets,
"cpu_topology_cores": hypervisor.CPUInfo.Topology.Cores,
"cpu_topology_threads": hypervisor.CPUInfo.Topology.Threads,
"current_workload": hypervisor.CurrentWorkload,
"disk_available_least": hypervisor.DiskAvailableLeast,
"free_disk_gb": hypervisor.FreeDiskGB,
"free_ram_mb": hypervisor.FreeRamMB,
"local_gb": hypervisor.LocalGB,
"local_gb_used": hypervisor.LocalGBUsed,
"memory_mb": hypervisor.MemoryMB,
"memory_mb_used": hypervisor.MemoryMBUsed,
"running_vms": hypervisor.RunningVMs,
"vcpus": hypervisor.VCPUs,
"vcpus_used": hypervisor.VCPUsUsed,
}
acc.AddFields("openstack_hypervisor", fields, tags)
}
}
// accumulateServices accumulates statistics of services.
func (o *OpenStack) accumulateServices(acc telegraf.Accumulator) {
for _, service := range o.openstackServices {
tags := map[string]string{
"name": service.Type,
}
fields := map[string]interface{}{
"service_id": service.ID,
"service_enabled": service.Enabled,
}
acc.AddFields("openstack_service", fields, tags)
}
}
// accumulateServer accumulates statistics of a server.
func (o *OpenStack) accumulateServer(acc telegraf.Accumulator, server servers.Server, hostName string) {
tags := map[string]string{}
// Extract the flavor details to avoid joins (ignore errors and leave as zero values) // Extract the flavor details to avoid joins (ignore errors and leave as zero values)
var vcpus, ram, disk int var vcpus, ram, disk int
if flavorIDInterface, ok := server.Flavor["id"]; ok { if flavorIDInterface, found := server.Flavor["id"]; found {
if flavorID, ok := flavorIDInterface.(string); ok { if flavorID, ok := flavorIDInterface.(string); ok {
tags["flavor"] = flavorID tags["flavor"] = flavorID
if flavor, ok := o.openstackFlavors[flavorID]; ok { if flavor, ok := o.openstackFlavors[flavorID]; ok {
@ -921,23 +933,11 @@ func (o *OpenStack) accumulateServer(acc telegraf.Accumulator, server servers.Se
} }
} }
} }
if imageIDInterface, ok := server.Image["id"]; ok { if imageIDInterface, found := server.Image["id"]; found {
if imageID, ok := imageIDInterface.(string); ok { if imageID, ok := imageIDInterface.(string); ok {
tags["image"] = imageID tags["image"] = imageID
} }
} }
// Try derive the associated project
project := "unknown"
if p, ok := o.openstackProjects[server.TenantID]; ok {
project = p.Name
}
tags["tenant_id"] = server.TenantID
tags["name"] = server.Name
tags["host_id"] = server.HostID
tags["status"] = strings.ToLower(server.Status)
tags["key_name"] = server.KeyName
tags["host_name"] = hostName
tags["project"] = project
fields := map[string]interface{}{ fields := map[string]interface{}{
"id": server.ID, "id": server.ID,
"progress": server.Progress, "progress": server.Progress,
@ -968,44 +968,84 @@ func (o *OpenStack) accumulateServer(acc telegraf.Accumulator, server servers.Se
acc.AddFields("openstack_server", fields, tags) acc.AddFields("openstack_server", fields, tags)
} }
} }
}
return nil
} }
// accumulateServerDiagnostics accumulates statistics from the compute(nova) service. func (o *OpenStack) gatherServerDiagnostics(ctx context.Context, acc telegraf.Accumulator) error {
// currently only supports 'libvirt' driver. page, err := servers.List(o.compute, &servers.ListOpts{AllTenants: o.AllTenants}).AllPages(ctx)
func (o *OpenStack) accumulateServerDiagnostics(acc telegraf.Accumulator, _, serverID string, diagnostic map[string]interface{}) { if err != nil {
tags := map[string]string{ return fmt.Errorf("unable to list servers: %w", err)
"server_id": serverID,
} }
fields := map[string]interface{}{} extractedServers, err := servers.ExtractServers(page)
if err != nil {
return fmt.Errorf("unable to extract servers: %w", err)
}
for i := range extractedServers {
server := &extractedServers[i]
if server.Status != "ACTIVE" {
continue
}
diagnostic, err := diagnostics.Get(ctx, o.compute, server.ID).Extract()
if err != nil {
acc.AddError(fmt.Errorf("unable to get diagnostics for server %q: %w", server.ID, err))
continue
}
portName := make(map[string]bool) portName := make(map[string]bool)
storageName := make(map[string]bool) storageName := make(map[string]bool)
memoryStats := make(map[string]interface{}) memoryStats := make(map[string]interface{})
cpus := make(map[string]interface{})
for k, v := range diagnostic { for k, v := range diagnostic {
if typePort.MatchString(k) { if typePort.MatchString(k) {
portName[strings.Split(k, "_")[0]] = true portName[strings.Split(k, "_")[0]] = true
} else if typeCPU.MatchString(k) { } else if typeCPU.MatchString(k) {
fields[k] = v cpus[k] = v
} else if typeStorage.MatchString(k) { } else if typeStorage.MatchString(k) {
storageName[strings.Split(k, "_")[0]] = true storageName[strings.Split(k, "_")[0]] = true
} else { } else {
memoryStats[k] = v memoryStats[k] = v
} }
} }
fields["memory"] = memoryStats["memory"] nPorts := strconv.Itoa(len(portName))
fields["memory-actual"] = memoryStats["memory-actual"] nDisks := strconv.Itoa(len(storageName))
fields["memory-rss"] = memoryStats["memory-rss"]
fields["memory-swap_in"] = memoryStats["memory-swap_in"] // Add metrics for disks
tags["no_of_ports"] = strconv.Itoa(len(portName)) fields := map[string]interface{}{
tags["no_of_disks"] = strconv.Itoa(len(storageName)) "memory": memoryStats["memory"],
"memory-actual": memoryStats["memory-actual"],
"memory-rss": memoryStats["memory-rss"],
"memory-swap_in": memoryStats["memory-swap_in"],
}
for k, v := range cpus {
fields[k] = v
}
for key := range storageName { for key := range storageName {
fields["disk_errors"] = diagnostic[key+"_errors"] fields["disk_errors"] = diagnostic[key+"_errors"]
fields["disk_read"] = diagnostic[key+"_read"] fields["disk_read"] = diagnostic[key+"_read"]
fields["disk_read_req"] = diagnostic[key+"_read_req"] fields["disk_read_req"] = diagnostic[key+"_read_req"]
fields["disk_write"] = diagnostic[key+"_write"] fields["disk_write"] = diagnostic[key+"_write"]
fields["disk_write_req"] = diagnostic[key+"_write_req"] fields["disk_write_req"] = diagnostic[key+"_write_req"]
tags["disk_name"] = key tags := map[string]string{
"server_id": server.ID,
"no_of_ports": nPorts,
"no_of_disks": nDisks,
"disk_name": key,
}
acc.AddFields("openstack_server_diagnostics", fields, tags) acc.AddFields("openstack_server_diagnostics", fields, tags)
} }
// Add metrics for network ports
fields = map[string]interface{}{
"memory": memoryStats["memory"],
"memory-actual": memoryStats["memory-actual"],
"memory-rss": memoryStats["memory-rss"],
"memory-swap_in": memoryStats["memory-swap_in"],
}
for k, v := range cpus {
fields[k] = v
}
for key := range portName { for key := range portName {
fields["port_rx"] = diagnostic[key+"_rx"] fields["port_rx"] = diagnostic[key+"_rx"]
fields["port_rx_drop"] = diagnostic[key+"_rx_drop"] fields["port_rx_drop"] = diagnostic[key+"_rx_drop"]
@ -1015,9 +1055,16 @@ func (o *OpenStack) accumulateServerDiagnostics(acc telegraf.Accumulator, _, ser
fields["port_tx_drop"] = diagnostic[key+"_tx_drop"] fields["port_tx_drop"] = diagnostic[key+"_tx_drop"]
fields["port_tx_errors"] = diagnostic[key+"_tx_errors"] fields["port_tx_errors"] = diagnostic[key+"_tx_errors"]
fields["port_tx_packets"] = diagnostic[key+"_tx_packets"] fields["port_tx_packets"] = diagnostic[key+"_tx_packets"]
tags["port_name"] = key tags := map[string]string{
"server_id": server.ID,
"no_of_ports": nPorts,
"no_of_disks": nDisks,
"port_name": key,
}
acc.AddFields("openstack_server_diagnostics", fields, tags) acc.AddFields("openstack_server_diagnostics", fields, tags)
} }
}
return nil
} }
// init registers a callback which creates a new OpenStack input instance. // init registers a callback which creates a new OpenStack input instance.
@ -1028,6 +1075,7 @@ func init() {
Project: "admin", Project: "admin",
TagPrefix: "openstack_tag_", TagPrefix: "openstack_tag_",
TagValue: "true", TagValue: "true",
AllTenants: true,
} }
}) })
} }

View File

@ -16,13 +16,15 @@
password = "password" password = "password"
## Available services are: ## Available services are:
## "agents", "aggregates", "cinder_services", "flavors", "hypervisors", "networks", ## "agents", "aggregates", "cinder_services", "flavors", "hypervisors",
## "nova_services", "ports", "projects", "servers", "services", "stacks", "storage_pools", ## "networks", "nova_services", "ports", "projects", "servers",
## "subnets", "volumes" ## "serverdiagnostics", "services", "stacks", "storage_pools", "subnets",
## "volumes"
# enabled_services = ["services", "projects", "hypervisors", "flavors", "networks", "volumes"] # enabled_services = ["services", "projects", "hypervisors", "flavors", "networks", "volumes"]
## Collect Server Diagnostics ## Query all instances of all tenants for the volumes and server services
# server_diagnotics = false ## NOTE: Usually this is only permitted for administrators!
# query_all_tenants = true
## output secrets (such as adminPass(for server) and UserID(for volume)). ## output secrets (such as adminPass(for server) and UserID(for volume)).
# output_secrets = false # output_secrets = false