diff --git a/plugins/inputs/all/all.go b/plugins/inputs/all/all.go index c115e62bc..95106f209 100644 --- a/plugins/inputs/all/all.go +++ b/plugins/inputs/all/all.go @@ -211,6 +211,7 @@ import ( _ "github.com/influxdata/telegraf/plugins/inputs/wireguard" _ "github.com/influxdata/telegraf/plugins/inputs/wireless" _ "github.com/influxdata/telegraf/plugins/inputs/x509_cert" + _ "github.com/influxdata/telegraf/plugins/inputs/xtremio" _ "github.com/influxdata/telegraf/plugins/inputs/zfs" _ "github.com/influxdata/telegraf/plugins/inputs/zipkin" _ "github.com/influxdata/telegraf/plugins/inputs/zookeeper" diff --git a/plugins/inputs/xtremio/README.md b/plugins/inputs/xtremio/README.md new file mode 100644 index 000000000..f646207bc --- /dev/null +++ b/plugins/inputs/xtremio/README.md @@ -0,0 +1,113 @@ +# XtremIO Input Plugin + +The `xtremio` plugin gathers metrics from a Dell EMC XtremIO Storage Array's V3 Rest API. Documentation can be found [here](https://dl.dell.com/content/docu96624_xtremio-storage-array-x1-and-x2-cluster-types-with-xms-6-3-0-to-6-3-3-and-xios-4-0-15-to-4-0-31-and-6-0-0-to-6-3-3-restful-api-3-x-guide.pdf?language=en_us) + +## Configuration + +```toml +[[inputs.xtremio]] + ## XtremIO User Interface Endpoint + url = "https://xtremio.example.com/" # required + + ## Credentials + username = "user1" + password = "pass123" + + ## Metrics to collect from the XtremIO + # collectors = ["bbus","clusters","ssds","volumes","xms"] + + ## Optional TLS Config + # tls_ca = "/etc/telegraf/ca.pem" + # tls_cert = "/etc/telegraf/cert.pem" + # tls_key = "/etc/telegraf/key.pem" + ## Use SSL but skip chain & host verification + # insecure_skip_verify = false +``` + +## Metrics + +- bbus + - tags: + - serial_number + - guid + - power_feed + - name + - model_name + - fields: + - bbus_power + - bbus_average_daily_temp + - bbus_enabled + - bbus_ups_need_battery_replacement + - bbus_ups_low_battery_no_input + +- clusters + - tags: + - hardware_platform + - license_id + - guid + - name + - sys_psnt_serial_number + - fields: + - clusters_compression_factor + - clusters_percent_memory_in_use + - clusters_read_iops + - clusters_write_iops + - clusters_number_of_volumes + - clusters_free_ssd_space_in_percent + - clusters_ssd_num + - clusters_data_reduction_ratio + +- ssds + - tags: + - model_name + - firmware_version + - ssd_uid + - guid + - sys_name + - serial_number + - fields: + - ssds_ssd_size + - ssds_ssd_space_in_use + - ssds_write_iops + - ssds_read_iops + - ssds_write_bandwidth + - ssds_read_bandwidth + - ssds_num_bad_sectors + +- volumes + - tags: + - guid + - sys_name + - name + - fields: + - volumes_read_iops + - volumes_write_iops + - volumes_read_latency + - volumes_write_latency + - volumes_data_reduction_ratio + - volumes_provisioned_space + - volumes_used_space + +- xms + - tags: + - guid + - name + - version + - xms_ip + - fields: + - xms_write_iops + - xms_read_iops + - xms_overall_efficiency_ratio + - xms_ssd_space_in_use + - xms_ram_in_use + - xms_ram_total + - xms_cpu_usage_total + - xms_write_latency + - xms_read_latency + - xms_user_accounts_count + +## Example Output + +> xio,guid=abcdefghifklmnopqrstuvwxyz111111,host=HOSTNAME,model_name=Eaton\ 5P\ 1550,name=X2-BBU,power_feed=PWR-B,serial_number=SER1234567890 bbus_average_daily_temp=22i,bbus_enabled=1i,bbus_power=286i,bbus_ups_low_battery_no_input=0i,bbus_ups_need_battery_replacement=0i 1638295340000000000 +> xio,guid=abcdefghifklmnopqrstuvwxyz222222,host=HOSTNAME,model_name=Eaton\ 5P\ 1550,name=X1-BBU,power_feed=PWR-A,serial_number=SER1234567891 bbus_average_daily_temp=22i,bbus_enabled=1i,bbus_power=246i,bbus_ups_low_battery_no_input=0i,bbus_ups_need_battery_replacement=0i 1638295340000000000 +> xio,guid=abcdefghifklmnopqrstuvwxyz333333,hardware_platform=X1,host=HOSTNAME,license_id=LIC123456789,name=SERVER01,sys_psnt_serial_number=FNM01234567890 clusters_compression_factor=1.5160012465000001,clusters_data_reduction_ratio=2.1613617899,clusters_free_ssd_space_in_percent=34i,clusters_number_of_volumes=36i,clusters_percent_memory_in_use=29i,clusters_read_iops=331i,clusters_ssd_num=50i,clusters_write_iops=4649i 1638295341000000000 diff --git a/plugins/inputs/xtremio/testdata/sample_bbu_response.json b/plugins/inputs/xtremio/testdata/sample_bbu_response.json new file mode 100644 index 000000000..8f5b818d6 --- /dev/null +++ b/plugins/inputs/xtremio/testdata/sample_bbu_response.json @@ -0,0 +1,20 @@ +{ + "content": { + "is-low-battery-has-input": "false", + "serial-number": "A123B45678", + "guid": "987654321abcdef", + "brick-name": "X1", + "ups-battery-charge-in-percent": 100, + "power": 244, + "avg-daily-temp": 23, + "fw-version": "01.02.0034", + "sys-name": "ABCXIO001", + "power-feed": "PWR-A", + "ups-load-in-percent": 21, + "name": "X1-BBU", + "enabled-state": "enabled", + "is-low-battery-no-input": "false", + "ups-need-battery-replacement": "false", + "model-name": "Eaton Model Name" + } +} diff --git a/plugins/inputs/xtremio/testdata/sample_get_bbu_response.json b/plugins/inputs/xtremio/testdata/sample_get_bbu_response.json new file mode 100644 index 000000000..1f0a90c49 --- /dev/null +++ b/plugins/inputs/xtremio/testdata/sample_get_bbu_response.json @@ -0,0 +1,15 @@ +{ + "bbus": [ + { + "href": "https://127.0.0.1/api/json/v3/types/bbus/987654321abcdef", + "name": "X1-BBU", + "sys-name": "ABCXIO001" + } + ], + "links": [ + { + "href": "https://127.0.0.1/api/json/v3/types/bbus/", + "rel": "self" + } + ] +} diff --git a/plugins/inputs/xtremio/xtremio.go b/plugins/inputs/xtremio/xtremio.go new file mode 100644 index 000000000..8bc194e8c --- /dev/null +++ b/plugins/inputs/xtremio/xtremio.go @@ -0,0 +1,402 @@ +package xtremio + +import ( + "encoding/json" + "errors" + "fmt" + "io/ioutil" + "net/http" + "strings" + "sync" + + "github.com/influxdata/telegraf" + "github.com/influxdata/telegraf/internal/choice" + "github.com/influxdata/telegraf/plugins/common/tls" + "github.com/influxdata/telegraf/plugins/inputs" +) + +type XtremIO struct { + Username string `toml:"username"` + Password string `toml:"password"` + URL string `toml:"url"` + Collectors []string `toml:"collectors"` + Log telegraf.Logger `toml:"-"` + tls.ClientConfig + + cookie *http.Cookie + client *http.Client +} + +const sampleConfig = ` + ## XtremIO User Interface Endpoint + url = "https://xtremio.example.com/" # required + + ## Credentials + username = "user1" + password = "pass123" + + ## Metrics to collect from the XtremIO + # collectors = ["bbus","clusters","ssds","volumes","xms"] + + ## Optional TLS Config + # tls_ca = "/etc/telegraf/ca.pem" + # tls_cert = "/etc/telegraf/cert.pem" + # tls_key = "/etc/telegraf/key.pem" + ## Use SSL but skip chain & host verification + # insecure_skip_verify = false +` + +// Description will appear directly above the plugin definition in the config file +func (xio *XtremIO) Description() string { + return `Gathers Metrics From a Dell EMC XtremIO Storage Array's V3 API` +} + +// SampleConfig will populate the sample configuration portion of the plugin's configuration +func (xio *XtremIO) SampleConfig() string { + return sampleConfig +} + +func (xio *XtremIO) Init() error { + if xio.Username == "" { + return errors.New("username cannot be empty") + } + if xio.Password == "" { + return errors.New("password cannot be empty") + } + if xio.URL == "" { + return errors.New("url cannot be empty") + } + + availableCollectors := []string{"bbus", "clusters", "ssds", "volumes", "xms"} + if len(xio.Collectors) == 0 { + xio.Collectors = availableCollectors + } + + for _, collector := range xio.Collectors { + if !choice.Contains(collector, availableCollectors) { + return fmt.Errorf("specified collector %q isn't supported", collector) + } + } + + tlsCfg, err := xio.ClientConfig.TLSConfig() + if err != nil { + return err + } + + xio.client = &http.Client{ + Transport: &http.Transport{ + TLSClientConfig: tlsCfg, + }, + } + + return nil +} + +func (xio *XtremIO) Gather(acc telegraf.Accumulator) error { + if err := xio.authenticate(); err != nil { + return err + } + if xio.cookie == nil { + return errors.New("no authentication cookie set") + } + + var wg sync.WaitGroup + for _, collector := range xio.Collectors { + wg.Add(1) + go func(collector string) { + defer wg.Done() + + resp, err := xio.call(collector) + if err != nil { + acc.AddError(err) + return + } + + data := CollectorResponse{} + err = json.Unmarshal([]byte(resp), &data) + if err != nil { + acc.AddError(err) + } + + var arr []HREF + switch collector { + case "bbus": + arr = data.BBUs + case "clusters": + arr = data.Clusters + case "ssds": + arr = data.SSDs + case "volumes": + arr = data.Volumes + case "xms": + arr = data.XMS + } + + for _, item := range arr { + itemSplit := strings.Split(item.Href, "/") + if len(itemSplit) < 1 { + continue + } + url := collector + "/" + itemSplit[len(itemSplit)-1] + + // Each collector is ran in a goroutine so they can be run in parallel. + // Each collector does an initial query to build out the subqueries it + // needs to run, which are started here in nested goroutines. A future + // refactor opportunity would be for the intial collector goroutines to + // return the results while exiting the goroutine, and then a series of + // goroutines can be kicked off for the subqueries. That way there is no + // nesting of goroutines. + switch collector { + case "bbus": + wg.Add(1) + go xio.gatherBBUs(acc, url, &wg) + case "clusters": + wg.Add(1) + go xio.gatherClusters(acc, url, &wg) + case "ssds": + wg.Add(1) + go xio.gatherSSDs(acc, url, &wg) + case "volumes": + wg.Add(1) + go xio.gatherVolumes(acc, url, &wg) + case "xms": + wg.Add(1) + go xio.gatherXMS(acc, url, &wg) + default: + acc.AddError(fmt.Errorf("specified collector %q isn't supported", collector)) + } + } + }(collector) + } + wg.Wait() + + // At the beginning of every collection, we re-authenticate. + // We reset this cookie so we don't accidentally use an + // expired cookie, we can just check if it's nil and know + // that we either need to re-authenticate or that the + // authentication failed to set the cookie. + xio.cookie = nil + + return nil +} + +func (xio *XtremIO) gatherBBUs(acc telegraf.Accumulator, url string, wg *sync.WaitGroup) { + defer wg.Done() + resp, err := xio.call(url) + if err != nil { + acc.AddError(err) + return + } + + data := BBU{} + err = json.Unmarshal([]byte(resp), &data) + if err != nil { + acc.AddError(err) + return + } + + tags := map[string]string{ + "serial_number": data.Content.Serial, + "guid": data.Content.GUID, + "power_feed": data.Content.PowerFeed, + "name": data.Content.Name, + "model_name": data.Content.ModelName, + } + fields := map[string]interface{}{ + "bbus_power": data.Content.BBUPower, + "bbus_average_daily_temp": data.Content.BBUDailyTemp, + "bbus_enabled": (data.Content.BBUEnabled == "enabled"), + "bbus_ups_need_battery_replacement": data.Content.BBUNeedBat, + "bbus_ups_low_battery_no_input": data.Content.BBULowBat, + } + + acc.AddFields("xio", fields, tags) +} + +func (xio *XtremIO) gatherClusters(acc telegraf.Accumulator, url string, wg *sync.WaitGroup) { + defer wg.Done() + resp, err := xio.call(url) + if err != nil { + acc.AddError(err) + return + } + + data := Clusters{} + err = json.Unmarshal([]byte(resp), &data) + if err != nil { + acc.AddError(err) + return + } + + tags := map[string]string{ + "hardware_platform": data.Content.HardwarePlatform, + "license_id": data.Content.LicenseID, + "guid": data.Content.GUID, + "name": data.Content.Name, + "sys_psnt_serial_number": data.Content.SerialNumber, + } + fields := map[string]interface{}{ + "clusters_compression_factor": data.Content.CompressionFactor, + "clusters_percent_memory_in_use": data.Content.MemoryUsed, + "clusters_read_iops": data.Content.ReadIops, + "clusters_write_iops": data.Content.WriteIops, + "clusters_number_of_volumes": data.Content.NumVolumes, + "clusters_free_ssd_space_in_percent": data.Content.FreeSSDSpace, + "clusters_ssd_num": data.Content.NumSSDs, + "clusters_data_reduction_ratio": data.Content.DataReductionRatio, + } + + acc.AddFields("xio", fields, tags) +} + +func (xio *XtremIO) gatherSSDs(acc telegraf.Accumulator, url string, wg *sync.WaitGroup) { + defer wg.Done() + resp, err := xio.call(url) + if err != nil { + acc.AddError(err) + return + } + + data := SSD{} + err = json.Unmarshal([]byte(resp), &data) + if err != nil { + acc.AddError(err) + return + } + + tags := map[string]string{ + "model_name": data.Content.ModelName, + "firmware_version": data.Content.FirmwareVersion, + "ssd_uid": data.Content.SSDuid, + "guid": data.Content.GUID, + "sys_name": data.Content.SysName, + "serial_number": data.Content.SerialNumber, + } + fields := map[string]interface{}{ + "ssds_ssd_size": data.Content.Size, + "ssds_ssd_space_in_use": data.Content.SpaceUsed, + "ssds_write_iops": data.Content.WriteIops, + "ssds_read_iops": data.Content.ReadIops, + "ssds_write_bandwidth": data.Content.WriteBandwidth, + "ssds_read_bandwidth": data.Content.ReadBandwidth, + "ssds_num_bad_sectors": data.Content.NumBadSectors, + } + + acc.AddFields("xio", fields, tags) +} + +func (xio *XtremIO) gatherVolumes(acc telegraf.Accumulator, url string, wg *sync.WaitGroup) { + defer wg.Done() + resp, err := xio.call(url) + if err != nil { + acc.AddError(err) + return + } + + data := Volumes{} + err = json.Unmarshal([]byte(resp), &data) + if err != nil { + acc.AddError(err) + return + } + + tags := map[string]string{ + "guid": data.Content.GUID, + "sys_name": data.Content.SysName, + "name": data.Content.Name, + } + fields := map[string]interface{}{ + "volumes_read_iops": data.Content.ReadIops, + "volumes_write_iops": data.Content.WriteIops, + "volumes_read_latency": data.Content.ReadLatency, + "volumes_write_latency": data.Content.WriteLatency, + "volumes_data_reduction_ratio": data.Content.DataReductionRatio, + "volumes_provisioned_space": data.Content.ProvisionedSpace, + "volumes_used_space": data.Content.UsedSpace, + } + + acc.AddFields("xio", fields, tags) +} + +func (xio *XtremIO) gatherXMS(acc telegraf.Accumulator, url string, wg *sync.WaitGroup) { + defer wg.Done() + resp, err := xio.call(url) + if err != nil { + acc.AddError(err) + return + } + + data := XMS{} + err = json.Unmarshal([]byte(resp), &data) + if err != nil { + acc.AddError(err) + return + } + + tags := map[string]string{ + "guid": data.Content.GUID, + "name": data.Content.Name, + "version": data.Content.Version, + "xms_ip": data.Content.IP, + } + fields := map[string]interface{}{ + "xms_write_iops": data.Content.WriteIops, + "xms_read_iops": data.Content.ReadIops, + "xms_overall_efficiency_ratio": data.Content.EfficiencyRatio, + "xms_ssd_space_in_use": data.Content.SpaceUsed, + "xms_ram_in_use": data.Content.RAMUsage, + "xms_ram_total": data.Content.RAMTotal, + "xms_cpu_usage_total": data.Content.CPUUsage, + "xms_write_latency": data.Content.WriteLatency, + "xms_read_latency": data.Content.ReadLatency, + "xms_user_accounts_count": data.Content.NumAccounts, + } + + acc.AddFields("xio", fields, tags) +} + +func (xio *XtremIO) call(endpoint string) (string, error) { + req, err := http.NewRequest("GET", xio.URL+"/api/json/v3/types/"+endpoint, nil) + if err != nil { + return "", err + } + req.Header.Set("Content-Type", "application/json") + req.Header.Set("Accept", "application/json") + req.AddCookie(xio.cookie) + resp, err := xio.client.Do(req) + if err != nil { + return "", err + } + defer resp.Body.Close() + data, err := ioutil.ReadAll(resp.Body) + if err != nil { + return "", err + } + return string(data), nil +} + +func (xio *XtremIO) authenticate() error { + req, err := http.NewRequest("GET", xio.URL+"/api/json/v3/commands/login", nil) + if err != nil { + return err + } + req.SetBasicAuth(xio.Username, xio.Password) + resp, err := xio.client.Do(req) + if err != nil { + return err + } + defer resp.Body.Close() + for _, cookie := range resp.Cookies() { + if cookie.Name == "sessid" { + xio.cookie = cookie + break + } + } + return nil +} + +func init() { + inputs.Add("xtremio", func() telegraf.Input { + return &XtremIO{} + }) +} diff --git a/plugins/inputs/xtremio/xtremio_test.go b/plugins/inputs/xtremio/xtremio_test.go new file mode 100644 index 000000000..c4ddb6451 --- /dev/null +++ b/plugins/inputs/xtremio/xtremio_test.go @@ -0,0 +1,202 @@ +package xtremio + +import ( + "fmt" + "github.com/stretchr/testify/require" + "io/ioutil" + "net/http" + "net/http/httptest" + "os" + "path/filepath" + "testing" + "time" + + "github.com/influxdata/telegraf" + "github.com/influxdata/telegraf/testutil" +) + +var testdataDir = getTestdataDir() + +func TestInitDefault(t *testing.T) { + // This test should succeed with the default initialization. + plugin := &XtremIO{ + Username: "testuser", + Password: "testpass", + URL: "http://example.com", + Log: testutil.Logger{}, + } + + // Test the initialization succeeds + require.NoError(t, plugin.Init()) + + // Also test that default values are set correctly + require.Equal(t, "testuser", plugin.Username) + require.Equal(t, "testpass", plugin.Password) + require.Equal(t, "http://example.com", plugin.URL) +} + +func TestInitFail(t *testing.T) { + tests := []struct { + name string + plugin *XtremIO + expected string + }{ + { + name: "all empty", + plugin: &XtremIO{}, + expected: "username cannot be empty", + }, + { + name: "no username", + plugin: &XtremIO{Password: "testpass", URL: "http://example.com"}, + expected: "username cannot be empty", + }, + { + name: "no password", + plugin: &XtremIO{Username: "testuser", URL: "http://example.com"}, + expected: "password cannot be empty", + }, + { + name: "no url", + plugin: &XtremIO{Username: "testuser", Password: "testpass"}, + expected: "url cannot be empty", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tt.plugin.Log = testutil.Logger{} + err := tt.plugin.Init() + require.Error(t, err) + require.EqualError(t, err, tt.expected) + }) + } +} + +func TestFixedValue(t *testing.T) { + ts := httptest.NewServer( + http.HandlerFunc( + func(w http.ResponseWriter, r *http.Request) { + if r.URL.Path == "/api/json/v3/commands/login" { + cookie := &http.Cookie{Name: "sessid", Value: "cookie:123456789"} + http.SetCookie(w, cookie) + w.WriteHeader(http.StatusOK) + _, err := fmt.Fprintln(w, "authentication succeeded") + require.NoError(t, err) + } else if r.URL.Path == "/api/json/v3/types/bbus" { + sampleGetBBUsResponse, err := ioutil.ReadFile(filepath.Join(testdataDir, "sample_get_bbu_response.json")) + require.NoError(t, err) + w.WriteHeader(http.StatusOK) + _, err = fmt.Fprintln(w, string(sampleGetBBUsResponse)) + require.NoError(t, err) + } else if r.URL.Path == "/api/json/v3/types/bbus/987654321abcdef" { + sampleBBUResponseOne, err := ioutil.ReadFile(filepath.Join(testdataDir, "sample_bbu_response.json")) + require.NoError(t, err) + w.WriteHeader(http.StatusOK) + _, err = fmt.Fprintln(w, string(sampleBBUResponseOne)) + require.NoError(t, err) + } + }, + ), + ) + defer ts.Close() + + tests := []struct { + name string + plugin *XtremIO + expected []telegraf.Metric + }{ + { + name: "gather bbus only", + plugin: &XtremIO{ + Username: "testuser", + Password: "testpass", + URL: ts.URL, + Collectors: []string{"bbus"}, + }, + expected: []telegraf.Metric{ + testutil.MustMetric( + "xio", + map[string]string{ + "serial_number": "A123B45678", + "guid": "987654321abcdef", + "power_feed": "PWR-A", + "name": "X1-BBU", + "model_name": "Eaton Model Name", + }, + map[string]interface{}{ + "bbus_power": 244, + "bbus_average_daily_temp": 23, + "bbus_enabled": true, + "bbus_ups_need_battery_replacement": false, + "bbus_ups_low_battery_no_input": false, + }, + time.Unix(0, 0), + ), + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var acc testutil.Accumulator + tt.plugin.Log = testutil.Logger{} + require.NoError(t, tt.plugin.Init()) + require.NoError(t, tt.plugin.Gather(&acc)) + require.Len(t, acc.Errors, 0, "found errors accumulated by acc.AddError()") + acc.Wait(len(tt.expected)) + testutil.RequireMetricsEqual(t, tt.expected, acc.GetTelegrafMetrics(), testutil.IgnoreTime()) + }) + } +} + +func TestAuthenticationFailed(t *testing.T) { + ts := httptest.NewServer( + http.HandlerFunc( + func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusBadRequest) + _, err := fmt.Fprintln(w, "bad request") + require.NoError(t, err) + }, + ), + ) + defer ts.Close() + tests := []struct { + name string + plugin *XtremIO + expected string + }{ + { + name: "authentication failed", + plugin: &XtremIO{ + Username: "usertest", + Password: "userpass", + URL: ts.URL, + }, + expected: "no authentication cookie set", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var acc testutil.Accumulator + + tt.plugin.Log = testutil.Logger{} + require.NoError(t, tt.plugin.Init()) + + err := tt.plugin.Gather(&acc) + require.Error(t, err) + require.EqualError(t, err, tt.expected) + }) + } +} + +func getTestdataDir() string { + dir, err := os.Getwd() + if err != nil { + // if we cannot even establish the test directory, further progress is meaningless + panic(err) + } + + return filepath.Join(dir, "testdata") +} diff --git a/plugins/inputs/xtremio/xtremio_types.go b/plugins/inputs/xtremio/xtremio_types.go new file mode 100644 index 000000000..3ce7d1e0e --- /dev/null +++ b/plugins/inputs/xtremio/xtremio_types.go @@ -0,0 +1,98 @@ +package xtremio + +type BBU struct { + Content struct { + Serial string `json:"serial-number"` + GUID string `json:"guid"` + PowerFeed string `json:"power-feed"` + Name string `json:"Name"` + ModelName string `json:"model-name"` + BBUPower int `json:"power"` + BBUDailyTemp int `json:"avg-daily-temp"` + BBUEnabled string `json:"enabled-state"` + BBUNeedBat bool `json:"ups-need-battery-replacement,string"` + BBULowBat bool `json:"is-low-battery-no-input,string"` + } +} + +type Clusters struct { + Content struct { + HardwarePlatform string `json:"hardware-platform"` + LicenseID string `json:"license-id"` + GUID string `json:"guid"` + Name string `json:"name"` + SerialNumber string `json:"sys-psnt-serial-number"` + CompressionFactor float64 `json:"compression-factor"` + MemoryUsed int `json:"total-memory-in-use-in-percent"` + ReadIops int `json:"rd-iops,string"` + WriteIops int `json:"wr-iops,string"` + NumVolumes int `json:"num-of-vols"` + FreeSSDSpace int `json:"free-ud-ssd-space-in-percent"` + NumSSDs int `json:"num-of-ssds"` + DataReductionRatio float64 `json:"data-reduction-ratio"` + } +} + +type SSD struct { + Content struct { + ModelName string `json:"model-name"` + FirmwareVersion string `json:"fw-version"` + SSDuid string `json:"ssd-uid"` + GUID string `json:"guid"` + SysName string `json:"sys-name"` + SerialNumber string `json:"serial-number"` + Size int `json:"ssd-size,string"` + SpaceUsed int `json:"ssd-space-in-use,string"` + WriteIops int `json:"wr-iops,string"` + ReadIops int `json:"rd-iops,string"` + WriteBandwidth int `json:"wr-bw,string"` + ReadBandwidth int `json:"rd-bw,string"` + NumBadSectors int `json:"num-bad-sectors"` + } +} + +type Volumes struct { + Content struct { + GUID string `json:"guid"` + SysName string `json:"sys-name"` + Name string `json:"name"` + ReadIops int `json:"rd-iops,string"` + WriteIops int `json:"wr-iops,string"` + ReadLatency int `json:"rd-latency,string"` + WriteLatency int `json:"wr-latency,string"` + DataReductionRatio float64 `json:"data-reduction-ratio,string"` + ProvisionedSpace int `json:"vol-size,string"` + UsedSpace int `json:"logical-space-in-use,string"` + } +} + +type XMS struct { + Content struct { + GUID string `json:"guid"` + Name string `json:"name"` + Version string `json:"version"` + IP string `json:"xms-ip"` + WriteIops int `json:"wr-iops,string"` + ReadIops int `json:"rd-iops,string"` + EfficiencyRatio float64 `json:"overall-efficiency-ratio,string"` + SpaceUsed int `json:"ssd-space-in-use,string"` + RAMUsage int `json:"ram-usage,string"` + RAMTotal int `json:"ram-total,string"` + CPUUsage float64 `json:"cpu"` + WriteLatency int `json:"wr-latency,string"` + ReadLatency int `json:"rd-latency,string"` + NumAccounts int `json:"num-of-user-accounts"` + } +} + +type HREF struct { + Href string `json:"href"` +} + +type CollectorResponse struct { + BBUs []HREF `json:"bbus"` + Clusters []HREF `json:"clusters"` + SSDs []HREF `json:"ssds"` + Volumes []HREF `json:"volumes"` + XMS []HREF `json:"xmss"` +}