diff --git a/README.md b/README.md index b938341..5776395 100644 --- a/README.md +++ b/README.md @@ -66,8 +66,8 @@ Use the `identify` mode to automatically discover some properties of a network d $ thola identify Usage: - thola identify [flags] -Specify the address of the network device using the `--ip` flag. + thola identify [host] [flags] +Specify the address of the network device in the `[host]` argument. The `--format` flag modifies the format of the output. `--format pretty` is set by default and is useful when reading the output manually. Other options are `json` and `xml`. $ thola identify 10.204.2.90 diff --git a/api/request_handler.go b/api/request_handler.go index 4e153f2..5c4e985 100644 --- a/api/request_handler.go +++ b/api/request_handler.go @@ -312,6 +312,33 @@ func StartAPI() { // $ref: '#/definitions/OutputError' e.POST("/check/sbc", checkSBC) + // swagger:operation POST /check/server check checkServer + // --- + // summary: Check a linux server. + // consumes: + // - application/json + // - application/xml + // produces: + // - application/json + // - application/xml + // parameters: + // - name: body + // in: body + // description: Request to process. + // required: true + // schema: + // $ref: '#/definitions/CheckServerRequest' + // responses: + // 200: + // description: Returns the response. + // schema: + // $ref: '#/definitions/CheckServerResponse' + // 400: + // description: Returns an error with more details in the body. + // schema: + // $ref: '#/definitions/OutputError' + e.POST("/check/server", checkServer) + // swagger:operation POST /check/hardware-health check checkSBC // --- // summary: Check an hardware health of an device. @@ -501,6 +528,33 @@ func StartAPI() { // $ref: '#/definitions/OutputError' e.POST("/read/sbc", readSBC) + // swagger:operation POST /read/server read readServer + // --- + // summary: Reads out server data of a device. + // consumes: + // - application/json + // - application/xml + // produces: + // - application/json + // - application/xml + // parameters: + // - name: body + // in: body + // description: Request to process. + // required: true + // schema: + // $ref: '#/definitions/ReadServerRequest' + // responses: + // 200: + // description: Returns the response. + // schema: + // $ref: '#/definitions/ReadServerResponse' + // 400: + // description: Returns an error with more details in the body. + // schema: + // $ref: '#/definitions/OutputError' + e.POST("/read/server", readServer) + // swagger:operation POST /read/hardware-health read hardware health // --- // summary: Reads out hardware health data of a device. @@ -699,6 +753,18 @@ func checkSBC(ctx echo.Context) error { return returnInFormat(ctx, http.StatusOK, resp) } +func checkServer(ctx echo.Context) error { + r := request.CheckServerRequest{} + if err := ctx.Bind(&r); err != nil { + return err + } + resp, err := handleAPIRequest(ctx, &r, &r.BaseRequest.DeviceData.IPAddress) + if err != nil { + return handleError(ctx, err) + } + return returnInFormat(ctx, http.StatusOK, resp) +} + func checkHardwareHealth(ctx echo.Context) error { r := request.CheckHardwareHealthRequest{} if err := ctx.Bind(&r); err != nil { @@ -783,6 +849,18 @@ func readSBC(ctx echo.Context) error { return returnInFormat(ctx, http.StatusOK, resp) } +func readServer(ctx echo.Context) error { + r := request.ReadServerRequest{} + if err := ctx.Bind(&r); err != nil { + return err + } + resp, err := handleAPIRequest(ctx, &r, &r.BaseRequest.DeviceData.IPAddress) + if err != nil { + return handleError(ctx, err) + } + return returnInFormat(ctx, http.StatusOK, resp) +} + func readHardwareHealth(ctx echo.Context) error { r := request.ReadHardwareHealthRequest{} if err := ctx.Bind(&r); err != nil { diff --git a/cmd/check_server.go b/cmd/check_server.go new file mode 100644 index 0000000..2b9ba56 --- /dev/null +++ b/cmd/check_server.go @@ -0,0 +1,24 @@ +package cmd + +import ( + "github.com/inexio/thola/core/request" + "github.com/spf13/cobra" +) + +func init() { + addDeviceFlags(checkServerCMD) + checkCMD.AddCommand(checkServerCMD) +} + +var checkServerCMD = &cobra.Command{ + Use: "server", + Short: "Check the server specific metrics of a device", + Long: "Checks the server specific metrics of a device.\n\n" + + "The usage will be printed as performance data.", + Run: func(cmd *cobra.Command, args []string) { + r := request.CheckServerRequest{ + CheckDeviceRequest: getCheckDeviceRequest(args[0]), + } + handleRequest(&r) + }, +} diff --git a/cmd/device_init.go b/cmd/device_init.go index b77e388..b1ad348 100644 --- a/cmd/device_init.go +++ b/cmd/device_init.go @@ -53,15 +53,6 @@ func addDeviceFlags(cmd *cobra.Command) { } func bindDeviceFlags(cmd *cobra.Command) error { - if x := cmd.Flags().Lookup("ip"); x != nil { - err := viper.BindPFlag("device.ip", x) - if err != nil { - log.Error(). - AnErr("Error", err). - Msg("Can't bind flag ip") - return err - } - } if x := cmd.Flags().Lookup("timeout"); x != nil { err := viper.BindPFlag("request.timeout", x) if err != nil { diff --git a/cmd/read_server.go b/cmd/read_server.go new file mode 100644 index 0000000..bbae418 --- /dev/null +++ b/cmd/read_server.go @@ -0,0 +1,23 @@ +package cmd + +import ( + "github.com/inexio/thola/core/request" + "github.com/spf13/cobra" +) + +func init() { + addDeviceFlags(readServerCMD) + readCMD.AddCommand(readServerCMD) +} + +var readServerCMD = &cobra.Command{ + Use: "server", + Short: "Read out server specific information of a device", + Long: "Read out server specific information of a device like disk usage or process count.", + Run: func(cmd *cobra.Command, args []string) { + request := request.ReadServerRequest{ + ReadRequest: getReadRequest(args[0]), + } + handleRequest(&request) + }, +} diff --git a/config/device-classes/generic/arista_eos.yaml b/config/device-classes/generic/arista_eos.yaml index 394ee75..a95e0f8 100644 --- a/config/device-classes/generic/arista_eos.yaml +++ b/config/device-classes/generic/arista_eos.yaml @@ -9,4 +9,15 @@ match: - "Arista Networks EOS" identify: - properties: \ No newline at end of file + properties: + vendor: + - detection: constant + value: "Arista Networks" + os_version: + - detection: SysDescription + oid: "1.3.6.1.2.1.1.1.0" + operators: + - type: modify + modify_method: regexSubmatch + regex: 'Arista Networks EOS version ([^\s]+)' + format: "$1" \ No newline at end of file diff --git a/config/device-classes/generic/extremeos.yaml b/config/device-classes/generic/extremeos.yaml new file mode 100644 index 0000000..c9f3a8c --- /dev/null +++ b/config/device-classes/generic/extremeos.yaml @@ -0,0 +1,22 @@ +name: "extremeos" + +match: + logical_operator: "OR" + conditions: + - type: SysObjectID + match_mode: startsWith + values: + - .1.3.6.1.4.1.1916.2.291 + +identify: + properties: + vendor: + - detection: constant + value: "Extreme Networks" + os_version: + - detection: SysDescription + operators: + - type: modify + modify_method: regexSubmatch + regex: 'ExtremeXOS \(EXOS-VM\) version ([^\s]+)' + format: "$1" \ No newline at end of file diff --git a/config/device-classes/generic/linux.yaml b/config/device-classes/generic/linux.yaml index 046d678..88ce5f5 100644 --- a/config/device-classes/generic/linux.yaml +++ b/config/device-classes/generic/linux.yaml @@ -4,6 +4,7 @@ config: components: cpu: true memory: true + server: true match: logical_operator: OR @@ -37,4 +38,14 @@ components: modify_method: divide value: detection: constant - value: 100 \ No newline at end of file + value: 100 + server: + procs: + - detection: snmpget + oid: ".1.3.6.1.2.1.25.1.6.0" + disk: + - detection: snmpget + value: ".1.3.6.1.4.1.2021.6.9.0" + users: + - detection: snmpget + oid: "1.3.6.1.2.1.25.1.5.0" \ No newline at end of file diff --git a/config/device-classes/generic/pfsense.yaml b/config/device-classes/generic/pfsense.yaml new file mode 100644 index 0000000..0b535e8 --- /dev/null +++ b/config/device-classes/generic/pfsense.yaml @@ -0,0 +1,30 @@ +name: "pfsense" + +match: + logical_operator: "OR" + conditions: + - type: SysObjectID + match_mode: startsWith + values: + - .1.3.6.1.4.1.12325 + +identify: + properties: + vendor: + - detection: constant + value: "Pfsense" + os_version: + - detection: snmpget + oid: "1.3.6.1.2.1.25.6.3.1.2.1" + operators: + - type: modify + modify_method: regexSubmatch + regex: 'FreeBSD: FreeBSD ([^\s]+)' + format: "$1" + model: + - detection: SysDescription + operators: + - type: modify + modify_method: regexSubmatch + regex: '([^\s]+) pfSense.localdomain ([^\s]+)' + format: "$1 $2" \ No newline at end of file diff --git a/config/device-classes/generic/vyos.yaml b/config/device-classes/generic/vyos.yaml new file mode 100644 index 0000000..c928afc --- /dev/null +++ b/config/device-classes/generic/vyos.yaml @@ -0,0 +1,22 @@ +name: "vyos" + +match: + logical_operator: "OR" + conditions: + - type: SysObjectID + match_mode: startsWith + values: + - .1.3.6.1.4.1.30803 + +identify: + properties: + vendor: + - detection: constant + value: "Vyatta" + os_version: + - detection: SysDescription + operators: + - type: modify + modify_method: regexSubmatch + regex: 'Vyatta VyOS ([^\s]+)' + format: "$1" \ No newline at end of file diff --git a/core/communicator/base.go b/core/communicator/base.go index 079d95c..cb3da40 100644 --- a/core/communicator/base.go +++ b/core/communicator/base.go @@ -59,6 +59,18 @@ func (c *baseCommunicator) GetMemoryComponentMemoryUsage(_ context.Context) (flo return 0, tholaerr.NewNotImplementedError("function is not implemented for this communicator") } +func (c *baseCommunicator) GetServerComponentDisk(_ context.Context) (int, error) { + return 0, tholaerr.NewNotImplementedError("function is not implemented for this communicator") +} + +func (c *baseCommunicator) GetServerComponentProcs(_ context.Context) (int, error) { + return 0, tholaerr.NewNotImplementedError("function is not implemented for this communicator") +} + +func (c *baseCommunicator) GetServerComponentUsers(_ context.Context) (int, error) { + return 0, tholaerr.NewNotImplementedError("function is not implemented for this communicator") +} + func (c *baseCommunicator) GetUPSComponentAlarmLowVoltageDisconnect(_ context.Context) (int, error) { return 0, tholaerr.NewNotImplementedError("function is not implemented for this communicator") } diff --git a/core/communicator/device_class.go b/core/communicator/device_class.go index c7d876f..3cf2e7f 100644 --- a/core/communicator/device_class.go +++ b/core/communicator/device_class.go @@ -1,3 +1,6 @@ +// Package communicator contains the logic for interacting with device classes. +// It contains methods that read out the .yaml files representing device classes. +// On top of that, code communicators that extend .yaml files can be added here. package communicator import ( @@ -32,6 +35,7 @@ const ( cpuComponent memoryComponent sbcComponent + serverComponent hardwareHealthComponent ) @@ -69,6 +73,7 @@ type deviceClassComponents struct { cpu *deviceClassComponentsCPU memory *deviceClassComponentsMemory sbc *deviceClassComponentsSBC + server *deviceClassComponentsServer hardwareHealth *deviceClassComponentsHardwareHealth } @@ -111,6 +116,13 @@ type deviceClassComponentsSBC struct { systemHealthScore propertyReader } +// deviceClassComponentsServer represents the server components part of a device class. +type deviceClassComponentsServer struct { + disk propertyReader + procs propertyReader + users propertyReader +} + // deviceClassComponentsHardwareHealth represents the sbc components part of a device class. type deviceClassComponentsHardwareHealth struct { environmentMonitorState propertyReader @@ -153,12 +165,13 @@ type deviceClassSNMP struct { MaxRepetitions uint32 `yaml:"max_repetitions"` } -// logicalOperator represents a logical operator (OR or AND) +// logicalOperator represents a logical operator (OR or AND). type logicalOperator string // matchMode represents a match mode that is used to match a condition. type matchMode string +// yamlDeviceClass represents the structure and the parts of a yaml device class. type yamlDeviceClass struct { Name string `yaml:"name"` Match interface{} `yaml:"match"` @@ -167,19 +180,23 @@ type yamlDeviceClass struct { Components yamlDeviceClassComponents `yaml:"components"` } +// yamlDeviceClassIdentify represents the identify part of a yaml device class. type yamlDeviceClassIdentify struct { Properties *yamlDeviceClassIdentifyProperties `yaml:"properties"` } +// yamlDeviceClassComponents represents the components part of a yaml device class. type yamlDeviceClassComponents struct { Interfaces *yamlComponentsInterfaces `yaml:"interfaces"` UPS *yamlComponentsUPSProperties `yaml:"ups"` CPU *yamlComponentsCPUProperties `yaml:"cpu"` Memory *yamlComponentsMemoryProperties `yaml:"memory"` SBC *yamlComponentsSBCProperties `yaml:"sbc"` + Server *yamlComponentsServerProperties `yaml:"server"` HardwareHealth *yamlComponentsHardwareHealthProperties `yaml:"hardware_health"` } +// yamlDeviceClassConfig represents the config part of a yaml device class. type yamlDeviceClassConfig struct { SNMP deviceClassSNMP `yaml:"snmp"` Components map[string]bool `yaml:"components"` @@ -190,6 +207,7 @@ type yamlConditionSet struct { Conditions []interface{} } +// yamlDeviceClassIdentifyProperties represents the identify properties of a yaml device class. type yamlDeviceClassIdentifyProperties struct { Vendor []interface{} `yaml:"vendor"` Model []interface{} `yaml:"model"` @@ -198,6 +216,11 @@ type yamlDeviceClassIdentifyProperties struct { OSVersion []interface{} `yaml:"os_version"` } +// +// Here are definitions of components of yaml device classes. +// + +// yamlComponentsUPSProperties represents the specific properties of ups components of a yaml device class. type yamlComponentsUPSProperties struct { AlarmLowVoltageDisconnect []interface{} `yaml:"alarm_low_voltage_disconnect"` BatteryAmperage []interface{} `yaml:"battery_amperage"` @@ -212,15 +235,18 @@ type yamlComponentsUPSProperties struct { SystemVoltage []interface{} `yaml:"system_voltage"` } +// yamlComponentsCPUProperties represents the specific properties of cpu components of a yaml device class. type yamlComponentsCPUProperties struct { Load []interface{} `yaml:"load"` Temperature []interface{} `yaml:"temperature"` } +// yamlComponentsMemoryProperties represents the specific properties of memory components of a yaml device class. type yamlComponentsMemoryProperties struct { Usage []interface{} `yaml:"usage"` } +// yamlComponentsSBCProperties represents the specific properties of sbc components of a yaml device class. type yamlComponentsSBCProperties struct { Agents interface{} `yaml:"agents"` Realms interface{} `yaml:"realms"` @@ -233,12 +259,24 @@ type yamlComponentsSBCProperties struct { SystemHealthScore []interface{} `yaml:"system_health_score"` } +// yamlComponentsServerProperties represents the specific properties of server components of a yaml device class. +type yamlComponentsServerProperties struct { + Disk []interface{} `yaml:"disk"` + Procs []interface{} `yaml:"procs"` + Users []interface{} `yaml:"users"` +} + +// yamlComponentsHardwareHealthProperties represents the specific properties of hardware health components of a yaml device class. type yamlComponentsHardwareHealthProperties struct { EnvironmentMonitorState []interface{} `yaml:"environment_monitor_state"` Fans interface{} `yaml:"fans"` PowerSupply interface{} `yaml:"power_supply"` } +// +// Here are definitions of interfaces of yaml device classes. +// + type yamlComponentsInterfaces struct { Count string `yaml:"count"` IfTable interface{} `yaml:"ifTable"` @@ -649,6 +687,14 @@ func (y *yamlDeviceClassComponents) convert() (deviceClassComponents, error) { components.sbc = &sbc } + if y.Server != nil { + server, err := y.Server.convert() + if err != nil { + return deviceClassComponents{}, errors.Wrap(err, "failed to read yaml server properties") + } + components.server = &server + } + if y.HardwareHealth != nil { hardwareHealth, err := y.HardwareHealth.convert() if err != nil { @@ -933,6 +979,31 @@ func (y *yamlComponentsMemoryProperties) convert() (deviceClassComponentsMemory, return properties, nil } +func (y *yamlComponentsServerProperties) convert() (deviceClassComponentsServer, error) { + var properties deviceClassComponentsServer + var err error + + if y.Disk != nil { + properties.disk, err = convertYamlProperty(y.Disk, propertyDefault) + if err != nil { + return deviceClassComponentsServer{}, errors.Wrap(err, "failed to convert disk property to property reader") + } + } + if y.Procs != nil { + properties.procs, err = convertYamlProperty(y.Procs, propertyDefault) + if err != nil { + return deviceClassComponentsServer{}, errors.Wrap(err, "failed to convert procs property to property reader") + } + } + if y.Users != nil { + properties.users, err = convertYamlProperty(y.Users, propertyDefault) + if err != nil { + return deviceClassComponentsServer{}, errors.Wrap(err, "failed to convert users property to property reader") + } + } + return properties, nil +} + func (y *yamlComponentsSBCProperties) convert() (deviceClassComponentsSBC, error) { var properties deviceClassComponentsSBC var err error @@ -1669,6 +1740,8 @@ func createComponent(component string) (deviceClassComponent, error) { return memoryComponent, nil case "sbc": return sbcComponent, nil + case "server": + return serverComponent, nil case "hardware_health": return hardwareHealthComponent, nil default: @@ -1691,6 +1764,8 @@ func (d *deviceClassComponent) toString() (string, error) { return "memory", nil case sbcComponent: return "sbc", nil + case serverComponent: + return "server", nil case hardwareHealthComponent: return "hardware_health", nil default: diff --git a/core/communicator/device_class_communicator.go b/core/communicator/device_class_communicator.go index 4684403..c65481c 100644 --- a/core/communicator/device_class_communicator.go +++ b/core/communicator/device_class_communicator.go @@ -632,6 +632,63 @@ func (o *deviceClassCommunicator) GetSBCComponentSystemHealthScore(ctx context.C return result, nil } +func (o *deviceClassCommunicator) GetServerComponentDisk(ctx context.Context) (int, error) { + if o.components.server == nil || o.components.server.disk == nil { + log.Ctx(ctx).Trace().Str("property", "ServerComponentDisk").Str("device_class", o.name).Msg("no detection information available") + return 0, tholaerr.NewNotImplementedError("no detection information available") + } + logger := log.Ctx(ctx).With().Str("property", "ServerComponentDisk").Logger() + ctx = logger.WithContext(ctx) + res, err := o.components.server.disk.getProperty(ctx) + if err != nil { + log.Ctx(ctx).Trace().Err(err).Msg("failed to get property") + return 0, errors.Wrap(err, "failed to get ServerComponentDisk") + } + r, err := res.Int() + if err != nil { + return 0, errors.Wrapf(err, "failed to convert value '%s' to int", res.String()) + } + return r, nil +} + +func (o *deviceClassCommunicator) GetServerComponentProcs(ctx context.Context) (int, error) { + if o.components.server == nil || o.components.server.procs == nil { + log.Ctx(ctx).Trace().Str("property", "ServerComponentProcs").Str("device_class", o.name).Msg("no detection information available") + return 0, tholaerr.NewNotImplementedError("no detection information available") + } + logger := log.Ctx(ctx).With().Str("property", "ServerComponentProcs").Logger() + ctx = logger.WithContext(ctx) + res, err := o.components.server.procs.getProperty(ctx) + if err != nil { + log.Ctx(ctx).Trace().Err(err).Msg("failed to get property") + return 0, errors.Wrap(err, "failed to get ServerComponentProcs") + } + r, err := res.Int() + if err != nil { + return 0, errors.Wrapf(err, "failed to convert value '%s' to int", res.String()) + } + return r, nil +} + +func (o *deviceClassCommunicator) GetServerComponentUsers(ctx context.Context) (int, error) { + if o.components.server == nil || o.components.server.users == nil { + log.Ctx(ctx).Trace().Str("property", "ServerComponentUsers").Str("device_class", o.name).Msg("no detection information available") + return 0, tholaerr.NewNotImplementedError("no detection information available") + } + logger := log.Ctx(ctx).With().Str("property", "ServerComponentUsers").Logger() + ctx = logger.WithContext(ctx) + res, err := o.components.server.users.getProperty(ctx) + if err != nil { + log.Ctx(ctx).Trace().Err(err).Msg("failed to get property") + return 0, errors.Wrap(err, "failed to get ServerComponentUsers") + } + r, err := res.Int() + if err != nil { + return 0, errors.Wrapf(err, "failed to convert value '%s' to int", res.String()) + } + return r, nil +} + func (o *deviceClassCommunicator) GetHardwareHealthComponentEnvironmentMonitorState(ctx context.Context) (int, error) { if o.components.hardwareHealth == nil || o.components.hardwareHealth.environmentMonitorState == nil { log.Ctx(ctx).Trace().Str("property", "HardwareHealthComponentEnvironmentMonitorState").Str("device_class", o.name).Msg("no detection information available") diff --git a/core/communicator/network_device_communicator.go b/core/communicator/network_device_communicator.go index c1b7094..4e06679 100644 --- a/core/communicator/network_device_communicator.go +++ b/core/communicator/network_device_communicator.go @@ -16,6 +16,7 @@ type NetworkDeviceCommunicator interface { GetCPUComponent(ctx context.Context) (device.CPUComponent, error) GetUPSComponent(ctx context.Context) (device.UPSComponent, error) GetSBCComponent(ctx context.Context) (device.SBCComponent, error) + GetServerComponent(ctx context.Context) (device.ServerComponent, error) GetHardwareHealthComponent(ctx context.Context) (device.HardwareHealthComponent, error) availableCommunicatorFunctions } @@ -33,6 +34,7 @@ type availableCommunicatorFunctions interface { availableMemoryCommunicatorFunctions availableUPSCommunicatorFunctions availableSBCCommunicatorFunctions + availableServerCommunicatorFunctions availableHardwareHealthCommunicatorFunctions } @@ -71,6 +73,12 @@ type availableSBCCommunicatorFunctions interface { GetSBCComponentSystemHealthScore(ctx context.Context) (int, error) } +type availableServerCommunicatorFunctions interface { + GetServerComponentDisk(ctx context.Context) (int, error) + GetServerComponentProcs(ctx context.Context) (int, error) + GetServerComponentUsers(ctx context.Context) (int, error) +} + type availableHardwareHealthCommunicatorFunctions interface { GetHardwareHealthComponentFans(ctx context.Context) ([]device.HardwareHealthComponentFan, error) GetHardwareHealthComponentPowerSupply(ctx context.Context) ([]device.HardwareHealthComponentPowerSupply, error) @@ -464,6 +472,52 @@ func (c *networkDeviceCommunicator) GetSBCComponent(ctx context.Context) (device return sbc, nil } +func (c *networkDeviceCommunicator) GetServerComponent(ctx context.Context) (device.ServerComponent, error) { + if !c.deviceClassCommunicator.hasAvailableComponent(serverComponent) { + return device.ServerComponent{}, tholaerr.NewComponentNotFoundError("no server component available for this device") + } + + var server device.ServerComponent + + empty := true + + disk, err := c.head.GetServerComponentDisk(ctx) + if err != nil { + if !tholaerr.IsNotFoundError(err) && !tholaerr.IsNotImplementedError(err) { + return device.ServerComponent{}, errors.Wrap(err, "error occurred during get server component disk") + } + } else { + server.Disk = &disk + empty = false + } + + procs, err := c.head.GetServerComponentProcs(ctx) + if err != nil { + if !tholaerr.IsNotFoundError(err) && !tholaerr.IsNotImplementedError(err) { + return device.ServerComponent{}, errors.Wrap(err, "error occurred during get server component procs") + } + } else { + server.Procs = &procs + empty = false + } + + users, err := c.head.GetServerComponentUsers(ctx) + if err != nil { + if !tholaerr.IsNotFoundError(err) && !tholaerr.IsNotImplementedError(err) { + return device.ServerComponent{}, errors.Wrap(err, "error occurred during get server component users") + } + } else { + server.Users = &users + empty = false + } + + if empty { + return device.ServerComponent{}, tholaerr.NewNotFoundError("no server data available") + } + + return server, nil +} + func (c *networkDeviceCommunicator) GetHardwareHealthComponent(ctx context.Context) (device.HardwareHealthComponent, error) { if !c.deviceClassCommunicator.hasAvailableComponent(hardwareHealthComponent) { return device.HardwareHealthComponent{}, tholaerr.NewComponentNotFoundError("no sbc component available for this device") @@ -669,6 +723,48 @@ func (c *networkDeviceCommunicator) GetMemoryComponentMemoryUsage(ctx context.Co return res.(float64), err } +func (c *networkDeviceCommunicator) GetServerComponentDisk(ctx context.Context) (int, error) { + if !c.deviceClassCommunicator.hasAvailableComponent(serverComponent) { + return 0, tholaerr.NewComponentNotFoundError("no server component available for this device") + } + fClass := newCommunicatorAdapter(c.deviceClassCommunicator).getServerDisk + fCom := utility.IfThenElse(c.codeCommunicator != nil, adapterFunc(newCommunicatorAdapter(c.codeCommunicator).getServerDisk), emptyAdapterFunc).(adapterFunc) + fSub := utility.IfThenElse(c.sub != nil, adapterFunc(newCommunicatorAdapter(c.sub).getServerDisk), emptyAdapterFunc).(adapterFunc) + res, err := c.executeWithRecursion(fClass, fCom, fSub, ctx) + if err != nil { + return 0, err + } + return res.(int), err +} + +func (c *networkDeviceCommunicator) GetServerComponentProcs(ctx context.Context) (int, error) { + if !c.deviceClassCommunicator.hasAvailableComponent(serverComponent) { + return 0, tholaerr.NewComponentNotFoundError("no server component available for this device") + } + fClass := newCommunicatorAdapter(c.deviceClassCommunicator).getServerProcs + fCom := utility.IfThenElse(c.codeCommunicator != nil, adapterFunc(newCommunicatorAdapter(c.codeCommunicator).getServerProcs), emptyAdapterFunc).(adapterFunc) + fSub := utility.IfThenElse(c.sub != nil, adapterFunc(newCommunicatorAdapter(c.sub).getServerProcs), emptyAdapterFunc).(adapterFunc) + res, err := c.executeWithRecursion(fClass, fCom, fSub, ctx) + if err != nil { + return 0, err + } + return res.(int), err +} + +func (c *networkDeviceCommunicator) GetServerComponentUsers(ctx context.Context) (int, error) { + if !c.deviceClassCommunicator.hasAvailableComponent(serverComponent) { + return 0, tholaerr.NewComponentNotFoundError("no server component available for this device") + } + fClass := newCommunicatorAdapter(c.deviceClassCommunicator).getServerUsers + fCom := utility.IfThenElse(c.codeCommunicator != nil, adapterFunc(newCommunicatorAdapter(c.codeCommunicator).getServerUsers), emptyAdapterFunc).(adapterFunc) + fSub := utility.IfThenElse(c.sub != nil, adapterFunc(newCommunicatorAdapter(c.sub).getServerUsers), emptyAdapterFunc).(adapterFunc) + res, err := c.executeWithRecursion(fClass, fCom, fSub, ctx) + if err != nil { + return 0, err + } + return res.(int), err +} + func (c *networkDeviceCommunicator) GetUPSComponentAlarmLowVoltageDisconnect(ctx context.Context) (int, error) { fClass := newCommunicatorAdapter(c.deviceClassCommunicator).getUPSComponentAlarmLowVoltageDisconnect fCom := utility.IfThenElse(c.codeCommunicator != nil, adapterFunc(newCommunicatorAdapter(c.codeCommunicator).getUPSComponentAlarmLowVoltageDisconnect), emptyAdapterFunc).(adapterFunc) diff --git a/core/communicator/network_device_communicator_adapter.go b/core/communicator/network_device_communicator_adapter.go index 3c61449..694838e 100644 --- a/core/communicator/network_device_communicator_adapter.go +++ b/core/communicator/network_device_communicator_adapter.go @@ -23,6 +23,7 @@ type communicatorAdapter interface { communicatorAdapterUPS communicatorAdapterMemory communicatorAdapterSCB + communicatorAdapterServer communicatorAdapterHardwareHealth } @@ -35,6 +36,12 @@ type communicatorAdapterMemory interface { getMemoryUsage(...interface{}) (interface{}, error) } +type communicatorAdapterServer interface { + getServerDisk(...interface{}) (interface{}, error) + getServerProcs(...interface{}) (interface{}, error) + getServerUsers(...interface{}) (interface{}, error) +} + type communicatorAdapterUPS interface { getUPSComponentAlarmLowVoltageDisconnect(...interface{}) (interface{}, error) getUPSComponentBatteryAmperage(...interface{}) (interface{}, error) @@ -117,6 +124,18 @@ func (a *adapter) getMemoryUsage(i ...interface{}) (interface{}, error) { return a.com.GetMemoryComponentMemoryUsage(i[0].(context.Context)) } +func (a *adapter) getServerDisk(i ...interface{}) (interface{}, error) { + return a.com.GetServerComponentDisk(i[0].(context.Context)) +} + +func (a *adapter) getServerProcs(i ...interface{}) (interface{}, error) { + return a.com.GetServerComponentProcs(i[0].(context.Context)) +} + +func (a *adapter) getServerUsers(i ...interface{}) (interface{}, error) { + return a.com.GetServerComponentUsers(i[0].(context.Context)) +} + func (a *adapter) getUPSComponentAlarmLowVoltageDisconnect(i ...interface{}) (interface{}, error) { return a.com.GetUPSComponentAlarmLowVoltageDisconnect(i[0].(context.Context)) } diff --git a/core/device/device.go b/core/device/device.go index a5fe955..a2f464e 100644 --- a/core/device/device.go +++ b/core/device/device.go @@ -190,6 +190,13 @@ type UPSComponent struct { SystemVoltage *float64 `yaml:"system_voltage" json:"system_voltage" xml:"system_voltage"` } +// ServerComponent represents a server component +type ServerComponent struct { + Disk *int `yaml:"disk" json:"disk" xml:"disk"` + Procs *int `yaml:"procs" json:"procs" xml:"procs"` + Users *int `yaml:"users" json:"users" xml:"users"` +} + // SBCComponent represents a SBC component type SBCComponent struct { Agents []SBCComponentAgent `yaml:"agents" json:"agents" xml:"agents"` diff --git a/core/request/check_server_request.go b/core/request/check_server_request.go new file mode 100644 index 0000000..2f0442a --- /dev/null +++ b/core/request/check_server_request.go @@ -0,0 +1,20 @@ +package request + +import "context" + +// CheckServerRequest +// +// CheckServerRequest is a the request struct for the check server request. +// +// swagger:model +type CheckServerRequest struct { + CheckDeviceRequest + ServerThresholds CheckThresholds `json:"serverThresholds" xml:"serverThresholds"` +} + +func (r *CheckServerRequest) validate(ctx context.Context) error { + if err := r.ServerThresholds.validate(); err != nil { + return err + } + return r.CheckDeviceRequest.validate(ctx) +} diff --git a/core/request/check_server_request_process.go b/core/request/check_server_request_process.go new file mode 100644 index 0000000..a5104b6 --- /dev/null +++ b/core/request/check_server_request_process.go @@ -0,0 +1,43 @@ +// +build !client + +package request + +import ( + "context" + "github.com/inexio/go-monitoringplugin" +) + +func (r *CheckServerRequest) process(ctx context.Context) (Response, error) { + r.init() + + serverRequest := ReadServerRequest{ReadRequest{r.BaseRequest}} + response, err := serverRequest.process(ctx) + if r.mon.UpdateStatusOnError(err, monitoringplugin.UNKNOWN, "error while processing read server request", true) { + return &CheckResponse{r.mon.GetInfo()}, nil + } + server := response.(*ReadServerResponse) + + if server.Server.Disk != nil { + err = r.mon.AddPerformanceDataPoint(monitoringplugin.NewPerformanceDataPoint("disk_usage", *server.Server.Disk, "")) + if r.mon.UpdateStatusOnError(err, monitoringplugin.UNKNOWN, "error while adding performance data point", true) { + r.mon.PrintPerformanceData(false) + return &CheckResponse{r.mon.GetInfo()}, nil + } + } + if server.Server.Procs != nil { + err = r.mon.AddPerformanceDataPoint(monitoringplugin.NewPerformanceDataPoint("procs", *server.Server.Procs, "")) + if r.mon.UpdateStatusOnError(err, monitoringplugin.UNKNOWN, "error while adding performance data point", true) { + r.mon.PrintPerformanceData(false) + return &CheckResponse{r.mon.GetInfo()}, nil + } + } + if server.Server.Users != nil { + err = r.mon.AddPerformanceDataPoint(monitoringplugin.NewPerformanceDataPoint("users", *server.Server.Users, "")) + if r.mon.UpdateStatusOnError(err, monitoringplugin.UNKNOWN, "error while adding performance data point", true) { + r.mon.PrintPerformanceData(false) + return &CheckResponse{r.mon.GetInfo()}, nil + } + } + + return &CheckResponse{r.mon.GetInfo()}, nil +} diff --git a/core/request/client_process.go b/core/request/client_process.go index 2083bd1..8a641b2 100644 --- a/core/request/client_process.go +++ b/core/request/client_process.go @@ -108,6 +108,10 @@ func (r *CheckMemoryUsageRequest) process(ctx context.Context) (Response, error) return checkProcess(ctx, r, "check/memory-usage"), nil } +func (r *CheckServerRequest) process(ctx context.Context) (Response, error) { + return checkProcess(ctx, r, "check/server"), nil +} + func (r *CheckCPULoadRequest) process(ctx context.Context) (Response, error) { return checkProcess(ctx, r, "check/cpu-load"), nil } @@ -200,6 +204,20 @@ func (r *ReadSBCRequest) process(ctx context.Context) (Response, error) { return &res, nil } +func (r *ReadServerRequest) process(ctx context.Context) (Response, error) { + apiFormat := viper.GetString("target-api-format") + responseBody, err := sendToAPI(ctx, r, "read/server", apiFormat) + if err != nil { + return nil, err + } + var res ReadServerResponse + err = parser.ToStruct(responseBody, apiFormat, &res) + if err != nil { + return nil, errors.Wrap(err, "failed to parse api response body to thola response") + } + return &res, nil +} + func (r *ReadHardwareHealthRequest) process(ctx context.Context) (Response, error) { apiFormat := viper.GetString("target-api-format") responseBody, err := sendToAPI(ctx, r, "read/hardware-health", apiFormat) diff --git a/core/request/read_server_request.go b/core/request/read_server_request.go new file mode 100644 index 0000000..29888c0 --- /dev/null +++ b/core/request/read_server_request.go @@ -0,0 +1,22 @@ +package request + +import "github.com/inexio/thola/core/device" + +// ReadServerRequest +// +// ReadServerRequest is a the request struct for the read server request. +// +// swagger:model +type ReadServerRequest struct { + ReadRequest +} + +// ReadServerResponse +// +// ReadServerResponse is a the response struct for the read server response. +// +// swagger:model +type ReadServerResponse struct { + Server device.ServerComponent `yaml:"server" json:"server" xml:"server"` + ReadResponse +} diff --git a/core/request/read_server_request_process.go b/core/request/read_server_request_process.go new file mode 100644 index 0000000..4c42eb4 --- /dev/null +++ b/core/request/read_server_request_process.go @@ -0,0 +1,24 @@ +// +build !client + +package request + +import ( + "context" + "github.com/pkg/errors" +) + +func (r *ReadServerRequest) process(ctx context.Context) (Response, error) { + com, err := GetCommunicator(ctx, r.BaseRequest) + if err != nil { + return nil, errors.Wrap(err, "failed to get communicator") + } + + result, err := com.GetServerComponent(ctx) + if err != nil { + return nil, errors.Wrap(err, "can't get server components") + } + + return &ReadServerResponse{ + Server: result, + }, nil +} diff --git a/test/testdata/devices/arista_eos/device_1/test_data.json b/test/testdata/devices/arista_eos/device_1/test_data.json index c829363..f7ec60b 100644 --- a/test/testdata/devices/arista_eos/device_1/test_data.json +++ b/test/testdata/devices/arista_eos/device_1/test_data.json @@ -4,11 +4,11 @@ "identify": { "class": "arista_eos", "properties": { - "vendor": null, + "vendor": "Arista Networks", "model": null, "model_series": null, "serial_number": null, - "os_version": null + "os_version": "4.16.14M" } }, "readCountInterfaces": {