-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
bfb0d06
commit c137bd5
Showing
7 changed files
with
321 additions
and
6 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,30 +1,72 @@ | ||
package prometheus | ||
|
||
import ( | ||
"github.com/luthermonson/go-proxmox" | ||
"github.com/prometheus/client_golang/prometheus" | ||
"github.com/starttoaster/proxmox-exporter/internal/logger" | ||
wrappedProxmox "github.com/starttoaster/proxmox-exporter/internal/proxmox" | ||
) | ||
|
||
// Collector contains all prometheus metric Descs | ||
type Collector struct { | ||
nodeUp *prometheus.Desc | ||
up *prometheus.Desc | ||
} | ||
|
||
// NewCollector constructor function for Collector | ||
func NewCollector() *Collector { | ||
return &Collector{ | ||
nodeUp: prometheus.NewDesc(fqAddPrefix("node_up"), | ||
"Shows whether nodes in a proxmox cluster are up.", | ||
[]string{}, nil, | ||
up: prometheus.NewDesc(fqAddPrefix("up"), | ||
"Shows whether nodes and vms in a proxmox cluster are up. (0=down,1=up)", | ||
[]string{"type", "name"}, | ||
nil, | ||
), | ||
} | ||
} | ||
|
||
// Describe contains all the prometheus descriptors for this metric collector | ||
func (c *Collector) Describe(ch chan<- *prometheus.Desc) { | ||
ch <- c.nodeUp | ||
ch <- c.up | ||
} | ||
|
||
// Collect instructs the prometheus client how to collect the metrics for each descriptor | ||
func (c *Collector) Collect(ch chan<- prometheus.Metric) { | ||
ch <- prometheus.MustNewConstMetric(c.nodeUp, prometheus.GaugeValue, 1.0) | ||
// Retrieve node statuses for the cluster | ||
nodeStatuses, err := wrappedProxmox.Nodes() | ||
if err != nil { | ||
logger.Logger.Error(err.Error()) | ||
return | ||
} | ||
|
||
// Retrieve node info for each node from statuses | ||
var nodes []*proxmox.Node | ||
for _, nodeStatus := range nodeStatuses { | ||
// Add node up metric | ||
ch <- prometheus.MustNewConstMetric(c.up, prometheus.GaugeValue, float64(nodeStatus.Online), "node", nodeStatus.Name) | ||
|
||
// Get node from node status's name | ||
node, err := wrappedProxmox.Node(nodeStatus.Name) | ||
if err != nil { | ||
logger.Logger.Error(err.Error()) | ||
return | ||
} | ||
nodes = append(nodes, node) | ||
} | ||
|
||
for _, node := range nodes { | ||
// Get VMs for node | ||
vms, err := wrappedProxmox.VirtualMachinesOnNode(node) | ||
if err != nil { | ||
logger.Logger.Error(err.Error()) | ||
return | ||
} | ||
|
||
for _, vm := range vms { | ||
// Add vm up metric | ||
var vmUp float64 = 0.0 | ||
if vm.IsRunning() { | ||
vmUp = 1.0 | ||
} | ||
ch <- prometheus.MustNewConstMetric(c.up, prometheus.GaugeValue, vmUp, "qemu", vm.Name) | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,110 @@ | ||
package proxmox | ||
|
||
import ( | ||
"fmt" | ||
"net/http" | ||
"net/url" | ||
"strings" | ||
) | ||
|
||
const ( | ||
defaultBaseURL = "https://localhost:8006/" | ||
apiPath = "api2/json/" | ||
) | ||
|
||
// Client for the Proxmox API | ||
type Client struct { | ||
// HTTP retryable client for the API | ||
client *http.Client | ||
|
||
// Base URL for API requests. Defaults to https://localhost:8006/, | ||
// but can be changed to any remote endpoint. | ||
baseURL *url.URL | ||
|
||
// tokenID is the identifier given for a Proxmox API token | ||
tokenID string | ||
|
||
// token is the token secret | ||
token string | ||
|
||
// Services for each resource in the Proxmox API | ||
Nodes *NodeService | ||
} | ||
|
||
// NewClient returns a new Proxmox API client | ||
func NewClient(tokenID string, token string, options ...ClientOptionFunc) (*Client, error) { | ||
if token == "" || tokenID == "" { | ||
return nil, fmt.Errorf("can not create Proxmox API client without a token ID and token") | ||
} | ||
|
||
c := &Client{ | ||
tokenID: tokenID, | ||
token: token, | ||
} | ||
|
||
// Set the client default fields | ||
c.setBaseURL(defaultBaseURL) | ||
c.setHttpClient(&http.Client{}) | ||
|
||
// Apply any given options | ||
for _, fn := range options { | ||
if fn == nil { | ||
continue | ||
} | ||
if err := fn(c); err != nil { | ||
return nil, err | ||
} | ||
} | ||
|
||
// Create all the Proxmox API services | ||
c.Nodes = &NodeService{client: c} | ||
|
||
return c, nil | ||
} | ||
|
||
// ClientOptionFunc can be used to customize a new Proxmox API client | ||
type ClientOptionFunc func(*Client) error | ||
|
||
// WithBaseURL sets the URL for API requests to something other than localhost. | ||
// API path is applied automatically if unspecified. | ||
// Default: "https://localhost:8006/" | ||
func WithBaseURL(urlStr string) ClientOptionFunc { | ||
return func(c *Client) error { | ||
return c.setBaseURL(urlStr) | ||
} | ||
} | ||
|
||
// setBaseURL sets the URL for API requests | ||
func (c *Client) setBaseURL(urlStr string) error { | ||
// Make sure the given URL end with a slash | ||
if !strings.HasSuffix(urlStr, "/") { | ||
urlStr += "/" | ||
} | ||
|
||
baseURL, err := url.Parse(urlStr) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
if !strings.HasSuffix(baseURL.Path, apiPath) { | ||
baseURL.Path += apiPath | ||
} | ||
|
||
// Update the base URL of the client | ||
c.baseURL = baseURL | ||
|
||
return nil | ||
} | ||
|
||
// WithHttpClient sets the HTTP client for API requests to something other than the default Go http Client | ||
func WithHttpClient(client *http.Client) ClientOptionFunc { | ||
return func(c *Client) error { | ||
return c.setHttpClient(client) | ||
} | ||
} | ||
|
||
// setHttpClient sets the HTTP client for API requests | ||
func (c *Client) setHttpClient(client *http.Client) error { | ||
c.client = client | ||
return nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
package proxmox | ||
|
||
import ( | ||
"net/http" | ||
) | ||
|
||
// NodeService is the service that encapsulates node API methods | ||
type NodeService struct { | ||
client *Client | ||
} | ||
|
||
// Nodes is a list of Node types | ||
type Nodes []Node | ||
|
||
// Node contains data attributes for a node in the Proxmox nodes API | ||
type Node struct { | ||
CPU float64 `json:"cpu"` | ||
Disk int64 `json:"disk"` | ||
ID string `json:"id"` | ||
Level string `json:"level"` | ||
Maxcpu int `json:"maxcpu"` | ||
Maxdisk int64 `json:"maxdisk"` | ||
Maxmem int64 `json:"maxmem"` | ||
Mem int64 `json:"mem"` | ||
Node string `json:"node"` | ||
SslFingerprint string `json:"ssl_fingerprint"` | ||
Status string `json:"status"` | ||
Type string `json:"type"` | ||
Uptime int `json:"uptime"` | ||
} | ||
|
||
// Get makes a GET request to the /nodes endpoint | ||
func (s *NodeService) Get() (*Nodes, *http.Response, error) { | ||
u := "nodes" | ||
req, err := s.client.NewRequest(http.MethodGet, u, nil) | ||
if err != nil { | ||
return nil, nil, err | ||
} | ||
|
||
d := new(Nodes) | ||
resp, err := s.client.Do(req, &d) | ||
if err != nil { | ||
return nil, resp, err | ||
} | ||
|
||
return d, resp, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,86 @@ | ||
package proxmox | ||
|
||
import ( | ||
"encoding/json" | ||
"fmt" | ||
"io" | ||
"net/http" | ||
"net/url" | ||
|
||
"github.com/google/go-querystring/query" | ||
) | ||
|
||
// NewRequest creates a new request. | ||
// Method should be a valid http request method. | ||
// Path should be an API path relative to the client's base URL. | ||
// Path should not have a preceding '/' | ||
// If specified, the value pointed to by opt is encoded into the query string of the URL. | ||
func (c *Client) NewRequest(method, path string, opt interface{}) (*http.Request, error) { | ||
u := *c.baseURL | ||
unescaped, err := url.PathUnescape(path) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
// Set the encoded path data | ||
u.RawPath = c.baseURL.Path + path | ||
u.Path = c.baseURL.Path + unescaped | ||
|
||
// Set query parameters if any are provided | ||
if opt != nil { | ||
q, err := query.Values(opt) | ||
if err != nil { | ||
return nil, err | ||
} | ||
u.RawQuery = q.Encode() | ||
} | ||
|
||
// Create request | ||
req, err := http.NewRequest(method, u.String(), nil) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
// Set request header if making a POST or PUT | ||
if req.Method == http.MethodPost || req.Method == http.MethodPut { | ||
req.Header.Set("Content-Type", "x-www-form-urlencoded") | ||
} | ||
|
||
return req, nil | ||
} | ||
|
||
// Do sends an API request. The response is stored in the value 'v' or returned as an error. | ||
// If v implements the io.Writer interface, the raw response body will be written to v, without json decoding it. | ||
func (c *Client) Do(req *http.Request, v interface{}) (*http.Response, error) { | ||
// Set auth header according to https://pve.proxmox.com/wiki/Proxmox_VE_API#Authentication | ||
req.Header.Set("Authorization", fmt.Sprintf("PVEAPIToken=%s=%s", c.tokenID, c.token)) | ||
|
||
// Do request | ||
resp, err := c.client.Do(req) | ||
if err != nil { | ||
return nil, err | ||
} | ||
defer resp.Body.Close() | ||
defer io.Copy(io.Discard, resp.Body) | ||
|
||
// Check for error API response and capture it as an error | ||
if resp.StatusCode > 399 { | ||
body, err := io.ReadAll(resp.Body) | ||
if err != nil { | ||
return nil, fmt.Errorf("error reading Proxmox response body: %v", err) | ||
} | ||
|
||
return resp, fmt.Errorf(string(body)) | ||
} | ||
|
||
// Copy body into v | ||
if v != nil { | ||
if w, ok := v.(io.Writer); ok { | ||
_, err = io.Copy(w, resp.Body) | ||
} else { | ||
err = json.NewDecoder(resp.Body).Decode(v) | ||
} | ||
} | ||
|
||
return resp, err | ||
} |