1
0
Fork 0

Adding upstream version 1.34.4.

Signed-off-by: Daniel Baumann <daniel@debian.org>
This commit is contained in:
Daniel Baumann 2025-05-24 07:26:29 +02:00
parent e393c3af3f
commit 4978089aab
Signed by: daniel
GPG key ID: FBB4F0E80A80222F
4963 changed files with 677545 additions and 0 deletions

View file

@ -0,0 +1,100 @@
# Proxmox Input Plugin
The proxmox plugin gathers metrics about containers and VMs using the Proxmox
API.
Telegraf minimum version: Telegraf 1.16.0
## Global configuration options <!-- @/docs/includes/plugin_config.md -->
In addition to the plugin-specific configuration settings, plugins support
additional global and plugin configuration settings. These settings are used to
modify metrics, tags, and field or create aliases and configure ordering, etc.
See the [CONFIGURATION.md][CONFIGURATION.md] for more details.
[CONFIGURATION.md]: ../../../docs/CONFIGURATION.md#plugins
## Configuration
```toml @sample.conf
# Provides metrics from Proxmox nodes (Proxmox Virtual Environment > 6.2).
[[inputs.proxmox]]
## API connection configuration. The API token was introduced in Proxmox v6.2.
## Required permissions for user and token: PVEAuditor role on /.
base_url = "https://localhost:8006/api2/json"
api_token = "USER@REALM!TOKENID=UUID"
## Node name, defaults to OS hostname
## Unless Telegraf is on the same host as Proxmox, setting this is required.
# node_name = ""
## Additional tags of the VM stats data to add as a tag
## Supported values are "vmid" and "status"
# additional_vmstats_tags = []
## Optional TLS Config
# tls_ca = "/etc/telegraf/ca.pem"
# tls_cert = "/etc/telegraf/cert.pem"
# tls_key = "/etc/telegraf/key.pem"
## Use TLS but skip chain & host verification
# insecure_skip_verify = false
## HTTP response timeout (default: 5s)
# response_timeout = "5s"
```
### Permissions
The plugin will need to have access to the Proxmox API. In Proxmox API tokens
are a subset of the corresponding user. This means an API token cannot execute
commands that the user cannot either.
For Telegraf, an API token and user must be provided with at least the
PVEAuditor role on /. Below is an example of creating a telegraf user and token
and then ensuring the user and token have the correct role:
```s
## Create a influx user with PVEAuditor role
pveum user add influx@pve
pveum acl modify / -role PVEAuditor -user influx@pve
## Create a token with the PVEAuditor role
pveum user token add influx@pve monitoring -privsep 1
pveum acl modify / -role PVEAuditor -token 'influx@pve!monitoring'
```
See this [Proxmox docs example][1] for further details.
[1]: https://pve.proxmox.com/wiki/User_Management#_limited_api_token_for_monitoring
## Metrics
- proxmox
- status
- uptime
- cpuload
- mem_used
- mem_total
- mem_free
- mem_used_percentage
- swap_used
- swap_total
- swap_free
- swap_used_percentage
- disk_used
- disk_total
- disk_free
- disk_used_percentage
### Tags
- node_fqdn - FQDN of the node telegraf is running on
- vm_name - Name of the VM/container
- vm_fqdn - FQDN of the VM/container
- vm_type - Type of the VM/container (lxc, qemu)
- vm_id - ID of the VM/container
## Example Output
```text
proxmox,host=pxnode,node_fqdn=pxnode.example.com,vm_fqdn=vm1.example.com,vm_id=112,vm_name=vm1,vm_type=lxc cpuload=0.147998116735236,disk_free=4461129728i,disk_total=5217320960i,disk_used=756191232i,disk_used_percentage=14,mem_free=1046827008i,mem_total=1073741824i,mem_used=26914816i,mem_used_percentage=2,status="running",swap_free=536698880i,swap_total=536870912i,swap_used=172032i,swap_used_percentage=0,uptime=1643793i 1595457277000000000
```

View file

@ -0,0 +1,293 @@
//go:generate ../../../tools/readme_config_includer/generator
package proxmox
import (
_ "embed"
"encoding/json"
"fmt"
"io"
"net/http"
"net/url"
"os"
"slices"
"strings"
"time"
"github.com/influxdata/telegraf"
"github.com/influxdata/telegraf/config"
"github.com/influxdata/telegraf/plugins/common/tls"
"github.com/influxdata/telegraf/plugins/inputs"
)
//go:embed sample.conf
var sampleConfig string
type Proxmox struct {
BaseURL string `toml:"base_url"`
APIToken string `toml:"api_token"`
ResponseTimeout config.Duration `toml:"response_timeout"`
NodeName string `toml:"node_name"`
AdditionalVmstatsTags []string `toml:"additional_vmstats_tags"`
Log telegraf.Logger `toml:"-"`
tls.ClientConfig
httpClient *http.Client
nodeSearchDomain string
requestFunction func(apiUrl string, method string, data url.Values) ([]byte, error)
}
func (*Proxmox) SampleConfig() string {
return sampleConfig
}
func (px *Proxmox) Init() error {
// Check parameters
for _, v := range px.AdditionalVmstatsTags {
switch v {
case "vmid", "status":
// Do nothing as those are valid values
default:
return fmt.Errorf("invalid additional vmstats tag %q", v)
}
}
// Set hostname as default node name for backwards compatibility
if px.NodeName == "" {
//nolint:errcheck // best attempt setting of NodeName
hostname, _ := os.Hostname()
px.NodeName = hostname
}
tlsCfg, err := px.ClientConfig.TLSConfig()
if err != nil {
return err
}
px.httpClient = &http.Client{
Transport: &http.Transport{
TLSClientConfig: tlsCfg,
},
Timeout: time.Duration(px.ResponseTimeout),
}
px.requestFunction = px.performRequest
return nil
}
func (px *Proxmox) Gather(acc telegraf.Accumulator) error {
if err := px.getNodeSearchDomain(); err != nil {
return fmt.Errorf("getting search domain failed: %w", err)
}
px.gatherVMData(acc, lxc)
px.gatherVMData(acc, qemu)
return nil
}
func (px *Proxmox) getNodeSearchDomain() error {
apiURL := "/nodes/" + px.NodeName + "/dns"
jsonData, err := px.requestFunction(apiURL, http.MethodGet, nil)
if err != nil {
return fmt.Errorf("requesting data failed: %w", err)
}
var nodeDNS nodeDNS
if err := json.Unmarshal(jsonData, &nodeDNS); err != nil {
return fmt.Errorf("decoding message failed: %w", err)
}
px.nodeSearchDomain = nodeDNS.Data.Searchdomain
return nil
}
func (px *Proxmox) performRequest(apiURL, method string, data url.Values) ([]byte, error) {
request, err := http.NewRequest(method, px.BaseURL+apiURL, strings.NewReader(data.Encode()))
if err != nil {
return nil, err
}
request.Header.Add("Authorization", "PVEAPIToken="+px.APIToken)
resp, err := px.httpClient.Do(request)
if err != nil {
return nil, err
}
defer resp.Body.Close()
responseBody, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}
return responseBody, nil
}
func (px *Proxmox) gatherVMData(acc telegraf.Accumulator, rt resourceType) {
vmStats, err := px.getVMStats(rt)
if err != nil {
px.Log.Errorf("Error getting VM stats: %v", err)
return
}
for _, vmStat := range vmStats.Data {
vmConfig, err := px.getVMConfig(vmStat.ID, rt)
if err != nil {
px.Log.Errorf("Error getting VM config: %v", err)
return
}
if vmConfig.Data.Template == 1 {
px.Log.Debugf("Ignoring template VM %s (%s)", vmStat.ID, vmStat.Name)
continue
}
currentVMStatus, err := px.getCurrentVMStatus(rt, vmStat.ID)
if err != nil {
px.Log.Errorf("Error getting VM current VM status: %v", err)
return
}
vmFQDN := vmConfig.Data.Hostname
if vmFQDN == "" {
vmFQDN = vmStat.Name
}
domain := vmConfig.Data.Searchdomain
if domain == "" {
domain = px.nodeSearchDomain
}
if domain != "" {
vmFQDN += "." + domain
}
nodeFQDN := px.NodeName
if px.nodeSearchDomain != "" {
nodeFQDN += "." + domain
}
tags := map[string]string{
"node_fqdn": nodeFQDN,
"vm_name": vmStat.Name,
"vm_fqdn": vmFQDN,
"vm_type": string(rt),
}
if slices.Contains(px.AdditionalVmstatsTags, "vmid") {
tags["vm_id"] = vmStat.ID.String()
}
if slices.Contains(px.AdditionalVmstatsTags, "status") {
tags["status"] = currentVMStatus.Status
}
memMetrics := getByteMetrics(currentVMStatus.TotalMem, currentVMStatus.UsedMem)
swapMetrics := getByteMetrics(currentVMStatus.TotalSwap, currentVMStatus.UsedSwap)
diskMetrics := getByteMetrics(currentVMStatus.TotalDisk, currentVMStatus.UsedDisk)
fields := map[string]interface{}{
"status": currentVMStatus.Status,
"uptime": jsonNumberToInt64(currentVMStatus.Uptime),
"cpuload": jsonNumberToFloat64(currentVMStatus.CPULoad),
"mem_used": memMetrics.used,
"mem_total": memMetrics.total,
"mem_free": memMetrics.free,
"mem_used_percentage": memMetrics.usedPercentage,
"swap_used": swapMetrics.used,
"swap_total": swapMetrics.total,
"swap_free": swapMetrics.free,
"swap_used_percentage": swapMetrics.usedPercentage,
"disk_used": diskMetrics.used,
"disk_total": diskMetrics.total,
"disk_free": diskMetrics.free,
"disk_used_percentage": diskMetrics.usedPercentage,
}
acc.AddFields("proxmox", fields, tags)
}
}
func (px *Proxmox) getCurrentVMStatus(rt resourceType, id json.Number) (vmStat, error) {
apiURL := "/nodes/" + px.NodeName + "/" + string(rt) + "/" + string(id) + "/status/current"
jsonData, err := px.requestFunction(apiURL, http.MethodGet, nil)
if err != nil {
return vmStat{}, err
}
var currentVMStatus vmCurrentStats
err = json.Unmarshal(jsonData, &currentVMStatus)
if err != nil {
return vmStat{}, err
}
return currentVMStatus.Data, nil
}
func (px *Proxmox) getVMStats(rt resourceType) (vmStats, error) {
apiURL := "/nodes/" + px.NodeName + "/" + string(rt)
jsonData, err := px.requestFunction(apiURL, http.MethodGet, nil)
if err != nil {
return vmStats{}, err
}
var vmStatistics vmStats
err = json.Unmarshal(jsonData, &vmStatistics)
if err != nil {
return vmStats{}, err
}
return vmStatistics, nil
}
func (px *Proxmox) getVMConfig(vmID json.Number, rt resourceType) (vmConfig, error) {
apiURL := "/nodes/" + px.NodeName + "/" + string(rt) + "/" + string(vmID) + "/config"
jsonData, err := px.requestFunction(apiURL, http.MethodGet, nil)
if err != nil {
return vmConfig{}, err
}
var vmCfg vmConfig
err = json.Unmarshal(jsonData, &vmCfg)
if err != nil {
return vmConfig{}, err
}
return vmCfg, nil
}
func getByteMetrics(total, used json.Number) metrics {
int64Total := jsonNumberToInt64(total)
int64Used := jsonNumberToInt64(used)
int64Free := int64Total - int64Used
usedPercentage := 0.0
if int64Total != 0 {
usedPercentage = float64(int64Used) * 100 / float64(int64Total)
}
return metrics{
total: int64Total,
used: int64Used,
free: int64Free,
usedPercentage: usedPercentage,
}
}
func jsonNumberToInt64(value json.Number) int64 {
int64Value, err := value.Int64()
if err != nil {
return 0
}
return int64Value
}
func jsonNumberToFloat64(value json.Number) float64 {
float64Value, err := value.Float64()
if err != nil {
return 0
}
return float64Value
}
func init() {
inputs.Add("proxmox", func() telegraf.Input {
return &Proxmox{}
})
}

View file

@ -0,0 +1,257 @@
package proxmox
import (
"net/url"
"strings"
"testing"
"time"
"github.com/stretchr/testify/require"
"github.com/influxdata/telegraf"
"github.com/influxdata/telegraf/metric"
"github.com/influxdata/telegraf/testutil"
)
var nodeSearchDomainTestData = `{"data":{"search":"test.example.com","dns1":"1.0.0.1"}}`
var qemuTestData = `{"data":[{"name":"qemu1","status":"running","maxdisk":10737418240,"cpu":0.029336643550795,"vmid":"113","uptime":2159739,` +
`"disk":0,"maxmem":2147483648,"mem":1722451796}]}`
var qemuConfigTestData = `{"data":{"hostname":"qemu1","searchdomain":"test.example.com"}}`
var lxcTestData = `{"data":[{"vmid":"111","type":"lxc","uptime":2078164,"swap":9412608,"disk":"744189952","maxmem":536870912,"mem":98500608,` +
`"maxswap":536870912,"cpu":0.00371567669193613,"status":"running","maxdisk":"5217320960","name":"container1"},{"vmid":112,"type":"lxc",` +
`"uptime":2078164,"swap":9412608,"disk":"744189952","maxmem":536870912,"mem":98500608,"maxswap":536870912,"cpu":0.00371567669193613,` +
`"status":"running","maxdisk":"5217320960","name":"container2"}]}`
var lxcConfigTestData = `{"data":{"hostname":"container1","searchdomain":"test.example.com"}}`
var lxcCurrentStatusTestData = `{"data":{"vmid":"111","type":"lxc","uptime":2078164,"swap":9412608,"disk":"744189952","maxmem":536870912,` +
`"mem":98500608,"maxswap":536870912,"cpu":0.00371567669193613,"status":"running","maxdisk":"5217320960","name":"container1"}}`
var qemuCurrentStatusTestData = `{"data":{"name":"qemu1","status":"running","maxdisk":10737418240,"cpu":0.029336643550795,"vmid":"113",` +
`"uptime":2159739,"disk":0,"maxmem":2147483648,"mem":1722451796}}`
func performTestRequest(apiURL, _ string, _ url.Values) ([]byte, error) {
var bytedata = []byte("")
if strings.HasSuffix(apiURL, "dns") {
bytedata = []byte(nodeSearchDomainTestData)
} else if strings.HasSuffix(apiURL, "qemu") {
bytedata = []byte(qemuTestData)
} else if strings.HasSuffix(apiURL, "113/config") {
bytedata = []byte(qemuConfigTestData)
} else if strings.HasSuffix(apiURL, "lxc") {
bytedata = []byte(lxcTestData)
} else if strings.HasSuffix(apiURL, "111/config") {
bytedata = []byte(lxcConfigTestData)
} else if strings.HasSuffix(apiURL, "111/status/current") {
bytedata = []byte(lxcCurrentStatusTestData)
} else if strings.HasSuffix(apiURL, "113/status/current") {
bytedata = []byte(qemuCurrentStatusTestData)
}
return bytedata, nil
}
func TestGetNodeSearchDomain(t *testing.T) {
px := &Proxmox{
NodeName: "testnode",
Log: testutil.Logger{},
}
require.NoError(t, px.Init())
px.requestFunction = performTestRequest
require.NoError(t, px.getNodeSearchDomain())
require.Equal(t, "test.example.com", px.nodeSearchDomain)
}
func TestGatherLxcData(t *testing.T) {
px := &Proxmox{
NodeName: "testnode",
Log: testutil.Logger{},
nodeSearchDomain: "test.example.com",
}
require.NoError(t, px.Init())
px.requestFunction = performTestRequest
var acc testutil.Accumulator
px.gatherVMData(&acc, lxc)
expected := []telegraf.Metric{
metric.New(
"proxmox",
map[string]string{
"node_fqdn": "testnode.test.example.com",
"vm_name": "container1",
"vm_fqdn": "container1.test.example.com",
"vm_type": "lxc",
},
map[string]interface{}{
"status": "running",
"uptime": int64(2078164),
"cpuload": float64(0.00371567669193613),
"mem_used": int64(98500608),
"mem_total": int64(536870912),
"mem_free": int64(438370304),
"mem_used_percentage": float64(18.34716796875),
"swap_used": int64(9412608),
"swap_total": int64(536870912),
"swap_free": int64(527458304),
"swap_used_percentage": float64(1.75323486328125),
"disk_used": int64(744189952),
"disk_total": int64(5217320960),
"disk_free": int64(4473131008),
"disk_used_percentage": float64(14.26383306117322),
},
time.Unix(0, 0),
),
}
testutil.RequireMetricsEqual(t, expected, acc.GetTelegrafMetrics(), testutil.IgnoreTime())
}
func TestGatherQemuData(t *testing.T) {
px := &Proxmox{
NodeName: "testnode",
Log: testutil.Logger{},
nodeSearchDomain: "test.example.com",
}
require.NoError(t, px.Init())
px.requestFunction = performTestRequest
var acc testutil.Accumulator
px.gatherVMData(&acc, qemu)
expected := []telegraf.Metric{
metric.New(
"proxmox",
map[string]string{
"node_fqdn": "testnode.test.example.com",
"vm_name": "qemu1",
"vm_fqdn": "qemu1.test.example.com",
"vm_type": "qemu",
},
map[string]interface{}{
"status": "running",
"uptime": int64(2159739),
"cpuload": float64(0.029336643550795),
"mem_used": int64(1722451796),
"mem_total": int64(2147483648),
"mem_free": int64(425031852),
"mem_used_percentage": float64(80.20791206508875),
"swap_used": int64(0),
"swap_total": int64(0),
"swap_free": int64(0),
"swap_used_percentage": float64(0),
"disk_used": int64(0),
"disk_total": int64(10737418240),
"disk_free": int64(10737418240),
"disk_used_percentage": float64(0),
},
time.Unix(0, 0),
),
}
testutil.RequireMetricsEqual(t, expected, acc.GetTelegrafMetrics(), testutil.IgnoreTime())
}
func TestGatherLxcDataWithID(t *testing.T) {
px := &Proxmox{
NodeName: "testnode",
AdditionalVmstatsTags: []string{"vmid"},
Log: testutil.Logger{},
nodeSearchDomain: "test.example.com",
}
require.NoError(t, px.Init())
px.requestFunction = performTestRequest
var acc testutil.Accumulator
px.gatherVMData(&acc, lxc)
expected := []telegraf.Metric{
metric.New(
"proxmox",
map[string]string{
"node_fqdn": "testnode.test.example.com",
"vm_name": "container1",
"vm_fqdn": "container1.test.example.com",
"vm_type": "lxc",
"vm_id": "111",
},
map[string]interface{}{
"status": "running",
"uptime": int64(2078164),
"cpuload": float64(0.00371567669193613),
"mem_used": int64(98500608),
"mem_total": int64(536870912),
"mem_free": int64(438370304),
"mem_used_percentage": float64(18.34716796875),
"swap_used": int64(9412608),
"swap_total": int64(536870912),
"swap_free": int64(527458304),
"swap_used_percentage": float64(1.75323486328125),
"disk_used": int64(744189952),
"disk_total": int64(5217320960),
"disk_free": int64(4473131008),
"disk_used_percentage": float64(14.26383306117322),
},
time.Unix(0, 0),
),
}
testutil.RequireMetricsEqual(t, expected, acc.GetTelegrafMetrics(), testutil.IgnoreTime())
}
func TestGatherQemuDataWithID(t *testing.T) {
px := &Proxmox{
NodeName: "testnode",
AdditionalVmstatsTags: []string{"vmid"},
Log: testutil.Logger{},
nodeSearchDomain: "test.example.com",
}
require.NoError(t, px.Init())
px.requestFunction = performTestRequest
var acc testutil.Accumulator
px.gatherVMData(&acc, qemu)
expected := []telegraf.Metric{
metric.New(
"proxmox",
map[string]string{
"node_fqdn": "testnode.test.example.com",
"vm_name": "qemu1",
"vm_fqdn": "qemu1.test.example.com",
"vm_type": "qemu",
"vm_id": "113",
},
map[string]interface{}{
"status": "running",
"uptime": int64(2159739),
"cpuload": float64(0.029336643550795),
"mem_used": int64(1722451796),
"mem_total": int64(2147483648),
"mem_free": int64(425031852),
"mem_used_percentage": float64(80.20791206508875),
"swap_used": int64(0),
"swap_total": int64(0),
"swap_free": int64(0),
"swap_used_percentage": float64(0),
"disk_used": int64(0),
"disk_total": int64(10737418240),
"disk_free": int64(10737418240),
"disk_used_percentage": float64(0),
},
time.Unix(0, 0),
),
}
testutil.RequireMetricsEqual(t, expected, acc.GetTelegrafMetrics(), testutil.IgnoreTime())
}
func TestGather(t *testing.T) {
px := &Proxmox{
NodeName: "testnode",
Log: testutil.Logger{},
}
require.NoError(t, px.Init())
px.requestFunction = performTestRequest
var acc testutil.Accumulator
require.NoError(t, px.Gather(&acc))
// Results from both tests above
require.Equal(t, 30, acc.NFields())
}

View file

@ -0,0 +1,24 @@
# Provides metrics from Proxmox nodes (Proxmox Virtual Environment > 6.2).
[[inputs.proxmox]]
## API connection configuration. The API token was introduced in Proxmox v6.2.
## Required permissions for user and token: PVEAuditor role on /.
base_url = "https://localhost:8006/api2/json"
api_token = "USER@REALM!TOKENID=UUID"
## Node name, defaults to OS hostname
## Unless Telegraf is on the same host as Proxmox, setting this is required.
# node_name = ""
## Additional tags of the VM stats data to add as a tag
## Supported values are "vmid" and "status"
# additional_vmstats_tags = []
## Optional TLS Config
# tls_ca = "/etc/telegraf/ca.pem"
# tls_cert = "/etc/telegraf/cert.pem"
# tls_key = "/etc/telegraf/key.pem"
## Use TLS but skip chain & host verification
# insecure_skip_verify = false
## HTTP response timeout (default: 5s)
# response_timeout = "5s"

View file

@ -0,0 +1,55 @@
package proxmox
import (
"encoding/json"
)
var (
qemu resourceType = "qemu"
lxc resourceType = "lxc"
)
type resourceType string
type vmStats struct {
Data []vmStat `json:"data"`
}
type vmCurrentStats struct {
Data vmStat `json:"data"`
}
type vmStat struct {
ID json.Number `json:"vmid"`
Name string `json:"name"`
Status string `json:"status"`
UsedMem json.Number `json:"mem"`
TotalMem json.Number `json:"maxmem"`
UsedDisk json.Number `json:"disk"`
TotalDisk json.Number `json:"maxdisk"`
UsedSwap json.Number `json:"swap"`
TotalSwap json.Number `json:"maxswap"`
Uptime json.Number `json:"uptime"`
CPULoad json.Number `json:"cpu"`
}
type vmConfig struct {
Data struct {
Searchdomain string `json:"searchdomain"`
Hostname string `json:"hostname"`
Template int `json:"template"`
} `json:"data"`
}
type nodeDNS struct {
Data struct {
Searchdomain string `json:"search"`
} `json:"data"`
}
type metrics struct {
total int64
used int64
free int64
usedPercentage float64
}