/
client.go
executable file
·148 lines (127 loc) · 3.84 KB
/
client.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
package proxmox
import (
"crypto/tls"
"fmt"
"net/http"
"net/url"
"strings"
"time"
"github.com/patrickmn/go-cache"
log "github.com/starttoaster/proxmox-exporter/internal/logger"
proxmox "github.com/starttoaster/go-proxmox"
)
var (
// ClusterName gets populated with the proxmox cluster's cluster name on clustered PVE instances
ClusterName string
clients map[string]wrappedClient
banDuration = time.Duration(1 * time.Minute)
cash *cache.Cache
)
type wrappedClient struct {
client *proxmox.Client
banned bool
bannedUntil time.Time
}
// Init constructs a proxmox API client for this package taking in a token
func Init(endpoints []string, tokenID, token string, tlsVerify bool) error {
// Fail early if endpoints slice is 0 length
if len(endpoints) == 0 {
return fmt.Errorf("no Proxmox API endpoints supplied")
}
// Define http client, for optional insecure API endpoints
httpClient := http.Client{
Transport: &http.Transport{
TLSClientConfig: &tls.Config{
InsecureSkipVerify: tlsVerify,
},
},
}
// Make and init proxmox client map
clients = make(map[string]wrappedClient)
for _, endpoint := range endpoints {
// Parse URL for hostname
parsedURL, err := url.Parse(endpoint)
if err != nil {
return fmt.Errorf("error parsing URL: \"%s\"", err)
}
hostname := parsedURL.Hostname()
// Create API client
log.Logger.Debug("Creating Proxmox client", "endpoint", endpoint, "hostname", hostname)
c, err := proxmox.NewClient(tokenID, token,
proxmox.WithBaseURL(endpoint),
proxmox.WithHTTPClient(&httpClient),
)
if err != nil {
return fmt.Errorf("error creating API client for exporter: %w", err)
}
// Add client to map
clients[hostname] = wrappedClient{
client: c,
}
}
// init cache -- at longest, cache will live for 29 seconds
// which should ensure metrics are updated if scraping in 30 second intervals
// TODO should cache lifespan and cache expiration intervals be user configurable?
cash = cache.New(24*time.Second, 5*time.Second)
// Maintain client bans
go refreshClientBans()
retrieveClusterName()
return nil
}
func retrieveClusterName() {
// Retrieve cluster status -- if clustered
clusterStatus, err := GetClusterStatus()
if err != nil {
return
}
// Exit if no data returned
if len(clusterStatus.Data) == 0 {
return
}
// Parse out cluster name
for _, cluster := range clusterStatus.Data {
if strings.EqualFold(cluster.Type, "cluster") {
ClusterName = cluster.Name
break
}
}
if ClusterName != "" {
log.Logger.Info("discovered PVE cluster", "cluster", ClusterName)
}
}
// refreshClientBans iterates over the configured clients and checks if their ban is still valid over time
func refreshClientBans() {
for {
// Loop through clients from client map
for name, c := range clients {
// Check if the client is banned -- if banned we need to check if the client's banUntil time has expired
if c.banned && time.Now().After(c.bannedUntil) {
// If the ban expired, make a request, see if it succeeds, and unban it if successful. Increase the ban timer if not
_, _, err := c.client.Nodes.GetNodes()
if err == nil {
// Unban client - request successful
log.Logger.Debug("unbanning client, test request successful", "name", name)
clients[name] = wrappedClient{
client: c.client,
}
continue
} else {
// Re-up ban timer - request failed
log.Logger.Debug("re-upping ban on client, test request failed", "name", name, "error", err)
banClient(name, c)
continue
}
}
}
time.Sleep(5 * time.Second)
}
}
// banClient bans a client for the defined duration
func banClient(name string, c wrappedClient) {
log.Logger.Debug("banning client", "name", name, "duration", banDuration)
clients[name] = wrappedClient{
client: c.client,
banned: true,
bannedUntil: time.Now().Add(banDuration),
}
}