1package main
2
3//
4// cbsummary - a command-line utility for creating a summary report for a set of clusters
5//
6
7import (
8	"encoding/json"
9	"flag"
10	"fmt"
11	"io/ioutil"
12	"time"
13)
14
15// data type for holding cluster info
16
17// count of buckets of different types
18type BucketSummary struct {
19	Emphemeral int `json:"ephemeral"`
20	Membase    int `json:"membase"`
21	Memcached  int `json:"memcached"`
22	Total      int `json:"total"`
23}
24
25// cluster settings
26type ClusterSettings struct {
27	//Compaction CompactionSettings `json:"compaction"`
28	EnableAutoFailover bool   `json:"enable_auto_failover"`
29	FailoverTimeout    int    `json:"failover_timeout"`
30	IndexStorageMode   string `json:"index_storage_mode"`
31}
32
33type ClusterInfo struct {
34	AdminAuditEnabled bool            `json:"adminAuditEnabled"`
35	AdminLDAPEnabled  bool            `json:"adminLDAPEnabled"`
36	Buckets           BucketSummary   `json:"buckets"`
37	Cluster_Settings  ClusterSettings `json:"cluester_settings"`
38}
39
40type SummaryInfo struct {
41	NumClusters   int              `json:"#clusters"`
42	TotalNumNodes int              `json:"#nodes"`
43	NodeVersions  map[string]int   `json:"#nodeVersions"`
44	Clusters      []interface{}    `json:"clusters"`
45}
46
47type ClusterError struct {
48    TheCluster Cluster `json:"error_with_cluster"`
49    ErrMsg string      `json:"error_message"`
50}
51
52
53// flags for the command-line
54
55var CONFIG_FILE = flag.String("config", "", "Config file listing clusters and credentials to summarize.")
56var OUTPUT_FILE = flag.String("output", "", "Name for output file (default cbsummary.out.<timestamp>).")
57var HELP = flag.Bool("help", false, "Print a help message.")
58
59func main() {
60	flag.Parse()
61
62    // help message
63    if *HELP || len(*CONFIG_FILE) == 0 {
64        fmt.Printf("usage: cbsummary --config=<config file> [--output=<output file>]\n\n")
65        fmt.Printf("  cbsummary connects to a set of Couchbase clusters and generates a summary report.\n\n")
66        fmt.Printf("  The config file contains JSON specifying an array of information on each cluster,\n")
67        fmt.Printf("  giving the Couchbase login/password and one or more IP addresses for cluster nodes.\n")
68        fmt.Printf("  An example config file giving information about 2 clusters is:\n\n")
69        fmt.Printf("  { \"clusters\": [\n")
70        fmt.Printf("    {\"login\": \"Administrator\", \"pass\": \"password1\", \"nodes\": [\"http://192.168.1.1:8091\"]},\n")
71        fmt.Printf("    {\"login\": \"Administrator\", \"pass\": \"password2\", \"nodes\": [\"http://192.166.1.1:8091\",\"http://192.16.1.2:8091\"]}\n")
72        fmt.Printf("  ]}\n\n")
73        fmt.Printf("  The summary report is sent to the file 'cbsummary.out.<timestamp>', unless a different\n")
74        fmt.Printf("  file name is specified with the --output option.\n\n")
75        return
76    }
77
78	// need some configuration
79	if CONFIG_FILE == nil || len(*CONFIG_FILE) == 0 {
80		fmt.Printf("You must specify a configuration file.\n\n")
81		return
82	}
83
84	var output_file string
85	if OUTPUT_FILE == nil || len(*OUTPUT_FILE) == 0 {
86		now := time.Now()
87		output_file = fmt.Sprintf("cbsummary.out.%04d-%02d-%02d-%02d:%02d:%02d", now.Year(), now.Month(), now.Day(),
88			now.Hour(), now.Minute(), now.Second())
89	} else {
90		output_file = *OUTPUT_FILE
91	}
92
93	// load the configuration
94
95	config, err := ioutil.ReadFile(*CONFIG_FILE)
96	if err != nil {
97		fmt.Printf("Error reading configuration file %s: %s\n\n", *CONFIG_FILE, err)
98		return
99	}
100
101	// parse the configuration as JSON
102	var clusters ClusterList
103	err = json.Unmarshal(config, &clusters)
104	if err != nil {
105		fmt.Printf("Error parsing configuration file %s: %s\n\n", *CONFIG_FILE, err)
106		return
107	}
108
109	fmt.Printf("Working from config file: %s\n", *CONFIG_FILE)
110
111	clusterSummary := new(SummaryInfo)
112	clusterSummary.NumClusters = len(clusters.Clusters)
113	clusterSummary.TotalNumNodes = 0
114	clusterSummary.NodeVersions = make(map[string]int)
115	clusterSummary.Clusters = make([]interface{}, len(clusters.Clusters))
116
117	// loop through the clusters
118	for cnum, cluster := range clusters.Clusters {
119		//fmt.Printf("\n\nCluster login: %s pass %s nodes: %v\n", cluster.Login, cluster.Pass, cluster.Nodes)
120		var thisCluster *ClusterSummary
121		var cerr error
122
123		for _, node := range cluster.Nodes {
124			client := CreateRestClient(node, cluster.Login, cluster.Pass, nil)
125
126			// get /pools and /pools/defaults
127			pools, err := client.GetPoolsData()
128			if err != nil {
129			    cerr = err
130				fmt.Printf("Error getting bucket settings from node %s: %v\n", node, err)
131				continue // try the next node
132			}
133
134			poolsDefaults, err := client.GetPoolsDefaultData()
135
136			if err != nil {
137			    cerr = err
138				fmt.Printf("Error getting pools/default from node %s: %v\n", node, err)
139				continue // try the next node
140			}
141
142			// if we make it this far, we have both /pools and /pools/defaults
143
144            thisCluster = new(ClusterSummary)
145			thisCluster.ImplementationVersion = pools.ImplementationVersion
146			thisCluster.IsEnterprise = pools.IsEnterprise
147			thisCluster.Uuid = pools.Uuid
148
149			thisCluster.Balanced = poolsDefaults.Balanced
150			thisCluster.ClusterName = poolsDefaults.ClusterName
151			thisCluster.FtsMemoryQuota = poolsDefaults.FtsMemoryQuota
152			thisCluster.IndexMemoryQuota = poolsDefaults.IndexMemoryQuota
153			thisCluster.MemoryQuota = poolsDefaults.MemoryQuota
154			thisCluster.Name = poolsDefaults.Name
155			thisCluster.NodeCount = len(poolsDefaults.Nodes)
156			thisCluster.Nodes = poolsDefaults.Nodes
157			thisCluster.RebalanceStatus = poolsDefaults.RebalanceStatus
158			thisCluster.StorageTotals = poolsDefaults.StorageTotals
159
160			// for each of the nodes in this cluster, show the distribution of versions
161			nodeVersions := make(map[string]int)
162			for _, nodeInfo := range poolsDefaults.Nodes {
163				nodeVersions[nodeInfo.Version] = nodeVersions[nodeInfo.Version] + 1
164				clusterSummary.NodeVersions[nodeInfo.Version] = clusterSummary.NodeVersions[nodeInfo.Version] + 1
165			}
166			thisCluster.NodeVersions = nodeVersions
167
168            clusterSummary.Clusters[cnum] = thisCluster
169			clusterSummary.TotalNumNodes = clusterSummary.TotalNumNodes + len(poolsDefaults.Nodes)
170
171			//  debugging output
172			//body, err := json.Marshal(clusterSummary.Clusters[cnum])
173			//if (err == nil) {
174			//    fmt.Printf("%s\n\n",string(body))
175			//}
176
177			// when we've gotten all the info, break from this look to look at the next cluster
178
179			break
180		}
181
182		// if we get this far with thisCluster unset, we need to replace it with a
183		// different item indicating the error.
184
185		if (thisCluster == nil) {
186		    //fmt.Printf("Failed to contact cluster, error: %v\n",cerr)
187    		errorStatus := new(ClusterError)
188    		errorStatus.TheCluster = cluster
189    		errorStatus.ErrMsg = cerr.Error()
190    		clusterSummary.Clusters[cnum] = errorStatus
191		}
192	}
193
194	body, err := json.MarshalIndent(clusterSummary,"","  ")
195	if err != nil {
196		fmt.Printf("Error marshalling summary: %v\n", err)
197		return
198	}
199
200	err = ioutil.WriteFile(output_file,body,0644)
201	if err != nil {
202		fmt.Printf("Error writing output file %s: %v\n", output_file,err)
203		return
204	}
205
206    fmt.Printf("Wrote information on %d clusters to file %s.\n",clusterSummary.NumClusters,output_file)
207}
208