1package godo
2
3import (
4	"context"
5	"fmt"
6	"net/http"
7	"time"
8
9	"github.com/digitalocean/godo/metrics"
10)
11
12const (
13	monitoringBasePath     = "v2/monitoring"
14	alertPolicyBasePath    = monitoringBasePath + "/alerts"
15	dropletMetricsBasePath = monitoringBasePath + "/metrics/droplet"
16
17	DropletCPUUtilizationPercent        = "v1/insights/droplet/cpu"
18	DropletMemoryUtilizationPercent     = "v1/insights/droplet/memory_utilization_percent"
19	DropletDiskUtilizationPercent       = "v1/insights/droplet/disk_utilization_percent"
20	DropletPublicOutboundBandwidthRate  = "v1/insights/droplet/public_outbound_bandwidth"
21	DropletPublicInboundBandwidthRate   = "v1/insights/droplet/public_inbound_bandwidth"
22	DropletPrivateOutboundBandwidthRate = "v1/insights/droplet/private_outbound_bandwidth"
23	DropletPrivateInboundBandwidthRate  = "v1/insights/droplet/private_inbound_bandwidth"
24	DropletDiskReadRate                 = "v1/insights/droplet/disk_read"
25	DropletDiskWriteRate                = "v1/insights/droplet/disk_write"
26	DropletOneMinuteLoadAverage         = "v1/insights/droplet/load_1"
27	DropletFiveMinuteLoadAverage        = "v1/insights/droplet/load_5"
28	DropletFifteenMinuteLoadAverage     = "v1/insights/droplet/load_15"
29)
30
31// MonitoringService is an interface for interfacing with the
32// monitoring endpoints of the DigitalOcean API
33// See: https://docs.digitalocean.com/reference/api/api-reference/#tag/Monitoring
34type MonitoringService interface {
35	ListAlertPolicies(context.Context, *ListOptions) ([]AlertPolicy, *Response, error)
36	GetAlertPolicy(context.Context, string) (*AlertPolicy, *Response, error)
37	CreateAlertPolicy(context.Context, *AlertPolicyCreateRequest) (*AlertPolicy, *Response, error)
38	UpdateAlertPolicy(context.Context, string, *AlertPolicyUpdateRequest) (*AlertPolicy, *Response, error)
39	DeleteAlertPolicy(context.Context, string) (*Response, error)
40
41	GetDropletBandwidth(context.Context, *DropletBandwidthMetricsRequest) (*MetricsResponse, *Response, error)
42	GetDropletAvailableMemory(context.Context, *DropletMetricsRequest) (*MetricsResponse, *Response, error)
43	GetDropletCPU(context.Context, *DropletMetricsRequest) (*MetricsResponse, *Response, error)
44	GetDropletFilesystemFree(context.Context, *DropletMetricsRequest) (*MetricsResponse, *Response, error)
45	GetDropletFilesystemSize(context.Context, *DropletMetricsRequest) (*MetricsResponse, *Response, error)
46	GetDropletLoad1(context.Context, *DropletMetricsRequest) (*MetricsResponse, *Response, error)
47	GetDropletLoad5(context.Context, *DropletMetricsRequest) (*MetricsResponse, *Response, error)
48	GetDropletLoad15(context.Context, *DropletMetricsRequest) (*MetricsResponse, *Response, error)
49	GetDropletCachedMemory(context.Context, *DropletMetricsRequest) (*MetricsResponse, *Response, error)
50	GetDropletFreeMemory(context.Context, *DropletMetricsRequest) (*MetricsResponse, *Response, error)
51	GetDropletTotalMemory(context.Context, *DropletMetricsRequest) (*MetricsResponse, *Response, error)
52}
53
54// MonitoringServiceOp handles communication with monitoring related methods of the
55// DigitalOcean API.
56type MonitoringServiceOp struct {
57	client *Client
58}
59
60var _ MonitoringService = &MonitoringServiceOp{}
61
62// AlertPolicy represents a DigitalOcean alert policy
63type AlertPolicy struct {
64	UUID        string          `json:"uuid"`
65	Type        string          `json:"type"`
66	Description string          `json:"description"`
67	Compare     AlertPolicyComp `json:"compare"`
68	Value       float32         `json:"value"`
69	Window      string          `json:"window"`
70	Entities    []string        `json:"entities"`
71	Tags        []string        `json:"tags"`
72	Alerts      Alerts          `json:"alerts"`
73	Enabled     bool            `json:"enabled"`
74}
75
76// Alerts represents the alerts section of an alert policy
77type Alerts struct {
78	Slack []SlackDetails `json:"slack"`
79	Email []string       `json:"email"`
80}
81
82// SlackDetails represents the details required to send a slack alert
83type SlackDetails struct {
84	URL     string `json:"url"`
85	Channel string `json:"channel"`
86}
87
88// AlertPolicyComp represents an alert policy comparison operation
89type AlertPolicyComp string
90
91const (
92	// GreaterThan is the comparison >
93	GreaterThan AlertPolicyComp = "GreaterThan"
94	// LessThan is the comparison <
95	LessThan AlertPolicyComp = "LessThan"
96)
97
98// AlertPolicyCreateRequest holds the info for creating a new alert policy
99type AlertPolicyCreateRequest struct {
100	Type        string          `json:"type"`
101	Description string          `json:"description"`
102	Compare     AlertPolicyComp `json:"compare"`
103	Value       float32         `json:"value"`
104	Window      string          `json:"window"`
105	Entities    []string        `json:"entities"`
106	Tags        []string        `json:"tags"`
107	Alerts      Alerts          `json:"alerts"`
108	Enabled     *bool           `json:"enabled"`
109}
110
111// AlertPolicyUpdateRequest holds the info for updating an existing alert policy
112type AlertPolicyUpdateRequest struct {
113	Type        string          `json:"type"`
114	Description string          `json:"description"`
115	Compare     AlertPolicyComp `json:"compare"`
116	Value       float32         `json:"value"`
117	Window      string          `json:"window"`
118	Entities    []string        `json:"entities"`
119	Tags        []string        `json:"tags"`
120	Alerts      Alerts          `json:"alerts"`
121	Enabled     *bool           `json:"enabled"`
122}
123
124type alertPoliciesRoot struct {
125	AlertPolicies []AlertPolicy `json:"policies"`
126	Links         *Links        `json:"links"`
127	Meta          *Meta         `json:"meta"`
128}
129
130type alertPolicyRoot struct {
131	AlertPolicy *AlertPolicy `json:"policy,omitempty"`
132}
133
134// DropletMetricsRequest holds the information needed to retrieve Droplet various metrics.
135type DropletMetricsRequest struct {
136	HostID string
137	Start  time.Time
138	End    time.Time
139}
140
141// DropletBandwidthMetricsRequest holds the information needed to retrieve Droplet bandwidth metrics.
142type DropletBandwidthMetricsRequest struct {
143	DropletMetricsRequest
144	Interface string
145	Direction string
146}
147
148// MetricsResponse holds a Metrics query response.
149type MetricsResponse struct {
150	Status string      `json:"status"`
151	Data   MetricsData `json:"data"`
152}
153
154// MetricsData holds the data portion of a Metrics response.
155type MetricsData struct {
156	ResultType string                 `json:"resultType"`
157	Result     []metrics.SampleStream `json:"result"`
158}
159
160// ListAlertPolicies all alert policies
161func (s *MonitoringServiceOp) ListAlertPolicies(ctx context.Context, opt *ListOptions) ([]AlertPolicy, *Response, error) {
162	path := alertPolicyBasePath
163	path, err := addOptions(path, opt)
164
165	if err != nil {
166		return nil, nil, err
167	}
168
169	req, err := s.client.NewRequest(ctx, http.MethodGet, path, nil)
170	if err != nil {
171		return nil, nil, err
172	}
173
174	root := new(alertPoliciesRoot)
175	resp, err := s.client.Do(ctx, req, root)
176	if err != nil {
177		return nil, resp, err
178	}
179	if l := root.Links; l != nil {
180		resp.Links = l
181	}
182	if m := root.Meta; m != nil {
183		resp.Meta = m
184	}
185	return root.AlertPolicies, resp, err
186}
187
188// GetAlertPolicy gets a single alert policy
189func (s *MonitoringServiceOp) GetAlertPolicy(ctx context.Context, uuid string) (*AlertPolicy, *Response, error) {
190	path := fmt.Sprintf("%s/%s", alertPolicyBasePath, uuid)
191
192	req, err := s.client.NewRequest(ctx, http.MethodGet, path, nil)
193	if err != nil {
194		return nil, nil, err
195	}
196
197	root := new(alertPolicyRoot)
198	resp, err := s.client.Do(ctx, req, root)
199	if err != nil {
200		return nil, resp, err
201	}
202
203	return root.AlertPolicy, resp, err
204}
205
206// CreateAlertPolicy creates a new alert policy
207func (s *MonitoringServiceOp) CreateAlertPolicy(ctx context.Context, createRequest *AlertPolicyCreateRequest) (*AlertPolicy, *Response, error) {
208	if createRequest == nil {
209		return nil, nil, NewArgError("createRequest", "cannot be nil")
210	}
211
212	req, err := s.client.NewRequest(ctx, http.MethodPost, alertPolicyBasePath, createRequest)
213	if err != nil {
214		return nil, nil, err
215	}
216
217	root := new(alertPolicyRoot)
218	resp, err := s.client.Do(ctx, req, root)
219	if err != nil {
220		return nil, resp, err
221	}
222
223	return root.AlertPolicy, resp, err
224}
225
226// UpdateAlertPolicy updates an existing alert policy
227func (s *MonitoringServiceOp) UpdateAlertPolicy(ctx context.Context, uuid string, updateRequest *AlertPolicyUpdateRequest) (*AlertPolicy, *Response, error) {
228	if uuid == "" {
229		return nil, nil, NewArgError("uuid", "cannot be empty")
230	}
231	if updateRequest == nil {
232		return nil, nil, NewArgError("updateRequest", "cannot be nil")
233	}
234
235	path := fmt.Sprintf("%s/%s", alertPolicyBasePath, uuid)
236	req, err := s.client.NewRequest(ctx, http.MethodPut, path, updateRequest)
237	if err != nil {
238		return nil, nil, err
239	}
240
241	root := new(alertPolicyRoot)
242	resp, err := s.client.Do(ctx, req, root)
243	if err != nil {
244		return nil, resp, err
245	}
246
247	return root.AlertPolicy, resp, err
248}
249
250// DeleteAlertPolicy deletes an existing alert policy
251func (s *MonitoringServiceOp) DeleteAlertPolicy(ctx context.Context, uuid string) (*Response, error) {
252	if uuid == "" {
253		return nil, NewArgError("uuid", "cannot be empty")
254	}
255
256	path := fmt.Sprintf("%s/%s", alertPolicyBasePath, uuid)
257	req, err := s.client.NewRequest(ctx, http.MethodDelete, path, nil)
258	if err != nil {
259		return nil, err
260	}
261
262	resp, err := s.client.Do(ctx, req, nil)
263
264	return resp, err
265}
266
267// GetDropletBandwidth retrieves Droplet bandwidth metrics.
268func (s *MonitoringServiceOp) GetDropletBandwidth(ctx context.Context, args *DropletBandwidthMetricsRequest) (*MetricsResponse, *Response, error) {
269	path := dropletMetricsBasePath + "/bandwidth"
270	req, err := s.client.NewRequest(ctx, http.MethodGet, path, nil)
271	if err != nil {
272		return nil, nil, err
273	}
274
275	q := req.URL.Query()
276	q.Add("host_id", args.HostID)
277	q.Add("interface", args.Interface)
278	q.Add("direction", args.Direction)
279	q.Add("start", fmt.Sprintf("%d", args.Start.Unix()))
280	q.Add("end", fmt.Sprintf("%d", args.End.Unix()))
281	req.URL.RawQuery = q.Encode()
282
283	root := new(MetricsResponse)
284	resp, err := s.client.Do(ctx, req, root)
285
286	return root, resp, err
287}
288
289// GetDropletCPU retrieves Droplet CPU metrics.
290func (s *MonitoringServiceOp) GetDropletCPU(ctx context.Context, args *DropletMetricsRequest) (*MetricsResponse, *Response, error) {
291	return s.getDropletMetrics(ctx, "/cpu", args)
292}
293
294// GetDropletFilesystemFree retrieves Droplet filesystem free metrics.
295func (s *MonitoringServiceOp) GetDropletFilesystemFree(ctx context.Context, args *DropletMetricsRequest) (*MetricsResponse, *Response, error) {
296	return s.getDropletMetrics(ctx, "/filesystem_free", args)
297}
298
299// GetDropletFilesystemSize retrieves Droplet filesystem size metrics.
300func (s *MonitoringServiceOp) GetDropletFilesystemSize(ctx context.Context, args *DropletMetricsRequest) (*MetricsResponse, *Response, error) {
301	return s.getDropletMetrics(ctx, "/filesystem_size", args)
302}
303
304// GetDropletLoad1 retrieves Droplet load 1 metrics.
305func (s *MonitoringServiceOp) GetDropletLoad1(ctx context.Context, args *DropletMetricsRequest) (*MetricsResponse, *Response, error) {
306	return s.getDropletMetrics(ctx, "/load_1", args)
307}
308
309// GetDropletLoad5 retrieves Droplet load 5 metrics.
310func (s *MonitoringServiceOp) GetDropletLoad5(ctx context.Context, args *DropletMetricsRequest) (*MetricsResponse, *Response, error) {
311	return s.getDropletMetrics(ctx, "/load_5", args)
312}
313
314// GetDropletLoad15 retrieves Droplet load 15 metrics.
315func (s *MonitoringServiceOp) GetDropletLoad15(ctx context.Context, args *DropletMetricsRequest) (*MetricsResponse, *Response, error) {
316	return s.getDropletMetrics(ctx, "/load_15", args)
317}
318
319// GetDropletCachedMemory retrieves Droplet cached memory metrics.
320func (s *MonitoringServiceOp) GetDropletCachedMemory(ctx context.Context, args *DropletMetricsRequest) (*MetricsResponse, *Response, error) {
321	return s.getDropletMetrics(ctx, "/memory_cached", args)
322}
323
324// GetDropletFreeMemory retrieves Droplet free memory metrics.
325func (s *MonitoringServiceOp) GetDropletFreeMemory(ctx context.Context, args *DropletMetricsRequest) (*MetricsResponse, *Response, error) {
326	return s.getDropletMetrics(ctx, "/memory_free", args)
327}
328
329// GetDropletTotalMemory retrieves Droplet total memory metrics.
330func (s *MonitoringServiceOp) GetDropletTotalMemory(ctx context.Context, args *DropletMetricsRequest) (*MetricsResponse, *Response, error) {
331	return s.getDropletMetrics(ctx, "/memory_total", args)
332}
333
334// GetDropletAvailableMemory retrieves Droplet available memory metrics.
335func (s *MonitoringServiceOp) GetDropletAvailableMemory(ctx context.Context, args *DropletMetricsRequest) (*MetricsResponse, *Response, error) {
336	return s.getDropletMetrics(ctx, "/memory_available", args)
337}
338
339func (s *MonitoringServiceOp) getDropletMetrics(ctx context.Context, path string, args *DropletMetricsRequest) (*MetricsResponse, *Response, error) {
340	fullPath := dropletMetricsBasePath + path
341	req, err := s.client.NewRequest(ctx, http.MethodGet, fullPath, nil)
342	if err != nil {
343		return nil, nil, err
344	}
345
346	q := req.URL.Query()
347	q.Add("host_id", args.HostID)
348	q.Add("start", fmt.Sprintf("%d", args.Start.Unix()))
349	q.Add("end", fmt.Sprintf("%d", args.End.Unix()))
350	req.URL.RawQuery = q.Encode()
351
352	root := new(MetricsResponse)
353	resp, err := s.client.Do(ctx, req, root)
354
355	return root, resp, err
356}
357