├── .dockerignore ├── internal ├── handler │ ├── favicon.ico │ ├── exporter_with_context.go │ ├── index.go │ ├── index.html.gotmpl │ ├── middleware.go │ └── handler.go └── cli │ └── config │ ├── testdata │ └── valid_config.yml │ ├── token_map.go │ ├── config.go │ └── config_test.go ├── client ├── tools.go ├── generate.go ├── u_n_m_s_api_client.go ├── devices │ ├── devices_client.go │ ├── get_devices_id_statistics_parameters.go │ ├── get_devices_parameters.go │ ├── get_devices_responses.go │ └── get_devices_id_statistics_responses.go └── openapi-lite.json ├── renovate.json ├── .github ├── .kodiak.toml └── workflows │ ├── test.yml │ └── build-publish.yml ├── Dockerfile ├── main.go ├── exporter ├── internal_metrics.go ├── extra_metrics.go ├── device.go ├── ping_rtt.go ├── device_test.go └── exporter.go ├── models ├── coordinates_x_y.go ├── interface_status.go ├── interface_statistics.go ├── list_of_coordinates.go ├── latest_backup.go ├── error.go ├── interface_identification.go ├── device_statistics.go ├── semver_version.go ├── device_upgrade.go ├── semver.go ├── device_meta.go ├── device_firmware.go ├── device_overview.go ├── site.go ├── device_interface_schema.go ├── device_status_overview.go └── device_identification.go ├── LICENSE.md ├── go.mod └── README.md /.dockerignore: -------------------------------------------------------------------------------- 1 | Dockerfile 2 | -------------------------------------------------------------------------------- /internal/handler/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ffddorf/unms-exporter/HEAD/internal/handler/favicon.ico -------------------------------------------------------------------------------- /client/tools.go: -------------------------------------------------------------------------------- 1 | // +build tools 2 | 3 | package tools 4 | 5 | import _ "github.com/go-swagger/go-swagger/cmd/swagger" 6 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["config:base"], 3 | "postUpdateOptions": ["gomodTidy", "gomodUpdateImportPaths"] 4 | } 5 | -------------------------------------------------------------------------------- /.github/.kodiak.toml: -------------------------------------------------------------------------------- 1 | version = 1 2 | merge.blocking_labels = ["WIP", "blocked"] 3 | merge.notify_on_conflict = false 4 | approve.auto_approve_usernames = ["renovate"] 5 | -------------------------------------------------------------------------------- /client/generate.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | //go:generate go run -mod=mod github.com/go-swagger/go-swagger/cmd/swagger generate client --spec=openapi-lite.json --target=../ 4 | -------------------------------------------------------------------------------- /internal/cli/config/testdata/valid_config.yml: -------------------------------------------------------------------------------- 1 | log_level: 3 2 | listen: "[::1]:1234" 3 | token: 4 | a.example.com: abc 5 | extra_metrics: 6 | - ping 7 | - link 8 | - wifi 9 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.23-alpine AS build 2 | 3 | WORKDIR /src 4 | COPY go.mod go.sum ./ 5 | RUN go mod download 6 | 7 | ENV CGO_ENABLED=0 8 | 9 | COPY . ./ 10 | RUN go build -ldflags="-s -w" -trimpath -o unms-exporter main.go 11 | 12 | FROM alpine 13 | 14 | RUN apk add --no-cache tzdata ca-certificates 15 | COPY --from=build /src/unms-exporter /usr/local/bin/ 16 | 17 | CMD ["/usr/local/bin/unms-exporter"] 18 | -------------------------------------------------------------------------------- /internal/handler/exporter_with_context.go: -------------------------------------------------------------------------------- 1 | package handler 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/ffddorf/unms-exporter/exporter" 7 | prom "github.com/prometheus/client_golang/prometheus" 8 | ) 9 | 10 | type withContext struct { 11 | ctx context.Context 12 | exporter *exporter.Exporter 13 | } 14 | 15 | var _ prom.Collector = (*withContext)(nil) 16 | 17 | func (e *withContext) Describe(out chan<- *prom.Desc) { 18 | e.exporter.DescribeContext(e.ctx, out) 19 | } 20 | 21 | func (e *withContext) Collect(out chan<- prom.Metric) { 22 | e.exporter.CollectContext(e.ctx, out) 23 | } 24 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "net/http" 5 | "os" 6 | 7 | "github.com/ffddorf/unms-exporter/internal/cli/config" 8 | "github.com/ffddorf/unms-exporter/internal/handler" 9 | "github.com/sirupsen/logrus" 10 | ) 11 | 12 | func main() { 13 | log := logrus.New() 14 | conf, err := config.New(os.Args[1:]) 15 | if err != nil { 16 | log.WithError(err).Fatal("configuration failure") 17 | } 18 | 19 | log.SetLevel(conf.LogLevel) 20 | 21 | h, err := handler.New(log, conf) 22 | if err != nil { 23 | log.WithError(err).Fatal("failed to setup exporter") 24 | } 25 | h = handler.Logging(log, h) 26 | 27 | log.WithField("addr", conf.ServerAddr).Info("Server starting...") 28 | if err := http.ListenAndServe(conf.ServerAddr, h); err != nil { 29 | log.WithError(err).Warn("HTTP server failed") 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /internal/cli/config/token_map.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import "strings" 4 | 5 | type tokenMap map[string]string 6 | 7 | // Decode implements the envconfig.Decoder interface. Its purpose is to 8 | // parse the UNMS_EXPORTER_TOKEN environment variable, which takes the 9 | // following form: 10 | // 11 | // UNMS_EXPORTER_TOKEN ::= ("," )* 12 | // ::= "=" 13 | // 14 | // The "dns-name" follows common convention (for details, see RFC 1123). 15 | // The "token" is the API access token generated by UNMS and usually 16 | // takes the shape of a UUID. 17 | func (toks *tokenMap) Decode(v string) error { 18 | t := make(map[string]string) 19 | for _, pair := range strings.Split(v, ",") { 20 | kv := strings.SplitN(pair, "=", 2) 21 | t[kv[0]] = kv[1] 22 | } 23 | *toks = tokenMap(t) 24 | return nil 25 | } 26 | -------------------------------------------------------------------------------- /exporter/internal_metrics.go: -------------------------------------------------------------------------------- 1 | package exporter 2 | 3 | import ( 4 | prom "github.com/prometheus/client_golang/prometheus" 5 | ) 6 | 7 | type internalMetrics struct { 8 | success prom.Counter 9 | errors prom.Counter 10 | } 11 | 12 | func newInternalMetrics() internalMetrics { 13 | success := prom.NewCounter(prom.CounterOpts{ 14 | Namespace: namespace, 15 | Subsystem: "collector", 16 | Name: "success", 17 | Help: "Indicating if the scrape was successful", 18 | }) 19 | errors := prom.NewCounter(prom.CounterOpts{ 20 | Namespace: namespace, 21 | Subsystem: "collector", 22 | Name: "errors", 23 | Help: "Errors encountered while exporting metrics", 24 | }) 25 | 26 | return internalMetrics{success, errors} 27 | } 28 | 29 | func (m *internalMetrics) Describe(out chan<- *prom.Desc) { 30 | m.success.Describe(out) 31 | m.errors.Describe(out) 32 | } 33 | 34 | func (m *internalMetrics) Collect(out chan<- prom.Metric) { 35 | m.success.Collect(out) 36 | m.errors.Collect(out) 37 | } 38 | -------------------------------------------------------------------------------- /internal/handler/index.go: -------------------------------------------------------------------------------- 1 | package handler 2 | 3 | import ( 4 | "bytes" 5 | _ "embed" 6 | "html/template" 7 | "io" 8 | "net/http" 9 | "sort" 10 | ) 11 | 12 | //go:embed favicon.ico 13 | var faviconRaw []byte 14 | 15 | //go:embed index.html.gotmpl 16 | var indexRaw string 17 | var indexTpl = template.Must(template.New("index").Parse(indexRaw)) 18 | 19 | type indexVars struct { 20 | Instances []string 21 | } 22 | 23 | func (h *handler) getIndex(w http.ResponseWriter, r *http.Request) { 24 | var vars indexVars 25 | for name := range h.targets { 26 | vars.Instances = append(vars.Instances, name) 27 | } 28 | sort.Strings(vars.Instances) 29 | 30 | if err := indexTpl.Execute(w, vars); err != nil { 31 | h.log.WithError(err).Error("failed to serve index template") 32 | } 33 | } 34 | 35 | func (h *handler) getFavicon(w http.ResponseWriter, r *http.Request) { 36 | w.Header().Set("Content-Type", "image/vnd.microsoft.icon") 37 | w.Header().Set("X-Content-Type-Options", "nosniff") 38 | w.WriteHeader(http.StatusOK) 39 | 40 | ico := bytes.NewBuffer(faviconRaw) 41 | if _, err := io.Copy(w, ico); err != nil { 42 | h.log.WithError(err).Error("failed to serve favicon") 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /internal/handler/index.html.gotmpl: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | UNMS Exporter 8 | 17 | 18 | 19 |

UNMS Exporter

20 | 21 |
    22 |
  • 23 | Exporter metrics 24 |
  • 25 | {{ range .Instances }} 26 |
  • 27 | Target: {{.}} 28 |
  • 29 | {{ end }} 30 |
31 | 32 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /models/coordinates_x_y.go: -------------------------------------------------------------------------------- 1 | // Code generated by go-swagger; DO NOT EDIT. 2 | 3 | package models 4 | 5 | // This file was generated by the swagger tool. 6 | // Editing this file might prove futile when you re-run the swagger generate command 7 | 8 | import ( 9 | "context" 10 | 11 | "github.com/go-openapi/strfmt" 12 | "github.com/go-openapi/swag" 13 | ) 14 | 15 | // CoordinatesXY coordinates x y 16 | // 17 | // swagger:model CoordinatesXY. 18 | type CoordinatesXY struct { 19 | 20 | // x 21 | X float64 `json:"x,omitempty"` 22 | 23 | // y 24 | Y float64 `json:"y,omitempty"` 25 | } 26 | 27 | // Validate validates this coordinates x y 28 | func (m *CoordinatesXY) Validate(formats strfmt.Registry) error { 29 | return nil 30 | } 31 | 32 | // ContextValidate validates this coordinates x y based on context it is used 33 | func (m *CoordinatesXY) ContextValidate(ctx context.Context, formats strfmt.Registry) error { 34 | return nil 35 | } 36 | 37 | // MarshalBinary interface implementation 38 | func (m *CoordinatesXY) MarshalBinary() ([]byte, error) { 39 | if m == nil { 40 | return nil, nil 41 | } 42 | return swag.WriteJSON(m) 43 | } 44 | 45 | // UnmarshalBinary interface implementation 46 | func (m *CoordinatesXY) UnmarshalBinary(b []byte) error { 47 | var res CoordinatesXY 48 | if err := swag.ReadJSON(b, &res); err != nil { 49 | return err 50 | } 51 | *m = res 52 | return nil 53 | } 54 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright 2021 Freifunk Düsseldorf e.V. 2 | 3 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 4 | 5 | 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 6 | 7 | 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 8 | 9 | 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. 10 | 11 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 12 | -------------------------------------------------------------------------------- /internal/handler/middleware.go: -------------------------------------------------------------------------------- 1 | package handler 2 | 3 | import ( 4 | "net/http" 5 | "time" 6 | 7 | "github.com/sirupsen/logrus" 8 | ) 9 | 10 | type logWrapper struct { 11 | size int 12 | status int 13 | start time.Time 14 | http.ResponseWriter 15 | } 16 | 17 | func (lw *logWrapper) WriteHeader(code int) { 18 | lw.status = code 19 | lw.ResponseWriter.WriteHeader(code) 20 | } 21 | 22 | func (lw *logWrapper) Write(b []byte) (int, error) { 23 | if lw.status == 0 { 24 | // promhttp does not call WriteHeader 25 | lw.status = http.StatusOK 26 | } 27 | n, err := lw.ResponseWriter.Write(b) 28 | lw.size += n 29 | return n, err 30 | } 31 | 32 | func (lw *logWrapper) Log(logger logrus.FieldLogger, method, url string) { 33 | log := logger.WithFields(logrus.Fields{ 34 | "url": url, 35 | "method": method, 36 | "duration": time.Since(lw.start), 37 | "size": lw.size, 38 | "status": lw.status, 39 | }) 40 | switch s := lw.status; { 41 | case 100 <= s && s <= 299: 42 | log.Debug("served request") 43 | case 300 <= s && s <= 399: 44 | log.Info("served request") 45 | case 400 <= s && s <= 499: 46 | log.Warn("served request") 47 | default: // s < 100 or s >= 500: 48 | log.Error("served request") 49 | } 50 | } 51 | 52 | func Logging(logger logrus.FieldLogger, next http.Handler) http.Handler { 53 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 54 | capture := &logWrapper{ 55 | start: time.Now(), 56 | ResponseWriter: w, 57 | } 58 | defer capture.Log(logger, r.Method, r.URL.String()) 59 | 60 | next.ServeHTTP(capture, r) 61 | }) 62 | } 63 | -------------------------------------------------------------------------------- /exporter/extra_metrics.go: -------------------------------------------------------------------------------- 1 | package exporter 2 | 3 | import "fmt" 4 | 5 | // ExtraMetrics is used to instruct Exporter to fetch additional metrics, 6 | // not captured by the UNMS API /devices endpoint. 7 | // 8 | // These metrics may require extra HTTP requests, usually one per device, 9 | // so it might not be desirable to have them included by default. 10 | // 11 | // Expect this type to gain additional fields in the future. Currently, 12 | // enabling ping metrics will also fetch (but discard) additional metrics 13 | // from the /devices/{id}/statitics API endpoint, like temperature, link 14 | // capacity, and many more values. 15 | type ExtraMetrics struct { 16 | Ping bool 17 | } 18 | 19 | var pingMetrics = map[string]metricSpec{ 20 | "ping_loss_ratio": newSpec("Ping packet loss ratio", nil), 21 | "ping_rtt_best_seconds": newSpec("Best ping round trip time in seconds", nil), 22 | "ping_rtt_mean_seconds": newSpec("Mean ping round trip time in seconds", nil), 23 | "ping_rtt_worst_seconds": newSpec("Worst ping round trip time in seconds", nil), 24 | "ping_rtt_std_deviation_seconds": newSpec("Standard deviation for ping round trip time in seconds", nil), 25 | } 26 | 27 | func (e *Exporter) SetExtras(extras []string) error { 28 | e.extras = ExtraMetrics{} // reset all values 29 | for _, x := range extras { 30 | switch x { 31 | case "ping": 32 | e.extras.Ping = true 33 | default: 34 | return fmt.Errorf("unknown extra metric: %q", x) 35 | } 36 | } 37 | 38 | for name, spec := range pingMetrics { 39 | if _, exists := e.metrics[name]; !exists && e.extras.Ping { 40 | e.metrics[name] = spec.intoDesc(name) 41 | } else if !e.extras.Ping { 42 | delete(e.metrics, name) 43 | } 44 | } 45 | 46 | return nil 47 | } 48 | -------------------------------------------------------------------------------- /models/interface_status.go: -------------------------------------------------------------------------------- 1 | // Code generated by go-swagger; DO NOT EDIT. 2 | 3 | package models 4 | 5 | // This file was generated by the swagger tool. 6 | // Editing this file might prove futile when you re-run the swagger generate command 7 | 8 | import ( 9 | "context" 10 | 11 | "github.com/go-openapi/strfmt" 12 | "github.com/go-openapi/swag" 13 | ) 14 | 15 | // InterfaceStatus interface status 16 | // 17 | // swagger:model InterfaceStatus 18 | type InterfaceStatus struct { 19 | 20 | // current speed 21 | // Example: 1000-full 22 | CurrentSpeed string `json:"currentSpeed,omitempty"` 23 | 24 | // description 25 | // Example: 1 Gbps - Full Duplex 26 | Description string `json:"description,omitempty"` 27 | 28 | // plugged 29 | // Example: true 30 | Plugged bool `json:"plugged,omitempty"` 31 | 32 | // speed 33 | // Example: auto 34 | Speed string `json:"speed,omitempty"` 35 | 36 | // status 37 | // Example: active 38 | Status string `json:"status,omitempty"` 39 | } 40 | 41 | // Validate validates this interface status 42 | func (m *InterfaceStatus) Validate(formats strfmt.Registry) error { 43 | return nil 44 | } 45 | 46 | // ContextValidate validates this interface status based on context it is used 47 | func (m *InterfaceStatus) ContextValidate(ctx context.Context, formats strfmt.Registry) error { 48 | return nil 49 | } 50 | 51 | // MarshalBinary interface implementation 52 | func (m *InterfaceStatus) MarshalBinary() ([]byte, error) { 53 | if m == nil { 54 | return nil, nil 55 | } 56 | return swag.WriteJSON(m) 57 | } 58 | 59 | // UnmarshalBinary interface implementation 60 | func (m *InterfaceStatus) UnmarshalBinary(b []byte) error { 61 | var res InterfaceStatus 62 | if err := swag.ReadJSON(b, &res); err != nil { 63 | return err 64 | } 65 | *m = res 66 | return nil 67 | } 68 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test, Lint 2 | 3 | on: 4 | push: 5 | pull_request_target: 6 | 7 | env: 8 | GO_VERSION: "1.21" 9 | 10 | jobs: 11 | test: 12 | name: Run tests 13 | runs-on: ubuntu-latest 14 | 15 | if: ${{ github.event_name == 'push' || github.event.pull_request.base.repo.full_name != github.event.pull_request.head.repo.full_name }} 16 | 17 | steps: 18 | - name: Setup Go ${{ env.GO_VERSION }} 19 | uses: actions/setup-go@v5 20 | with: 21 | go-version: ${{ env.GO_VERSION }} 22 | 23 | - name: Checkout code 24 | uses: actions/checkout@v4 25 | 26 | - name: Cache Go modules 27 | uses: actions/cache@v4.2.0 28 | with: 29 | path: ~/go/pkg/mod 30 | key: ${{ runner.os }}-${{ env.GO_VERSION }}-go-${{ hashFiles('**/go.sum') }} 31 | restore-keys: | 32 | ${{ runner.os }}-${{ env.GO_VERSION }}-go- 33 | 34 | - name: Run tests 35 | run: go test -race -covermode=atomic ./... 36 | 37 | lint: 38 | name: Run linter 39 | runs-on: ubuntu-latest 40 | 41 | if: ${{ github.event_name == 'push' || github.event.pull_request.base.repo.full_name != github.event.pull_request.head.repo.full_name }} 42 | 43 | steps: 44 | - name: Setup Go ${{ env.GO_VERSION }} 45 | uses: actions/setup-go@v5 46 | with: 47 | go-version: ${{ env.GO_VERSION }} 48 | 49 | - name: Checkout code 50 | uses: actions/checkout@v4 51 | 52 | - name: Cache Go modules 53 | uses: actions/cache@v4.2.0 54 | with: 55 | path: ~/go/pkg/mod 56 | key: ${{ runner.os }}-${{ env.GO_VERSION }}-go-${{ hashFiles('**/go.sum') }} 57 | restore-keys: | 58 | ${{ runner.os }}-${{ env.GO_VERSION }}-go- 59 | 60 | - name: Run linter 61 | uses: golangci/golangci-lint-action@v6 62 | with: 63 | version: latest 64 | skip-cache: true 65 | -------------------------------------------------------------------------------- /exporter/device.go: -------------------------------------------------------------------------------- 1 | package exporter 2 | 3 | import ( 4 | "context" 5 | "time" 6 | 7 | "github.com/ffddorf/unms-exporter/client/devices" 8 | "github.com/ffddorf/unms-exporter/models" 9 | ) 10 | 11 | var defaultWithInterfaces = true 12 | 13 | type Device struct { 14 | Statistics *models.DeviceStatistics 15 | *models.DeviceStatusOverview 16 | } 17 | 18 | func (e *Exporter) fetchDeviceData(ctx context.Context) ([]Device, error) { 19 | ctx, cancel := context.WithTimeout(ctx, 30*time.Second) 20 | defer cancel() 21 | 22 | params := &devices.GetDevicesParams{ 23 | WithInterfaces: &defaultWithInterfaces, 24 | Context: ctx, 25 | } 26 | devicesResponse, err := e.api.Devices.GetDevices(params) 27 | if err != nil { 28 | return nil, err 29 | } 30 | 31 | data := make([]Device, 0, len(devicesResponse.Payload)) 32 | for _, overview := range devicesResponse.Payload { 33 | if overview.Identification == nil { 34 | continue 35 | } 36 | dev := Device{nil, overview} 37 | 38 | if e.extras.Ping { 39 | if id := derefOrEmpty(overview.Identification.ID); id != "" { 40 | params := &devices.GetDevicesIDStatisticsParams{ 41 | ID: id, 42 | Interval: "hour", // smallest interval possible 43 | Context: ctx, 44 | } 45 | statisticsResponse, err := e.api.Devices.GetDevicesIDStatistics(params) 46 | if err != nil { 47 | return nil, err 48 | } 49 | dev.Statistics = statisticsResponse.Payload 50 | } 51 | } 52 | data = append(data, dev) 53 | } 54 | 55 | return data, nil 56 | } 57 | 58 | func (dev *Device) PingMetrics() *PingMetrics { 59 | if dev.Statistics == nil || len(dev.Statistics.Ping) == 0 { 60 | return nil 61 | } 62 | 63 | m := NewHistory(len(dev.Statistics.Ping)) 64 | for _, xy := range dev.Statistics.Ping { 65 | if xy == nil { 66 | m.Add(0, true) 67 | continue 68 | } 69 | 70 | rtt := time.Duration(xy.Y * float64(time.Millisecond)) 71 | m.Add(rtt, false) 72 | } 73 | 74 | return m.Compute() 75 | } 76 | -------------------------------------------------------------------------------- /models/interface_statistics.go: -------------------------------------------------------------------------------- 1 | // Code generated by go-swagger; DO NOT EDIT. 2 | 3 | package models 4 | 5 | // This file was generated by the swagger tool. 6 | // Editing this file might prove futile when you re-run the swagger generate command 7 | 8 | import ( 9 | "context" 10 | 11 | "github.com/go-openapi/strfmt" 12 | "github.com/go-openapi/swag" 13 | ) 14 | 15 | // InterfaceStatistics interface statistics 16 | // 17 | // swagger:model InterfaceStatistics 18 | type InterfaceStatistics struct { 19 | 20 | // dropped 21 | // Example: 0 22 | Dropped float64 `json:"dropped,omitempty"` 23 | 24 | // errors 25 | // Example: 0 26 | Errors float64 `json:"errors,omitempty"` 27 | 28 | // poe power 29 | // Example: 736 30 | PoePower float64 `json:"poePower,omitempty"` 31 | 32 | // rxbytes 33 | // Example: 7487858302 34 | Rxbytes float64 `json:"rxbytes,omitempty"` 35 | 36 | // rxrate 37 | // Example: 3440 38 | Rxrate float64 `json:"rxrate,omitempty"` 39 | 40 | // txbytes 41 | // Example: 368737600 42 | Txbytes float64 `json:"txbytes,omitempty"` 43 | 44 | // txrate 45 | // Example: 736 46 | Txrate float64 `json:"txrate,omitempty"` 47 | } 48 | 49 | // Validate validates this interface statistics 50 | func (m *InterfaceStatistics) Validate(formats strfmt.Registry) error { 51 | return nil 52 | } 53 | 54 | // ContextValidate validates this interface statistics based on context it is used 55 | func (m *InterfaceStatistics) ContextValidate(ctx context.Context, formats strfmt.Registry) error { 56 | return nil 57 | } 58 | 59 | // MarshalBinary interface implementation 60 | func (m *InterfaceStatistics) MarshalBinary() ([]byte, error) { 61 | if m == nil { 62 | return nil, nil 63 | } 64 | return swag.WriteJSON(m) 65 | } 66 | 67 | // UnmarshalBinary interface implementation 68 | func (m *InterfaceStatistics) UnmarshalBinary(b []byte) error { 69 | var res InterfaceStatistics 70 | if err := swag.ReadJSON(b, &res); err != nil { 71 | return err 72 | } 73 | *m = res 74 | return nil 75 | } 76 | -------------------------------------------------------------------------------- /models/list_of_coordinates.go: -------------------------------------------------------------------------------- 1 | // Code generated by go-swagger; DO NOT EDIT. 2 | 3 | package models 4 | 5 | // This file was generated by the swagger tool. 6 | // Editing this file might prove futile when you re-run the swagger generate command 7 | 8 | import ( 9 | "context" 10 | "strconv" 11 | 12 | "github.com/go-openapi/errors" 13 | "github.com/go-openapi/strfmt" 14 | "github.com/go-openapi/swag" 15 | ) 16 | 17 | // ListOfCoordinates list of coordinates 18 | // 19 | // swagger:model ListOfCoordinates 20 | type ListOfCoordinates []*CoordinatesXY 21 | 22 | // Validate validates this list of coordinates 23 | func (m ListOfCoordinates) Validate(formats strfmt.Registry) error { 24 | var res []error 25 | 26 | for i := 0; i < len(m); i++ { 27 | if swag.IsZero(m[i]) { // not required 28 | continue 29 | } 30 | 31 | if m[i] != nil { 32 | if err := m[i].Validate(formats); err != nil { 33 | if ve, ok := err.(*errors.Validation); ok { 34 | return ve.ValidateName(strconv.Itoa(i)) 35 | } else if ce, ok := err.(*errors.CompositeError); ok { 36 | return ce.ValidateName(strconv.Itoa(i)) 37 | } 38 | return err 39 | } 40 | } 41 | 42 | } 43 | 44 | if len(res) > 0 { 45 | return errors.CompositeValidationError(res...) 46 | } 47 | return nil 48 | } 49 | 50 | // ContextValidate validate this list of coordinates based on the context it is used 51 | func (m ListOfCoordinates) ContextValidate(ctx context.Context, formats strfmt.Registry) error { 52 | var res []error 53 | 54 | for i := 0; i < len(m); i++ { 55 | 56 | if m[i] != nil { 57 | 58 | if swag.IsZero(m[i]) { // not required 59 | return nil 60 | } 61 | 62 | if err := m[i].ContextValidate(ctx, formats); err != nil { 63 | if ve, ok := err.(*errors.Validation); ok { 64 | return ve.ValidateName(strconv.Itoa(i)) 65 | } else if ce, ok := err.(*errors.CompositeError); ok { 66 | return ce.ValidateName(strconv.Itoa(i)) 67 | } 68 | return err 69 | } 70 | } 71 | 72 | } 73 | 74 | if len(res) > 0 { 75 | return errors.CompositeValidationError(res...) 76 | } 77 | return nil 78 | } 79 | -------------------------------------------------------------------------------- /models/latest_backup.go: -------------------------------------------------------------------------------- 1 | // Code generated by go-swagger; DO NOT EDIT. 2 | 3 | package models 4 | 5 | // This file was generated by the swagger tool. 6 | // Editing this file might prove futile when you re-run the swagger generate command 7 | 8 | import ( 9 | "context" 10 | 11 | "github.com/go-openapi/errors" 12 | "github.com/go-openapi/strfmt" 13 | "github.com/go-openapi/swag" 14 | "github.com/go-openapi/validate" 15 | ) 16 | 17 | // LatestBackup Latest backup info. 18 | // 19 | // swagger:model latestBackup 20 | type LatestBackup struct { 21 | 22 | // Latest backup ID. 23 | // Required: true 24 | ID *string `json:"id"` 25 | 26 | // Latest backup timestamp. 27 | // Example: 2018-11-14T15:20:32.004Z 28 | // Required: true 29 | // Format: date-time 30 | Timestamp *strfmt.DateTime `json:"timestamp"` 31 | } 32 | 33 | // Validate validates this latest backup 34 | func (m *LatestBackup) Validate(formats strfmt.Registry) error { 35 | var res []error 36 | 37 | if err := m.validateID(formats); err != nil { 38 | res = append(res, err) 39 | } 40 | 41 | if err := m.validateTimestamp(formats); err != nil { 42 | res = append(res, err) 43 | } 44 | 45 | if len(res) > 0 { 46 | return errors.CompositeValidationError(res...) 47 | } 48 | return nil 49 | } 50 | 51 | func (m *LatestBackup) validateID(formats strfmt.Registry) error { 52 | 53 | if err := validate.Required("id", "body", m.ID); err != nil { 54 | return err 55 | } 56 | 57 | return nil 58 | } 59 | 60 | func (m *LatestBackup) validateTimestamp(formats strfmt.Registry) error { 61 | 62 | if err := validate.Required("timestamp", "body", m.Timestamp); err != nil { 63 | return err 64 | } 65 | 66 | if err := validate.FormatOf("timestamp", "body", "date-time", m.Timestamp.String(), formats); err != nil { 67 | return err 68 | } 69 | 70 | return nil 71 | } 72 | 73 | // ContextValidate validates this latest backup based on context it is used 74 | func (m *LatestBackup) ContextValidate(ctx context.Context, formats strfmt.Registry) error { 75 | return nil 76 | } 77 | 78 | // MarshalBinary interface implementation 79 | func (m *LatestBackup) MarshalBinary() ([]byte, error) { 80 | if m == nil { 81 | return nil, nil 82 | } 83 | return swag.WriteJSON(m) 84 | } 85 | 86 | // UnmarshalBinary interface implementation 87 | func (m *LatestBackup) UnmarshalBinary(b []byte) error { 88 | var res LatestBackup 89 | if err := swag.ReadJSON(b, &res); err != nil { 90 | return err 91 | } 92 | *m = res 93 | return nil 94 | } 95 | -------------------------------------------------------------------------------- /internal/cli/config/config.go: -------------------------------------------------------------------------------- 1 | // Package config implements CLI configuration management. 2 | package config 3 | 4 | import ( 5 | "errors" 6 | "fmt" 7 | 8 | "github.com/kelseyhightower/envconfig" 9 | "github.com/sirupsen/logrus" 10 | "github.com/spf13/pflag" 11 | "github.com/spf13/viper" 12 | ) 13 | 14 | const envPrefix = "UNMS_EXPORTER" 15 | 16 | var ( 17 | DefaultServerAddress = "[::]:9806" 18 | DefaultLogLevel = logrus.InfoLevel 19 | ) 20 | 21 | type Config struct { 22 | ServerAddr string `mapstructure:"listen" split_words:"true"` 23 | LogLevel logrus.Level `mapstructure:"log_level" split_words:"true"` 24 | TokenPerHost tokenMap `mapstructure:"token" envconfig:"token"` 25 | ExtraMetrics []string `mapstructure:"extra_metrics" split_words:"true"` 26 | } 27 | 28 | func New(args []string) (*Config, error) { 29 | conf := &Config{ 30 | ServerAddr: DefaultServerAddress, 31 | LogLevel: DefaultLogLevel, 32 | } 33 | if err := envconfig.Process(envPrefix, conf); err != nil { 34 | return nil, fmt.Errorf("invalid environment variables: %w", err) 35 | } 36 | 37 | flags := pflag.NewFlagSet("unms_exporter", pflag.ContinueOnError) 38 | flags.StringP("listen", "l", conf.ServerAddr, "Address for the exporter to listen on") 39 | flags.StringP("config", "c", "", "Config file to use") 40 | flags.StringSliceVar(&conf.ExtraMetrics, "extra-metrics", conf.ExtraMetrics, "Enable additional metrics") 41 | if err := flags.Parse(args); err != nil { 42 | return nil, fmt.Errorf("failed to parse flags: %w", err) 43 | } 44 | 45 | v := viper.NewWithOptions(viper.KeyDelimiter("::")) 46 | if err := v.BindPFlags(flags); err != nil { 47 | // this should not happen 48 | return nil, fmt.Errorf("invalid config setup: %w", err) 49 | } 50 | 51 | if path := v.GetString("config"); path != "" { 52 | v.SetConfigFile(path) 53 | if err := v.ReadInConfig(); err != nil { 54 | return nil, fmt.Errorf("invalid config file %q: %w", path, err) 55 | } 56 | } 57 | if err := v.Unmarshal(conf); err != nil { 58 | return nil, fmt.Errorf("invalid command line flags: %w", err) 59 | } 60 | if err := conf.validate(); err != nil { 61 | return nil, fmt.Errorf("invalid config settings: %w", err) 62 | } 63 | 64 | return conf, nil 65 | } 66 | 67 | func (c *Config) validate() error { 68 | if len(c.TokenPerHost) < 1 { 69 | return errors.New("No token configured") 70 | } 71 | if c.ServerAddr == "" { 72 | return errors.New("Server addr can't be blank") 73 | } 74 | return nil 75 | } 76 | -------------------------------------------------------------------------------- /models/error.go: -------------------------------------------------------------------------------- 1 | // Code generated by go-swagger; DO NOT EDIT. 2 | 3 | package models 4 | 5 | // This file was generated by the swagger tool. 6 | // Editing this file might prove futile when you re-run the swagger generate command 7 | 8 | import ( 9 | "context" 10 | 11 | "github.com/go-openapi/errors" 12 | "github.com/go-openapi/strfmt" 13 | "github.com/go-openapi/swag" 14 | "github.com/go-openapi/validate" 15 | ) 16 | 17 | // Error error 18 | // 19 | // swagger:model Error 20 | type Error struct { 21 | 22 | // error 23 | // Required: true 24 | Error *string `json:"error"` 25 | 26 | // message 27 | Message string `json:"message,omitempty"` 28 | 29 | // status code 30 | // Required: true 31 | // Maximum: 599 32 | // Minimum: 400 33 | StatusCode *float64 `json:"statusCode"` 34 | 35 | // validation 36 | Validation interface{} `json:"validation,omitempty"` 37 | } 38 | 39 | // Validate validates this error 40 | func (m *Error) Validate(formats strfmt.Registry) error { 41 | var res []error 42 | 43 | if err := m.validateError(formats); err != nil { 44 | res = append(res, err) 45 | } 46 | 47 | if err := m.validateStatusCode(formats); err != nil { 48 | res = append(res, err) 49 | } 50 | 51 | if len(res) > 0 { 52 | return errors.CompositeValidationError(res...) 53 | } 54 | return nil 55 | } 56 | 57 | func (m *Error) validateError(formats strfmt.Registry) error { 58 | 59 | if err := validate.Required("error", "body", m.Error); err != nil { 60 | return err 61 | } 62 | 63 | return nil 64 | } 65 | 66 | func (m *Error) validateStatusCode(formats strfmt.Registry) error { 67 | 68 | if err := validate.Required("statusCode", "body", m.StatusCode); err != nil { 69 | return err 70 | } 71 | 72 | if err := validate.Minimum("statusCode", "body", *m.StatusCode, 400, false); err != nil { 73 | return err 74 | } 75 | 76 | if err := validate.Maximum("statusCode", "body", *m.StatusCode, 599, false); err != nil { 77 | return err 78 | } 79 | 80 | return nil 81 | } 82 | 83 | // ContextValidate validates this error based on context it is used 84 | func (m *Error) ContextValidate(ctx context.Context, formats strfmt.Registry) error { 85 | return nil 86 | } 87 | 88 | // MarshalBinary interface implementation 89 | func (m *Error) MarshalBinary() ([]byte, error) { 90 | if m == nil { 91 | return nil, nil 92 | } 93 | return swag.WriteJSON(m) 94 | } 95 | 96 | // UnmarshalBinary interface implementation 97 | func (m *Error) UnmarshalBinary(b []byte) error { 98 | var res Error 99 | if err := swag.ReadJSON(b, &res); err != nil { 100 | return err 101 | } 102 | *m = res 103 | return nil 104 | } 105 | -------------------------------------------------------------------------------- /models/interface_identification.go: -------------------------------------------------------------------------------- 1 | // Code generated by go-swagger; DO NOT EDIT. 2 | 3 | package models 4 | 5 | // This file was generated by the swagger tool. 6 | // Editing this file might prove futile when you re-run the swagger generate command 7 | 8 | import ( 9 | "context" 10 | 11 | "github.com/go-openapi/errors" 12 | "github.com/go-openapi/strfmt" 13 | "github.com/go-openapi/swag" 14 | "github.com/go-openapi/validate" 15 | ) 16 | 17 | // InterfaceIdentification interface identification 18 | // 19 | // swagger:model InterfaceIdentification 20 | type InterfaceIdentification struct { 21 | 22 | // Nullable string. 23 | // Example: Uplink 24 | Description *string `json:"description,omitempty"` 25 | 26 | // Computed display name from name and description 27 | // Example: eth0 28 | DisplayName string `json:"displayName,omitempty"` 29 | 30 | // mac 31 | // Example: fc:ec:da:03:bb:a8 32 | // Pattern: ^([0-9a-fA-F][0-9a-fA-F]:){5}([0-9a-fA-F][0-9a-fA-F])$|^([0-9a-fA-F]){12}$ 33 | Mac string `json:"mac,omitempty"` 34 | 35 | // Interface name. 36 | // Example: eth0 37 | Name string `json:"name,omitempty"` 38 | 39 | // Physical port position. 40 | // Example: 0 41 | Position int64 `json:"position,omitempty"` 42 | 43 | // type 44 | // Example: eth 45 | Type string `json:"type,omitempty"` 46 | } 47 | 48 | // Validate validates this interface identification 49 | func (m *InterfaceIdentification) Validate(formats strfmt.Registry) error { 50 | var res []error 51 | 52 | if err := m.validateMac(formats); err != nil { 53 | res = append(res, err) 54 | } 55 | 56 | if len(res) > 0 { 57 | return errors.CompositeValidationError(res...) 58 | } 59 | return nil 60 | } 61 | 62 | func (m *InterfaceIdentification) validateMac(formats strfmt.Registry) error { 63 | if swag.IsZero(m.Mac) { // not required 64 | return nil 65 | } 66 | 67 | if err := validate.Pattern("mac", "body", m.Mac, `^([0-9a-fA-F][0-9a-fA-F]:){5}([0-9a-fA-F][0-9a-fA-F])$|^([0-9a-fA-F]){12}$`); err != nil { 68 | return err 69 | } 70 | 71 | return nil 72 | } 73 | 74 | // ContextValidate validates this interface identification based on context it is used 75 | func (m *InterfaceIdentification) ContextValidate(ctx context.Context, formats strfmt.Registry) error { 76 | return nil 77 | } 78 | 79 | // MarshalBinary interface implementation 80 | func (m *InterfaceIdentification) MarshalBinary() ([]byte, error) { 81 | if m == nil { 82 | return nil, nil 83 | } 84 | return swag.WriteJSON(m) 85 | } 86 | 87 | // UnmarshalBinary interface implementation 88 | func (m *InterfaceIdentification) UnmarshalBinary(b []byte) error { 89 | var res InterfaceIdentification 90 | if err := swag.ReadJSON(b, &res); err != nil { 91 | return err 92 | } 93 | *m = res 94 | return nil 95 | } 96 | -------------------------------------------------------------------------------- /exporter/ping_rtt.go: -------------------------------------------------------------------------------- 1 | package exporter 2 | 3 | import ( 4 | "math" 5 | "sort" 6 | "time" 7 | ) 8 | 9 | // PingMetrics is a dumb data point computed from a list of PingResults. 10 | type PingMetrics struct { 11 | PacketsSent int // number of packets sent 12 | PacketsLost int // number of packets lost 13 | Best time.Duration // best RTT 14 | Worst time.Duration // worst RTT 15 | Median time.Duration // median RTT 16 | Mean time.Duration // mean RTT 17 | StdDev time.Duration // RTT std deviation 18 | } 19 | 20 | // PingResult stores the information about a single ping, in particular 21 | // the round-trip time or whether the packet was lost. 22 | type PingResult struct { 23 | RTT time.Duration 24 | Lost bool 25 | } 26 | 27 | // PingHistory represents the ping history for a single node/device. 28 | type PingHistory []PingResult 29 | 30 | // NewHistory creates a new History object with a specific capacity. 31 | func NewHistory(capacity int) PingHistory { 32 | return make(PingHistory, 0, capacity) 33 | } 34 | 35 | // AddResult saves a ping result into the internal history. 36 | func (h *PingHistory) Add(rtt time.Duration, lost bool) { 37 | *h = append(*h, PingResult{RTT: rtt, Lost: lost}) 38 | } 39 | 40 | // Compute aggregates the result history into a single data point. 41 | func (h PingHistory) Compute() *PingMetrics { 42 | numFailure := 0 43 | numTotal := len(h) 44 | 45 | if numTotal == 0 { 46 | return nil 47 | } 48 | 49 | data := make([]float64, 0, numTotal) 50 | var best, worst, mean, stddev, total, sumSquares float64 51 | 52 | for _, curr := range h { 53 | if curr.Lost { 54 | numFailure++ 55 | continue 56 | } 57 | 58 | rtt := curr.RTT.Seconds() 59 | if rtt < best || len(data) == 0 { 60 | best = rtt 61 | } 62 | if rtt > worst || len(data) == 0 { 63 | worst = rtt 64 | } 65 | data = append(data, rtt) 66 | total += rtt 67 | } 68 | 69 | size := float64(numTotal - numFailure) 70 | mean = total / size 71 | for _, rtt := range data { 72 | sumSquares += math.Pow(rtt-mean, 2) 73 | } 74 | stddev = math.Sqrt(sumSquares / size) 75 | 76 | median := math.NaN() 77 | if l := len(data); l > 0 { 78 | sort.Float64Slice(data).Sort() 79 | if l%2 == 0 { 80 | median = (data[l/2-1] + data[l/2]) / 2 81 | } else { 82 | median = data[l/2] 83 | } 84 | } 85 | 86 | return &PingMetrics{ 87 | PacketsSent: numTotal, 88 | PacketsLost: numFailure, 89 | Best: time.Duration(best * float64(time.Second)), 90 | Worst: time.Duration(worst * float64(time.Second)), 91 | Median: time.Duration(median * float64(time.Second)), 92 | Mean: time.Duration(mean * float64(time.Second)), 93 | StdDev: time.Duration(stddev * float64(time.Second)), 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /internal/handler/handler.go: -------------------------------------------------------------------------------- 1 | package handler 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | "strings" 7 | 8 | "github.com/ffddorf/unms-exporter/exporter" 9 | "github.com/ffddorf/unms-exporter/internal/cli/config" 10 | "github.com/prometheus/client_golang/prometheus" 11 | "github.com/prometheus/client_golang/prometheus/collectors" 12 | "github.com/prometheus/client_golang/prometheus/promhttp" 13 | "github.com/sirupsen/logrus" 14 | ) 15 | 16 | type handler struct { 17 | base *prometheus.Registry 18 | targets map[string]*exporter.Exporter 19 | log logrus.FieldLogger 20 | } 21 | 22 | func New(logger logrus.FieldLogger, cfg *config.Config) (http.Handler, error) { 23 | reg := prometheus.NewPedanticRegistry() 24 | reg.MustRegister( 25 | collectors.NewBuildInfoCollector(), 26 | collectors.NewGoCollector(), 27 | ) 28 | 29 | exporters := make(map[string]*exporter.Exporter) 30 | for host, token := range cfg.TokenPerHost { 31 | host := strings.ToLower(host) 32 | exporter := exporter.New(logger, host, token) 33 | if err := exporter.SetExtras(cfg.ExtraMetrics); err != nil { 34 | return nil, err 35 | } 36 | exporters[host] = exporter 37 | } 38 | 39 | return &handler{ 40 | base: reg, 41 | targets: exporters, 42 | log: logger.WithField("component", "exporter"), 43 | }, nil 44 | } 45 | 46 | // ServeHTTP realizes a very rudimentary routing. 47 | func (h *handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { 48 | if r.Method != http.MethodGet { 49 | h.errorResponse(w, http.StatusMethodNotAllowed) 50 | return 51 | } 52 | 53 | switch r.URL.Path { 54 | case "/": 55 | h.getIndex(w, r) 56 | case "/metrics": 57 | h.getMetrics(w, r) 58 | case "/favicon.ico": 59 | h.getFavicon(w, r) 60 | default: 61 | http.NotFound(w, r) 62 | } 63 | } 64 | 65 | func (h *handler) errorResponse(w http.ResponseWriter, code int) { 66 | text := fmt.Sprintf("%d %s", code, http.StatusText(code)) 67 | http.Error(w, text, code) 68 | } 69 | 70 | func (h *handler) getMetrics(w http.ResponseWriter, r *http.Request) { 71 | target := r.URL.Query().Get("target") 72 | log := h.log 73 | reg := h.base 74 | 75 | if target != "" { // /metrics?target= 76 | exporter, ok := h.targets[target] 77 | if !ok { 78 | h.errorResponse(w, http.StatusNotFound) 79 | return 80 | } 81 | 82 | log = log.WithField("target", target) 83 | reg = prometheus.NewPedanticRegistry() 84 | withCtx := withContext{ 85 | ctx: r.Context(), 86 | exporter: exporter, 87 | } 88 | if err := reg.Register(&withCtx); err != nil { 89 | log.WithError(err).Error("invalid exporter") 90 | h.errorResponse(w, http.StatusInternalServerError) 91 | } 92 | } 93 | 94 | promhttp.HandlerFor(reg, promhttp.HandlerOpts{ 95 | ErrorLog: log, 96 | }).ServeHTTP(w, r) 97 | } 98 | -------------------------------------------------------------------------------- /models/device_statistics.go: -------------------------------------------------------------------------------- 1 | // Code generated by go-swagger; DO NOT EDIT. 2 | 3 | package models 4 | 5 | // This file was generated by the swagger tool. 6 | // Editing this file might prove futile when you re-run the swagger generate command 7 | 8 | import ( 9 | "context" 10 | 11 | "github.com/go-openapi/errors" 12 | "github.com/go-openapi/strfmt" 13 | "github.com/go-openapi/swag" 14 | ) 15 | 16 | // DeviceStatistics device statistics 17 | // 18 | // swagger:model DeviceStatistics 19 | type DeviceStatistics struct { 20 | 21 | // ping 22 | Ping ListOfCoordinates `json:"ping,omitempty"` 23 | } 24 | 25 | // Validate validates this device statistics 26 | func (m *DeviceStatistics) Validate(formats strfmt.Registry) error { 27 | var res []error 28 | 29 | if err := m.validatePing(formats); err != nil { 30 | res = append(res, err) 31 | } 32 | 33 | if len(res) > 0 { 34 | return errors.CompositeValidationError(res...) 35 | } 36 | return nil 37 | } 38 | 39 | func (m *DeviceStatistics) validatePing(formats strfmt.Registry) error { 40 | if swag.IsZero(m.Ping) { // not required 41 | return nil 42 | } 43 | 44 | if err := m.Ping.Validate(formats); err != nil { 45 | if ve, ok := err.(*errors.Validation); ok { 46 | return ve.ValidateName("ping") 47 | } else if ce, ok := err.(*errors.CompositeError); ok { 48 | return ce.ValidateName("ping") 49 | } 50 | return err 51 | } 52 | 53 | return nil 54 | } 55 | 56 | // ContextValidate validate this device statistics based on the context it is used 57 | func (m *DeviceStatistics) ContextValidate(ctx context.Context, formats strfmt.Registry) error { 58 | var res []error 59 | 60 | if err := m.contextValidatePing(ctx, formats); err != nil { 61 | res = append(res, err) 62 | } 63 | 64 | if len(res) > 0 { 65 | return errors.CompositeValidationError(res...) 66 | } 67 | return nil 68 | } 69 | 70 | func (m *DeviceStatistics) contextValidatePing(ctx context.Context, formats strfmt.Registry) error { 71 | 72 | if err := m.Ping.ContextValidate(ctx, formats); err != nil { 73 | if ve, ok := err.(*errors.Validation); ok { 74 | return ve.ValidateName("ping") 75 | } else if ce, ok := err.(*errors.CompositeError); ok { 76 | return ce.ValidateName("ping") 77 | } 78 | return err 79 | } 80 | 81 | return nil 82 | } 83 | 84 | // MarshalBinary interface implementation 85 | func (m *DeviceStatistics) MarshalBinary() ([]byte, error) { 86 | if m == nil { 87 | return nil, nil 88 | } 89 | return swag.WriteJSON(m) 90 | } 91 | 92 | // UnmarshalBinary interface implementation 93 | func (m *DeviceStatistics) UnmarshalBinary(b []byte) error { 94 | var res DeviceStatistics 95 | if err := swag.ReadJSON(b, &res); err != nil { 96 | return err 97 | } 98 | *m = res 99 | return nil 100 | } 101 | -------------------------------------------------------------------------------- /models/semver_version.go: -------------------------------------------------------------------------------- 1 | // Code generated by go-swagger; DO NOT EDIT. 2 | 3 | package models 4 | 5 | // This file was generated by the swagger tool. 6 | // Editing this file might prove futile when you re-run the swagger generate command 7 | 8 | import ( 9 | "context" 10 | 11 | "github.com/go-openapi/errors" 12 | "github.com/go-openapi/strfmt" 13 | "github.com/go-openapi/swag" 14 | "github.com/go-openapi/validate" 15 | ) 16 | 17 | // SemverVersion semver version 18 | // 19 | // swagger:model semverVersion 20 | type SemverVersion struct { 21 | 22 | // major 23 | // Example: 1 24 | // Required: true 25 | Major *float64 `json:"major"` 26 | 27 | // minor 28 | // Example: 10 29 | // Required: true 30 | Minor *float64 `json:"minor"` 31 | 32 | // order 33 | // Example: 65546.8.0 34 | Order string `json:"order,omitempty"` 35 | 36 | // patch 37 | // Example: 8 38 | // Required: true 39 | Patch *float64 `json:"patch"` 40 | } 41 | 42 | // Validate validates this semver version 43 | func (m *SemverVersion) Validate(formats strfmt.Registry) error { 44 | var res []error 45 | 46 | if err := m.validateMajor(formats); err != nil { 47 | res = append(res, err) 48 | } 49 | 50 | if err := m.validateMinor(formats); err != nil { 51 | res = append(res, err) 52 | } 53 | 54 | if err := m.validatePatch(formats); err != nil { 55 | res = append(res, err) 56 | } 57 | 58 | if len(res) > 0 { 59 | return errors.CompositeValidationError(res...) 60 | } 61 | return nil 62 | } 63 | 64 | func (m *SemverVersion) validateMajor(formats strfmt.Registry) error { 65 | 66 | if err := validate.Required("major", "body", m.Major); err != nil { 67 | return err 68 | } 69 | 70 | return nil 71 | } 72 | 73 | func (m *SemverVersion) validateMinor(formats strfmt.Registry) error { 74 | 75 | if err := validate.Required("minor", "body", m.Minor); err != nil { 76 | return err 77 | } 78 | 79 | return nil 80 | } 81 | 82 | func (m *SemverVersion) validatePatch(formats strfmt.Registry) error { 83 | 84 | if err := validate.Required("patch", "body", m.Patch); err != nil { 85 | return err 86 | } 87 | 88 | return nil 89 | } 90 | 91 | // ContextValidate validates this semver version based on context it is used 92 | func (m *SemverVersion) ContextValidate(ctx context.Context, formats strfmt.Registry) error { 93 | return nil 94 | } 95 | 96 | // MarshalBinary interface implementation 97 | func (m *SemverVersion) MarshalBinary() ([]byte, error) { 98 | if m == nil { 99 | return nil, nil 100 | } 101 | return swag.WriteJSON(m) 102 | } 103 | 104 | // UnmarshalBinary interface implementation 105 | func (m *SemverVersion) UnmarshalBinary(b []byte) error { 106 | var res SemverVersion 107 | if err := swag.ReadJSON(b, &res); err != nil { 108 | return err 109 | } 110 | *m = res 111 | return nil 112 | } 113 | -------------------------------------------------------------------------------- /exporter/device_test.go: -------------------------------------------------------------------------------- 1 | package exporter 2 | 3 | import ( 4 | "testing" 5 | "time" 6 | 7 | "github.com/ffddorf/unms-exporter/models" 8 | ) 9 | 10 | const ( 11 | ms = time.Millisecond 12 | µs = time.Microsecond //nolint:asciicheck 13 | ) 14 | 15 | type metricExpectation map[string]struct { 16 | actual interface{} 17 | satisfied bool 18 | } 19 | 20 | func comparePingMetrics(t *testing.T, expectations metricExpectation, actual *PingMetrics) { 21 | t.Helper() 22 | 23 | anyFailure := false 24 | for field, expectation := range expectations { 25 | if !expectation.satisfied { 26 | anyFailure = true 27 | t.Errorf("unexpected value for field %q: %v", field, expectation.actual) 28 | } 29 | } 30 | if anyFailure { 31 | t.FailNow() 32 | } 33 | } 34 | 35 | func TestDevice_PingMetrics_connected(t *testing.T) { 36 | t.Parallel() 37 | 38 | subject := Device{ 39 | Statistics: &models.DeviceStatistics{ 40 | Ping: models.ListOfCoordinates{{Y: 5}, {Y: 10}, {Y: 25}, {Y: 15}, {Y: 1}}, // x values are ignored 41 | }, 42 | } 43 | 44 | actual := subject.PingMetrics() 45 | if actual == nil { 46 | t.Fatal("expected PingMetrics() to return somthing, got nil") 47 | } 48 | 49 | comparePingMetrics(t, metricExpectation{ 50 | "packets sent": {actual.PacketsSent, actual.PacketsSent == 5}, 51 | "packets lost": {actual.PacketsLost, actual.PacketsLost == 0}, 52 | "rtt best": {actual.Best, actual.Best == 1*ms}, 53 | "rtt worst": {actual.Worst, actual.Worst == 25*ms}, 54 | "rtt median": {actual.Median, actual.Median == 10*ms}, 55 | "rtt meain": {actual.Mean, actual.Mean == 11200*µs}, // 11.2ms 56 | "rtt std dev": {actual.StdDev, 8350*µs < actual.StdDev && actual.StdDev < 8360*µs}, // ~8.352245ms 57 | }, actual) 58 | } 59 | 60 | func TestDevice_PingMetrics_missingPackets(t *testing.T) { 61 | t.Parallel() 62 | 63 | subject := Device{ 64 | Statistics: &models.DeviceStatistics{ 65 | Ping: models.ListOfCoordinates{nil, {Y: 100}, {Y: 250}, nil, {Y: 120}}, 66 | }, 67 | } 68 | 69 | actual := subject.PingMetrics() 70 | if actual == nil { 71 | t.Fatal("expected PingMetrics() to return somthing, got nil") 72 | } 73 | 74 | comparePingMetrics(t, metricExpectation{ 75 | "packets sent": {actual.PacketsSent, actual.PacketsSent == 5}, 76 | "packets lost": {actual.PacketsLost, actual.PacketsLost == 2}, 77 | "rtt best": {actual.Best, actual.Best == 100*ms}, 78 | "rtt worst": {actual.Worst, actual.Worst == 250*ms}, 79 | "rtt median": {actual.Median, actual.Median == 120*ms}, 80 | "rtt meain": {actual.Mean, 156666*µs < actual.Mean && actual.Mean < 156667*µs}, // 156.66666ms 81 | "rtt std dev": {actual.StdDev, 66499*µs < actual.StdDev && actual.StdDev < 66500*µs}, // ~66.499791ms 82 | }, actual) 83 | } 84 | 85 | func TestDevice_PingMetrics_disconnected(t *testing.T) { 86 | t.Parallel() 87 | 88 | if actual := (&Device{}).PingMetrics(); actual != nil { 89 | t.Errorf("expected PingMetrics() to return nil, got %+v", actual) 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /.github/workflows/build-publish.yml: -------------------------------------------------------------------------------- 1 | name: Container Image 2 | 3 | on: 4 | push: 5 | pull_request_target: 6 | 7 | jobs: 8 | build: 9 | name: Build & Publish 10 | runs-on: ubuntu-24.04 11 | 12 | # run on same-repo pushes or forks 13 | # this avoids running the action twice for 14 | # pull requests from same-repo branches 15 | if: ${{ github.event_name == 'push' || github.event.pull_request.base.repo.full_name != github.event.pull_request.head.repo.full_name }} 16 | 17 | steps: 18 | - name: Compute Labels 19 | if: ${{ github.event_name == 'push' }} 20 | id: meta 21 | uses: docker/metadata-action@v5 22 | with: 23 | images: quay.io/ffddorf/unms-exporter 24 | # generate Docker tags based on the following events/attributes 25 | tags: | 26 | type=ref,event=branch 27 | type=ref,event=pr 28 | type=semver,pattern={{version}} 29 | type=semver,pattern={{major}}.{{minor}} 30 | type=semver,pattern={{major}} 31 | type=sha 32 | labels: | 33 | org.opencontainers.image.url=https://github.com/ffddorf/unms-exporter 34 | org.opencontainers.image.source=https://github.com/ffddorf/unms-exporter 35 | org.opencontainers.image.revision=45bf621f0ff5cbc7f04d51f5899220b83f088b4d 36 | 37 | - id: short-sha 38 | if: ${{ github.event_name == 'pull_request_target' }} 39 | uses: actions/github-script@v7.0.1 40 | with: 41 | script: | 42 | const sha = context.payload.pull_request.head.sha.substr(0,7) 43 | core.setOutput('sha', sha) 44 | - name: Compute Labels for fork 45 | if: ${{ github.event_name == 'pull_request_target' }} 46 | id: forkmeta 47 | uses: docker/metadata-action@v5 48 | with: 49 | images: quay.io/ffddorf/unms-exporter 50 | # generate Docker tags based on the following events/attributes 51 | tags: | 52 | type=ref,event=pr 53 | type=raw,sha-${{ steps.short-sha.outputs.sha }} 54 | labels: | 55 | org.opencontainers.image.url=https://github.com/${{ github.event.pull_request.head.repo.full_name }} 56 | org.opencontainers.image.source=https://github.com/${{ github.event.pull_request.head.repo.full_name }} 57 | org.opencontainers.image.revision=${{ github.event.pull_request.head.sha }} 58 | 59 | - name: Login to Quay 60 | uses: docker/login-action@v3 61 | with: 62 | registry: quay.io 63 | username: ffddorf+github_actions 64 | password: ${{ secrets.QUAY_BOT_PASSWORD }} 65 | 66 | - name: Build Image 67 | if: ${{ github.event_name == 'push' }} 68 | uses: docker/build-push-action@v6 69 | with: 70 | tags: ${{ steps.meta.outputs.tags }} 71 | labels: ${{ steps.meta.outputs.labels }} 72 | push: true 73 | 74 | - name: Build Image for fork PR 75 | if: ${{ github.event_name == 'pull_request_target' }} 76 | uses: docker/build-push-action@v6 77 | with: 78 | context: https://github.com/${{ github.event.pull_request.head.repo.full_name }}.git#${{ github.event.pull_request.head.sha }} 79 | tags: ${{ steps.forkmeta.outputs.tags }} 80 | labels: ${{ steps.forkmeta.outputs.labels }} 81 | push: true 82 | -------------------------------------------------------------------------------- /client/u_n_m_s_api_client.go: -------------------------------------------------------------------------------- 1 | // Code generated by go-swagger; DO NOT EDIT. 2 | 3 | package client 4 | 5 | // This file was generated by the swagger tool. 6 | // Editing this file might prove futile when you re-run the swagger generate command 7 | 8 | import ( 9 | "github.com/go-openapi/runtime" 10 | httptransport "github.com/go-openapi/runtime/client" 11 | "github.com/go-openapi/strfmt" 12 | 13 | "github.com/ffddorf/unms-exporter/client/devices" 14 | ) 15 | 16 | // Default u n m s API HTTP client. 17 | var Default = NewHTTPClient(nil) 18 | 19 | const ( 20 | // DefaultHost is the default Host 21 | // found in Meta (info) section of spec file 22 | DefaultHost string = "localhost" 23 | // DefaultBasePath is the default BasePath 24 | // found in Meta (info) section of spec file 25 | DefaultBasePath string = "/nms/api/v2.1" 26 | ) 27 | 28 | // DefaultSchemes are the default schemes found in Meta (info) section of spec file 29 | var DefaultSchemes = []string{"http"} 30 | 31 | // NewHTTPClient creates a new u n m s API HTTP client. 32 | func NewHTTPClient(formats strfmt.Registry) *UNMSAPI { 33 | return NewHTTPClientWithConfig(formats, nil) 34 | } 35 | 36 | // NewHTTPClientWithConfig creates a new u n m s API HTTP client, 37 | // using a customizable transport config. 38 | func NewHTTPClientWithConfig(formats strfmt.Registry, cfg *TransportConfig) *UNMSAPI { 39 | // ensure nullable parameters have default 40 | if cfg == nil { 41 | cfg = DefaultTransportConfig() 42 | } 43 | 44 | // create transport and client 45 | transport := httptransport.New(cfg.Host, cfg.BasePath, cfg.Schemes) 46 | return New(transport, formats) 47 | } 48 | 49 | // New creates a new u n m s API client 50 | func New(transport runtime.ClientTransport, formats strfmt.Registry) *UNMSAPI { 51 | // ensure nullable parameters have default 52 | if formats == nil { 53 | formats = strfmt.Default 54 | } 55 | 56 | cli := new(UNMSAPI) 57 | cli.Transport = transport 58 | cli.Devices = devices.New(transport, formats) 59 | return cli 60 | } 61 | 62 | // DefaultTransportConfig creates a TransportConfig with the 63 | // default settings taken from the meta section of the spec file. 64 | func DefaultTransportConfig() *TransportConfig { 65 | return &TransportConfig{ 66 | Host: DefaultHost, 67 | BasePath: DefaultBasePath, 68 | Schemes: DefaultSchemes, 69 | } 70 | } 71 | 72 | // TransportConfig contains the transport related info, 73 | // found in the meta section of the spec file. 74 | type TransportConfig struct { 75 | Host string 76 | BasePath string 77 | Schemes []string 78 | } 79 | 80 | // WithHost overrides the default host, 81 | // provided by the meta section of the spec file. 82 | func (cfg *TransportConfig) WithHost(host string) *TransportConfig { 83 | cfg.Host = host 84 | return cfg 85 | } 86 | 87 | // WithBasePath overrides the default basePath, 88 | // provided by the meta section of the spec file. 89 | func (cfg *TransportConfig) WithBasePath(basePath string) *TransportConfig { 90 | cfg.BasePath = basePath 91 | return cfg 92 | } 93 | 94 | // WithSchemes overrides the default schemes, 95 | // provided by the meta section of the spec file. 96 | func (cfg *TransportConfig) WithSchemes(schemes []string) *TransportConfig { 97 | cfg.Schemes = schemes 98 | return cfg 99 | } 100 | 101 | // UNMSAPI is a client for u n m s API 102 | type UNMSAPI struct { 103 | Devices devices.ClientService 104 | 105 | Transport runtime.ClientTransport 106 | } 107 | 108 | // SetTransport changes the transport on the client and all its subresources 109 | func (c *UNMSAPI) SetTransport(transport runtime.ClientTransport) { 110 | c.Transport = transport 111 | c.Devices.SetTransport(transport) 112 | } 113 | -------------------------------------------------------------------------------- /models/device_upgrade.go: -------------------------------------------------------------------------------- 1 | // Code generated by go-swagger; DO NOT EDIT. 2 | 3 | package models 4 | 5 | // This file was generated by the swagger tool. 6 | // Editing this file might prove futile when you re-run the swagger generate command 7 | 8 | import ( 9 | "context" 10 | 11 | "github.com/go-openapi/errors" 12 | "github.com/go-openapi/strfmt" 13 | "github.com/go-openapi/swag" 14 | "github.com/go-openapi/validate" 15 | ) 16 | 17 | // DeviceUpgrade device upgrade 18 | // 19 | // swagger:model DeviceUpgrade 20 | type DeviceUpgrade struct { 21 | 22 | // firmware 23 | // Required: true 24 | Firmware *SemverVersion `json:"firmware"` 25 | 26 | // progress 27 | // Required: true 28 | Progress *float64 `json:"progress"` 29 | 30 | // status 31 | // Required: true 32 | Status *string `json:"status"` 33 | } 34 | 35 | // Validate validates this device upgrade 36 | func (m *DeviceUpgrade) Validate(formats strfmt.Registry) error { 37 | var res []error 38 | 39 | if err := m.validateFirmware(formats); err != nil { 40 | res = append(res, err) 41 | } 42 | 43 | if err := m.validateProgress(formats); err != nil { 44 | res = append(res, err) 45 | } 46 | 47 | if err := m.validateStatus(formats); err != nil { 48 | res = append(res, err) 49 | } 50 | 51 | if len(res) > 0 { 52 | return errors.CompositeValidationError(res...) 53 | } 54 | return nil 55 | } 56 | 57 | func (m *DeviceUpgrade) validateFirmware(formats strfmt.Registry) error { 58 | 59 | if err := validate.Required("firmware", "body", m.Firmware); err != nil { 60 | return err 61 | } 62 | 63 | if m.Firmware != nil { 64 | if err := m.Firmware.Validate(formats); err != nil { 65 | if ve, ok := err.(*errors.Validation); ok { 66 | return ve.ValidateName("firmware") 67 | } else if ce, ok := err.(*errors.CompositeError); ok { 68 | return ce.ValidateName("firmware") 69 | } 70 | return err 71 | } 72 | } 73 | 74 | return nil 75 | } 76 | 77 | func (m *DeviceUpgrade) validateProgress(formats strfmt.Registry) error { 78 | 79 | if err := validate.Required("progress", "body", m.Progress); err != nil { 80 | return err 81 | } 82 | 83 | return nil 84 | } 85 | 86 | func (m *DeviceUpgrade) validateStatus(formats strfmt.Registry) error { 87 | 88 | if err := validate.Required("status", "body", m.Status); err != nil { 89 | return err 90 | } 91 | 92 | return nil 93 | } 94 | 95 | // ContextValidate validate this device upgrade based on the context it is used 96 | func (m *DeviceUpgrade) ContextValidate(ctx context.Context, formats strfmt.Registry) error { 97 | var res []error 98 | 99 | if err := m.contextValidateFirmware(ctx, formats); err != nil { 100 | res = append(res, err) 101 | } 102 | 103 | if len(res) > 0 { 104 | return errors.CompositeValidationError(res...) 105 | } 106 | return nil 107 | } 108 | 109 | func (m *DeviceUpgrade) contextValidateFirmware(ctx context.Context, formats strfmt.Registry) error { 110 | 111 | if m.Firmware != nil { 112 | 113 | if err := m.Firmware.ContextValidate(ctx, formats); err != nil { 114 | if ve, ok := err.(*errors.Validation); ok { 115 | return ve.ValidateName("firmware") 116 | } else if ce, ok := err.(*errors.CompositeError); ok { 117 | return ce.ValidateName("firmware") 118 | } 119 | return err 120 | } 121 | } 122 | 123 | return nil 124 | } 125 | 126 | // MarshalBinary interface implementation 127 | func (m *DeviceUpgrade) MarshalBinary() ([]byte, error) { 128 | if m == nil { 129 | return nil, nil 130 | } 131 | return swag.WriteJSON(m) 132 | } 133 | 134 | // UnmarshalBinary interface implementation 135 | func (m *DeviceUpgrade) UnmarshalBinary(b []byte) error { 136 | var res DeviceUpgrade 137 | if err := swag.ReadJSON(b, &res); err != nil { 138 | return err 139 | } 140 | *m = res 141 | return nil 142 | } 143 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/ffddorf/unms-exporter 2 | 3 | go 1.21 4 | 5 | require ( 6 | github.com/go-openapi/errors v0.21.0 7 | github.com/go-openapi/runtime v0.26.2 8 | github.com/go-openapi/strfmt v0.22.0 9 | github.com/go-openapi/swag v0.22.7 10 | github.com/go-openapi/validate v0.22.6 11 | github.com/go-swagger/go-swagger v0.30.5 12 | github.com/kelseyhightower/envconfig v1.4.0 13 | github.com/prometheus/client_golang v1.18.0 14 | github.com/sirupsen/logrus v1.9.3 15 | github.com/spf13/pflag v1.0.5 16 | github.com/spf13/viper v1.18.2 17 | github.com/stretchr/testify v1.10.0 18 | ) 19 | 20 | require ( 21 | github.com/Masterminds/goutils v1.1.1 // indirect 22 | github.com/Masterminds/semver/v3 v3.2.1 // indirect 23 | github.com/Masterminds/sprig/v3 v3.2.3 // indirect 24 | github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect 25 | github.com/beorn7/perks v1.0.1 // indirect 26 | github.com/cespare/xxhash/v2 v2.2.0 // indirect 27 | github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect 28 | github.com/felixge/httpsnoop v1.0.4 // indirect 29 | github.com/fsnotify/fsnotify v1.7.0 // indirect 30 | github.com/go-logr/logr v1.4.1 // indirect 31 | github.com/go-logr/stdr v1.2.2 // indirect 32 | github.com/go-openapi/analysis v0.22.1 // indirect 33 | github.com/go-openapi/inflect v0.19.0 // indirect 34 | github.com/go-openapi/jsonpointer v0.20.2 // indirect 35 | github.com/go-openapi/jsonreference v0.20.4 // indirect 36 | github.com/go-openapi/loads v0.21.5 // indirect 37 | github.com/go-openapi/spec v0.20.13 // indirect 38 | github.com/google/uuid v1.5.0 // indirect 39 | github.com/gorilla/handlers v1.5.2 // indirect 40 | github.com/hashicorp/hcl v1.0.0 // indirect 41 | github.com/huandu/xstrings v1.4.0 // indirect 42 | github.com/imdario/mergo v0.3.16 // indirect 43 | github.com/jessevdk/go-flags v1.5.0 // indirect 44 | github.com/josharian/intern v1.0.0 // indirect 45 | github.com/kr/pretty v0.3.1 // indirect 46 | github.com/kr/text v0.2.0 // indirect 47 | github.com/magiconair/properties v1.8.7 // indirect 48 | github.com/mailru/easyjson v0.7.7 // indirect 49 | github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 // indirect 50 | github.com/mitchellh/copystructure v1.2.0 // indirect 51 | github.com/mitchellh/mapstructure v1.5.0 // indirect 52 | github.com/mitchellh/reflectwalk v1.0.2 // indirect 53 | github.com/oklog/ulid v1.3.1 // indirect 54 | github.com/opentracing/opentracing-go v1.2.0 // indirect 55 | github.com/pelletier/go-toml/v2 v2.1.1 // indirect 56 | github.com/pkg/errors v0.9.1 // indirect 57 | github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect 58 | github.com/prometheus/client_model v0.5.0 // indirect 59 | github.com/prometheus/common v0.45.0 // indirect 60 | github.com/prometheus/procfs v0.12.0 // indirect 61 | github.com/rogpeppe/go-internal v1.12.0 // indirect 62 | github.com/sagikazarmark/locafero v0.4.0 // indirect 63 | github.com/sagikazarmark/slog-shim v0.1.0 // indirect 64 | github.com/shopspring/decimal v1.3.1 // indirect 65 | github.com/sourcegraph/conc v0.3.0 // indirect 66 | github.com/spf13/afero v1.11.0 // indirect 67 | github.com/spf13/cast v1.6.0 // indirect 68 | github.com/subosito/gotenv v1.6.0 // indirect 69 | github.com/toqueteos/webbrowser v1.2.0 // indirect 70 | go.mongodb.org/mongo-driver v1.13.1 // indirect 71 | go.opentelemetry.io/otel v1.21.0 // indirect 72 | go.opentelemetry.io/otel/metric v1.21.0 // indirect 73 | go.opentelemetry.io/otel/trace v1.21.0 // indirect 74 | go.uber.org/atomic v1.9.0 // indirect 75 | go.uber.org/multierr v1.9.0 // indirect 76 | golang.org/x/crypto v0.17.0 // indirect 77 | golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect 78 | golang.org/x/mod v0.14.0 // indirect 79 | golang.org/x/sys v0.15.0 // indirect 80 | golang.org/x/text v0.14.0 // indirect 81 | golang.org/x/tools v0.16.1 // indirect 82 | google.golang.org/protobuf v1.32.0 // indirect 83 | gopkg.in/ini.v1 v1.67.0 // indirect 84 | gopkg.in/yaml.v2 v2.4.0 // indirect 85 | gopkg.in/yaml.v3 v3.0.1 // indirect 86 | ) 87 | -------------------------------------------------------------------------------- /models/semver.go: -------------------------------------------------------------------------------- 1 | // Code generated by go-swagger; DO NOT EDIT. 2 | 3 | package models 4 | 5 | // This file was generated by the swagger tool. 6 | // Editing this file might prove futile when you re-run the swagger generate command 7 | 8 | import ( 9 | "context" 10 | 11 | "github.com/go-openapi/errors" 12 | "github.com/go-openapi/strfmt" 13 | "github.com/go-openapi/swag" 14 | "github.com/go-openapi/validate" 15 | ) 16 | 17 | // Semver semver 18 | // 19 | // swagger:model semver 20 | type Semver struct { 21 | 22 | // current 23 | // Required: true 24 | Current *SemverVersion `json:"current"` 25 | 26 | // latest 27 | // Required: true 28 | Latest *SemverVersion `json:"latest"` 29 | } 30 | 31 | // Validate validates this semver 32 | func (m *Semver) Validate(formats strfmt.Registry) error { 33 | var res []error 34 | 35 | if err := m.validateCurrent(formats); err != nil { 36 | res = append(res, err) 37 | } 38 | 39 | if err := m.validateLatest(formats); err != nil { 40 | res = append(res, err) 41 | } 42 | 43 | if len(res) > 0 { 44 | return errors.CompositeValidationError(res...) 45 | } 46 | return nil 47 | } 48 | 49 | func (m *Semver) validateCurrent(formats strfmt.Registry) error { 50 | 51 | if err := validate.Required("current", "body", m.Current); err != nil { 52 | return err 53 | } 54 | 55 | if m.Current != nil { 56 | if err := m.Current.Validate(formats); err != nil { 57 | if ve, ok := err.(*errors.Validation); ok { 58 | return ve.ValidateName("current") 59 | } else if ce, ok := err.(*errors.CompositeError); ok { 60 | return ce.ValidateName("current") 61 | } 62 | return err 63 | } 64 | } 65 | 66 | return nil 67 | } 68 | 69 | func (m *Semver) validateLatest(formats strfmt.Registry) error { 70 | 71 | if err := validate.Required("latest", "body", m.Latest); err != nil { 72 | return err 73 | } 74 | 75 | if m.Latest != nil { 76 | if err := m.Latest.Validate(formats); err != nil { 77 | if ve, ok := err.(*errors.Validation); ok { 78 | return ve.ValidateName("latest") 79 | } else if ce, ok := err.(*errors.CompositeError); ok { 80 | return ce.ValidateName("latest") 81 | } 82 | return err 83 | } 84 | } 85 | 86 | return nil 87 | } 88 | 89 | // ContextValidate validate this semver based on the context it is used 90 | func (m *Semver) ContextValidate(ctx context.Context, formats strfmt.Registry) error { 91 | var res []error 92 | 93 | if err := m.contextValidateCurrent(ctx, formats); err != nil { 94 | res = append(res, err) 95 | } 96 | 97 | if err := m.contextValidateLatest(ctx, formats); err != nil { 98 | res = append(res, err) 99 | } 100 | 101 | if len(res) > 0 { 102 | return errors.CompositeValidationError(res...) 103 | } 104 | return nil 105 | } 106 | 107 | func (m *Semver) contextValidateCurrent(ctx context.Context, formats strfmt.Registry) error { 108 | 109 | if m.Current != nil { 110 | 111 | if err := m.Current.ContextValidate(ctx, formats); err != nil { 112 | if ve, ok := err.(*errors.Validation); ok { 113 | return ve.ValidateName("current") 114 | } else if ce, ok := err.(*errors.CompositeError); ok { 115 | return ce.ValidateName("current") 116 | } 117 | return err 118 | } 119 | } 120 | 121 | return nil 122 | } 123 | 124 | func (m *Semver) contextValidateLatest(ctx context.Context, formats strfmt.Registry) error { 125 | 126 | if m.Latest != nil { 127 | 128 | if err := m.Latest.ContextValidate(ctx, formats); err != nil { 129 | if ve, ok := err.(*errors.Validation); ok { 130 | return ve.ValidateName("latest") 131 | } else if ce, ok := err.(*errors.CompositeError); ok { 132 | return ce.ValidateName("latest") 133 | } 134 | return err 135 | } 136 | } 137 | 138 | return nil 139 | } 140 | 141 | // MarshalBinary interface implementation 142 | func (m *Semver) MarshalBinary() ([]byte, error) { 143 | if m == nil { 144 | return nil, nil 145 | } 146 | return swag.WriteJSON(m) 147 | } 148 | 149 | // UnmarshalBinary interface implementation 150 | func (m *Semver) UnmarshalBinary(b []byte) error { 151 | var res Semver 152 | if err := swag.ReadJSON(b, &res); err != nil { 153 | return err 154 | } 155 | *m = res 156 | return nil 157 | } 158 | -------------------------------------------------------------------------------- /models/device_meta.go: -------------------------------------------------------------------------------- 1 | // Code generated by go-swagger; DO NOT EDIT. 2 | 3 | package models 4 | 5 | // This file was generated by the swagger tool. 6 | // Editing this file might prove futile when you re-run the swagger generate command 7 | 8 | import ( 9 | "context" 10 | 11 | "github.com/go-openapi/errors" 12 | "github.com/go-openapi/strfmt" 13 | "github.com/go-openapi/swag" 14 | "github.com/go-openapi/validate" 15 | ) 16 | 17 | // DeviceMeta device meta 18 | // 19 | // swagger:model DeviceMeta 20 | type DeviceMeta struct { 21 | 22 | // alias 23 | // Max Length: 30 24 | Alias string `json:"alias,omitempty"` 25 | 26 | // Custom IP address in IPv4 or IPv6 format. 27 | // Example: 192.168.1.22 28 | CustomIPAddress string `json:"customIpAddress,omitempty"` 29 | 30 | // failed message decryption 31 | // Required: true 32 | FailedMessageDecryption *bool `json:"failedMessageDecryption"` 33 | 34 | // maintenance 35 | // Required: true 36 | Maintenance *bool `json:"maintenance"` 37 | 38 | // note 39 | // Max Length: 300 40 | Note string `json:"note,omitempty"` 41 | 42 | // restart timestamp 43 | // Example: 2018-11-14T15:20:32.004Z 44 | // Required: true 45 | // Format: date-time 46 | RestartTimestamp *strfmt.DateTime `json:"restartTimestamp"` 47 | } 48 | 49 | // Validate validates this device meta 50 | func (m *DeviceMeta) Validate(formats strfmt.Registry) error { 51 | var res []error 52 | 53 | if err := m.validateAlias(formats); err != nil { 54 | res = append(res, err) 55 | } 56 | 57 | if err := m.validateFailedMessageDecryption(formats); err != nil { 58 | res = append(res, err) 59 | } 60 | 61 | if err := m.validateMaintenance(formats); err != nil { 62 | res = append(res, err) 63 | } 64 | 65 | if err := m.validateNote(formats); err != nil { 66 | res = append(res, err) 67 | } 68 | 69 | if err := m.validateRestartTimestamp(formats); err != nil { 70 | res = append(res, err) 71 | } 72 | 73 | if len(res) > 0 { 74 | return errors.CompositeValidationError(res...) 75 | } 76 | return nil 77 | } 78 | 79 | func (m *DeviceMeta) validateAlias(formats strfmt.Registry) error { 80 | if swag.IsZero(m.Alias) { // not required 81 | return nil 82 | } 83 | 84 | if err := validate.MaxLength("alias", "body", m.Alias, 30); err != nil { 85 | return err 86 | } 87 | 88 | return nil 89 | } 90 | 91 | func (m *DeviceMeta) validateFailedMessageDecryption(formats strfmt.Registry) error { 92 | 93 | if err := validate.Required("failedMessageDecryption", "body", m.FailedMessageDecryption); err != nil { 94 | return err 95 | } 96 | 97 | return nil 98 | } 99 | 100 | func (m *DeviceMeta) validateMaintenance(formats strfmt.Registry) error { 101 | 102 | if err := validate.Required("maintenance", "body", m.Maintenance); err != nil { 103 | return err 104 | } 105 | 106 | return nil 107 | } 108 | 109 | func (m *DeviceMeta) validateNote(formats strfmt.Registry) error { 110 | if swag.IsZero(m.Note) { // not required 111 | return nil 112 | } 113 | 114 | if err := validate.MaxLength("note", "body", m.Note, 300); err != nil { 115 | return err 116 | } 117 | 118 | return nil 119 | } 120 | 121 | func (m *DeviceMeta) validateRestartTimestamp(formats strfmt.Registry) error { 122 | 123 | if err := validate.Required("restartTimestamp", "body", m.RestartTimestamp); err != nil { 124 | return err 125 | } 126 | 127 | if err := validate.FormatOf("restartTimestamp", "body", "date-time", m.RestartTimestamp.String(), formats); err != nil { 128 | return err 129 | } 130 | 131 | return nil 132 | } 133 | 134 | // ContextValidate validates this device meta based on context it is used 135 | func (m *DeviceMeta) ContextValidate(ctx context.Context, formats strfmt.Registry) error { 136 | return nil 137 | } 138 | 139 | // MarshalBinary interface implementation 140 | func (m *DeviceMeta) MarshalBinary() ([]byte, error) { 141 | if m == nil { 142 | return nil, nil 143 | } 144 | return swag.WriteJSON(m) 145 | } 146 | 147 | // UnmarshalBinary interface implementation 148 | func (m *DeviceMeta) UnmarshalBinary(b []byte) error { 149 | var res DeviceMeta 150 | if err := swag.ReadJSON(b, &res); err != nil { 151 | return err 152 | } 153 | *m = res 154 | return nil 155 | } 156 | -------------------------------------------------------------------------------- /client/devices/devices_client.go: -------------------------------------------------------------------------------- 1 | // Code generated by go-swagger; DO NOT EDIT. 2 | 3 | package devices 4 | 5 | // This file was generated by the swagger tool. 6 | // Editing this file might prove futile when you re-run the swagger generate command 7 | 8 | import ( 9 | "fmt" 10 | 11 | "github.com/go-openapi/runtime" 12 | "github.com/go-openapi/strfmt" 13 | ) 14 | 15 | // New creates a new devices API client. 16 | func New(transport runtime.ClientTransport, formats strfmt.Registry) ClientService { 17 | return &Client{transport: transport, formats: formats} 18 | } 19 | 20 | /* 21 | Client for devices API 22 | */ 23 | type Client struct { 24 | transport runtime.ClientTransport 25 | formats strfmt.Registry 26 | } 27 | 28 | // ClientOption is the option for Client methods 29 | type ClientOption func(*runtime.ClientOperation) 30 | 31 | // ClientService is the interface for Client methods 32 | type ClientService interface { 33 | GetDevices(params *GetDevicesParams, opts ...ClientOption) (*GetDevicesOK, error) 34 | 35 | GetDevicesIDStatistics(params *GetDevicesIDStatisticsParams, opts ...ClientOption) (*GetDevicesIDStatisticsOK, error) 36 | 37 | SetTransport(transport runtime.ClientTransport) 38 | } 39 | 40 | /* 41 | GetDevices lists of all devices in u n m s 42 | */ 43 | func (a *Client) GetDevices(params *GetDevicesParams, opts ...ClientOption) (*GetDevicesOK, error) { 44 | // TODO: Validate the params before sending 45 | if params == nil { 46 | params = NewGetDevicesParams() 47 | } 48 | op := &runtime.ClientOperation{ 49 | ID: "getDevices", 50 | Method: "GET", 51 | PathPattern: "/devices", 52 | ProducesMediaTypes: []string{"application/json"}, 53 | ConsumesMediaTypes: []string{"application/json"}, 54 | Schemes: []string{"http"}, 55 | Params: params, 56 | Reader: &GetDevicesReader{formats: a.formats}, 57 | Context: params.Context, 58 | Client: params.HTTPClient, 59 | } 60 | for _, opt := range opts { 61 | opt(op) 62 | } 63 | 64 | result, err := a.transport.Submit(op) 65 | if err != nil { 66 | return nil, err 67 | } 68 | success, ok := result.(*GetDevicesOK) 69 | if ok { 70 | return success, nil 71 | } 72 | // unexpected success response 73 | // safeguard: normally, absent a default response, unknown success responses return an error above: so this is a codegen issue 74 | msg := fmt.Sprintf("unexpected success response for getDevices: API contract not enforced by server. Client expected to get an error, but got: %T", result) 75 | panic(msg) 76 | } 77 | 78 | /* 79 | GetDevicesIDStatistics returns device statistics 80 | */ 81 | func (a *Client) GetDevicesIDStatistics(params *GetDevicesIDStatisticsParams, opts ...ClientOption) (*GetDevicesIDStatisticsOK, error) { 82 | // TODO: Validate the params before sending 83 | if params == nil { 84 | params = NewGetDevicesIDStatisticsParams() 85 | } 86 | op := &runtime.ClientOperation{ 87 | ID: "getDevicesIdStatistics", 88 | Method: "GET", 89 | PathPattern: "/devices/{id}/statistics", 90 | ProducesMediaTypes: []string{"application/json"}, 91 | ConsumesMediaTypes: []string{"application/json"}, 92 | Schemes: []string{"http"}, 93 | Params: params, 94 | Reader: &GetDevicesIDStatisticsReader{formats: a.formats}, 95 | Context: params.Context, 96 | Client: params.HTTPClient, 97 | } 98 | for _, opt := range opts { 99 | opt(op) 100 | } 101 | 102 | result, err := a.transport.Submit(op) 103 | if err != nil { 104 | return nil, err 105 | } 106 | success, ok := result.(*GetDevicesIDStatisticsOK) 107 | if ok { 108 | return success, nil 109 | } 110 | // unexpected success response 111 | // safeguard: normally, absent a default response, unknown success responses return an error above: so this is a codegen issue 112 | msg := fmt.Sprintf("unexpected success response for getDevicesIdStatistics: API contract not enforced by server. Client expected to get an error, but got: %T", result) 113 | panic(msg) 114 | } 115 | 116 | // SetTransport changes the transport on the client 117 | func (a *Client) SetTransport(transport runtime.ClientTransport) { 118 | a.transport = transport 119 | } 120 | -------------------------------------------------------------------------------- /models/device_firmware.go: -------------------------------------------------------------------------------- 1 | // Code generated by go-swagger; DO NOT EDIT. 2 | 3 | package models 4 | 5 | // This file was generated by the swagger tool. 6 | // Editing this file might prove futile when you re-run the swagger generate command 7 | 8 | import ( 9 | "context" 10 | 11 | "github.com/go-openapi/errors" 12 | "github.com/go-openapi/strfmt" 13 | "github.com/go-openapi/swag" 14 | "github.com/go-openapi/validate" 15 | ) 16 | 17 | // DeviceFirmware device firmware 18 | // 19 | // swagger:model DeviceFirmware 20 | type DeviceFirmware struct { 21 | 22 | // Is firmware compatible with UNMS 23 | // Required: true 24 | Compatible *bool `json:"compatible"` 25 | 26 | // Current firmware version. 27 | // Required: true 28 | Current *string `json:"current"` 29 | 30 | // Latest known firmware version. 31 | // Required: true 32 | Latest *string `json:"latest"` 33 | 34 | // semver 35 | Semver *Semver `json:"semver,omitempty"` 36 | } 37 | 38 | // Validate validates this device firmware 39 | func (m *DeviceFirmware) Validate(formats strfmt.Registry) error { 40 | var res []error 41 | 42 | if err := m.validateCompatible(formats); err != nil { 43 | res = append(res, err) 44 | } 45 | 46 | if err := m.validateCurrent(formats); err != nil { 47 | res = append(res, err) 48 | } 49 | 50 | if err := m.validateLatest(formats); err != nil { 51 | res = append(res, err) 52 | } 53 | 54 | if err := m.validateSemver(formats); err != nil { 55 | res = append(res, err) 56 | } 57 | 58 | if len(res) > 0 { 59 | return errors.CompositeValidationError(res...) 60 | } 61 | return nil 62 | } 63 | 64 | func (m *DeviceFirmware) validateCompatible(formats strfmt.Registry) error { 65 | 66 | if err := validate.Required("compatible", "body", m.Compatible); err != nil { 67 | return err 68 | } 69 | 70 | return nil 71 | } 72 | 73 | func (m *DeviceFirmware) validateCurrent(formats strfmt.Registry) error { 74 | 75 | if err := validate.Required("current", "body", m.Current); err != nil { 76 | return err 77 | } 78 | 79 | return nil 80 | } 81 | 82 | func (m *DeviceFirmware) validateLatest(formats strfmt.Registry) error { 83 | 84 | if err := validate.Required("latest", "body", m.Latest); err != nil { 85 | return err 86 | } 87 | 88 | return nil 89 | } 90 | 91 | func (m *DeviceFirmware) validateSemver(formats strfmt.Registry) error { 92 | if swag.IsZero(m.Semver) { // not required 93 | return nil 94 | } 95 | 96 | if m.Semver != nil { 97 | if err := m.Semver.Validate(formats); err != nil { 98 | if ve, ok := err.(*errors.Validation); ok { 99 | return ve.ValidateName("semver") 100 | } else if ce, ok := err.(*errors.CompositeError); ok { 101 | return ce.ValidateName("semver") 102 | } 103 | return err 104 | } 105 | } 106 | 107 | return nil 108 | } 109 | 110 | // ContextValidate validate this device firmware based on the context it is used 111 | func (m *DeviceFirmware) ContextValidate(ctx context.Context, formats strfmt.Registry) error { 112 | var res []error 113 | 114 | if err := m.contextValidateSemver(ctx, formats); err != nil { 115 | res = append(res, err) 116 | } 117 | 118 | if len(res) > 0 { 119 | return errors.CompositeValidationError(res...) 120 | } 121 | return nil 122 | } 123 | 124 | func (m *DeviceFirmware) contextValidateSemver(ctx context.Context, formats strfmt.Registry) error { 125 | 126 | if m.Semver != nil { 127 | 128 | if swag.IsZero(m.Semver) { // not required 129 | return nil 130 | } 131 | 132 | if err := m.Semver.ContextValidate(ctx, formats); err != nil { 133 | if ve, ok := err.(*errors.Validation); ok { 134 | return ve.ValidateName("semver") 135 | } else if ce, ok := err.(*errors.CompositeError); ok { 136 | return ce.ValidateName("semver") 137 | } 138 | return err 139 | } 140 | } 141 | 142 | return nil 143 | } 144 | 145 | // MarshalBinary interface implementation 146 | func (m *DeviceFirmware) MarshalBinary() ([]byte, error) { 147 | if m == nil { 148 | return nil, nil 149 | } 150 | return swag.WriteJSON(m) 151 | } 152 | 153 | // UnmarshalBinary interface implementation 154 | func (m *DeviceFirmware) UnmarshalBinary(b []byte) error { 155 | var res DeviceFirmware 156 | if err := swag.ReadJSON(b, &res); err != nil { 157 | return err 158 | } 159 | *m = res 160 | return nil 161 | } 162 | -------------------------------------------------------------------------------- /internal/cli/config/config_test.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "os" 5 | "sync" 6 | "testing" 7 | 8 | "github.com/sirupsen/logrus" 9 | "github.com/stretchr/testify/assert" 10 | "github.com/stretchr/testify/require" 11 | ) 12 | 13 | func TestNew_emptyEnv(t *testing.T) { 14 | NewEnvTest(nil).Run(func() { 15 | conf, err := New(nil) 16 | require.EqualError(t, err, "invalid config settings: No token configured") 17 | assert.Nil(t, conf) 18 | }) 19 | } 20 | 21 | func TestNew_minimalEnv(t *testing.T) { 22 | NewEnvTest(map[string]string{ 23 | "UNMS_EXPORTER_TOKEN": "a=b", 24 | }).Run(func() { 25 | conf, err := New(nil) 26 | require.NoError(t, err) 27 | assert.EqualValues(t, &Config{ 28 | ServerAddr: DefaultServerAddress, 29 | LogLevel: DefaultLogLevel, 30 | TokenPerHost: tokenMap{"a": "b"}, 31 | ExtraMetrics: nil, 32 | }, conf) 33 | }) 34 | } 35 | 36 | func TestNew_multipleToken(t *testing.T) { 37 | NewEnvTest(map[string]string{ 38 | "UNMS_EXPORTER_TOKEN": "a=b,c=d,e==f", 39 | }).Run(func() { 40 | conf, err := New(nil) 41 | require.NoError(t, err) 42 | assert.EqualValues(t, &Config{ 43 | ServerAddr: DefaultServerAddress, 44 | LogLevel: DefaultLogLevel, 45 | TokenPerHost: tokenMap{ 46 | "a": "b", 47 | "c": "d", 48 | "e": "=f", 49 | }, 50 | ExtraMetrics: nil, 51 | }, conf) 52 | }) 53 | } 54 | 55 | func TestNew_flagsTakePriorityOverEnv(t *testing.T) { 56 | NewEnvTest(map[string]string{ 57 | "UNMS_EXPORTER_TOKEN": "a=b", 58 | "UNMS_EXPORTER_LISTEN": "[::1]:1234", 59 | }).Run(func() { 60 | conf, err := New([]string{"--listen", "[fe80::1]:9806"}) 61 | require.NoError(t, err) 62 | assert.EqualValues(t, &Config{ 63 | ServerAddr: "[fe80::1]:9806", 64 | LogLevel: DefaultLogLevel, 65 | TokenPerHost: tokenMap{"a": "b"}, 66 | ExtraMetrics: nil, 67 | }, conf) 68 | }) 69 | } 70 | 71 | func TestNew_withConfigFile(t *testing.T) { 72 | NewEnvTest(nil).Run(func() { 73 | conf, err := New([]string{"--config", "testdata/valid_config.yml"}) 74 | require.NoError(t, err) 75 | assert.EqualValues(t, &Config{ 76 | ServerAddr: "[::1]:1234", 77 | LogLevel: logrus.WarnLevel, 78 | TokenPerHost: tokenMap{"a.example.com": "abc"}, 79 | ExtraMetrics: []string{"ping", "link", "wifi"}, 80 | }, conf) 81 | }) 82 | } 83 | 84 | func TestNew_extraMetricsFromEnv(t *testing.T) { 85 | NewEnvTest(map[string]string{ 86 | "UNMS_EXPORTER_TOKEN": "a=b", 87 | "UNMS_EXPORTER_EXTRA_METRICS": "ping,ccq", 88 | }).Run(func() { 89 | conf, err := New(nil) 90 | require.NoError(t, err) 91 | assert.EqualValues(t, &Config{ 92 | ServerAddr: DefaultServerAddress, 93 | LogLevel: DefaultLogLevel, 94 | TokenPerHost: tokenMap{"a": "b"}, 95 | ExtraMetrics: []string{"ping", "ccq"}, 96 | }, conf) 97 | }) 98 | } 99 | 100 | func TestNew_extraMetricsFromSingleFlag(t *testing.T) { 101 | NewEnvTest(map[string]string{ 102 | "UNMS_EXPORTER_TOKEN": "a=b", 103 | }).Run(func() { 104 | conf, err := New([]string{"--extra-metrics", "ping,ccq"}) 105 | require.NoError(t, err) 106 | assert.EqualValues(t, &Config{ 107 | ServerAddr: DefaultServerAddress, 108 | LogLevel: DefaultLogLevel, 109 | TokenPerHost: tokenMap{"a": "b"}, 110 | ExtraMetrics: []string{"ping", "ccq"}, 111 | }, conf) 112 | }) 113 | } 114 | 115 | func TestNew_extraMetricsFromMultipleFlags(t *testing.T) { 116 | NewEnvTest(map[string]string{ 117 | "UNMS_EXPORTER_TOKEN": "a=b", 118 | }).Run(func() { 119 | conf, err := New([]string{"--extra-metrics", "link", "--extra-metrics", "wifi"}) 120 | require.NoError(t, err) 121 | assert.EqualValues(t, &Config{ 122 | ServerAddr: DefaultServerAddress, 123 | LogLevel: DefaultLogLevel, 124 | TokenPerHost: tokenMap{"a": "b"}, 125 | ExtraMetrics: []string{"link", "wifi"}, 126 | }, conf) 127 | }) 128 | } 129 | 130 | type envTest struct { 131 | orig map[string]string 132 | test map[string]string 133 | } 134 | 135 | func NewEnvTest(env map[string]string) *envTest { 136 | test := &envTest{ 137 | orig: make(map[string]string), 138 | test: env, 139 | } 140 | for k := range env { 141 | if orig := os.Getenv(k); orig != "" { 142 | test.orig[k] = orig 143 | } 144 | } 145 | return test 146 | } 147 | 148 | // there can only be at most one test modifying the environment 149 | var envTestMu sync.Mutex 150 | 151 | func (e *envTest) Run(runner func()) { 152 | envTestMu.Lock() 153 | defer envTestMu.Unlock() 154 | 155 | e.Apply(e.test) // set test env 156 | defer e.Clear() // remove any test env vars 157 | defer e.Apply(e.orig) // reset original env 158 | 159 | runner() 160 | } 161 | 162 | func (e *envTest) Apply(env map[string]string) { 163 | for k, v := range env { 164 | os.Setenv(k, v) 165 | } 166 | } 167 | 168 | func (e *envTest) Clear() { 169 | for k := range e.test { 170 | os.Unsetenv(k) 171 | } 172 | } 173 | -------------------------------------------------------------------------------- /models/device_overview.go: -------------------------------------------------------------------------------- 1 | // Code generated by go-swagger; DO NOT EDIT. 2 | 3 | package models 4 | 5 | // This file was generated by the swagger tool. 6 | // Editing this file might prove futile when you re-run the swagger generate command 7 | 8 | import ( 9 | "context" 10 | 11 | "github.com/go-openapi/errors" 12 | "github.com/go-openapi/strfmt" 13 | "github.com/go-openapi/swag" 14 | "github.com/go-openapi/validate" 15 | ) 16 | 17 | // DeviceOverview Read-only basic device/client overview attributes. 18 | // 19 | // swagger:model DeviceOverview 20 | type DeviceOverview struct { 21 | 22 | // battery capacity 23 | BatteryCapacity float64 `json:"batteryCapacity,omitempty"` 24 | 25 | // battery time 26 | BatteryTime float64 `json:"batteryTime,omitempty"` 27 | 28 | // Nullable property in milliamperes. 29 | BiasCurrent float64 `json:"biasCurrent,omitempty"` 30 | 31 | // TRUE if device can be upgraded. 32 | CanUpgrade bool `json:"canUpgrade,omitempty"` 33 | 34 | // channel width 35 | ChannelWidth float64 `json:"channelWidth,omitempty"` 36 | 37 | // Current cpu load. 38 | CPU float64 `json:"cpu,omitempty"` 39 | 40 | // created at 41 | // Format: date-time 42 | CreatedAt strfmt.DateTime `json:"createdAt,omitempty"` 43 | 44 | // Nullable property in meters. 45 | Distance float64 `json:"distance,omitempty"` 46 | 47 | // downlink capacity 48 | DownlinkCapacity int64 `json:"downlinkCapacity,omitempty"` 49 | 50 | // Nullable prop; current frequency (only for airmax devices). 51 | Frequency float64 `json:"frequency,omitempty"` 52 | 53 | // TRUE if device is in location mode. 54 | IsLocateRunning bool `json:"isLocateRunning,omitempty"` 55 | 56 | // Last seen timestamp in ISO format. 57 | // Example: 2018-11-14T15:20:32.004Z 58 | // Format: date-time 59 | LastSeen strfmt.DateTime `json:"lastSeen,omitempty"` 60 | 61 | // power status 62 | PowerStatus float64 `json:"powerStatus,omitempty"` 63 | 64 | // Current memory usage. 65 | RAM float64 `json:"ram,omitempty"` 66 | 67 | // Theoretical max remote signal level. 68 | // Example: -55 69 | RemoteSignalMax float64 `json:"remoteSignalMax,omitempty"` 70 | 71 | // TRUE if device is running on battery 72 | RunningOnBattery bool `json:"runningOnBattery,omitempty"` 73 | 74 | // Nullable prop; current signal level (only for airmax devices), for example -55 dBm. 75 | // Example: -55 76 | Signal float64 `json:"signal,omitempty"` 77 | 78 | // Theoretical max local signal level. 79 | // Example: -55 80 | SignalMax float64 `json:"signalMax,omitempty"` 81 | 82 | // Count of stations (only for airmax and aircube). 83 | StationsCount float64 `json:"stationsCount,omitempty"` 84 | 85 | // Read-only value generated by UNMS. 86 | Status string `json:"status,omitempty"` 87 | 88 | // temperature 89 | Temperature float64 `json:"temperature,omitempty"` 90 | 91 | // theoretical downlink capacity 92 | TheoreticalDownlinkCapacity int64 `json:"theoreticalDownlinkCapacity,omitempty"` 93 | 94 | // theoretical max downlink capacity 95 | TheoreticalMaxDownlinkCapacity int64 `json:"theoreticalMaxDownlinkCapacity,omitempty"` 96 | 97 | // theoretical max uplink capacity 98 | TheoreticalMaxUplinkCapacity int64 `json:"theoreticalMaxUplinkCapacity,omitempty"` 99 | 100 | // theoretical uplink capacity 101 | TheoreticalUplinkCapacity int64 `json:"theoreticalUplinkCapacity,omitempty"` 102 | 103 | // transmit power 104 | TransmitPower float64 `json:"transmitPower,omitempty"` 105 | 106 | // uplink capacity 107 | UplinkCapacity int64 `json:"uplinkCapacity,omitempty"` 108 | 109 | // Uptime in seconds. 110 | Uptime float64 `json:"uptime,omitempty"` 111 | 112 | // System input voltage in V. 113 | Voltage float64 `json:"voltage,omitempty"` 114 | 115 | // wireless mode 116 | WirelessMode string `json:"wirelessMode,omitempty"` 117 | } 118 | 119 | // Validate validates this device overview 120 | func (m *DeviceOverview) Validate(formats strfmt.Registry) error { 121 | var res []error 122 | 123 | if err := m.validateCreatedAt(formats); err != nil { 124 | res = append(res, err) 125 | } 126 | 127 | if err := m.validateLastSeen(formats); err != nil { 128 | res = append(res, err) 129 | } 130 | 131 | if len(res) > 0 { 132 | return errors.CompositeValidationError(res...) 133 | } 134 | return nil 135 | } 136 | 137 | func (m *DeviceOverview) validateCreatedAt(formats strfmt.Registry) error { 138 | if swag.IsZero(m.CreatedAt) { // not required 139 | return nil 140 | } 141 | 142 | if err := validate.FormatOf("createdAt", "body", "date-time", m.CreatedAt.String(), formats); err != nil { 143 | return err 144 | } 145 | 146 | return nil 147 | } 148 | 149 | func (m *DeviceOverview) validateLastSeen(formats strfmt.Registry) error { 150 | if swag.IsZero(m.LastSeen) { // not required 151 | return nil 152 | } 153 | 154 | if err := validate.FormatOf("lastSeen", "body", "date-time", m.LastSeen.String(), formats); err != nil { 155 | return err 156 | } 157 | 158 | return nil 159 | } 160 | 161 | // ContextValidate validates this device overview based on context it is used 162 | func (m *DeviceOverview) ContextValidate(ctx context.Context, formats strfmt.Registry) error { 163 | return nil 164 | } 165 | 166 | // MarshalBinary interface implementation 167 | func (m *DeviceOverview) MarshalBinary() ([]byte, error) { 168 | if m == nil { 169 | return nil, nil 170 | } 171 | return swag.WriteJSON(m) 172 | } 173 | 174 | // UnmarshalBinary interface implementation 175 | func (m *DeviceOverview) UnmarshalBinary(b []byte) error { 176 | var res DeviceOverview 177 | if err := swag.ReadJSON(b, &res); err != nil { 178 | return err 179 | } 180 | *m = res 181 | return nil 182 | } 183 | -------------------------------------------------------------------------------- /models/site.go: -------------------------------------------------------------------------------- 1 | // Code generated by go-swagger; DO NOT EDIT. 2 | 3 | package models 4 | 5 | // This file was generated by the swagger tool. 6 | // Editing this file might prove futile when you re-run the swagger generate command 7 | 8 | import ( 9 | "context" 10 | "encoding/json" 11 | 12 | "github.com/go-openapi/errors" 13 | "github.com/go-openapi/strfmt" 14 | "github.com/go-openapi/swag" 15 | "github.com/go-openapi/validate" 16 | ) 17 | 18 | // Site site 19 | // 20 | // swagger:model site 21 | type Site struct { 22 | 23 | // Site ID. 24 | // Example: f7ac9cad-ea28-4390-93c8-7add010e8ee3 25 | // Required: true 26 | ID *string `json:"id"` 27 | 28 | // Site name. 29 | // Example: Mount Everest 30 | Name string `json:"name,omitempty"` 31 | 32 | // parent 33 | Parent *Site `json:"parent,omitempty"` 34 | 35 | // Status of the site. 36 | // Example: active 37 | // Required: true 38 | // Enum: [active disconnected inactive] 39 | Status *string `json:"status"` 40 | 41 | // Type of the site. 42 | // Example: site 43 | // Required: true 44 | // Enum: [site endpoint] 45 | Type *string `json:"type"` 46 | } 47 | 48 | // Validate validates this site 49 | func (m *Site) Validate(formats strfmt.Registry) error { 50 | var res []error 51 | 52 | if err := m.validateID(formats); err != nil { 53 | res = append(res, err) 54 | } 55 | 56 | if err := m.validateParent(formats); err != nil { 57 | res = append(res, err) 58 | } 59 | 60 | if err := m.validateStatus(formats); err != nil { 61 | res = append(res, err) 62 | } 63 | 64 | if err := m.validateType(formats); err != nil { 65 | res = append(res, err) 66 | } 67 | 68 | if len(res) > 0 { 69 | return errors.CompositeValidationError(res...) 70 | } 71 | return nil 72 | } 73 | 74 | func (m *Site) validateID(formats strfmt.Registry) error { 75 | 76 | if err := validate.Required("id", "body", m.ID); err != nil { 77 | return err 78 | } 79 | 80 | return nil 81 | } 82 | 83 | func (m *Site) validateParent(formats strfmt.Registry) error { 84 | if swag.IsZero(m.Parent) { // not required 85 | return nil 86 | } 87 | 88 | if m.Parent != nil { 89 | if err := m.Parent.Validate(formats); err != nil { 90 | if ve, ok := err.(*errors.Validation); ok { 91 | return ve.ValidateName("parent") 92 | } else if ce, ok := err.(*errors.CompositeError); ok { 93 | return ce.ValidateName("parent") 94 | } 95 | return err 96 | } 97 | } 98 | 99 | return nil 100 | } 101 | 102 | var siteTypeStatusPropEnum []interface{} 103 | 104 | func init() { 105 | var res []string 106 | if err := json.Unmarshal([]byte(`["active","disconnected","inactive"]`), &res); err != nil { 107 | panic(err) 108 | } 109 | for _, v := range res { 110 | siteTypeStatusPropEnum = append(siteTypeStatusPropEnum, v) 111 | } 112 | } 113 | 114 | const ( 115 | 116 | // SiteStatusActive captures enum value "active" 117 | SiteStatusActive string = "active" 118 | 119 | // SiteStatusDisconnected captures enum value "disconnected" 120 | SiteStatusDisconnected string = "disconnected" 121 | 122 | // SiteStatusInactive captures enum value "inactive" 123 | SiteStatusInactive string = "inactive" 124 | ) 125 | 126 | // prop value enum 127 | func (m *Site) validateStatusEnum(path, location string, value string) error { 128 | if err := validate.EnumCase(path, location, value, siteTypeStatusPropEnum, true); err != nil { 129 | return err 130 | } 131 | return nil 132 | } 133 | 134 | func (m *Site) validateStatus(formats strfmt.Registry) error { 135 | 136 | if err := validate.Required("status", "body", m.Status); err != nil { 137 | return err 138 | } 139 | 140 | // value enum 141 | if err := m.validateStatusEnum("status", "body", *m.Status); err != nil { 142 | return err 143 | } 144 | 145 | return nil 146 | } 147 | 148 | var siteTypeTypePropEnum []interface{} 149 | 150 | func init() { 151 | var res []string 152 | if err := json.Unmarshal([]byte(`["site","endpoint"]`), &res); err != nil { 153 | panic(err) 154 | } 155 | for _, v := range res { 156 | siteTypeTypePropEnum = append(siteTypeTypePropEnum, v) 157 | } 158 | } 159 | 160 | const ( 161 | 162 | // SiteTypeSite captures enum value "site" 163 | SiteTypeSite string = "site" 164 | 165 | // SiteTypeEndpoint captures enum value "endpoint" 166 | SiteTypeEndpoint string = "endpoint" 167 | ) 168 | 169 | // prop value enum 170 | func (m *Site) validateTypeEnum(path, location string, value string) error { 171 | if err := validate.EnumCase(path, location, value, siteTypeTypePropEnum, true); err != nil { 172 | return err 173 | } 174 | return nil 175 | } 176 | 177 | func (m *Site) validateType(formats strfmt.Registry) error { 178 | 179 | if err := validate.Required("type", "body", m.Type); err != nil { 180 | return err 181 | } 182 | 183 | // value enum 184 | if err := m.validateTypeEnum("type", "body", *m.Type); err != nil { 185 | return err 186 | } 187 | 188 | return nil 189 | } 190 | 191 | // ContextValidate validate this site based on the context it is used 192 | func (m *Site) ContextValidate(ctx context.Context, formats strfmt.Registry) error { 193 | var res []error 194 | 195 | if err := m.contextValidateParent(ctx, formats); err != nil { 196 | res = append(res, err) 197 | } 198 | 199 | if len(res) > 0 { 200 | return errors.CompositeValidationError(res...) 201 | } 202 | return nil 203 | } 204 | 205 | func (m *Site) contextValidateParent(ctx context.Context, formats strfmt.Registry) error { 206 | 207 | if m.Parent != nil { 208 | 209 | if swag.IsZero(m.Parent) { // not required 210 | return nil 211 | } 212 | 213 | if err := m.Parent.ContextValidate(ctx, formats); err != nil { 214 | if ve, ok := err.(*errors.Validation); ok { 215 | return ve.ValidateName("parent") 216 | } else if ce, ok := err.(*errors.CompositeError); ok { 217 | return ce.ValidateName("parent") 218 | } 219 | return err 220 | } 221 | } 222 | 223 | return nil 224 | } 225 | 226 | // MarshalBinary interface implementation 227 | func (m *Site) MarshalBinary() ([]byte, error) { 228 | if m == nil { 229 | return nil, nil 230 | } 231 | return swag.WriteJSON(m) 232 | } 233 | 234 | // UnmarshalBinary interface implementation 235 | func (m *Site) UnmarshalBinary(b []byte) error { 236 | var res Site 237 | if err := swag.ReadJSON(b, &res); err != nil { 238 | return err 239 | } 240 | *m = res 241 | return nil 242 | } 243 | -------------------------------------------------------------------------------- /models/device_interface_schema.go: -------------------------------------------------------------------------------- 1 | // Code generated by go-swagger; DO NOT EDIT. 2 | 3 | package models 4 | 5 | // This file was generated by the swagger tool. 6 | // Editing this file might prove futile when you re-run the swagger generate command 7 | 8 | import ( 9 | "context" 10 | 11 | "github.com/go-openapi/errors" 12 | "github.com/go-openapi/strfmt" 13 | "github.com/go-openapi/swag" 14 | "github.com/go-openapi/validate" 15 | ) 16 | 17 | // DeviceInterfaceSchema device interface schema 18 | // 19 | // swagger:model DeviceInterfaceSchema 20 | type DeviceInterfaceSchema struct { 21 | 22 | // can display statistics 23 | // Example: true 24 | CanDisplayStatistics bool `json:"canDisplayStatistics,omitempty"` 25 | 26 | // enabled 27 | // Example: true 28 | Enabled bool `json:"enabled,omitempty"` 29 | 30 | // identification 31 | // Required: true 32 | Identification *InterfaceIdentification `json:"identification"` 33 | 34 | // speed 35 | // Example: auto 36 | // Pattern: ^autodetect|auto|\d+-(half|full)$ 37 | Speed string `json:"speed,omitempty"` 38 | 39 | // statistics 40 | Statistics *InterfaceStatistics `json:"statistics,omitempty"` 41 | 42 | // status 43 | Status *InterfaceStatus `json:"status,omitempty"` 44 | } 45 | 46 | // Validate validates this device interface schema 47 | func (m *DeviceInterfaceSchema) Validate(formats strfmt.Registry) error { 48 | var res []error 49 | 50 | if err := m.validateIdentification(formats); err != nil { 51 | res = append(res, err) 52 | } 53 | 54 | if err := m.validateSpeed(formats); err != nil { 55 | res = append(res, err) 56 | } 57 | 58 | if err := m.validateStatistics(formats); err != nil { 59 | res = append(res, err) 60 | } 61 | 62 | if err := m.validateStatus(formats); err != nil { 63 | res = append(res, err) 64 | } 65 | 66 | if len(res) > 0 { 67 | return errors.CompositeValidationError(res...) 68 | } 69 | return nil 70 | } 71 | 72 | func (m *DeviceInterfaceSchema) validateIdentification(formats strfmt.Registry) error { 73 | 74 | if err := validate.Required("identification", "body", m.Identification); err != nil { 75 | return err 76 | } 77 | 78 | if m.Identification != nil { 79 | if err := m.Identification.Validate(formats); err != nil { 80 | if ve, ok := err.(*errors.Validation); ok { 81 | return ve.ValidateName("identification") 82 | } else if ce, ok := err.(*errors.CompositeError); ok { 83 | return ce.ValidateName("identification") 84 | } 85 | return err 86 | } 87 | } 88 | 89 | return nil 90 | } 91 | 92 | func (m *DeviceInterfaceSchema) validateSpeed(formats strfmt.Registry) error { 93 | if swag.IsZero(m.Speed) { // not required 94 | return nil 95 | } 96 | 97 | if err := validate.Pattern("speed", "body", m.Speed, `^autodetect|auto|\d+-(half|full)$`); err != nil { 98 | return err 99 | } 100 | 101 | return nil 102 | } 103 | 104 | func (m *DeviceInterfaceSchema) validateStatistics(formats strfmt.Registry) error { 105 | if swag.IsZero(m.Statistics) { // not required 106 | return nil 107 | } 108 | 109 | if m.Statistics != nil { 110 | if err := m.Statistics.Validate(formats); err != nil { 111 | if ve, ok := err.(*errors.Validation); ok { 112 | return ve.ValidateName("statistics") 113 | } else if ce, ok := err.(*errors.CompositeError); ok { 114 | return ce.ValidateName("statistics") 115 | } 116 | return err 117 | } 118 | } 119 | 120 | return nil 121 | } 122 | 123 | func (m *DeviceInterfaceSchema) validateStatus(formats strfmt.Registry) error { 124 | if swag.IsZero(m.Status) { // not required 125 | return nil 126 | } 127 | 128 | if m.Status != nil { 129 | if err := m.Status.Validate(formats); err != nil { 130 | if ve, ok := err.(*errors.Validation); ok { 131 | return ve.ValidateName("status") 132 | } else if ce, ok := err.(*errors.CompositeError); ok { 133 | return ce.ValidateName("status") 134 | } 135 | return err 136 | } 137 | } 138 | 139 | return nil 140 | } 141 | 142 | // ContextValidate validate this device interface schema based on the context it is used 143 | func (m *DeviceInterfaceSchema) ContextValidate(ctx context.Context, formats strfmt.Registry) error { 144 | var res []error 145 | 146 | if err := m.contextValidateIdentification(ctx, formats); err != nil { 147 | res = append(res, err) 148 | } 149 | 150 | if err := m.contextValidateStatistics(ctx, formats); err != nil { 151 | res = append(res, err) 152 | } 153 | 154 | if err := m.contextValidateStatus(ctx, formats); err != nil { 155 | res = append(res, err) 156 | } 157 | 158 | if len(res) > 0 { 159 | return errors.CompositeValidationError(res...) 160 | } 161 | return nil 162 | } 163 | 164 | func (m *DeviceInterfaceSchema) contextValidateIdentification(ctx context.Context, formats strfmt.Registry) error { 165 | 166 | if m.Identification != nil { 167 | 168 | if err := m.Identification.ContextValidate(ctx, formats); err != nil { 169 | if ve, ok := err.(*errors.Validation); ok { 170 | return ve.ValidateName("identification") 171 | } else if ce, ok := err.(*errors.CompositeError); ok { 172 | return ce.ValidateName("identification") 173 | } 174 | return err 175 | } 176 | } 177 | 178 | return nil 179 | } 180 | 181 | func (m *DeviceInterfaceSchema) contextValidateStatistics(ctx context.Context, formats strfmt.Registry) error { 182 | 183 | if m.Statistics != nil { 184 | 185 | if swag.IsZero(m.Statistics) { // not required 186 | return nil 187 | } 188 | 189 | if err := m.Statistics.ContextValidate(ctx, formats); err != nil { 190 | if ve, ok := err.(*errors.Validation); ok { 191 | return ve.ValidateName("statistics") 192 | } else if ce, ok := err.(*errors.CompositeError); ok { 193 | return ce.ValidateName("statistics") 194 | } 195 | return err 196 | } 197 | } 198 | 199 | return nil 200 | } 201 | 202 | func (m *DeviceInterfaceSchema) contextValidateStatus(ctx context.Context, formats strfmt.Registry) error { 203 | 204 | if m.Status != nil { 205 | 206 | if swag.IsZero(m.Status) { // not required 207 | return nil 208 | } 209 | 210 | if err := m.Status.ContextValidate(ctx, formats); err != nil { 211 | if ve, ok := err.(*errors.Validation); ok { 212 | return ve.ValidateName("status") 213 | } else if ce, ok := err.(*errors.CompositeError); ok { 214 | return ce.ValidateName("status") 215 | } 216 | return err 217 | } 218 | } 219 | 220 | return nil 221 | } 222 | 223 | // MarshalBinary interface implementation 224 | func (m *DeviceInterfaceSchema) MarshalBinary() ([]byte, error) { 225 | if m == nil { 226 | return nil, nil 227 | } 228 | return swag.WriteJSON(m) 229 | } 230 | 231 | // UnmarshalBinary interface implementation 232 | func (m *DeviceInterfaceSchema) UnmarshalBinary(b []byte) error { 233 | var res DeviceInterfaceSchema 234 | if err := swag.ReadJSON(b, &res); err != nil { 235 | return err 236 | } 237 | *m = res 238 | return nil 239 | } 240 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # UNMS Exporter for Prometheus 2 | 3 | Small daemon offering UNMS device statistics as Prometheus metrics. 4 | 5 | ## Deployment 6 | 7 | Premade Docker Images are available at [quay.io](https://quay.io/repository/ffddorf/unms-exporter). 8 | 9 | ```bash 10 | docker pull quay.io/ffddorf/unms-exporter 11 | ``` 12 | 13 | ## Configuration 14 | 15 | ### Options 16 | 17 | Config can be specified via a YAML file, as args or from environment variables. 18 | 19 | ### Listen Address 20 | 21 | - Config: `listen` 22 | - Args: `--listen` or `-l` 23 | - Env: `UNMS_EXPORTER_SERVER_ADDR` 24 | 25 | Address the exporter should listen on. Defaults to `[::]:9806`. 26 | 27 | ### Config File Location 28 | 29 | - Args: `--config` or `-c` 30 | 31 | Location of the YAML config file to load. 32 | 33 | ### Log Verbosity 34 | 35 | - Config: `log_level` 36 | - Env: `UNMS_EXPORTER_LOG_LEVEL` 37 | 38 | Log verbosity level. Defaults to `info`. Use `debug` to get more details. 39 | 40 | ### UNMS API Tokens 41 | 42 | - Config: `token` 43 | - Env: `UNMS_EXPORTER_TOKEN` 44 | - use a comma-separated list of `instance=token` values 45 | 46 | Configures an API token per UNMS instance. 47 | 48 |
Example: config file (click to open) 49 | 50 | ```yaml 51 | # config.yaml 52 | token: 53 | my-unms-instance.example.org: "my token" 54 | unms.example.com: "token123" 55 | ``` 56 | 57 | ```console 58 | $ unms-exporter --config config.yaml 59 | ``` 60 | 61 |
62 |
Example: environment variable (click to open) 63 | 64 | ```console 65 | $ UNMS_EXPORTER_TOKEN="my-unms-instance.example.org=my token,unms.example.com=token123" \ 66 | unms-exporter 67 | ``` 68 | 69 |
70 | 71 | ### Extra metrics 72 | 73 | - Config: `extra_metrics` (as Array) 74 | - Args: `--extra-metrics` (as comma-separated list) 75 | - Env: `UNMS_EXPORTER_EXTRA_METRICS` (as comma-separated list) 76 | 77 | Enable additional metrics to be exported. These metrics may require extra 78 | HTTP requests, usually one per device, so they are disabled by default. 79 | 80 |
Example: config file (click to open) 81 | 82 | ```yaml 83 | # config.yaml 84 | extras: 85 | - ping 86 | ``` 87 | 88 | ```console 89 | $ unms-exporter --config config.yaml 90 | ``` 91 | 92 |
93 |
Example: environment variable (click to open) 94 | 95 | ```console 96 | $ UNMS_EXPORTER_EXTRA_METRICS="ping" \ 97 | unms-exporter 98 | ``` 99 | 100 |
101 |
Example: command line argument (click to open) 102 | 103 | ```console 104 | $ unms-exporter --extra-metrics="ping" 105 | ``` 106 | 107 |
108 | 109 | #### Available metrics 110 | 111 | - `ping`: Fetch statistical data from UNMS and extract and export 112 | Ping RTT measurements between UNMS and the device. 113 | 114 |
Exported metrics (click to open) 115 | 116 | - `ping_loss_ratio`: Packet loss ratio (range 0-1, with 0.33 meaning 33% packet loss) 117 | - `ping_rtt_best_seconds`: Best round trip time, in seconds 118 | - `ping_rtt_mean_seconds`: Mean round trip time, in seconds 119 | - `ping_rtt_worst_seconds`: Worst round trip time, in seconds 120 | - `ping_rtt_std_deviation_seconds`: Standard deviation for round trip time, in seconds 121 | 122 |
123 | 124 | Further data is available, but not currently exported (see the API 125 | documentation for the `/devices/{id}/statistics` endpoint on your UNMS 126 | installation to get an overview). Feel free to [open a new issue][] to 127 | inquire whether an integration into the exporter is feasable. 128 | 129 | [open a new issue]: https://github.com/ffddorf/unms-exporter/issues/new 130 | 131 | ## Prometheus Scrape Setup 132 | 133 | The exporter follows the convention for exporters. The UNMS instance to target should be specified using the `target` query parameter. 134 | 135 | Here is how to achieve this using a static prometheus config: 136 | 137 | ```yaml 138 | scrape_configs: 139 | - job_name: exporters 140 | static_configs: 141 | - exporter.example.org:9806 # UNMS exporter 142 | - exporter.example.org:9100 # node exporter 143 | - ... 144 | 145 | - job_name: unms_exporter 146 | # for a static target "unms.example.org", rewrite it to 147 | # "exporter.example.org:9806/metrics?target=unms.example.org", 148 | # but keep "unms.example.org" as instance label 149 | relabel_configs: 150 | - source_labels: [__address__] 151 | target_label: instance 152 | - source_labels: [__address__] 153 | target_label: __param_target 154 | - replacement: 'exporter.example.org:9806' 155 | target_label: __address__ 156 | static_configs: 157 | - targets: 158 | - my-unms-instance.example.org 159 | ``` 160 | 161 |
Upgrade from v0.1.2 or earlier (click to open) 162 | 163 | Previous versions did expose the UNMS metrics under any path on the exporter, 164 | i.e. the following URLs were handled identically: 165 | 166 | - `http://localhost:9806/?target=my-unms-instance.example.org` 167 | - `http://localhost:9806/metrics?target=my-unms-instance.example.org` 168 | - `http://localhost:9806/this/is/all/ignored?target=my-unms-instance.example.org` 169 | 170 | Additionally, the UNMS exporter has returned a mixed set of internal and 171 | instance-specific metrics. 172 | 173 | This has changed and now follows best practices. All UNMS-specific metrics 174 | are now available *only* on the following URL: 175 | 176 | - `http://localhost:9806/metrics?target=my-unms-instance.example.org` 177 | 178 | Additionally, internal metrics (e.g. Go runtime statistics) can be retrieved 179 | by omitting the `target` parameter: 180 | 181 | - `http://localhost:9806/metrics` 182 | 183 |
184 | 185 | ## Available Metrics 186 | 187 | ### Device wide 188 | 189 | - `device_cpu`: CPU load average in percent 190 | - `device_ram`: RAM usage in percent 191 | - `device_enabled`: Indicating if device is enabled in UNMS 192 | - `device_maintenance`: Indicating if device is in maintenance mode (useful for muting alerts) 193 | - `device_uptime`: Uptime in seconds 194 | - `device_last_seen`: Last seen as unix timestamp 195 | - `device_last_backup`: Time of last backup as unix timestamp 196 | 197 | ### Per Interface 198 | 199 | - `interface_enabled`: Indicating if interface is enabled 200 | - `interface_plugged`: Indicating if interface has a cable plugged 201 | - `interface_up`: Indicating if interface is considered up 202 | - `interface_dropped`: Number of packets dropped 203 | - `interface_errors`: Number of interface errors 204 | - `interface_rx_bytes`: Bytes received since last reset 205 | - `interface_tx_bytes`: Bytes transmitted since last reset 206 | - `interface_rx_rate`: Bytes received rate (momentarily) 207 | - `interface_tx_rate`: Bytes transmitted rate (momentarily) 208 | - `interface_poe_power`: POE power consumption 209 | 210 | ### WAN Interface 211 | 212 | If an interface is marked as the WAN interface, these metrics are populated. 213 | 214 | - `wan_rx_bytes`: Bytes received since last reset 215 | - `wan_tx_bytes`: Bytes transmitted since last reset 216 | - `wan_rx_rate`: Bytes received rate (momentarily) 217 | - `wan_tx_rate`: Bytes transmitted rate (momentarily) 218 | -------------------------------------------------------------------------------- /client/devices/get_devices_id_statistics_parameters.go: -------------------------------------------------------------------------------- 1 | // Code generated by go-swagger; DO NOT EDIT. 2 | 3 | package devices 4 | 5 | // This file was generated by the swagger tool. 6 | // Editing this file might prove futile when you re-run the swagger generate command 7 | 8 | import ( 9 | "context" 10 | "net/http" 11 | "time" 12 | 13 | "github.com/go-openapi/errors" 14 | "github.com/go-openapi/runtime" 15 | cr "github.com/go-openapi/runtime/client" 16 | "github.com/go-openapi/strfmt" 17 | ) 18 | 19 | // NewGetDevicesIDStatisticsParams creates a new GetDevicesIDStatisticsParams object, 20 | // with the default timeout for this client. 21 | // 22 | // Default values are not hydrated, since defaults are normally applied by the API server side. 23 | // 24 | // To enforce default values in parameter, use SetDefaults or WithDefaults. 25 | func NewGetDevicesIDStatisticsParams() *GetDevicesIDStatisticsParams { 26 | return &GetDevicesIDStatisticsParams{ 27 | timeout: cr.DefaultTimeout, 28 | } 29 | } 30 | 31 | // NewGetDevicesIDStatisticsParamsWithTimeout creates a new GetDevicesIDStatisticsParams object 32 | // with the ability to set a timeout on a request. 33 | func NewGetDevicesIDStatisticsParamsWithTimeout(timeout time.Duration) *GetDevicesIDStatisticsParams { 34 | return &GetDevicesIDStatisticsParams{ 35 | timeout: timeout, 36 | } 37 | } 38 | 39 | // NewGetDevicesIDStatisticsParamsWithContext creates a new GetDevicesIDStatisticsParams object 40 | // with the ability to set a context for a request. 41 | func NewGetDevicesIDStatisticsParamsWithContext(ctx context.Context) *GetDevicesIDStatisticsParams { 42 | return &GetDevicesIDStatisticsParams{ 43 | Context: ctx, 44 | } 45 | } 46 | 47 | // NewGetDevicesIDStatisticsParamsWithHTTPClient creates a new GetDevicesIDStatisticsParams object 48 | // with the ability to set a custom HTTPClient for a request. 49 | func NewGetDevicesIDStatisticsParamsWithHTTPClient(client *http.Client) *GetDevicesIDStatisticsParams { 50 | return &GetDevicesIDStatisticsParams{ 51 | HTTPClient: client, 52 | } 53 | } 54 | 55 | /* 56 | GetDevicesIDStatisticsParams contains all the parameters to send to the API endpoint 57 | 58 | for the get devices Id statistics operation. 59 | 60 | Typically these are written to a http.Request. 61 | */ 62 | type GetDevicesIDStatisticsParams struct { 63 | 64 | // ID. 65 | ID string 66 | 67 | /* Interval. 68 | 69 | Interval 70 | */ 71 | Interval string 72 | 73 | // Period. 74 | Period string 75 | 76 | // Start. 77 | Start string 78 | 79 | timeout time.Duration 80 | Context context.Context 81 | HTTPClient *http.Client 82 | } 83 | 84 | // WithDefaults hydrates default values in the get devices Id statistics params (not the query body). 85 | // 86 | // All values with no default are reset to their zero value. 87 | func (o *GetDevicesIDStatisticsParams) WithDefaults() *GetDevicesIDStatisticsParams { 88 | o.SetDefaults() 89 | return o 90 | } 91 | 92 | // SetDefaults hydrates default values in the get devices Id statistics params (not the query body). 93 | // 94 | // All values with no default are reset to their zero value. 95 | func (o *GetDevicesIDStatisticsParams) SetDefaults() { 96 | // no default values defined for this parameter 97 | } 98 | 99 | // WithTimeout adds the timeout to the get devices Id statistics params 100 | func (o *GetDevicesIDStatisticsParams) WithTimeout(timeout time.Duration) *GetDevicesIDStatisticsParams { 101 | o.SetTimeout(timeout) 102 | return o 103 | } 104 | 105 | // SetTimeout adds the timeout to the get devices Id statistics params 106 | func (o *GetDevicesIDStatisticsParams) SetTimeout(timeout time.Duration) { 107 | o.timeout = timeout 108 | } 109 | 110 | // WithContext adds the context to the get devices Id statistics params 111 | func (o *GetDevicesIDStatisticsParams) WithContext(ctx context.Context) *GetDevicesIDStatisticsParams { 112 | o.SetContext(ctx) 113 | return o 114 | } 115 | 116 | // SetContext adds the context to the get devices Id statistics params 117 | func (o *GetDevicesIDStatisticsParams) SetContext(ctx context.Context) { 118 | o.Context = ctx 119 | } 120 | 121 | // WithHTTPClient adds the HTTPClient to the get devices Id statistics params 122 | func (o *GetDevicesIDStatisticsParams) WithHTTPClient(client *http.Client) *GetDevicesIDStatisticsParams { 123 | o.SetHTTPClient(client) 124 | return o 125 | } 126 | 127 | // SetHTTPClient adds the HTTPClient to the get devices Id statistics params 128 | func (o *GetDevicesIDStatisticsParams) SetHTTPClient(client *http.Client) { 129 | o.HTTPClient = client 130 | } 131 | 132 | // WithID adds the id to the get devices Id statistics params 133 | func (o *GetDevicesIDStatisticsParams) WithID(id string) *GetDevicesIDStatisticsParams { 134 | o.SetID(id) 135 | return o 136 | } 137 | 138 | // SetID adds the id to the get devices Id statistics params 139 | func (o *GetDevicesIDStatisticsParams) SetID(id string) { 140 | o.ID = id 141 | } 142 | 143 | // WithInterval adds the interval to the get devices Id statistics params 144 | func (o *GetDevicesIDStatisticsParams) WithInterval(interval string) *GetDevicesIDStatisticsParams { 145 | o.SetInterval(interval) 146 | return o 147 | } 148 | 149 | // SetInterval adds the interval to the get devices Id statistics params 150 | func (o *GetDevicesIDStatisticsParams) SetInterval(interval string) { 151 | o.Interval = interval 152 | } 153 | 154 | // WithPeriod adds the period to the get devices Id statistics params 155 | func (o *GetDevicesIDStatisticsParams) WithPeriod(period string) *GetDevicesIDStatisticsParams { 156 | o.SetPeriod(period) 157 | return o 158 | } 159 | 160 | // SetPeriod adds the period to the get devices Id statistics params 161 | func (o *GetDevicesIDStatisticsParams) SetPeriod(period string) { 162 | o.Period = period 163 | } 164 | 165 | // WithStart adds the start to the get devices Id statistics params 166 | func (o *GetDevicesIDStatisticsParams) WithStart(start string) *GetDevicesIDStatisticsParams { 167 | o.SetStart(start) 168 | return o 169 | } 170 | 171 | // SetStart adds the start to the get devices Id statistics params 172 | func (o *GetDevicesIDStatisticsParams) SetStart(start string) { 173 | o.Start = start 174 | } 175 | 176 | // WriteToRequest writes these params to a swagger request 177 | func (o *GetDevicesIDStatisticsParams) WriteToRequest(r runtime.ClientRequest, reg strfmt.Registry) error { 178 | 179 | if err := r.SetTimeout(o.timeout); err != nil { 180 | return err 181 | } 182 | var res []error 183 | 184 | // path param id 185 | if err := r.SetPathParam("id", o.ID); err != nil { 186 | return err 187 | } 188 | 189 | // query param interval 190 | qrInterval := o.Interval 191 | qInterval := qrInterval 192 | if qInterval != "" { 193 | 194 | if err := r.SetQueryParam("interval", qInterval); err != nil { 195 | return err 196 | } 197 | } 198 | 199 | // query param period 200 | qrPeriod := o.Period 201 | qPeriod := qrPeriod 202 | if qPeriod != "" { 203 | 204 | if err := r.SetQueryParam("period", qPeriod); err != nil { 205 | return err 206 | } 207 | } 208 | 209 | // query param start 210 | qrStart := o.Start 211 | qStart := qrStart 212 | if qStart != "" { 213 | 214 | if err := r.SetQueryParam("start", qStart); err != nil { 215 | return err 216 | } 217 | } 218 | 219 | if len(res) > 0 { 220 | return errors.CompositeValidationError(res...) 221 | } 222 | return nil 223 | } 224 | -------------------------------------------------------------------------------- /client/devices/get_devices_parameters.go: -------------------------------------------------------------------------------- 1 | // Code generated by go-swagger; DO NOT EDIT. 2 | 3 | package devices 4 | 5 | // This file was generated by the swagger tool. 6 | // Editing this file might prove futile when you re-run the swagger generate command 7 | 8 | import ( 9 | "context" 10 | "net/http" 11 | "time" 12 | 13 | "github.com/go-openapi/errors" 14 | "github.com/go-openapi/runtime" 15 | cr "github.com/go-openapi/runtime/client" 16 | "github.com/go-openapi/strfmt" 17 | "github.com/go-openapi/swag" 18 | ) 19 | 20 | // NewGetDevicesParams creates a new GetDevicesParams object, 21 | // with the default timeout for this client. 22 | // 23 | // Default values are not hydrated, since defaults are normally applied by the API server side. 24 | // 25 | // To enforce default values in parameter, use SetDefaults or WithDefaults. 26 | func NewGetDevicesParams() *GetDevicesParams { 27 | return &GetDevicesParams{ 28 | timeout: cr.DefaultTimeout, 29 | } 30 | } 31 | 32 | // NewGetDevicesParamsWithTimeout creates a new GetDevicesParams object 33 | // with the ability to set a timeout on a request. 34 | func NewGetDevicesParamsWithTimeout(timeout time.Duration) *GetDevicesParams { 35 | return &GetDevicesParams{ 36 | timeout: timeout, 37 | } 38 | } 39 | 40 | // NewGetDevicesParamsWithContext creates a new GetDevicesParams object 41 | // with the ability to set a context for a request. 42 | func NewGetDevicesParamsWithContext(ctx context.Context) *GetDevicesParams { 43 | return &GetDevicesParams{ 44 | Context: ctx, 45 | } 46 | } 47 | 48 | // NewGetDevicesParamsWithHTTPClient creates a new GetDevicesParams object 49 | // with the ability to set a custom HTTPClient for a request. 50 | func NewGetDevicesParamsWithHTTPClient(client *http.Client) *GetDevicesParams { 51 | return &GetDevicesParams{ 52 | HTTPClient: client, 53 | } 54 | } 55 | 56 | /* 57 | GetDevicesParams contains all the parameters to send to the API endpoint 58 | 59 | for the get devices operation. 60 | 61 | Typically these are written to a http.Request. 62 | */ 63 | type GetDevicesParams struct { 64 | 65 | // Authorized. 66 | Authorized *bool 67 | 68 | // Role. 69 | Role []string 70 | 71 | // SiteID. 72 | SiteID *string 73 | 74 | // Type. 75 | Type []string 76 | 77 | // WithInterfaces. 78 | WithInterfaces *bool 79 | 80 | timeout time.Duration 81 | Context context.Context 82 | HTTPClient *http.Client 83 | } 84 | 85 | // WithDefaults hydrates default values in the get devices params (not the query body). 86 | // 87 | // All values with no default are reset to their zero value. 88 | func (o *GetDevicesParams) WithDefaults() *GetDevicesParams { 89 | o.SetDefaults() 90 | return o 91 | } 92 | 93 | // SetDefaults hydrates default values in the get devices params (not the query body). 94 | // 95 | // All values with no default are reset to their zero value. 96 | func (o *GetDevicesParams) SetDefaults() { 97 | // no default values defined for this parameter 98 | } 99 | 100 | // WithTimeout adds the timeout to the get devices params 101 | func (o *GetDevicesParams) WithTimeout(timeout time.Duration) *GetDevicesParams { 102 | o.SetTimeout(timeout) 103 | return o 104 | } 105 | 106 | // SetTimeout adds the timeout to the get devices params 107 | func (o *GetDevicesParams) SetTimeout(timeout time.Duration) { 108 | o.timeout = timeout 109 | } 110 | 111 | // WithContext adds the context to the get devices params 112 | func (o *GetDevicesParams) WithContext(ctx context.Context) *GetDevicesParams { 113 | o.SetContext(ctx) 114 | return o 115 | } 116 | 117 | // SetContext adds the context to the get devices params 118 | func (o *GetDevicesParams) SetContext(ctx context.Context) { 119 | o.Context = ctx 120 | } 121 | 122 | // WithHTTPClient adds the HTTPClient to the get devices params 123 | func (o *GetDevicesParams) WithHTTPClient(client *http.Client) *GetDevicesParams { 124 | o.SetHTTPClient(client) 125 | return o 126 | } 127 | 128 | // SetHTTPClient adds the HTTPClient to the get devices params 129 | func (o *GetDevicesParams) SetHTTPClient(client *http.Client) { 130 | o.HTTPClient = client 131 | } 132 | 133 | // WithAuthorized adds the authorized to the get devices params 134 | func (o *GetDevicesParams) WithAuthorized(authorized *bool) *GetDevicesParams { 135 | o.SetAuthorized(authorized) 136 | return o 137 | } 138 | 139 | // SetAuthorized adds the authorized to the get devices params 140 | func (o *GetDevicesParams) SetAuthorized(authorized *bool) { 141 | o.Authorized = authorized 142 | } 143 | 144 | // WithRole adds the role to the get devices params 145 | func (o *GetDevicesParams) WithRole(role []string) *GetDevicesParams { 146 | o.SetRole(role) 147 | return o 148 | } 149 | 150 | // SetRole adds the role to the get devices params 151 | func (o *GetDevicesParams) SetRole(role []string) { 152 | o.Role = role 153 | } 154 | 155 | // WithSiteID adds the siteID to the get devices params 156 | func (o *GetDevicesParams) WithSiteID(siteID *string) *GetDevicesParams { 157 | o.SetSiteID(siteID) 158 | return o 159 | } 160 | 161 | // SetSiteID adds the siteId to the get devices params 162 | func (o *GetDevicesParams) SetSiteID(siteID *string) { 163 | o.SiteID = siteID 164 | } 165 | 166 | // WithType adds the typeVar to the get devices params 167 | func (o *GetDevicesParams) WithType(typeVar []string) *GetDevicesParams { 168 | o.SetType(typeVar) 169 | return o 170 | } 171 | 172 | // SetType adds the type to the get devices params 173 | func (o *GetDevicesParams) SetType(typeVar []string) { 174 | o.Type = typeVar 175 | } 176 | 177 | // WithWithInterfaces adds the withInterfaces to the get devices params 178 | func (o *GetDevicesParams) WithWithInterfaces(withInterfaces *bool) *GetDevicesParams { 179 | o.SetWithInterfaces(withInterfaces) 180 | return o 181 | } 182 | 183 | // SetWithInterfaces adds the withInterfaces to the get devices params 184 | func (o *GetDevicesParams) SetWithInterfaces(withInterfaces *bool) { 185 | o.WithInterfaces = withInterfaces 186 | } 187 | 188 | // WriteToRequest writes these params to a swagger request 189 | func (o *GetDevicesParams) WriteToRequest(r runtime.ClientRequest, reg strfmt.Registry) error { 190 | 191 | if err := r.SetTimeout(o.timeout); err != nil { 192 | return err 193 | } 194 | var res []error 195 | 196 | if o.Authorized != nil { 197 | 198 | // query param authorized 199 | var qrAuthorized bool 200 | 201 | if o.Authorized != nil { 202 | qrAuthorized = *o.Authorized 203 | } 204 | qAuthorized := swag.FormatBool(qrAuthorized) 205 | if qAuthorized != "" { 206 | 207 | if err := r.SetQueryParam("authorized", qAuthorized); err != nil { 208 | return err 209 | } 210 | } 211 | } 212 | 213 | if o.Role != nil { 214 | 215 | // binding items for role 216 | joinedRole := o.bindParamRole(reg) 217 | 218 | // query array param role 219 | if err := r.SetQueryParam("role", joinedRole...); err != nil { 220 | return err 221 | } 222 | } 223 | 224 | if o.SiteID != nil { 225 | 226 | // query param siteId 227 | var qrSiteID string 228 | 229 | if o.SiteID != nil { 230 | qrSiteID = *o.SiteID 231 | } 232 | qSiteID := qrSiteID 233 | if qSiteID != "" { 234 | 235 | if err := r.SetQueryParam("siteId", qSiteID); err != nil { 236 | return err 237 | } 238 | } 239 | } 240 | 241 | if o.Type != nil { 242 | 243 | // binding items for type 244 | joinedType := o.bindParamType(reg) 245 | 246 | // query array param type 247 | if err := r.SetQueryParam("type", joinedType...); err != nil { 248 | return err 249 | } 250 | } 251 | 252 | if o.WithInterfaces != nil { 253 | 254 | // query param withInterfaces 255 | var qrWithInterfaces bool 256 | 257 | if o.WithInterfaces != nil { 258 | qrWithInterfaces = *o.WithInterfaces 259 | } 260 | qWithInterfaces := swag.FormatBool(qrWithInterfaces) 261 | if qWithInterfaces != "" { 262 | 263 | if err := r.SetQueryParam("withInterfaces", qWithInterfaces); err != nil { 264 | return err 265 | } 266 | } 267 | } 268 | 269 | if len(res) > 0 { 270 | return errors.CompositeValidationError(res...) 271 | } 272 | return nil 273 | } 274 | 275 | // bindParamGetDevices binds the parameter role 276 | func (o *GetDevicesParams) bindParamRole(formats strfmt.Registry) []string { 277 | roleIR := o.Role 278 | 279 | var roleIC []string 280 | for _, roleIIR := range roleIR { // explode []string 281 | 282 | roleIIV := roleIIR // string as string 283 | roleIC = append(roleIC, roleIIV) 284 | } 285 | 286 | // items.CollectionFormat: "multi" 287 | roleIS := swag.JoinByFormat(roleIC, "multi") 288 | 289 | return roleIS 290 | } 291 | 292 | // bindParamGetDevices binds the parameter type 293 | func (o *GetDevicesParams) bindParamType(formats strfmt.Registry) []string { 294 | typeIR := o.Type 295 | 296 | var typeIC []string 297 | for _, typeIIR := range typeIR { // explode []string 298 | 299 | typeIIV := typeIIR // string as string 300 | typeIC = append(typeIC, typeIIV) 301 | } 302 | 303 | // items.CollectionFormat: "multi" 304 | typeIS := swag.JoinByFormat(typeIC, "multi") 305 | 306 | return typeIS 307 | } 308 | -------------------------------------------------------------------------------- /exporter/exporter.go: -------------------------------------------------------------------------------- 1 | package exporter 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "strconv" 7 | "time" 8 | 9 | "github.com/ffddorf/unms-exporter/client" 10 | "github.com/ffddorf/unms-exporter/models" 11 | openapi "github.com/go-openapi/runtime/client" 12 | "github.com/go-openapi/strfmt" 13 | prom "github.com/prometheus/client_golang/prometheus" 14 | "github.com/sirupsen/logrus" 15 | ) 16 | 17 | var _ prom.Collector = (*Exporter)(nil) 18 | 19 | const namespace = "unms" 20 | 21 | type metricSpec struct { 22 | help string 23 | labels []string 24 | } 25 | 26 | func newSpec(help string, labels []string) metricSpec { 27 | return metricSpec{help, labels} 28 | } 29 | 30 | var defaultLabels = []string{ 31 | "deviceId", "deviceName", "deviceMac", "role", "siteId", "siteName", 32 | } 33 | 34 | func (s metricSpec) intoDesc(name string) *prom.Desc { 35 | labels := make([]string, 0, len(s.labels)+len(defaultLabels)) 36 | labels = append(labels, defaultLabels...) 37 | labels = append(labels, s.labels...) 38 | return prom.NewDesc(namespace+"_"+name, s.help, labels, prom.Labels{}) 39 | } 40 | 41 | var interfaceLabels = []string{"ifName", "ifDescr", "ifPos", "ifType"} 42 | 43 | var metricSpecs = map[string]metricSpec{ 44 | "device_cpu": newSpec("CPU usage in percent", nil), 45 | "device_ram": newSpec("RAM usage in percent", nil), 46 | 47 | "device_enabled": newSpec("Whether device is enabled", nil), 48 | "device_maintenance": newSpec("Whether device is in maintenance", nil), 49 | 50 | "device_uptime": newSpec("Duration the device is up in seconds", nil), 51 | "device_last_seen": newSpec("Unix epoch when device was last seen", nil), 52 | "device_last_backup": newSpec("Unix epoch when last backup was made", nil), 53 | 54 | "interface_enabled": newSpec("Whether interface is enabled", interfaceLabels), 55 | "interface_plugged": newSpec("Whether interface has a plugged link", interfaceLabels), 56 | "interface_up": newSpec("Whether interface is up", interfaceLabels), 57 | 58 | "interface_dropped": newSpec("Number of packets dropped on an interface", interfaceLabels), 59 | "interface_errors": newSpec("Number of interface errors", interfaceLabels), 60 | "interface_rx_bytes": newSpec("Bytes received on an interface", interfaceLabels), 61 | "interface_tx_bytes": newSpec("Bytes sent on an interface", interfaceLabels), 62 | "interface_rx_rate": newSpec("Receive rate on an interface", interfaceLabels), 63 | "interface_tx_rate": newSpec("Transmit rate on an interface", interfaceLabels), 64 | "interface_poe_power": newSpec("POE power output on an interface", interfaceLabels), 65 | 66 | "wan_rx_bytes": newSpec("Bytes received on WAN interface", nil), 67 | "wan_tx_bytes": newSpec("Bytes sent on WAN interface", nil), 68 | "wan_rx_rate": newSpec("Receive rate on WAN interface", nil), 69 | "wan_tx_rate": newSpec("Transmit rate on WAN interface", nil), 70 | } 71 | 72 | type Exporter struct { 73 | api *client.UNMSAPI 74 | metrics map[string]*prom.Desc 75 | extras ExtraMetrics 76 | 77 | // Internal metrics about the exporter 78 | im internalMetrics 79 | log logrus.FieldLogger 80 | } 81 | 82 | func New(log logrus.FieldLogger, host string, token string) *Exporter { 83 | conf := client.DefaultTransportConfig() 84 | conf.Schemes = []string{"https"} 85 | conf.Host = host 86 | api := client.NewHTTPClientWithConfig(strfmt.Default, conf) 87 | 88 | client, ok := api.Transport.(*openapi.Runtime) 89 | if !ok { 90 | panic(fmt.Errorf("Invalid openapi transport: %T", api.Transport)) 91 | } 92 | auth := openapi.APIKeyAuth("x-auth-token", "header", token) 93 | client.DefaultAuthentication = auth 94 | 95 | metrics := make(map[string]*prom.Desc) 96 | for name, spec := range metricSpecs { 97 | metrics[name] = spec.intoDesc(name) 98 | } 99 | 100 | im := newInternalMetrics() 101 | return &Exporter{ 102 | api: api, 103 | metrics: metrics, 104 | im: im, 105 | log: log, 106 | } 107 | } 108 | 109 | func (e *Exporter) Describe(out chan<- *prom.Desc) { 110 | e.DescribeContext(context.Background(), out) 111 | } 112 | 113 | func (e *Exporter) DescribeContext(ctx context.Context, out chan<- *prom.Desc) { 114 | for _, desc := range e.metrics { 115 | out <- desc 116 | } 117 | e.im.Describe(out) 118 | } 119 | 120 | func (e *Exporter) Collect(out chan<- prom.Metric) { 121 | e.CollectContext(context.Background(), out) 122 | } 123 | 124 | func (e *Exporter) CollectContext(ctx context.Context, out chan<- prom.Metric) { 125 | defer e.im.Collect(out) 126 | 127 | if err := e.collectImpl(ctx, out); err != nil { 128 | e.log.WithError(err).Warn("Metric collection failed") 129 | e.im.errors.Inc() 130 | } else { 131 | e.im.success.Inc() 132 | } 133 | } 134 | 135 | func derefOrFalse(in *bool) bool { 136 | if in == nil { 137 | return false 138 | } 139 | return *in 140 | } 141 | 142 | func boolToGauge(in bool) float64 { 143 | if in { 144 | return 1 145 | } 146 | return 0 147 | } 148 | 149 | func timeToGauge(ts strfmt.DateTime) float64 { 150 | return float64(time.Time(ts).Unix()) 151 | } 152 | 153 | func (e *Exporter) newMetric(name string, typ prom.ValueType, val float64, labels ...string) prom.Metric { 154 | return prom.MustNewConstMetric(e.metrics[name], typ, val, labels...) 155 | } 156 | 157 | func (e *Exporter) collectImpl(ctx context.Context, out chan<- prom.Metric) error { 158 | devices, err := e.fetchDeviceData(ctx) 159 | if err != nil { 160 | return err 161 | } 162 | 163 | for _, device := range devices { 164 | siteID := "no-site-id" 165 | siteName := "no-site" 166 | if s := device.Identification.Site; s != nil { 167 | if s.ID != nil { 168 | siteID = *s.ID 169 | } 170 | if s.Name != "" { 171 | siteName = s.Name 172 | } 173 | } 174 | 175 | deviceLabels := []string{ 176 | *device.Identification.ID, // deviceId 177 | device.Identification.Name, // deviceName 178 | device.Identification.Mac, // mac 179 | device.Identification.Role, // role 180 | siteID, 181 | siteName, 182 | } 183 | 184 | out <- e.newMetric("device_enabled", prom.GaugeValue, boolToGauge(derefOrFalse(device.Enabled)), deviceLabels...) 185 | if device.Meta != nil { 186 | out <- e.newMetric("device_maintenance", prom.GaugeValue, boolToGauge(derefOrFalse(device.Meta.Maintenance)), deviceLabels...) 187 | } 188 | if device.Overview != nil { 189 | out <- e.newMetric("device_cpu", prom.GaugeValue, device.Overview.CPU, deviceLabels...) 190 | out <- e.newMetric("device_ram", prom.GaugeValue, device.Overview.RAM, deviceLabels...) 191 | out <- e.newMetric("device_uptime", prom.GaugeValue, device.Overview.Uptime, deviceLabels...) 192 | out <- e.newMetric("device_last_seen", prom.CounterValue, timeToGauge(device.Overview.LastSeen), deviceLabels...) 193 | } 194 | if device.LatestBackup != nil && device.LatestBackup.Timestamp != nil { 195 | out <- e.newMetric("device_last_backup", prom.GaugeValue, timeToGauge(*device.LatestBackup.Timestamp), deviceLabels...) 196 | } 197 | 198 | seenInterfaces := make(map[string]struct{}) 199 | 200 | var wanIF *models.DeviceInterfaceSchema 201 | for _, intf := range device.Interfaces { 202 | if intf.Identification == nil { 203 | continue 204 | } 205 | 206 | // sometimes UNMS duplicates an interface in the list. 207 | // skip it so we don't send duplicate metrics. 208 | if _, ok := seenInterfaces[intf.Identification.Name]; ok { 209 | continue 210 | } 211 | seenInterfaces[intf.Identification.Name] = struct{}{} 212 | 213 | if intf.Identification.Name == device.Identification.WanInterfaceID { 214 | wanIF = intf 215 | } 216 | 217 | intfLabels := make([]string, 0, len(deviceLabels)+len(interfaceLabels)) 218 | intfLabels = append(intfLabels, deviceLabels...) 219 | intfLabels = append(intfLabels, 220 | intf.Identification.Name, // ifName 221 | derefOrEmpty(intf.Identification.Description), // ifDescr 222 | strconv.FormatInt(intf.Identification.Position, 10), // ifPos 223 | intf.Identification.Type, // ifType 224 | ) 225 | 226 | out <- e.newMetric("interface_enabled", prom.GaugeValue, boolToGauge(intf.Enabled), intfLabels...) 227 | if intf.Status != nil { 228 | out <- e.newMetric("interface_plugged", prom.GaugeValue, boolToGauge(intf.Status.Plugged), intfLabels...) 229 | out <- e.newMetric("interface_up", prom.GaugeValue, boolToGauge(intf.Status.Status == "active"), intfLabels...) 230 | } 231 | 232 | if intf.Statistics != nil { 233 | out <- e.newMetric("interface_dropped", prom.CounterValue, intf.Statistics.Dropped, intfLabels...) 234 | out <- e.newMetric("interface_errors", prom.CounterValue, intf.Statistics.Errors, intfLabels...) 235 | out <- e.newMetric("interface_rx_bytes", prom.CounterValue, intf.Statistics.Rxbytes, intfLabels...) 236 | out <- e.newMetric("interface_tx_bytes", prom.CounterValue, intf.Statistics.Txbytes, intfLabels...) 237 | out <- e.newMetric("interface_rx_rate", prom.GaugeValue, intf.Statistics.Rxrate, intfLabels...) 238 | out <- e.newMetric("interface_tx_rate", prom.GaugeValue, intf.Statistics.Txrate, intfLabels...) 239 | out <- e.newMetric("interface_poe_power", prom.GaugeValue, intf.Statistics.PoePower, intfLabels...) 240 | } 241 | } 242 | 243 | // WAN metrics 244 | if wanIF != nil && wanIF.Statistics != nil { 245 | out <- e.newMetric("wan_rx_bytes", prom.CounterValue, wanIF.Statistics.Rxbytes, deviceLabels...) 246 | out <- e.newMetric("wan_tx_bytes", prom.CounterValue, wanIF.Statistics.Txbytes, deviceLabels...) 247 | out <- e.newMetric("wan_rx_rate", prom.GaugeValue, wanIF.Statistics.Rxrate, deviceLabels...) 248 | out <- e.newMetric("wan_tx_rate", prom.GaugeValue, wanIF.Statistics.Txrate, deviceLabels...) 249 | } 250 | 251 | // Ping metrics, if enabled 252 | if e.extras.Ping { 253 | ratio := 1.0 254 | if ping := device.PingMetrics(); ping != nil { 255 | if ping.PacketsSent > 0 { 256 | ratio = float64(ping.PacketsLost) / float64(ping.PacketsSent) 257 | } 258 | out <- e.newMetric("ping_rtt_best_seconds", prom.GaugeValue, ping.Best.Seconds(), deviceLabels...) 259 | out <- e.newMetric("ping_rtt_mean_seconds", prom.GaugeValue, ping.Mean.Seconds(), deviceLabels...) 260 | out <- e.newMetric("ping_rtt_worst_seconds", prom.GaugeValue, ping.Worst.Seconds(), deviceLabels...) 261 | out <- e.newMetric("ping_rtt_std_deviation_seconds", prom.GaugeValue, ping.StdDev.Seconds(), deviceLabels...) 262 | } 263 | out <- e.newMetric("ping_loss_ratio", prom.GaugeValue, ratio, deviceLabels...) 264 | } 265 | } 266 | 267 | return nil 268 | } 269 | 270 | func derefOrEmpty(in *string) string { 271 | if in == nil { 272 | return "" 273 | } 274 | return *in 275 | } 276 | -------------------------------------------------------------------------------- /models/device_status_overview.go: -------------------------------------------------------------------------------- 1 | // Code generated by go-swagger; DO NOT EDIT. 2 | 3 | package models 4 | 5 | // This file was generated by the swagger tool. 6 | // Editing this file might prove futile when you re-run the swagger generate command 7 | 8 | import ( 9 | "context" 10 | "strconv" 11 | 12 | "github.com/go-openapi/errors" 13 | "github.com/go-openapi/strfmt" 14 | "github.com/go-openapi/swag" 15 | "github.com/go-openapi/validate" 16 | ) 17 | 18 | // DeviceStatusOverview device status overview 19 | // 20 | // swagger:model DeviceStatusOverview 21 | type DeviceStatusOverview struct { 22 | 23 | // enabled 24 | // Required: true 25 | Enabled *bool `json:"enabled"` 26 | 27 | // firmware 28 | Firmware *DeviceFirmware `json:"firmware,omitempty"` 29 | 30 | // identification 31 | Identification *DeviceIdentification `json:"identification,omitempty"` 32 | 33 | // interfaces 34 | Interfaces []*DeviceInterfaceSchema `json:"interfaces"` 35 | 36 | // Custom IP address in IPv4 or IPv6 format. 37 | // Example: 192.168.1.22 38 | // Required: true 39 | IPAddress *string `json:"ipAddress"` 40 | 41 | // latest backup 42 | LatestBackup *LatestBackup `json:"latestBackup,omitempty"` 43 | 44 | // meta 45 | Meta *DeviceMeta `json:"meta,omitempty"` 46 | 47 | // mode 48 | Mode string `json:"mode,omitempty"` 49 | 50 | // overview 51 | Overview *DeviceOverview `json:"overview,omitempty"` 52 | 53 | // upgrade 54 | Upgrade *DeviceUpgrade `json:"upgrade,omitempty"` 55 | } 56 | 57 | // Validate validates this device status overview 58 | func (m *DeviceStatusOverview) Validate(formats strfmt.Registry) error { 59 | var res []error 60 | 61 | if err := m.validateEnabled(formats); err != nil { 62 | res = append(res, err) 63 | } 64 | 65 | if err := m.validateFirmware(formats); err != nil { 66 | res = append(res, err) 67 | } 68 | 69 | if err := m.validateIdentification(formats); err != nil { 70 | res = append(res, err) 71 | } 72 | 73 | if err := m.validateInterfaces(formats); err != nil { 74 | res = append(res, err) 75 | } 76 | 77 | if err := m.validateIPAddress(formats); err != nil { 78 | res = append(res, err) 79 | } 80 | 81 | if err := m.validateLatestBackup(formats); err != nil { 82 | res = append(res, err) 83 | } 84 | 85 | if err := m.validateMeta(formats); err != nil { 86 | res = append(res, err) 87 | } 88 | 89 | if err := m.validateOverview(formats); err != nil { 90 | res = append(res, err) 91 | } 92 | 93 | if err := m.validateUpgrade(formats); err != nil { 94 | res = append(res, err) 95 | } 96 | 97 | if len(res) > 0 { 98 | return errors.CompositeValidationError(res...) 99 | } 100 | return nil 101 | } 102 | 103 | func (m *DeviceStatusOverview) validateEnabled(formats strfmt.Registry) error { 104 | 105 | if err := validate.Required("enabled", "body", m.Enabled); err != nil { 106 | return err 107 | } 108 | 109 | return nil 110 | } 111 | 112 | func (m *DeviceStatusOverview) validateFirmware(formats strfmt.Registry) error { 113 | if swag.IsZero(m.Firmware) { // not required 114 | return nil 115 | } 116 | 117 | if m.Firmware != nil { 118 | if err := m.Firmware.Validate(formats); err != nil { 119 | if ve, ok := err.(*errors.Validation); ok { 120 | return ve.ValidateName("firmware") 121 | } else if ce, ok := err.(*errors.CompositeError); ok { 122 | return ce.ValidateName("firmware") 123 | } 124 | return err 125 | } 126 | } 127 | 128 | return nil 129 | } 130 | 131 | func (m *DeviceStatusOverview) validateIdentification(formats strfmt.Registry) error { 132 | if swag.IsZero(m.Identification) { // not required 133 | return nil 134 | } 135 | 136 | if m.Identification != nil { 137 | if err := m.Identification.Validate(formats); err != nil { 138 | if ve, ok := err.(*errors.Validation); ok { 139 | return ve.ValidateName("identification") 140 | } else if ce, ok := err.(*errors.CompositeError); ok { 141 | return ce.ValidateName("identification") 142 | } 143 | return err 144 | } 145 | } 146 | 147 | return nil 148 | } 149 | 150 | func (m *DeviceStatusOverview) validateInterfaces(formats strfmt.Registry) error { 151 | if swag.IsZero(m.Interfaces) { // not required 152 | return nil 153 | } 154 | 155 | for i := 0; i < len(m.Interfaces); i++ { 156 | if swag.IsZero(m.Interfaces[i]) { // not required 157 | continue 158 | } 159 | 160 | if m.Interfaces[i] != nil { 161 | if err := m.Interfaces[i].Validate(formats); err != nil { 162 | if ve, ok := err.(*errors.Validation); ok { 163 | return ve.ValidateName("interfaces" + "." + strconv.Itoa(i)) 164 | } else if ce, ok := err.(*errors.CompositeError); ok { 165 | return ce.ValidateName("interfaces" + "." + strconv.Itoa(i)) 166 | } 167 | return err 168 | } 169 | } 170 | 171 | } 172 | 173 | return nil 174 | } 175 | 176 | func (m *DeviceStatusOverview) validateIPAddress(formats strfmt.Registry) error { 177 | 178 | if err := validate.Required("ipAddress", "body", m.IPAddress); err != nil { 179 | return err 180 | } 181 | 182 | return nil 183 | } 184 | 185 | func (m *DeviceStatusOverview) validateLatestBackup(formats strfmt.Registry) error { 186 | if swag.IsZero(m.LatestBackup) { // not required 187 | return nil 188 | } 189 | 190 | if m.LatestBackup != nil { 191 | if err := m.LatestBackup.Validate(formats); err != nil { 192 | if ve, ok := err.(*errors.Validation); ok { 193 | return ve.ValidateName("latestBackup") 194 | } else if ce, ok := err.(*errors.CompositeError); ok { 195 | return ce.ValidateName("latestBackup") 196 | } 197 | return err 198 | } 199 | } 200 | 201 | return nil 202 | } 203 | 204 | func (m *DeviceStatusOverview) validateMeta(formats strfmt.Registry) error { 205 | if swag.IsZero(m.Meta) { // not required 206 | return nil 207 | } 208 | 209 | if m.Meta != nil { 210 | if err := m.Meta.Validate(formats); err != nil { 211 | if ve, ok := err.(*errors.Validation); ok { 212 | return ve.ValidateName("meta") 213 | } else if ce, ok := err.(*errors.CompositeError); ok { 214 | return ce.ValidateName("meta") 215 | } 216 | return err 217 | } 218 | } 219 | 220 | return nil 221 | } 222 | 223 | func (m *DeviceStatusOverview) validateOverview(formats strfmt.Registry) error { 224 | if swag.IsZero(m.Overview) { // not required 225 | return nil 226 | } 227 | 228 | if m.Overview != nil { 229 | if err := m.Overview.Validate(formats); err != nil { 230 | if ve, ok := err.(*errors.Validation); ok { 231 | return ve.ValidateName("overview") 232 | } else if ce, ok := err.(*errors.CompositeError); ok { 233 | return ce.ValidateName("overview") 234 | } 235 | return err 236 | } 237 | } 238 | 239 | return nil 240 | } 241 | 242 | func (m *DeviceStatusOverview) validateUpgrade(formats strfmt.Registry) error { 243 | if swag.IsZero(m.Upgrade) { // not required 244 | return nil 245 | } 246 | 247 | if m.Upgrade != nil { 248 | if err := m.Upgrade.Validate(formats); err != nil { 249 | if ve, ok := err.(*errors.Validation); ok { 250 | return ve.ValidateName("upgrade") 251 | } else if ce, ok := err.(*errors.CompositeError); ok { 252 | return ce.ValidateName("upgrade") 253 | } 254 | return err 255 | } 256 | } 257 | 258 | return nil 259 | } 260 | 261 | // ContextValidate validate this device status overview based on the context it is used 262 | func (m *DeviceStatusOverview) ContextValidate(ctx context.Context, formats strfmt.Registry) error { 263 | var res []error 264 | 265 | if err := m.contextValidateFirmware(ctx, formats); err != nil { 266 | res = append(res, err) 267 | } 268 | 269 | if err := m.contextValidateIdentification(ctx, formats); err != nil { 270 | res = append(res, err) 271 | } 272 | 273 | if err := m.contextValidateInterfaces(ctx, formats); err != nil { 274 | res = append(res, err) 275 | } 276 | 277 | if err := m.contextValidateLatestBackup(ctx, formats); err != nil { 278 | res = append(res, err) 279 | } 280 | 281 | if err := m.contextValidateMeta(ctx, formats); err != nil { 282 | res = append(res, err) 283 | } 284 | 285 | if err := m.contextValidateOverview(ctx, formats); err != nil { 286 | res = append(res, err) 287 | } 288 | 289 | if err := m.contextValidateUpgrade(ctx, formats); err != nil { 290 | res = append(res, err) 291 | } 292 | 293 | if len(res) > 0 { 294 | return errors.CompositeValidationError(res...) 295 | } 296 | return nil 297 | } 298 | 299 | func (m *DeviceStatusOverview) contextValidateFirmware(ctx context.Context, formats strfmt.Registry) error { 300 | 301 | if m.Firmware != nil { 302 | 303 | if swag.IsZero(m.Firmware) { // not required 304 | return nil 305 | } 306 | 307 | if err := m.Firmware.ContextValidate(ctx, formats); err != nil { 308 | if ve, ok := err.(*errors.Validation); ok { 309 | return ve.ValidateName("firmware") 310 | } else if ce, ok := err.(*errors.CompositeError); ok { 311 | return ce.ValidateName("firmware") 312 | } 313 | return err 314 | } 315 | } 316 | 317 | return nil 318 | } 319 | 320 | func (m *DeviceStatusOverview) contextValidateIdentification(ctx context.Context, formats strfmt.Registry) error { 321 | 322 | if m.Identification != nil { 323 | 324 | if swag.IsZero(m.Identification) { // not required 325 | return nil 326 | } 327 | 328 | if err := m.Identification.ContextValidate(ctx, formats); err != nil { 329 | if ve, ok := err.(*errors.Validation); ok { 330 | return ve.ValidateName("identification") 331 | } else if ce, ok := err.(*errors.CompositeError); ok { 332 | return ce.ValidateName("identification") 333 | } 334 | return err 335 | } 336 | } 337 | 338 | return nil 339 | } 340 | 341 | func (m *DeviceStatusOverview) contextValidateInterfaces(ctx context.Context, formats strfmt.Registry) error { 342 | 343 | for i := 0; i < len(m.Interfaces); i++ { 344 | 345 | if m.Interfaces[i] != nil { 346 | 347 | if swag.IsZero(m.Interfaces[i]) { // not required 348 | return nil 349 | } 350 | 351 | if err := m.Interfaces[i].ContextValidate(ctx, formats); err != nil { 352 | if ve, ok := err.(*errors.Validation); ok { 353 | return ve.ValidateName("interfaces" + "." + strconv.Itoa(i)) 354 | } else if ce, ok := err.(*errors.CompositeError); ok { 355 | return ce.ValidateName("interfaces" + "." + strconv.Itoa(i)) 356 | } 357 | return err 358 | } 359 | } 360 | 361 | } 362 | 363 | return nil 364 | } 365 | 366 | func (m *DeviceStatusOverview) contextValidateLatestBackup(ctx context.Context, formats strfmt.Registry) error { 367 | 368 | if m.LatestBackup != nil { 369 | 370 | if swag.IsZero(m.LatestBackup) { // not required 371 | return nil 372 | } 373 | 374 | if err := m.LatestBackup.ContextValidate(ctx, formats); err != nil { 375 | if ve, ok := err.(*errors.Validation); ok { 376 | return ve.ValidateName("latestBackup") 377 | } else if ce, ok := err.(*errors.CompositeError); ok { 378 | return ce.ValidateName("latestBackup") 379 | } 380 | return err 381 | } 382 | } 383 | 384 | return nil 385 | } 386 | 387 | func (m *DeviceStatusOverview) contextValidateMeta(ctx context.Context, formats strfmt.Registry) error { 388 | 389 | if m.Meta != nil { 390 | 391 | if swag.IsZero(m.Meta) { // not required 392 | return nil 393 | } 394 | 395 | if err := m.Meta.ContextValidate(ctx, formats); err != nil { 396 | if ve, ok := err.(*errors.Validation); ok { 397 | return ve.ValidateName("meta") 398 | } else if ce, ok := err.(*errors.CompositeError); ok { 399 | return ce.ValidateName("meta") 400 | } 401 | return err 402 | } 403 | } 404 | 405 | return nil 406 | } 407 | 408 | func (m *DeviceStatusOverview) contextValidateOverview(ctx context.Context, formats strfmt.Registry) error { 409 | 410 | if m.Overview != nil { 411 | 412 | if swag.IsZero(m.Overview) { // not required 413 | return nil 414 | } 415 | 416 | if err := m.Overview.ContextValidate(ctx, formats); err != nil { 417 | if ve, ok := err.(*errors.Validation); ok { 418 | return ve.ValidateName("overview") 419 | } else if ce, ok := err.(*errors.CompositeError); ok { 420 | return ce.ValidateName("overview") 421 | } 422 | return err 423 | } 424 | } 425 | 426 | return nil 427 | } 428 | 429 | func (m *DeviceStatusOverview) contextValidateUpgrade(ctx context.Context, formats strfmt.Registry) error { 430 | 431 | if m.Upgrade != nil { 432 | 433 | if swag.IsZero(m.Upgrade) { // not required 434 | return nil 435 | } 436 | 437 | if err := m.Upgrade.ContextValidate(ctx, formats); err != nil { 438 | if ve, ok := err.(*errors.Validation); ok { 439 | return ve.ValidateName("upgrade") 440 | } else if ce, ok := err.(*errors.CompositeError); ok { 441 | return ce.ValidateName("upgrade") 442 | } 443 | return err 444 | } 445 | } 446 | 447 | return nil 448 | } 449 | 450 | // MarshalBinary interface implementation 451 | func (m *DeviceStatusOverview) MarshalBinary() ([]byte, error) { 452 | if m == nil { 453 | return nil, nil 454 | } 455 | return swag.WriteJSON(m) 456 | } 457 | 458 | // UnmarshalBinary interface implementation 459 | func (m *DeviceStatusOverview) UnmarshalBinary(b []byte) error { 460 | var res DeviceStatusOverview 461 | if err := swag.ReadJSON(b, &res); err != nil { 462 | return err 463 | } 464 | *m = res 465 | return nil 466 | } 467 | -------------------------------------------------------------------------------- /client/devices/get_devices_responses.go: -------------------------------------------------------------------------------- 1 | // Code generated by go-swagger; DO NOT EDIT. 2 | 3 | package devices 4 | 5 | // This file was generated by the swagger tool. 6 | // Editing this file might prove futile when you re-run the swagger generate command 7 | 8 | import ( 9 | "fmt" 10 | "io" 11 | 12 | "github.com/go-openapi/runtime" 13 | "github.com/go-openapi/strfmt" 14 | 15 | "github.com/ffddorf/unms-exporter/models" 16 | ) 17 | 18 | // GetDevicesReader is a Reader for the GetDevices structure. 19 | type GetDevicesReader struct { 20 | formats strfmt.Registry 21 | } 22 | 23 | // ReadResponse reads a server response into the received o. 24 | func (o *GetDevicesReader) ReadResponse(response runtime.ClientResponse, consumer runtime.Consumer) (interface{}, error) { 25 | switch response.Code() { 26 | case 200: 27 | result := NewGetDevicesOK() 28 | if err := result.readResponse(response, consumer, o.formats); err != nil { 29 | return nil, err 30 | } 31 | return result, nil 32 | case 400: 33 | result := NewGetDevicesBadRequest() 34 | if err := result.readResponse(response, consumer, o.formats); err != nil { 35 | return nil, err 36 | } 37 | return nil, result 38 | case 401: 39 | result := NewGetDevicesUnauthorized() 40 | if err := result.readResponse(response, consumer, o.formats); err != nil { 41 | return nil, err 42 | } 43 | return nil, result 44 | case 403: 45 | result := NewGetDevicesForbidden() 46 | if err := result.readResponse(response, consumer, o.formats); err != nil { 47 | return nil, err 48 | } 49 | return nil, result 50 | case 500: 51 | result := NewGetDevicesInternalServerError() 52 | if err := result.readResponse(response, consumer, o.formats); err != nil { 53 | return nil, err 54 | } 55 | return nil, result 56 | default: 57 | return nil, runtime.NewAPIError("[GET /devices] getDevices", response, response.Code()) 58 | } 59 | } 60 | 61 | // NewGetDevicesOK creates a GetDevicesOK with default headers values 62 | func NewGetDevicesOK() *GetDevicesOK { 63 | return &GetDevicesOK{} 64 | } 65 | 66 | /* 67 | GetDevicesOK describes a response with status code 200, with default header values. 68 | 69 | Read-only overview for device list. 70 | */ 71 | type GetDevicesOK struct { 72 | Payload []*models.DeviceStatusOverview 73 | } 74 | 75 | // IsSuccess returns true when this get devices o k response has a 2xx status code 76 | func (o *GetDevicesOK) IsSuccess() bool { 77 | return true 78 | } 79 | 80 | // IsRedirect returns true when this get devices o k response has a 3xx status code 81 | func (o *GetDevicesOK) IsRedirect() bool { 82 | return false 83 | } 84 | 85 | // IsClientError returns true when this get devices o k response has a 4xx status code 86 | func (o *GetDevicesOK) IsClientError() bool { 87 | return false 88 | } 89 | 90 | // IsServerError returns true when this get devices o k response has a 5xx status code 91 | func (o *GetDevicesOK) IsServerError() bool { 92 | return false 93 | } 94 | 95 | // IsCode returns true when this get devices o k response a status code equal to that given 96 | func (o *GetDevicesOK) IsCode(code int) bool { 97 | return code == 200 98 | } 99 | 100 | // Code gets the status code for the get devices o k response 101 | func (o *GetDevicesOK) Code() int { 102 | return 200 103 | } 104 | 105 | func (o *GetDevicesOK) Error() string { 106 | return fmt.Sprintf("[GET /devices][%d] getDevicesOK %+v", 200, o.Payload) 107 | } 108 | 109 | func (o *GetDevicesOK) String() string { 110 | return fmt.Sprintf("[GET /devices][%d] getDevicesOK %+v", 200, o.Payload) 111 | } 112 | 113 | func (o *GetDevicesOK) GetPayload() []*models.DeviceStatusOverview { 114 | return o.Payload 115 | } 116 | 117 | func (o *GetDevicesOK) readResponse(response runtime.ClientResponse, consumer runtime.Consumer, formats strfmt.Registry) error { 118 | 119 | // response payload 120 | if err := consumer.Consume(response.Body(), &o.Payload); err != nil && err != io.EOF { 121 | return err 122 | } 123 | 124 | return nil 125 | } 126 | 127 | // NewGetDevicesBadRequest creates a GetDevicesBadRequest with default headers values 128 | func NewGetDevicesBadRequest() *GetDevicesBadRequest { 129 | return &GetDevicesBadRequest{} 130 | } 131 | 132 | /* 133 | GetDevicesBadRequest describes a response with status code 400, with default header values. 134 | 135 | Bad Request 136 | */ 137 | type GetDevicesBadRequest struct { 138 | Payload *models.Error 139 | } 140 | 141 | // IsSuccess returns true when this get devices bad request response has a 2xx status code 142 | func (o *GetDevicesBadRequest) IsSuccess() bool { 143 | return false 144 | } 145 | 146 | // IsRedirect returns true when this get devices bad request response has a 3xx status code 147 | func (o *GetDevicesBadRequest) IsRedirect() bool { 148 | return false 149 | } 150 | 151 | // IsClientError returns true when this get devices bad request response has a 4xx status code 152 | func (o *GetDevicesBadRequest) IsClientError() bool { 153 | return true 154 | } 155 | 156 | // IsServerError returns true when this get devices bad request response has a 5xx status code 157 | func (o *GetDevicesBadRequest) IsServerError() bool { 158 | return false 159 | } 160 | 161 | // IsCode returns true when this get devices bad request response a status code equal to that given 162 | func (o *GetDevicesBadRequest) IsCode(code int) bool { 163 | return code == 400 164 | } 165 | 166 | // Code gets the status code for the get devices bad request response 167 | func (o *GetDevicesBadRequest) Code() int { 168 | return 400 169 | } 170 | 171 | func (o *GetDevicesBadRequest) Error() string { 172 | return fmt.Sprintf("[GET /devices][%d] getDevicesBadRequest %+v", 400, o.Payload) 173 | } 174 | 175 | func (o *GetDevicesBadRequest) String() string { 176 | return fmt.Sprintf("[GET /devices][%d] getDevicesBadRequest %+v", 400, o.Payload) 177 | } 178 | 179 | func (o *GetDevicesBadRequest) GetPayload() *models.Error { 180 | return o.Payload 181 | } 182 | 183 | func (o *GetDevicesBadRequest) readResponse(response runtime.ClientResponse, consumer runtime.Consumer, formats strfmt.Registry) error { 184 | 185 | o.Payload = new(models.Error) 186 | 187 | // response payload 188 | if err := consumer.Consume(response.Body(), o.Payload); err != nil && err != io.EOF { 189 | return err 190 | } 191 | 192 | return nil 193 | } 194 | 195 | // NewGetDevicesUnauthorized creates a GetDevicesUnauthorized with default headers values 196 | func NewGetDevicesUnauthorized() *GetDevicesUnauthorized { 197 | return &GetDevicesUnauthorized{} 198 | } 199 | 200 | /* 201 | GetDevicesUnauthorized describes a response with status code 401, with default header values. 202 | 203 | Unauthorized 204 | */ 205 | type GetDevicesUnauthorized struct { 206 | Payload *models.Error 207 | } 208 | 209 | // IsSuccess returns true when this get devices unauthorized response has a 2xx status code 210 | func (o *GetDevicesUnauthorized) IsSuccess() bool { 211 | return false 212 | } 213 | 214 | // IsRedirect returns true when this get devices unauthorized response has a 3xx status code 215 | func (o *GetDevicesUnauthorized) IsRedirect() bool { 216 | return false 217 | } 218 | 219 | // IsClientError returns true when this get devices unauthorized response has a 4xx status code 220 | func (o *GetDevicesUnauthorized) IsClientError() bool { 221 | return true 222 | } 223 | 224 | // IsServerError returns true when this get devices unauthorized response has a 5xx status code 225 | func (o *GetDevicesUnauthorized) IsServerError() bool { 226 | return false 227 | } 228 | 229 | // IsCode returns true when this get devices unauthorized response a status code equal to that given 230 | func (o *GetDevicesUnauthorized) IsCode(code int) bool { 231 | return code == 401 232 | } 233 | 234 | // Code gets the status code for the get devices unauthorized response 235 | func (o *GetDevicesUnauthorized) Code() int { 236 | return 401 237 | } 238 | 239 | func (o *GetDevicesUnauthorized) Error() string { 240 | return fmt.Sprintf("[GET /devices][%d] getDevicesUnauthorized %+v", 401, o.Payload) 241 | } 242 | 243 | func (o *GetDevicesUnauthorized) String() string { 244 | return fmt.Sprintf("[GET /devices][%d] getDevicesUnauthorized %+v", 401, o.Payload) 245 | } 246 | 247 | func (o *GetDevicesUnauthorized) GetPayload() *models.Error { 248 | return o.Payload 249 | } 250 | 251 | func (o *GetDevicesUnauthorized) readResponse(response runtime.ClientResponse, consumer runtime.Consumer, formats strfmt.Registry) error { 252 | 253 | o.Payload = new(models.Error) 254 | 255 | // response payload 256 | if err := consumer.Consume(response.Body(), o.Payload); err != nil && err != io.EOF { 257 | return err 258 | } 259 | 260 | return nil 261 | } 262 | 263 | // NewGetDevicesForbidden creates a GetDevicesForbidden with default headers values 264 | func NewGetDevicesForbidden() *GetDevicesForbidden { 265 | return &GetDevicesForbidden{} 266 | } 267 | 268 | /* 269 | GetDevicesForbidden describes a response with status code 403, with default header values. 270 | 271 | Forbidden 272 | */ 273 | type GetDevicesForbidden struct { 274 | Payload *models.Error 275 | } 276 | 277 | // IsSuccess returns true when this get devices forbidden response has a 2xx status code 278 | func (o *GetDevicesForbidden) IsSuccess() bool { 279 | return false 280 | } 281 | 282 | // IsRedirect returns true when this get devices forbidden response has a 3xx status code 283 | func (o *GetDevicesForbidden) IsRedirect() bool { 284 | return false 285 | } 286 | 287 | // IsClientError returns true when this get devices forbidden response has a 4xx status code 288 | func (o *GetDevicesForbidden) IsClientError() bool { 289 | return true 290 | } 291 | 292 | // IsServerError returns true when this get devices forbidden response has a 5xx status code 293 | func (o *GetDevicesForbidden) IsServerError() bool { 294 | return false 295 | } 296 | 297 | // IsCode returns true when this get devices forbidden response a status code equal to that given 298 | func (o *GetDevicesForbidden) IsCode(code int) bool { 299 | return code == 403 300 | } 301 | 302 | // Code gets the status code for the get devices forbidden response 303 | func (o *GetDevicesForbidden) Code() int { 304 | return 403 305 | } 306 | 307 | func (o *GetDevicesForbidden) Error() string { 308 | return fmt.Sprintf("[GET /devices][%d] getDevicesForbidden %+v", 403, o.Payload) 309 | } 310 | 311 | func (o *GetDevicesForbidden) String() string { 312 | return fmt.Sprintf("[GET /devices][%d] getDevicesForbidden %+v", 403, o.Payload) 313 | } 314 | 315 | func (o *GetDevicesForbidden) GetPayload() *models.Error { 316 | return o.Payload 317 | } 318 | 319 | func (o *GetDevicesForbidden) readResponse(response runtime.ClientResponse, consumer runtime.Consumer, formats strfmt.Registry) error { 320 | 321 | o.Payload = new(models.Error) 322 | 323 | // response payload 324 | if err := consumer.Consume(response.Body(), o.Payload); err != nil && err != io.EOF { 325 | return err 326 | } 327 | 328 | return nil 329 | } 330 | 331 | // NewGetDevicesInternalServerError creates a GetDevicesInternalServerError with default headers values 332 | func NewGetDevicesInternalServerError() *GetDevicesInternalServerError { 333 | return &GetDevicesInternalServerError{} 334 | } 335 | 336 | /* 337 | GetDevicesInternalServerError describes a response with status code 500, with default header values. 338 | 339 | Internal Server Error 340 | */ 341 | type GetDevicesInternalServerError struct { 342 | Payload *models.Error 343 | } 344 | 345 | // IsSuccess returns true when this get devices internal server error response has a 2xx status code 346 | func (o *GetDevicesInternalServerError) IsSuccess() bool { 347 | return false 348 | } 349 | 350 | // IsRedirect returns true when this get devices internal server error response has a 3xx status code 351 | func (o *GetDevicesInternalServerError) IsRedirect() bool { 352 | return false 353 | } 354 | 355 | // IsClientError returns true when this get devices internal server error response has a 4xx status code 356 | func (o *GetDevicesInternalServerError) IsClientError() bool { 357 | return false 358 | } 359 | 360 | // IsServerError returns true when this get devices internal server error response has a 5xx status code 361 | func (o *GetDevicesInternalServerError) IsServerError() bool { 362 | return true 363 | } 364 | 365 | // IsCode returns true when this get devices internal server error response a status code equal to that given 366 | func (o *GetDevicesInternalServerError) IsCode(code int) bool { 367 | return code == 500 368 | } 369 | 370 | // Code gets the status code for the get devices internal server error response 371 | func (o *GetDevicesInternalServerError) Code() int { 372 | return 500 373 | } 374 | 375 | func (o *GetDevicesInternalServerError) Error() string { 376 | return fmt.Sprintf("[GET /devices][%d] getDevicesInternalServerError %+v", 500, o.Payload) 377 | } 378 | 379 | func (o *GetDevicesInternalServerError) String() string { 380 | return fmt.Sprintf("[GET /devices][%d] getDevicesInternalServerError %+v", 500, o.Payload) 381 | } 382 | 383 | func (o *GetDevicesInternalServerError) GetPayload() *models.Error { 384 | return o.Payload 385 | } 386 | 387 | func (o *GetDevicesInternalServerError) readResponse(response runtime.ClientResponse, consumer runtime.Consumer, formats strfmt.Registry) error { 388 | 389 | o.Payload = new(models.Error) 390 | 391 | // response payload 392 | if err := consumer.Consume(response.Body(), o.Payload); err != nil && err != io.EOF { 393 | return err 394 | } 395 | 396 | return nil 397 | } 398 | -------------------------------------------------------------------------------- /models/device_identification.go: -------------------------------------------------------------------------------- 1 | // Code generated by go-swagger; DO NOT EDIT. 2 | 3 | package models 4 | 5 | // This file was generated by the swagger tool. 6 | // Editing this file might prove futile when you re-run the swagger generate command 7 | 8 | import ( 9 | "context" 10 | "encoding/json" 11 | 12 | "github.com/go-openapi/errors" 13 | "github.com/go-openapi/strfmt" 14 | "github.com/go-openapi/swag" 15 | "github.com/go-openapi/validate" 16 | ) 17 | 18 | // DeviceIdentification Read-only identification attributes. 19 | // 20 | // swagger:model DeviceIdentification 21 | type DeviceIdentification struct { 22 | 23 | // Device is added to UNMS. 24 | Authorized bool `json:"authorized,omitempty"` 25 | 26 | // category 27 | // Enum: [optical wired wireless accessories] 28 | Category string `json:"category,omitempty"` 29 | 30 | // UNMS device alias or real name. 31 | DisplayName string `json:"displayName,omitempty"` 32 | 33 | // In SemVer format. 34 | FirmwareVersion string `json:"firmwareVersion,omitempty"` 35 | 36 | // hostname 37 | Hostname string `json:"hostname,omitempty"` 38 | 39 | // Device ID. 40 | // Example: f7ac9cad-ea28-4390-93c8-7add010e8ee3 41 | // Required: true 42 | ID *string `json:"id"` 43 | 44 | // Custom IP address in IPv4 or IPv6 format. 45 | // Example: 192.168.1.22 46 | IP string `json:"ip,omitempty"` 47 | 48 | // mac 49 | // Pattern: ^([0-9a-fA-F][0-9a-fA-F]:){5}([0-9a-fA-F][0-9a-fA-F])$|^([0-9a-fA-F]){12}$ 50 | Mac string `json:"mac,omitempty"` 51 | 52 | // Short names, for example UF-OLT. 53 | Model string `json:"model,omitempty"` 54 | 55 | // Full names, for example UFiber OLT. 56 | ModelName string `json:"modelName,omitempty"` 57 | 58 | // name 59 | Name string `json:"name,omitempty"` 60 | 61 | // Short name, for example e600. 62 | PlatformID string `json:"platformId,omitempty"` 63 | 64 | // platform name 65 | PlatformName string `json:"platformName,omitempty"` 66 | 67 | // role 68 | // Enum: [router switch gpon ap station other ups server wireless convertor gateway] 69 | Role string `json:"role,omitempty"` 70 | 71 | // serial number 72 | SerialNumber string `json:"serialNumber,omitempty"` 73 | 74 | // site 75 | Site *Site `json:"site,omitempty"` 76 | 77 | // started 78 | // Format: date-time 79 | Started strfmt.DateTime `json:"started,omitempty"` 80 | 81 | // Status of the station. 82 | // Example: active 83 | // Enum: [active connecting discovered inactive disabled disconnected unauthorized proposed unknown unplaced custom] 84 | Status string `json:"status,omitempty"` 85 | 86 | // type 87 | Type string `json:"type,omitempty"` 88 | 89 | // updated 90 | // Format: date-time 91 | Updated strfmt.DateTime `json:"updated,omitempty"` 92 | 93 | // wan interface Id 94 | WanInterfaceID string `json:"wanInterfaceId,omitempty"` 95 | } 96 | 97 | // Validate validates this device identification 98 | func (m *DeviceIdentification) Validate(formats strfmt.Registry) error { 99 | var res []error 100 | 101 | if err := m.validateCategory(formats); err != nil { 102 | res = append(res, err) 103 | } 104 | 105 | if err := m.validateID(formats); err != nil { 106 | res = append(res, err) 107 | } 108 | 109 | if err := m.validateMac(formats); err != nil { 110 | res = append(res, err) 111 | } 112 | 113 | if err := m.validateRole(formats); err != nil { 114 | res = append(res, err) 115 | } 116 | 117 | if err := m.validateSite(formats); err != nil { 118 | res = append(res, err) 119 | } 120 | 121 | if err := m.validateStarted(formats); err != nil { 122 | res = append(res, err) 123 | } 124 | 125 | if err := m.validateStatus(formats); err != nil { 126 | res = append(res, err) 127 | } 128 | 129 | if err := m.validateUpdated(formats); err != nil { 130 | res = append(res, err) 131 | } 132 | 133 | if len(res) > 0 { 134 | return errors.CompositeValidationError(res...) 135 | } 136 | return nil 137 | } 138 | 139 | var deviceIdentificationTypeCategoryPropEnum []interface{} 140 | 141 | func init() { 142 | var res []string 143 | if err := json.Unmarshal([]byte(`["optical","wired","wireless","accessories"]`), &res); err != nil { 144 | panic(err) 145 | } 146 | for _, v := range res { 147 | deviceIdentificationTypeCategoryPropEnum = append(deviceIdentificationTypeCategoryPropEnum, v) 148 | } 149 | } 150 | 151 | const ( 152 | 153 | // DeviceIdentificationCategoryOptical captures enum value "optical" 154 | DeviceIdentificationCategoryOptical string = "optical" 155 | 156 | // DeviceIdentificationCategoryWired captures enum value "wired" 157 | DeviceIdentificationCategoryWired string = "wired" 158 | 159 | // DeviceIdentificationCategoryWireless captures enum value "wireless" 160 | DeviceIdentificationCategoryWireless string = "wireless" 161 | 162 | // DeviceIdentificationCategoryAccessories captures enum value "accessories" 163 | DeviceIdentificationCategoryAccessories string = "accessories" 164 | ) 165 | 166 | // prop value enum 167 | func (m *DeviceIdentification) validateCategoryEnum(path, location string, value string) error { 168 | if err := validate.EnumCase(path, location, value, deviceIdentificationTypeCategoryPropEnum, true); err != nil { 169 | return err 170 | } 171 | return nil 172 | } 173 | 174 | func (m *DeviceIdentification) validateCategory(formats strfmt.Registry) error { 175 | if swag.IsZero(m.Category) { // not required 176 | return nil 177 | } 178 | 179 | // value enum 180 | if err := m.validateCategoryEnum("category", "body", m.Category); err != nil { 181 | return err 182 | } 183 | 184 | return nil 185 | } 186 | 187 | func (m *DeviceIdentification) validateID(formats strfmt.Registry) error { 188 | 189 | if err := validate.Required("id", "body", m.ID); err != nil { 190 | return err 191 | } 192 | 193 | return nil 194 | } 195 | 196 | func (m *DeviceIdentification) validateMac(formats strfmt.Registry) error { 197 | if swag.IsZero(m.Mac) { // not required 198 | return nil 199 | } 200 | 201 | if err := validate.Pattern("mac", "body", m.Mac, `^([0-9a-fA-F][0-9a-fA-F]:){5}([0-9a-fA-F][0-9a-fA-F])$|^([0-9a-fA-F]){12}$`); err != nil { 202 | return err 203 | } 204 | 205 | return nil 206 | } 207 | 208 | var deviceIdentificationTypeRolePropEnum []interface{} 209 | 210 | func init() { 211 | var res []string 212 | if err := json.Unmarshal([]byte(`["router","switch","gpon","ap","station","other","ups","server","wireless","convertor","gateway"]`), &res); err != nil { 213 | panic(err) 214 | } 215 | for _, v := range res { 216 | deviceIdentificationTypeRolePropEnum = append(deviceIdentificationTypeRolePropEnum, v) 217 | } 218 | } 219 | 220 | const ( 221 | 222 | // DeviceIdentificationRoleRouter captures enum value "router" 223 | DeviceIdentificationRoleRouter string = "router" 224 | 225 | // DeviceIdentificationRoleSwitch captures enum value "switch" 226 | DeviceIdentificationRoleSwitch string = "switch" 227 | 228 | // DeviceIdentificationRoleGpon captures enum value "gpon" 229 | DeviceIdentificationRoleGpon string = "gpon" 230 | 231 | // DeviceIdentificationRoleAp captures enum value "ap" 232 | DeviceIdentificationRoleAp string = "ap" 233 | 234 | // DeviceIdentificationRoleStation captures enum value "station" 235 | DeviceIdentificationRoleStation string = "station" 236 | 237 | // DeviceIdentificationRoleOther captures enum value "other" 238 | DeviceIdentificationRoleOther string = "other" 239 | 240 | // DeviceIdentificationRoleUps captures enum value "ups" 241 | DeviceIdentificationRoleUps string = "ups" 242 | 243 | // DeviceIdentificationRoleServer captures enum value "server" 244 | DeviceIdentificationRoleServer string = "server" 245 | 246 | // DeviceIdentificationRoleWireless captures enum value "wireless" 247 | DeviceIdentificationRoleWireless string = "wireless" 248 | 249 | // DeviceIdentificationRoleConvertor captures enum value "convertor" 250 | DeviceIdentificationRoleConvertor string = "convertor" 251 | 252 | // DeviceIdentificationRoleGateway captures enum value "gateway" 253 | DeviceIdentificationRoleGateway string = "gateway" 254 | ) 255 | 256 | // prop value enum 257 | func (m *DeviceIdentification) validateRoleEnum(path, location string, value string) error { 258 | if err := validate.EnumCase(path, location, value, deviceIdentificationTypeRolePropEnum, true); err != nil { 259 | return err 260 | } 261 | return nil 262 | } 263 | 264 | func (m *DeviceIdentification) validateRole(formats strfmt.Registry) error { 265 | if swag.IsZero(m.Role) { // not required 266 | return nil 267 | } 268 | 269 | // value enum 270 | if err := m.validateRoleEnum("role", "body", m.Role); err != nil { 271 | return err 272 | } 273 | 274 | return nil 275 | } 276 | 277 | func (m *DeviceIdentification) validateSite(formats strfmt.Registry) error { 278 | if swag.IsZero(m.Site) { // not required 279 | return nil 280 | } 281 | 282 | if m.Site != nil { 283 | if err := m.Site.Validate(formats); err != nil { 284 | if ve, ok := err.(*errors.Validation); ok { 285 | return ve.ValidateName("site") 286 | } else if ce, ok := err.(*errors.CompositeError); ok { 287 | return ce.ValidateName("site") 288 | } 289 | return err 290 | } 291 | } 292 | 293 | return nil 294 | } 295 | 296 | func (m *DeviceIdentification) validateStarted(formats strfmt.Registry) error { 297 | if swag.IsZero(m.Started) { // not required 298 | return nil 299 | } 300 | 301 | if err := validate.FormatOf("started", "body", "date-time", m.Started.String(), formats); err != nil { 302 | return err 303 | } 304 | 305 | return nil 306 | } 307 | 308 | var deviceIdentificationTypeStatusPropEnum []interface{} 309 | 310 | func init() { 311 | var res []string 312 | if err := json.Unmarshal([]byte(`["active","connecting","discovered","inactive","disabled","disconnected","unauthorized","proposed","unknown","unplaced","custom"]`), &res); err != nil { 313 | panic(err) 314 | } 315 | for _, v := range res { 316 | deviceIdentificationTypeStatusPropEnum = append(deviceIdentificationTypeStatusPropEnum, v) 317 | } 318 | } 319 | 320 | const ( 321 | 322 | // DeviceIdentificationStatusActive captures enum value "active" 323 | DeviceIdentificationStatusActive string = "active" 324 | 325 | // DeviceIdentificationStatusConnecting captures enum value "connecting" 326 | DeviceIdentificationStatusConnecting string = "connecting" 327 | 328 | // DeviceIdentificationStatusDiscovered captures enum value "discovered" 329 | DeviceIdentificationStatusDiscovered string = "discovered" 330 | 331 | // DeviceIdentificationStatusInactive captures enum value "inactive" 332 | DeviceIdentificationStatusInactive string = "inactive" 333 | 334 | // DeviceIdentificationStatusDisabled captures enum value "disabled" 335 | DeviceIdentificationStatusDisabled string = "disabled" 336 | 337 | // DeviceIdentificationStatusDisconnected captures enum value "disconnected" 338 | DeviceIdentificationStatusDisconnected string = "disconnected" 339 | 340 | // DeviceIdentificationStatusUnauthorized captures enum value "unauthorized" 341 | DeviceIdentificationStatusUnauthorized string = "unauthorized" 342 | 343 | // DeviceIdentificationStatusProposed captures enum value "proposed" 344 | DeviceIdentificationStatusProposed string = "proposed" 345 | 346 | // DeviceIdentificationStatusUnknown captures enum value "unknown" 347 | DeviceIdentificationStatusUnknown string = "unknown" 348 | 349 | // DeviceIdentificationStatusUnplaced captures enum value "unplaced" 350 | DeviceIdentificationStatusUnplaced string = "unplaced" 351 | 352 | // DeviceIdentificationStatusCustom captures enum value "custom" 353 | DeviceIdentificationStatusCustom string = "custom" 354 | ) 355 | 356 | // prop value enum 357 | func (m *DeviceIdentification) validateStatusEnum(path, location string, value string) error { 358 | if err := validate.EnumCase(path, location, value, deviceIdentificationTypeStatusPropEnum, true); err != nil { 359 | return err 360 | } 361 | return nil 362 | } 363 | 364 | func (m *DeviceIdentification) validateStatus(formats strfmt.Registry) error { 365 | if swag.IsZero(m.Status) { // not required 366 | return nil 367 | } 368 | 369 | // value enum 370 | if err := m.validateStatusEnum("status", "body", m.Status); err != nil { 371 | return err 372 | } 373 | 374 | return nil 375 | } 376 | 377 | func (m *DeviceIdentification) validateUpdated(formats strfmt.Registry) error { 378 | if swag.IsZero(m.Updated) { // not required 379 | return nil 380 | } 381 | 382 | if err := validate.FormatOf("updated", "body", "date-time", m.Updated.String(), formats); err != nil { 383 | return err 384 | } 385 | 386 | return nil 387 | } 388 | 389 | // ContextValidate validate this device identification based on the context it is used 390 | func (m *DeviceIdentification) ContextValidate(ctx context.Context, formats strfmt.Registry) error { 391 | var res []error 392 | 393 | if err := m.contextValidateSite(ctx, formats); err != nil { 394 | res = append(res, err) 395 | } 396 | 397 | if len(res) > 0 { 398 | return errors.CompositeValidationError(res...) 399 | } 400 | return nil 401 | } 402 | 403 | func (m *DeviceIdentification) contextValidateSite(ctx context.Context, formats strfmt.Registry) error { 404 | 405 | if m.Site != nil { 406 | 407 | if swag.IsZero(m.Site) { // not required 408 | return nil 409 | } 410 | 411 | if err := m.Site.ContextValidate(ctx, formats); err != nil { 412 | if ve, ok := err.(*errors.Validation); ok { 413 | return ve.ValidateName("site") 414 | } else if ce, ok := err.(*errors.CompositeError); ok { 415 | return ce.ValidateName("site") 416 | } 417 | return err 418 | } 419 | } 420 | 421 | return nil 422 | } 423 | 424 | // MarshalBinary interface implementation 425 | func (m *DeviceIdentification) MarshalBinary() ([]byte, error) { 426 | if m == nil { 427 | return nil, nil 428 | } 429 | return swag.WriteJSON(m) 430 | } 431 | 432 | // UnmarshalBinary interface implementation 433 | func (m *DeviceIdentification) UnmarshalBinary(b []byte) error { 434 | var res DeviceIdentification 435 | if err := swag.ReadJSON(b, &res); err != nil { 436 | return err 437 | } 438 | *m = res 439 | return nil 440 | } 441 | -------------------------------------------------------------------------------- /client/devices/get_devices_id_statistics_responses.go: -------------------------------------------------------------------------------- 1 | // Code generated by go-swagger; DO NOT EDIT. 2 | 3 | package devices 4 | 5 | // This file was generated by the swagger tool. 6 | // Editing this file might prove futile when you re-run the swagger generate command 7 | 8 | import ( 9 | "fmt" 10 | "io" 11 | 12 | "github.com/go-openapi/runtime" 13 | "github.com/go-openapi/strfmt" 14 | 15 | "github.com/ffddorf/unms-exporter/models" 16 | ) 17 | 18 | // GetDevicesIDStatisticsReader is a Reader for the GetDevicesIDStatistics structure. 19 | type GetDevicesIDStatisticsReader struct { 20 | formats strfmt.Registry 21 | } 22 | 23 | // ReadResponse reads a server response into the received o. 24 | func (o *GetDevicesIDStatisticsReader) ReadResponse(response runtime.ClientResponse, consumer runtime.Consumer) (interface{}, error) { 25 | switch response.Code() { 26 | case 200: 27 | result := NewGetDevicesIDStatisticsOK() 28 | if err := result.readResponse(response, consumer, o.formats); err != nil { 29 | return nil, err 30 | } 31 | return result, nil 32 | case 400: 33 | result := NewGetDevicesIDStatisticsBadRequest() 34 | if err := result.readResponse(response, consumer, o.formats); err != nil { 35 | return nil, err 36 | } 37 | return nil, result 38 | case 401: 39 | result := NewGetDevicesIDStatisticsUnauthorized() 40 | if err := result.readResponse(response, consumer, o.formats); err != nil { 41 | return nil, err 42 | } 43 | return nil, result 44 | case 403: 45 | result := NewGetDevicesIDStatisticsForbidden() 46 | if err := result.readResponse(response, consumer, o.formats); err != nil { 47 | return nil, err 48 | } 49 | return nil, result 50 | case 404: 51 | result := NewGetDevicesIDStatisticsNotFound() 52 | if err := result.readResponse(response, consumer, o.formats); err != nil { 53 | return nil, err 54 | } 55 | return nil, result 56 | case 500: 57 | result := NewGetDevicesIDStatisticsInternalServerError() 58 | if err := result.readResponse(response, consumer, o.formats); err != nil { 59 | return nil, err 60 | } 61 | return nil, result 62 | default: 63 | return nil, runtime.NewAPIError("[GET /devices/{id}/statistics] getDevicesIdStatistics", response, response.Code()) 64 | } 65 | } 66 | 67 | // NewGetDevicesIDStatisticsOK creates a GetDevicesIDStatisticsOK with default headers values 68 | func NewGetDevicesIDStatisticsOK() *GetDevicesIDStatisticsOK { 69 | return &GetDevicesIDStatisticsOK{} 70 | } 71 | 72 | /* 73 | GetDevicesIDStatisticsOK describes a response with status code 200, with default header values. 74 | 75 | Successful 76 | */ 77 | type GetDevicesIDStatisticsOK struct { 78 | Payload *models.DeviceStatistics 79 | } 80 | 81 | // IsSuccess returns true when this get devices Id statistics o k response has a 2xx status code 82 | func (o *GetDevicesIDStatisticsOK) IsSuccess() bool { 83 | return true 84 | } 85 | 86 | // IsRedirect returns true when this get devices Id statistics o k response has a 3xx status code 87 | func (o *GetDevicesIDStatisticsOK) IsRedirect() bool { 88 | return false 89 | } 90 | 91 | // IsClientError returns true when this get devices Id statistics o k response has a 4xx status code 92 | func (o *GetDevicesIDStatisticsOK) IsClientError() bool { 93 | return false 94 | } 95 | 96 | // IsServerError returns true when this get devices Id statistics o k response has a 5xx status code 97 | func (o *GetDevicesIDStatisticsOK) IsServerError() bool { 98 | return false 99 | } 100 | 101 | // IsCode returns true when this get devices Id statistics o k response a status code equal to that given 102 | func (o *GetDevicesIDStatisticsOK) IsCode(code int) bool { 103 | return code == 200 104 | } 105 | 106 | // Code gets the status code for the get devices Id statistics o k response 107 | func (o *GetDevicesIDStatisticsOK) Code() int { 108 | return 200 109 | } 110 | 111 | func (o *GetDevicesIDStatisticsOK) Error() string { 112 | return fmt.Sprintf("[GET /devices/{id}/statistics][%d] getDevicesIdStatisticsOK %+v", 200, o.Payload) 113 | } 114 | 115 | func (o *GetDevicesIDStatisticsOK) String() string { 116 | return fmt.Sprintf("[GET /devices/{id}/statistics][%d] getDevicesIdStatisticsOK %+v", 200, o.Payload) 117 | } 118 | 119 | func (o *GetDevicesIDStatisticsOK) GetPayload() *models.DeviceStatistics { 120 | return o.Payload 121 | } 122 | 123 | func (o *GetDevicesIDStatisticsOK) readResponse(response runtime.ClientResponse, consumer runtime.Consumer, formats strfmt.Registry) error { 124 | 125 | o.Payload = new(models.DeviceStatistics) 126 | 127 | // response payload 128 | if err := consumer.Consume(response.Body(), o.Payload); err != nil && err != io.EOF { 129 | return err 130 | } 131 | 132 | return nil 133 | } 134 | 135 | // NewGetDevicesIDStatisticsBadRequest creates a GetDevicesIDStatisticsBadRequest with default headers values 136 | func NewGetDevicesIDStatisticsBadRequest() *GetDevicesIDStatisticsBadRequest { 137 | return &GetDevicesIDStatisticsBadRequest{} 138 | } 139 | 140 | /* 141 | GetDevicesIDStatisticsBadRequest describes a response with status code 400, with default header values. 142 | 143 | Bad Request 144 | */ 145 | type GetDevicesIDStatisticsBadRequest struct { 146 | Payload *models.Error 147 | } 148 | 149 | // IsSuccess returns true when this get devices Id statistics bad request response has a 2xx status code 150 | func (o *GetDevicesIDStatisticsBadRequest) IsSuccess() bool { 151 | return false 152 | } 153 | 154 | // IsRedirect returns true when this get devices Id statistics bad request response has a 3xx status code 155 | func (o *GetDevicesIDStatisticsBadRequest) IsRedirect() bool { 156 | return false 157 | } 158 | 159 | // IsClientError returns true when this get devices Id statistics bad request response has a 4xx status code 160 | func (o *GetDevicesIDStatisticsBadRequest) IsClientError() bool { 161 | return true 162 | } 163 | 164 | // IsServerError returns true when this get devices Id statistics bad request response has a 5xx status code 165 | func (o *GetDevicesIDStatisticsBadRequest) IsServerError() bool { 166 | return false 167 | } 168 | 169 | // IsCode returns true when this get devices Id statistics bad request response a status code equal to that given 170 | func (o *GetDevicesIDStatisticsBadRequest) IsCode(code int) bool { 171 | return code == 400 172 | } 173 | 174 | // Code gets the status code for the get devices Id statistics bad request response 175 | func (o *GetDevicesIDStatisticsBadRequest) Code() int { 176 | return 400 177 | } 178 | 179 | func (o *GetDevicesIDStatisticsBadRequest) Error() string { 180 | return fmt.Sprintf("[GET /devices/{id}/statistics][%d] getDevicesIdStatisticsBadRequest %+v", 400, o.Payload) 181 | } 182 | 183 | func (o *GetDevicesIDStatisticsBadRequest) String() string { 184 | return fmt.Sprintf("[GET /devices/{id}/statistics][%d] getDevicesIdStatisticsBadRequest %+v", 400, o.Payload) 185 | } 186 | 187 | func (o *GetDevicesIDStatisticsBadRequest) GetPayload() *models.Error { 188 | return o.Payload 189 | } 190 | 191 | func (o *GetDevicesIDStatisticsBadRequest) readResponse(response runtime.ClientResponse, consumer runtime.Consumer, formats strfmt.Registry) error { 192 | 193 | o.Payload = new(models.Error) 194 | 195 | // response payload 196 | if err := consumer.Consume(response.Body(), o.Payload); err != nil && err != io.EOF { 197 | return err 198 | } 199 | 200 | return nil 201 | } 202 | 203 | // NewGetDevicesIDStatisticsUnauthorized creates a GetDevicesIDStatisticsUnauthorized with default headers values 204 | func NewGetDevicesIDStatisticsUnauthorized() *GetDevicesIDStatisticsUnauthorized { 205 | return &GetDevicesIDStatisticsUnauthorized{} 206 | } 207 | 208 | /* 209 | GetDevicesIDStatisticsUnauthorized describes a response with status code 401, with default header values. 210 | 211 | Unauthorized 212 | */ 213 | type GetDevicesIDStatisticsUnauthorized struct { 214 | Payload *models.Error 215 | } 216 | 217 | // IsSuccess returns true when this get devices Id statistics unauthorized response has a 2xx status code 218 | func (o *GetDevicesIDStatisticsUnauthorized) IsSuccess() bool { 219 | return false 220 | } 221 | 222 | // IsRedirect returns true when this get devices Id statistics unauthorized response has a 3xx status code 223 | func (o *GetDevicesIDStatisticsUnauthorized) IsRedirect() bool { 224 | return false 225 | } 226 | 227 | // IsClientError returns true when this get devices Id statistics unauthorized response has a 4xx status code 228 | func (o *GetDevicesIDStatisticsUnauthorized) IsClientError() bool { 229 | return true 230 | } 231 | 232 | // IsServerError returns true when this get devices Id statistics unauthorized response has a 5xx status code 233 | func (o *GetDevicesIDStatisticsUnauthorized) IsServerError() bool { 234 | return false 235 | } 236 | 237 | // IsCode returns true when this get devices Id statistics unauthorized response a status code equal to that given 238 | func (o *GetDevicesIDStatisticsUnauthorized) IsCode(code int) bool { 239 | return code == 401 240 | } 241 | 242 | // Code gets the status code for the get devices Id statistics unauthorized response 243 | func (o *GetDevicesIDStatisticsUnauthorized) Code() int { 244 | return 401 245 | } 246 | 247 | func (o *GetDevicesIDStatisticsUnauthorized) Error() string { 248 | return fmt.Sprintf("[GET /devices/{id}/statistics][%d] getDevicesIdStatisticsUnauthorized %+v", 401, o.Payload) 249 | } 250 | 251 | func (o *GetDevicesIDStatisticsUnauthorized) String() string { 252 | return fmt.Sprintf("[GET /devices/{id}/statistics][%d] getDevicesIdStatisticsUnauthorized %+v", 401, o.Payload) 253 | } 254 | 255 | func (o *GetDevicesIDStatisticsUnauthorized) GetPayload() *models.Error { 256 | return o.Payload 257 | } 258 | 259 | func (o *GetDevicesIDStatisticsUnauthorized) readResponse(response runtime.ClientResponse, consumer runtime.Consumer, formats strfmt.Registry) error { 260 | 261 | o.Payload = new(models.Error) 262 | 263 | // response payload 264 | if err := consumer.Consume(response.Body(), o.Payload); err != nil && err != io.EOF { 265 | return err 266 | } 267 | 268 | return nil 269 | } 270 | 271 | // NewGetDevicesIDStatisticsForbidden creates a GetDevicesIDStatisticsForbidden with default headers values 272 | func NewGetDevicesIDStatisticsForbidden() *GetDevicesIDStatisticsForbidden { 273 | return &GetDevicesIDStatisticsForbidden{} 274 | } 275 | 276 | /* 277 | GetDevicesIDStatisticsForbidden describes a response with status code 403, with default header values. 278 | 279 | Forbidden 280 | */ 281 | type GetDevicesIDStatisticsForbidden struct { 282 | Payload *models.Error 283 | } 284 | 285 | // IsSuccess returns true when this get devices Id statistics forbidden response has a 2xx status code 286 | func (o *GetDevicesIDStatisticsForbidden) IsSuccess() bool { 287 | return false 288 | } 289 | 290 | // IsRedirect returns true when this get devices Id statistics forbidden response has a 3xx status code 291 | func (o *GetDevicesIDStatisticsForbidden) IsRedirect() bool { 292 | return false 293 | } 294 | 295 | // IsClientError returns true when this get devices Id statistics forbidden response has a 4xx status code 296 | func (o *GetDevicesIDStatisticsForbidden) IsClientError() bool { 297 | return true 298 | } 299 | 300 | // IsServerError returns true when this get devices Id statistics forbidden response has a 5xx status code 301 | func (o *GetDevicesIDStatisticsForbidden) IsServerError() bool { 302 | return false 303 | } 304 | 305 | // IsCode returns true when this get devices Id statistics forbidden response a status code equal to that given 306 | func (o *GetDevicesIDStatisticsForbidden) IsCode(code int) bool { 307 | return code == 403 308 | } 309 | 310 | // Code gets the status code for the get devices Id statistics forbidden response 311 | func (o *GetDevicesIDStatisticsForbidden) Code() int { 312 | return 403 313 | } 314 | 315 | func (o *GetDevicesIDStatisticsForbidden) Error() string { 316 | return fmt.Sprintf("[GET /devices/{id}/statistics][%d] getDevicesIdStatisticsForbidden %+v", 403, o.Payload) 317 | } 318 | 319 | func (o *GetDevicesIDStatisticsForbidden) String() string { 320 | return fmt.Sprintf("[GET /devices/{id}/statistics][%d] getDevicesIdStatisticsForbidden %+v", 403, o.Payload) 321 | } 322 | 323 | func (o *GetDevicesIDStatisticsForbidden) GetPayload() *models.Error { 324 | return o.Payload 325 | } 326 | 327 | func (o *GetDevicesIDStatisticsForbidden) readResponse(response runtime.ClientResponse, consumer runtime.Consumer, formats strfmt.Registry) error { 328 | 329 | o.Payload = new(models.Error) 330 | 331 | // response payload 332 | if err := consumer.Consume(response.Body(), o.Payload); err != nil && err != io.EOF { 333 | return err 334 | } 335 | 336 | return nil 337 | } 338 | 339 | // NewGetDevicesIDStatisticsNotFound creates a GetDevicesIDStatisticsNotFound with default headers values 340 | func NewGetDevicesIDStatisticsNotFound() *GetDevicesIDStatisticsNotFound { 341 | return &GetDevicesIDStatisticsNotFound{} 342 | } 343 | 344 | /* 345 | GetDevicesIDStatisticsNotFound describes a response with status code 404, with default header values. 346 | 347 | Not Found 348 | */ 349 | type GetDevicesIDStatisticsNotFound struct { 350 | Payload *models.Error 351 | } 352 | 353 | // IsSuccess returns true when this get devices Id statistics not found response has a 2xx status code 354 | func (o *GetDevicesIDStatisticsNotFound) IsSuccess() bool { 355 | return false 356 | } 357 | 358 | // IsRedirect returns true when this get devices Id statistics not found response has a 3xx status code 359 | func (o *GetDevicesIDStatisticsNotFound) IsRedirect() bool { 360 | return false 361 | } 362 | 363 | // IsClientError returns true when this get devices Id statistics not found response has a 4xx status code 364 | func (o *GetDevicesIDStatisticsNotFound) IsClientError() bool { 365 | return true 366 | } 367 | 368 | // IsServerError returns true when this get devices Id statistics not found response has a 5xx status code 369 | func (o *GetDevicesIDStatisticsNotFound) IsServerError() bool { 370 | return false 371 | } 372 | 373 | // IsCode returns true when this get devices Id statistics not found response a status code equal to that given 374 | func (o *GetDevicesIDStatisticsNotFound) IsCode(code int) bool { 375 | return code == 404 376 | } 377 | 378 | // Code gets the status code for the get devices Id statistics not found response 379 | func (o *GetDevicesIDStatisticsNotFound) Code() int { 380 | return 404 381 | } 382 | 383 | func (o *GetDevicesIDStatisticsNotFound) Error() string { 384 | return fmt.Sprintf("[GET /devices/{id}/statistics][%d] getDevicesIdStatisticsNotFound %+v", 404, o.Payload) 385 | } 386 | 387 | func (o *GetDevicesIDStatisticsNotFound) String() string { 388 | return fmt.Sprintf("[GET /devices/{id}/statistics][%d] getDevicesIdStatisticsNotFound %+v", 404, o.Payload) 389 | } 390 | 391 | func (o *GetDevicesIDStatisticsNotFound) GetPayload() *models.Error { 392 | return o.Payload 393 | } 394 | 395 | func (o *GetDevicesIDStatisticsNotFound) readResponse(response runtime.ClientResponse, consumer runtime.Consumer, formats strfmt.Registry) error { 396 | 397 | o.Payload = new(models.Error) 398 | 399 | // response payload 400 | if err := consumer.Consume(response.Body(), o.Payload); err != nil && err != io.EOF { 401 | return err 402 | } 403 | 404 | return nil 405 | } 406 | 407 | // NewGetDevicesIDStatisticsInternalServerError creates a GetDevicesIDStatisticsInternalServerError with default headers values 408 | func NewGetDevicesIDStatisticsInternalServerError() *GetDevicesIDStatisticsInternalServerError { 409 | return &GetDevicesIDStatisticsInternalServerError{} 410 | } 411 | 412 | /* 413 | GetDevicesIDStatisticsInternalServerError describes a response with status code 500, with default header values. 414 | 415 | Internal Server Error 416 | */ 417 | type GetDevicesIDStatisticsInternalServerError struct { 418 | Payload *models.Error 419 | } 420 | 421 | // IsSuccess returns true when this get devices Id statistics internal server error response has a 2xx status code 422 | func (o *GetDevicesIDStatisticsInternalServerError) IsSuccess() bool { 423 | return false 424 | } 425 | 426 | // IsRedirect returns true when this get devices Id statistics internal server error response has a 3xx status code 427 | func (o *GetDevicesIDStatisticsInternalServerError) IsRedirect() bool { 428 | return false 429 | } 430 | 431 | // IsClientError returns true when this get devices Id statistics internal server error response has a 4xx status code 432 | func (o *GetDevicesIDStatisticsInternalServerError) IsClientError() bool { 433 | return false 434 | } 435 | 436 | // IsServerError returns true when this get devices Id statistics internal server error response has a 5xx status code 437 | func (o *GetDevicesIDStatisticsInternalServerError) IsServerError() bool { 438 | return true 439 | } 440 | 441 | // IsCode returns true when this get devices Id statistics internal server error response a status code equal to that given 442 | func (o *GetDevicesIDStatisticsInternalServerError) IsCode(code int) bool { 443 | return code == 500 444 | } 445 | 446 | // Code gets the status code for the get devices Id statistics internal server error response 447 | func (o *GetDevicesIDStatisticsInternalServerError) Code() int { 448 | return 500 449 | } 450 | 451 | func (o *GetDevicesIDStatisticsInternalServerError) Error() string { 452 | return fmt.Sprintf("[GET /devices/{id}/statistics][%d] getDevicesIdStatisticsInternalServerError %+v", 500, o.Payload) 453 | } 454 | 455 | func (o *GetDevicesIDStatisticsInternalServerError) String() string { 456 | return fmt.Sprintf("[GET /devices/{id}/statistics][%d] getDevicesIdStatisticsInternalServerError %+v", 500, o.Payload) 457 | } 458 | 459 | func (o *GetDevicesIDStatisticsInternalServerError) GetPayload() *models.Error { 460 | return o.Payload 461 | } 462 | 463 | func (o *GetDevicesIDStatisticsInternalServerError) readResponse(response runtime.ClientResponse, consumer runtime.Consumer, formats strfmt.Registry) error { 464 | 465 | o.Payload = new(models.Error) 466 | 467 | // response payload 468 | if err := consumer.Consume(response.Body(), o.Payload); err != nil && err != io.EOF { 469 | return err 470 | } 471 | 472 | return nil 473 | } 474 | -------------------------------------------------------------------------------- /client/openapi-lite.json: -------------------------------------------------------------------------------- 1 | { 2 | "swagger": "2.0", 3 | "basePath": "/nms/api/v2.1", 4 | "info": { 5 | "title": "UNMS API", 6 | "version": "1.0.0", 7 | "description": "This is a minimalistic description of the UNMS API centered on what the UNMS exporter needs." 8 | }, 9 | "securityDefinitions": { 10 | "UserSecurity": { 11 | "type": "apiKey", 12 | "in": "header", 13 | "name": "x-auth-token", 14 | "description": "User authorization token" 15 | } 16 | }, 17 | "produces": ["application/json"], 18 | "paths": { 19 | "/devices": { 20 | "get": { 21 | "summary": "List of all devices in UNMS.", 22 | "operationId": "getDevices", 23 | "parameters": [ 24 | { 25 | "type": "string", 26 | "x-format": { "guid": true }, 27 | "name": "siteId", 28 | "in": "query", 29 | "required": false 30 | }, 31 | { 32 | "type": "boolean", 33 | "name": "withInterfaces", 34 | "in": "query", 35 | "required": false 36 | }, 37 | { 38 | "type": "boolean", 39 | "name": "authorized", 40 | "in": "query", 41 | "required": false 42 | }, 43 | { 44 | "type": "array", 45 | "x-constraint": { "single": true }, 46 | "items": { 47 | "type": "string", 48 | "enum": [ 49 | "onu", 50 | "olt", 51 | "unmsr", 52 | "unmss", 53 | "erouter", 54 | "eswitch", 55 | "epower", 56 | "airCube", 57 | "airMax", 58 | "airFiber", 59 | "toughSwitch", 60 | "solarBeam", 61 | "wave", 62 | "blackBox" 63 | ] 64 | }, 65 | "collectionFormat": "multi", 66 | "name": "type", 67 | "in": "query", 68 | "required": false 69 | }, 70 | { 71 | "type": "array", 72 | "x-constraint": { "single": true }, 73 | "items": { 74 | "type": "string", 75 | "enum": [ 76 | "router", 77 | "switch", 78 | "gpon", 79 | "ap", 80 | "station", 81 | "other", 82 | "ups", 83 | "server", 84 | "wireless", 85 | "convertor", 86 | "gateway" 87 | ] 88 | }, 89 | "collectionFormat": "multi", 90 | "name": "role", 91 | "in": "query", 92 | "required": false 93 | } 94 | ], 95 | "produces": ["application/json"], 96 | "tags": ["Devices"], 97 | "responses": { 98 | "200": { 99 | "description": "Read-only overview for device list.", 100 | "schema": { 101 | "type": "array", 102 | "description": "Read-only overview for device list.", 103 | "items": { "$ref": "#/definitions/DeviceStatusOverview" } 104 | } 105 | }, 106 | "400": { 107 | "schema": { "$ref": "#/definitions/Error" }, 108 | "description": "Bad Request" 109 | }, 110 | "401": { 111 | "schema": { "$ref": "#/definitions/Error" }, 112 | "description": "Unauthorized" 113 | }, 114 | "403": { 115 | "schema": { "$ref": "#/definitions/Error" }, 116 | "description": "Forbidden" 117 | }, 118 | "500": { 119 | "schema": { "$ref": "#/definitions/Error" }, 120 | "description": "Internal Server Error" 121 | } 122 | } 123 | } 124 | }, 125 | "/devices/{id}/statistics": { 126 | "get": { 127 | "summary": "Return device statistics.", 128 | "operationId": "getDevicesIdStatistics", 129 | "parameters": [ 130 | { 131 | "type": "string", 132 | "x-format": { "guid": true }, 133 | "name": "id", 134 | "in": "path", 135 | "required": true 136 | }, 137 | { 138 | "type": "string", 139 | "description": "Interval", 140 | "enum": [ 141 | "hour", 142 | "fourhours", 143 | "day", 144 | "week", 145 | "month", 146 | "quarter", 147 | "year", 148 | "range" 149 | ], 150 | "name": "interval", 151 | "in": "query", 152 | "required": true 153 | }, 154 | { 155 | "type": "string", 156 | "name": "start", 157 | "in": "query", 158 | "required": true 159 | }, 160 | { 161 | "type": "string", 162 | "name": "period", 163 | "in": "query", 164 | "required": true 165 | } 166 | ], 167 | "produces": ["application/json"], 168 | "tags": ["Devices"], 169 | "responses": { 170 | "200": { 171 | "schema": { "$ref": "#/definitions/DeviceStatistics" }, 172 | "description": "Successful" 173 | }, 174 | "400": { 175 | "schema": { "$ref": "#/definitions/Error" }, 176 | "description": "Bad Request" 177 | }, 178 | "401": { 179 | "schema": { "$ref": "#/definitions/Error" }, 180 | "description": "Unauthorized" 181 | }, 182 | "403": { 183 | "schema": { "$ref": "#/definitions/Error" }, 184 | "description": "Forbidden" 185 | }, 186 | "404": { 187 | "schema": { "$ref": "#/definitions/Error" }, 188 | "description": "Not Found" 189 | }, 190 | "500": { 191 | "schema": { "$ref": "#/definitions/Error" }, 192 | "description": "Internal Server Error" 193 | } 194 | } 195 | } 196 | } 197 | }, 198 | "definitions": { 199 | "DeviceStatusOverview": { 200 | "type": "object", 201 | "properties": { 202 | "enabled": { "type": "boolean" }, 203 | "firmware": { "$ref": "#/definitions/DeviceFirmware" }, 204 | "identification": { "$ref": "#/definitions/DeviceIdentification" }, 205 | "ipAddress": { 206 | "type": "string", 207 | "description": "Custom IP address in IPv4 or IPv6 format.", 208 | "example": "192.168.1.22", 209 | "x-format": { "ip": { "cidr": "optional" } } 210 | }, 211 | "latestBackup": { "$ref": "#/definitions/latestBackup" }, 212 | "meta": { "$ref": "#/definitions/DeviceMeta" }, 213 | "mode": { "type": "string" }, 214 | "overview": { "$ref": "#/definitions/DeviceOverview" }, 215 | "upgrade": { "$ref": "#/definitions/DeviceUpgrade" }, 216 | "interfaces": { 217 | "type": "array", 218 | "items": { "$ref": "#/definitions/DeviceInterfaceSchema" } 219 | } 220 | }, 221 | "required": ["enabled", "ipAddress"] 222 | }, 223 | "DeviceFirmware": { 224 | "type": "object", 225 | "properties": { 226 | "current": { 227 | "type": "string", 228 | "description": "Current firmware version." 229 | }, 230 | "latest": { 231 | "type": "string", 232 | "description": "Latest known firmware version." 233 | }, 234 | "compatible": { 235 | "type": "boolean", 236 | "description": "Is firmware compatible with UNMS" 237 | }, 238 | "semver": { "$ref": "#/definitions/semver" } 239 | }, 240 | "required": ["current", "latest", "compatible"] 241 | }, 242 | "semver": { 243 | "type": "object", 244 | "properties": { 245 | "current": { "$ref": "#/definitions/semverVersion" }, 246 | "latest": { "$ref": "#/definitions/semverVersion" } 247 | }, 248 | "required": ["current", "latest"] 249 | }, 250 | "semverVersion": { 251 | "type": "object", 252 | "properties": { 253 | "major": { "type": "number", "example": 1 }, 254 | "minor": { "type": "number", "example": 10 }, 255 | "patch": { "type": "number", "example": 8 }, 256 | "order": { "type": "string", "example": "65546.8.0" } 257 | }, 258 | "required": ["major", "minor", "patch"] 259 | }, 260 | "DeviceIdentification": { 261 | "type": "object", 262 | "description": "Read-only identification attributes.", 263 | "properties": { 264 | "authorized": { 265 | "type": "boolean", 266 | "description": "Device is added to UNMS." 267 | }, 268 | "category": { 269 | "type": "string", 270 | "enum": ["optical", "wired", "wireless", "accessories"] 271 | }, 272 | "displayName": { 273 | "type": "string", 274 | "description": "UNMS device alias or real name." 275 | }, 276 | "firmwareVersion": { 277 | "type": "string", 278 | "description": "In SemVer format." 279 | }, 280 | "hostname": { "type": "string" }, 281 | "id": { 282 | "type": "string", 283 | "description": "Device ID.", 284 | "example": "f7ac9cad-ea28-4390-93c8-7add010e8ee3", 285 | "x-format": { "guid": true } 286 | }, 287 | "ip": { 288 | "type": "string", 289 | "description": "Custom IP address in IPv4 or IPv6 format.", 290 | "example": "192.168.1.22", 291 | "x-format": { "ip": { "cidr": "optional" } } 292 | }, 293 | "mac": { 294 | "type": "string", 295 | "pattern": "^([0-9a-fA-F][0-9a-fA-F]:){5}([0-9a-fA-F][0-9a-fA-F])$|^([0-9a-fA-F]){12}$" 296 | }, 297 | "model": { 298 | "type": "string", 299 | "description": "Short names, for example UF-OLT." 300 | }, 301 | "modelName": { 302 | "type": "string", 303 | "description": "Full names, for example UFiber OLT." 304 | }, 305 | "name": { "type": "string" }, 306 | "platformId": { 307 | "type": "string", 308 | "description": "Short name, for example e600." 309 | }, 310 | "platformName": { 311 | "type": "string" 312 | }, 313 | "role": { 314 | "type": "string", 315 | "enum": [ 316 | "router", 317 | "switch", 318 | "gpon", 319 | "ap", 320 | "station", 321 | "other", 322 | "ups", 323 | "server", 324 | "wireless", 325 | "convertor", 326 | "gateway" 327 | ] 328 | }, 329 | "serialNumber": { "type": "string" }, 330 | "site": { 331 | "$ref": "#/definitions/site" 332 | }, 333 | "started": { "type": "string", "format": "date-time" }, 334 | "status": { 335 | "type": "string", 336 | "description": "Status of the station.", 337 | "example": "active", 338 | "enum": [ 339 | "active", 340 | "connecting", 341 | "discovered", 342 | "inactive", 343 | "disabled", 344 | "disconnected", 345 | "unauthorized", 346 | "proposed", 347 | "unknown", 348 | "unplaced", 349 | "custom" 350 | ] 351 | }, 352 | "type": { 353 | "type": "string" 354 | }, 355 | "wanInterfaceId": { "type": "string" }, 356 | "updated": { "type": "string", "format": "date-time" } 357 | }, 358 | "required": ["id"] 359 | }, 360 | "site": { 361 | "type": "object", 362 | "properties": { 363 | "id": { 364 | "type": "string", 365 | "description": "Site ID.", 366 | "example": "f7ac9cad-ea28-4390-93c8-7add010e8ee3", 367 | "x-format": { "guid": true } 368 | }, 369 | "name": { 370 | "type": "string", 371 | "description": "Site name.", 372 | "example": "Mount Everest" 373 | }, 374 | "status": { 375 | "type": "string", 376 | "description": "Status of the site.", 377 | "example": "active", 378 | "enum": ["active", "disconnected", "inactive"] 379 | }, 380 | "type": { 381 | "type": "string", 382 | "description": "Type of the site.", 383 | "example": "site", 384 | "enum": ["site", "endpoint"] 385 | }, 386 | "parent": { "$ref": "#/definitions/site" } 387 | }, 388 | "required": ["id", "status", "type"] 389 | }, 390 | "latestBackup": { 391 | "type": "object", 392 | "description": "Latest backup info.", 393 | "properties": { 394 | "timestamp": { 395 | "type": "string", 396 | "format": "date-time", 397 | "description": "Latest backup timestamp.", 398 | "example": "2018-11-14T15:20:32.004Z" 399 | }, 400 | "id": { "type": "string", "description": "Latest backup ID." } 401 | }, 402 | "required": ["timestamp", "id"] 403 | }, 404 | "DeviceMeta": { 405 | "type": "object", 406 | "properties": { 407 | "alias": { "type": "string", "maxLength": 30 }, 408 | "customIpAddress": { 409 | "type": "string", 410 | "description": "Custom IP address in IPv4 or IPv6 format.", 411 | "example": "192.168.1.22", 412 | "x-format": { "ip": { "cidr": "optional" } } 413 | }, 414 | "failedMessageDecryption": { "type": "boolean" }, 415 | "maintenance": { "type": "boolean" }, 416 | "note": { "type": "string", "maxLength": 300 }, 417 | "restartTimestamp": { 418 | "type": "string", 419 | "format": "date-time", 420 | "example": "2018-11-14T15:20:32.004Z" 421 | } 422 | }, 423 | "required": ["failedMessageDecryption", "maintenance", "restartTimestamp"] 424 | }, 425 | "DeviceOverview": { 426 | "type": "object", 427 | "description": "Read-only basic device/client overview attributes.", 428 | "properties": { 429 | "batteryCapacity": { "type": "number" }, 430 | "batteryTime": { "type": "number" }, 431 | "biasCurrent": { 432 | "type": "number", 433 | "description": "Nullable property in milliamperes." 434 | }, 435 | "canUpgrade": { 436 | "type": "boolean", 437 | "description": "TRUE if device can be upgraded." 438 | }, 439 | "cpu": { "type": "number", "description": "Current cpu load." }, 440 | "createdAt": { "type": "string", "format": "date-time" }, 441 | "distance": { 442 | "type": "number", 443 | "description": "Nullable property in meters." 444 | }, 445 | "frequency": { 446 | "type": "number", 447 | "description": "Nullable prop; current frequency (only for airmax devices)." 448 | }, 449 | "isLocateRunning": { 450 | "type": "boolean", 451 | "description": "TRUE if device is in location mode." 452 | }, 453 | "lastSeen": { 454 | "type": "string", 455 | "format": "date-time", 456 | "description": "Last seen timestamp in ISO format.", 457 | "example": "2018-11-14T15:20:32.004Z" 458 | }, 459 | "powerStatus": { "type": "number" }, 460 | "runningOnBattery": { 461 | "type": "boolean", 462 | "description": "TRUE if device is running on battery" 463 | }, 464 | "ram": { "type": "number", "description": "Current memory usage." }, 465 | "signal": { 466 | "type": "number", 467 | "description": "Nullable prop; current signal level (only for airmax devices), for example -55 dBm.", 468 | "example": "-55" 469 | }, 470 | "signalMax": { 471 | "type": "number", 472 | "description": "Theoretical max local signal level.", 473 | "example": "-55" 474 | }, 475 | "remoteSignalMax": { 476 | "type": "number", 477 | "description": "Theoretical max remote signal level.", 478 | "example": "-55" 479 | }, 480 | "stationsCount": { 481 | "type": "number", 482 | "description": "Count of stations (only for airmax and aircube)." 483 | }, 484 | "status": { 485 | "type": "string", 486 | "description": "Read-only value generated by UNMS." 487 | }, 488 | "temperature": { "type": "number" }, 489 | "uptime": { "type": "number", "description": "Uptime in seconds." }, 490 | "voltage": { 491 | "type": "number", 492 | "description": "System input voltage in V." 493 | }, 494 | "downlinkCapacity": { "type": "integer" }, 495 | "uplinkCapacity": { "type": "integer" }, 496 | "theoreticalUplinkCapacity": { "type": "integer" }, 497 | "theoreticalDownlinkCapacity": { "type": "integer" }, 498 | "theoreticalMaxUplinkCapacity": { "type": "integer" }, 499 | "theoreticalMaxDownlinkCapacity": { "type": "integer" }, 500 | "channelWidth": { "type": "number" }, 501 | "transmitPower": { "type": "number" }, 502 | "wirelessMode": { "type": "string" } 503 | } 504 | }, 505 | "DeviceUpgrade": { 506 | "type": "object", 507 | "properties": { 508 | "status": { "type": "string" }, 509 | "progress": { "type": "number" }, 510 | "firmware": { "$ref": "#/definitions/semverVersion" } 511 | }, 512 | "required": ["status", "progress", "firmware"] 513 | }, 514 | "DeviceInterfaceSchema": { 515 | "type": "object", 516 | "properties": { 517 | "canDisplayStatistics": { "type": "boolean", "example": true }, 518 | "enabled": { "type": "boolean", "example": true }, 519 | "identification": { "$ref": "#/definitions/InterfaceIdentification" }, 520 | "speed": { 521 | "type": "string", 522 | "example": "auto", 523 | "pattern": "^autodetect|auto|\\d+-(half|full)$" 524 | }, 525 | "statistics": { "$ref": "#/definitions/InterfaceStatistics" }, 526 | "status": { "$ref": "#/definitions/InterfaceStatus" } 527 | }, 528 | "required": ["identification"] 529 | }, 530 | "InterfaceIdentification": { 531 | "type": "object", 532 | "properties": { 533 | "description": { 534 | "type": "string", 535 | "description": "Nullable string.", 536 | "example": "Uplink", 537 | "x-nullable": true 538 | }, 539 | "displayName": { 540 | "type": "string", 541 | "description": "Computed display name from name and description", 542 | "example": "eth0" 543 | }, 544 | "mac": { 545 | "type": "string", 546 | "example": "fc:ec:da:03:bb:a8", 547 | "pattern": "^([0-9a-fA-F][0-9a-fA-F]:){5}([0-9a-fA-F][0-9a-fA-F])$|^([0-9a-fA-F]){12}$" 548 | }, 549 | "name": { 550 | "type": "string", 551 | "description": "Interface name.", 552 | "example": "eth0" 553 | }, 554 | "position": { 555 | "type": "integer", 556 | "description": "Physical port position.", 557 | "example": 0 558 | }, 559 | "type": { "type": "string", "example": "eth" } 560 | } 561 | }, 562 | "InterfaceStatistics": { 563 | "type": "object", 564 | "properties": { 565 | "dropped": { "type": "number", "example": 0 }, 566 | "errors": { "type": "number", "example": 0 }, 567 | "rxbytes": { "type": "number", "example": 7487858302 }, 568 | "rxrate": { "type": "number", "example": 3440 }, 569 | "txbytes": { "type": "number", "example": 368737600 }, 570 | "txrate": { "type": "number", "example": 736 }, 571 | "poePower": { "type": "number", "example": 736 } 572 | } 573 | }, 574 | "InterfaceStatus": { 575 | "type": "object", 576 | "properties": { 577 | "currentSpeed": { "type": "string", "example": "1000-full" }, 578 | "description": { "type": "string", "example": "1 Gbps - Full Duplex" }, 579 | "plugged": { "type": "boolean", "example": true }, 580 | "speed": { "type": "string", "example": "auto" }, 581 | "status": { "type": "string", "example": "active" } 582 | } 583 | }, 584 | "CoordinatesXY.": { 585 | "type": "object", 586 | "properties": { 587 | "x": { "type": "number" }, 588 | "y": { "type": "number" } 589 | } 590 | }, 591 | "ListOfCoordinates": { 592 | "type": "array", 593 | "items": { "$ref": "#/definitions/CoordinatesXY." } 594 | }, 595 | "DeviceStatistics": { 596 | "type": "object", 597 | "properties": { 598 | "ping": { "$ref": "#/definitions/ListOfCoordinates" } 599 | } 600 | }, 601 | "Error": { 602 | "type": "object", 603 | "properties": { 604 | "statusCode": { "type": "number", "minimum": 400, "maximum": 599 }, 605 | "error": { "type": "string" }, 606 | "message": { "type": "string" }, 607 | "validation": { "type": "object" } 608 | }, 609 | "required": ["statusCode", "error"] 610 | } 611 | } 612 | } 613 | --------------------------------------------------------------------------------