1// Copyright 2016 Circonus, Inc. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5// Metric Cluster API support - Fetch, Create, Update, Delete, and Search
6// See: https://login.circonus.com/resources/api/calls/metric_cluster
7
8package api
9
10import (
11	"encoding/json"
12	"fmt"
13	"net/url"
14	"regexp"
15
16	"github.com/circonus-labs/circonus-gometrics/api/config"
17)
18
19// MetricQuery object
20type MetricQuery struct {
21	Query string `json:"query"`
22	Type  string `json:"type"`
23}
24
25// MetricCluster defines a metric cluster. See https://login.circonus.com/resources/api/calls/metric_cluster for more information.
26type MetricCluster struct {
27	CID                 string              `json:"_cid,omitempty"`                   // string
28	Description         string              `json:"description"`                      // string
29	MatchingMetrics     []string            `json:"_matching_metrics,omitempty"`      // [] len >= 1 (result info only, if query has extras - cannot be set)
30	MatchingUUIDMetrics map[string][]string `json:"_matching_uuid_metrics,omitempty"` // [] len >= 1 (result info only, if query has extras - cannot be set)
31	Name                string              `json:"name"`                             // string
32	Queries             []MetricQuery       `json:"queries"`                          // [] len >= 1
33	Tags                []string            `json:"tags"`                             // [] len >= 0
34}
35
36// NewMetricCluster returns a new MetricCluster (with defaults, if applicable)
37func NewMetricCluster() *MetricCluster {
38	return &MetricCluster{}
39}
40
41// FetchMetricCluster retrieves metric cluster with passed cid.
42func (a *API) FetchMetricCluster(cid CIDType, extras string) (*MetricCluster, error) {
43	if cid == nil || *cid == "" {
44		return nil, fmt.Errorf("Invalid metric cluster CID [none]")
45	}
46
47	clusterCID := string(*cid)
48
49	matched, err := regexp.MatchString(config.MetricClusterCIDRegex, clusterCID)
50	if err != nil {
51		return nil, err
52	}
53	if !matched {
54		return nil, fmt.Errorf("Invalid metric cluster CID [%s]", clusterCID)
55	}
56
57	reqURL := url.URL{
58		Path: clusterCID,
59	}
60
61	extra := ""
62	switch extras {
63	case "metrics":
64		extra = "_matching_metrics"
65	case "uuids":
66		extra = "_matching_uuid_metrics"
67	}
68
69	if extra != "" {
70		q := url.Values{}
71		q.Set("extra", extra)
72		reqURL.RawQuery = q.Encode()
73	}
74
75	result, err := a.Get(reqURL.String())
76	if err != nil {
77		return nil, err
78	}
79
80	if a.Debug {
81		a.Log.Printf("[DEBUG] fetch metric cluster, received JSON: %s", string(result))
82	}
83
84	cluster := &MetricCluster{}
85	if err := json.Unmarshal(result, cluster); err != nil {
86		return nil, err
87	}
88
89	return cluster, nil
90}
91
92// FetchMetricClusters retrieves all metric clusters available to API Token.
93func (a *API) FetchMetricClusters(extras string) (*[]MetricCluster, error) {
94	reqURL := url.URL{
95		Path: config.MetricClusterPrefix,
96	}
97
98	extra := ""
99	switch extras {
100	case "metrics":
101		extra = "_matching_metrics"
102	case "uuids":
103		extra = "_matching_uuid_metrics"
104	}
105
106	if extra != "" {
107		q := url.Values{}
108		q.Set("extra", extra)
109		reqURL.RawQuery = q.Encode()
110	}
111
112	result, err := a.Get(reqURL.String())
113	if err != nil {
114		return nil, err
115	}
116
117	var clusters []MetricCluster
118	if err := json.Unmarshal(result, &clusters); err != nil {
119		return nil, err
120	}
121
122	return &clusters, nil
123}
124
125// UpdateMetricCluster updates passed metric cluster.
126func (a *API) UpdateMetricCluster(cfg *MetricCluster) (*MetricCluster, error) {
127	if cfg == nil {
128		return nil, fmt.Errorf("Invalid metric cluster config [nil]")
129	}
130
131	clusterCID := string(cfg.CID)
132
133	matched, err := regexp.MatchString(config.MetricClusterCIDRegex, clusterCID)
134	if err != nil {
135		return nil, err
136	}
137	if !matched {
138		return nil, fmt.Errorf("Invalid metric cluster CID [%s]", clusterCID)
139	}
140
141	jsonCfg, err := json.Marshal(cfg)
142	if err != nil {
143		return nil, err
144	}
145
146	if a.Debug {
147		a.Log.Printf("[DEBUG] update metric cluster, sending JSON: %s", string(jsonCfg))
148	}
149
150	result, err := a.Put(clusterCID, jsonCfg)
151	if err != nil {
152		return nil, err
153	}
154
155	cluster := &MetricCluster{}
156	if err := json.Unmarshal(result, cluster); err != nil {
157		return nil, err
158	}
159
160	return cluster, nil
161}
162
163// CreateMetricCluster creates a new metric cluster.
164func (a *API) CreateMetricCluster(cfg *MetricCluster) (*MetricCluster, error) {
165	if cfg == nil {
166		return nil, fmt.Errorf("Invalid metric cluster config [nil]")
167	}
168
169	jsonCfg, err := json.Marshal(cfg)
170	if err != nil {
171		return nil, err
172	}
173
174	if a.Debug {
175		a.Log.Printf("[DEBUG] create metric cluster, sending JSON: %s", string(jsonCfg))
176	}
177
178	result, err := a.Post(config.MetricClusterPrefix, jsonCfg)
179	if err != nil {
180		return nil, err
181	}
182
183	cluster := &MetricCluster{}
184	if err := json.Unmarshal(result, cluster); err != nil {
185		return nil, err
186	}
187
188	return cluster, nil
189}
190
191// DeleteMetricCluster deletes passed metric cluster.
192func (a *API) DeleteMetricCluster(cfg *MetricCluster) (bool, error) {
193	if cfg == nil {
194		return false, fmt.Errorf("Invalid metric cluster config [nil]")
195	}
196	return a.DeleteMetricClusterByCID(CIDType(&cfg.CID))
197}
198
199// DeleteMetricClusterByCID deletes metric cluster with passed cid.
200func (a *API) DeleteMetricClusterByCID(cid CIDType) (bool, error) {
201	if cid == nil || *cid == "" {
202		return false, fmt.Errorf("Invalid metric cluster CID [none]")
203	}
204
205	clusterCID := string(*cid)
206
207	matched, err := regexp.MatchString(config.MetricClusterCIDRegex, clusterCID)
208	if err != nil {
209		return false, err
210	}
211	if !matched {
212		return false, fmt.Errorf("Invalid metric cluster CID [%s]", clusterCID)
213	}
214
215	_, err = a.Delete(clusterCID)
216	if err != nil {
217		return false, err
218	}
219
220	return true, nil
221}
222
223// SearchMetricClusters returns metric clusters matching the specified
224// search query and/or filter. If nil is passed for both parameters
225// all metric clusters will be returned.
226func (a *API) SearchMetricClusters(searchCriteria *SearchQueryType, filterCriteria *SearchFilterType) (*[]MetricCluster, error) {
227	q := url.Values{}
228
229	if searchCriteria != nil && *searchCriteria != "" {
230		q.Set("search", string(*searchCriteria))
231	}
232
233	if filterCriteria != nil && len(*filterCriteria) > 0 {
234		for filter, criteria := range *filterCriteria {
235			for _, val := range criteria {
236				q.Add(filter, val)
237			}
238		}
239	}
240
241	if q.Encode() == "" {
242		return a.FetchMetricClusters("")
243	}
244
245	reqURL := url.URL{
246		Path:     config.MetricClusterPrefix,
247		RawQuery: q.Encode(),
248	}
249
250	result, err := a.Get(reqURL.String())
251	if err != nil {
252		return nil, fmt.Errorf("[ERROR] API call error %+v", err)
253	}
254
255	var clusters []MetricCluster
256	if err := json.Unmarshal(result, &clusters); err != nil {
257		return nil, err
258	}
259
260	return &clusters, nil
261}
262