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