From a1eb9f55c6424a09d527200a5ca6558dda8d36e7 Mon Sep 17 00:00:00 2001 From: DaRK AnGeL <28630321+masterwishx@users.noreply.github.com> Date: Fri, 5 Jan 2024 17:46:26 +0200 Subject: [PATCH] fix(inputs.upsd): Add additional fields to upsd from NUT (#14447) --- plugins/inputs/upsd/README.md | 13 + plugins/inputs/upsd/sample.conf | 13 + .../expected.out | 1 + .../telegraf.conf | 1 + .../CyberPowerSystems_CP900EPFCLCD/types.dev | 49 ++ .../variables.dev | 49 ++ .../expected.out | 1 + .../telegraf.conf | 2 + .../types.dev | 49 ++ .../variables.dev | 49 ++ .../expected.out | 1 + .../telegraf.conf | 2 + .../types.dev | 49 ++ .../variables.dev | 49 ++ .../inputs/upsd/testcases/fake/expected.out | 1 + .../inputs/upsd/testcases/fake/telegraf.conf | 1 + plugins/inputs/upsd/testcases/fake/types.dev | 15 + .../inputs/upsd/testcases/fake/variables.dev | 15 + .../testcases/fake_force_float/expected.out | 1 + .../testcases/fake_force_float/telegraf.conf | 2 + .../upsd/testcases/fake_force_float/types.dev | 15 + .../testcases/fake_force_float/variables.dev | 15 + .../fake_force_float_with_string/expected.out | 1 + .../telegraf.conf | 3 + .../fake_force_float_with_string/types.dev | 16 + .../variables.dev | 16 + plugins/inputs/upsd/upsd.go | 157 ++++-- plugins/inputs/upsd/upsd_test.go | 460 +++++++++--------- 28 files changed, 762 insertions(+), 284 deletions(-) create mode 100644 plugins/inputs/upsd/testcases/CyberPowerSystems_CP900EPFCLCD/expected.out create mode 100644 plugins/inputs/upsd/testcases/CyberPowerSystems_CP900EPFCLCD/telegraf.conf create mode 100644 plugins/inputs/upsd/testcases/CyberPowerSystems_CP900EPFCLCD/types.dev create mode 100644 plugins/inputs/upsd/testcases/CyberPowerSystems_CP900EPFCLCD/variables.dev create mode 100644 plugins/inputs/upsd/testcases/CyberPowerSystems_CP900EPFCLCD_additional/expected.out create mode 100644 plugins/inputs/upsd/testcases/CyberPowerSystems_CP900EPFCLCD_additional/telegraf.conf create mode 100644 plugins/inputs/upsd/testcases/CyberPowerSystems_CP900EPFCLCD_additional/types.dev create mode 100644 plugins/inputs/upsd/testcases/CyberPowerSystems_CP900EPFCLCD_additional/variables.dev create mode 100644 plugins/inputs/upsd/testcases/CyberPowerSystems_CP900EPFCLCD_full/expected.out create mode 100644 plugins/inputs/upsd/testcases/CyberPowerSystems_CP900EPFCLCD_full/telegraf.conf create mode 100644 plugins/inputs/upsd/testcases/CyberPowerSystems_CP900EPFCLCD_full/types.dev create mode 100644 plugins/inputs/upsd/testcases/CyberPowerSystems_CP900EPFCLCD_full/variables.dev create mode 100644 plugins/inputs/upsd/testcases/fake/expected.out create mode 100644 plugins/inputs/upsd/testcases/fake/telegraf.conf create mode 100644 plugins/inputs/upsd/testcases/fake/types.dev create mode 100644 plugins/inputs/upsd/testcases/fake/variables.dev create mode 100644 plugins/inputs/upsd/testcases/fake_force_float/expected.out create mode 100644 plugins/inputs/upsd/testcases/fake_force_float/telegraf.conf create mode 100644 plugins/inputs/upsd/testcases/fake_force_float/types.dev create mode 100644 plugins/inputs/upsd/testcases/fake_force_float/variables.dev create mode 100644 plugins/inputs/upsd/testcases/fake_force_float_with_string/expected.out create mode 100644 plugins/inputs/upsd/testcases/fake_force_float_with_string/telegraf.conf create mode 100644 plugins/inputs/upsd/testcases/fake_force_float_with_string/types.dev create mode 100644 plugins/inputs/upsd/testcases/fake_force_float_with_string/variables.dev diff --git a/plugins/inputs/upsd/README.md b/plugins/inputs/upsd/README.md index acb6d7481..9452fc856 100644 --- a/plugins/inputs/upsd/README.md +++ b/plugins/inputs/upsd/README.md @@ -33,6 +33,19 @@ See the [CONFIGURATION.md][CONFIGURATION.md] for more details. ## consistently as floats to avoid database conflicts where some numbers are ## parsed as integers and others as floats. # force_float = false + + ## Collect additional fields if they are available for the UPS + ## The fields need to be specified as NUT variable names, see + ## https://networkupstools.org/docs/developer-guide.chunked/apas02.html + ## Wildcards are accepted. + # additional_fields = [] + + ## Dump information for debugging + ## Allows to print the raw variables (and corresponding types) as received + ## from the NUT server ONCE for each UPS. The output is only available when + ## running Telegraf in debug-mode. + ## Please attach this information when reporting issues! + # dump_raw_variables = false ``` ## Metrics diff --git a/plugins/inputs/upsd/sample.conf b/plugins/inputs/upsd/sample.conf index 362e0e3bf..da23b9137 100644 --- a/plugins/inputs/upsd/sample.conf +++ b/plugins/inputs/upsd/sample.conf @@ -12,3 +12,16 @@ ## consistently as floats to avoid database conflicts where some numbers are ## parsed as integers and others as floats. # force_float = false + + ## Collect additional fields if they are available for the UPS + ## The fields need to be specified as NUT variable names, see + ## https://networkupstools.org/docs/developer-guide.chunked/apas02.html + ## Wildcards are accepted. + # additional_fields = [] + + ## Dump information for debugging + ## Allows to print the raw variables (and corresponding types) as received + ## from the NUT server ONCE for each UPS. The output is only available when + ## running Telegraf in debug-mode. + ## Please attach this information when reporting issues! + # dump_raw_variables = false diff --git a/plugins/inputs/upsd/testcases/CyberPowerSystems_CP900EPFCLCD/expected.out b/plugins/inputs/upsd/testcases/CyberPowerSystems_CP900EPFCLCD/expected.out new file mode 100644 index 000000000..ad0463470 --- /dev/null +++ b/plugins/inputs/upsd/testcases/CyberPowerSystems_CP900EPFCLCD/expected.out @@ -0,0 +1 @@ +upsd,model=CP900EPFCLCD,serial=0,status_OL=true,ups_name=fake battery_charge_percent=100i,battery_mfr_date="CPS",battery_runtime_low=300i,battery_voltage=24,firmware="",input_transfer_high=260i,input_transfer_low=170i,input_voltage=228,load_percent=13i,nominal_battery_voltage=24i,nominal_input_voltage=230i,nominal_power=540i,output_voltage=228,status_flags=8u,time_left_ns=4020000000000i,ups_delay_shutdown=20i,ups_delay_start=30i,ups_status="OL" diff --git a/plugins/inputs/upsd/testcases/CyberPowerSystems_CP900EPFCLCD/telegraf.conf b/plugins/inputs/upsd/testcases/CyberPowerSystems_CP900EPFCLCD/telegraf.conf new file mode 100644 index 000000000..62d77bea7 --- /dev/null +++ b/plugins/inputs/upsd/testcases/CyberPowerSystems_CP900EPFCLCD/telegraf.conf @@ -0,0 +1 @@ +[[inputs.upsd]] diff --git a/plugins/inputs/upsd/testcases/CyberPowerSystems_CP900EPFCLCD/types.dev b/plugins/inputs/upsd/testcases/CyberPowerSystems_CP900EPFCLCD/types.dev new file mode 100644 index 000000000..2f47bac6d --- /dev/null +++ b/plugins/inputs/upsd/testcases/CyberPowerSystems_CP900EPFCLCD/types.dev @@ -0,0 +1,49 @@ +battery.charge: NUMBER +battery.charge.low: STRING +battery.charge.warning: NUMBER +battery.mfr.date: NUMBER +battery.runtime: NUMBER +battery.runtime.low: STRING +battery.type: NUMBER +battery.voltage: NUMBER +battery.voltage.nominal: NUMBER +device.mfr: NUMBER +device.model: NUMBER +device.serial: NUMBER +device.type: NUMBER +driver.debug: NUMBER +driver.flag.allow_killpower: NUMBER +driver.name: NUMBER +driver.parameter.pollfreq: NUMBER +driver.parameter.pollinterval: NUMBER +driver.parameter.port: NUMBER +driver.parameter.product: NUMBER +driver.parameter.productid: NUMBER +driver.parameter.serial: NUMBER +driver.parameter.synchronous: NUMBER +driver.parameter.vendor: NUMBER +driver.parameter.vendorid: NUMBER +driver.state: NUMBER +driver.version: +driver.version.data: NUMBER +driver.version.internal: NUMBER +driver.version.usb: NUMBER +input.transfer.high: STRING +input.transfer.low: STRING +input.voltage: NUMBER +input.voltage.nominal: NUMBER +output.voltage: NUMBER +ups.beeper.status: NUMBER +ups.delay.shutdown: STRING +ups.delay.start: STRING +ups.load: NUMBER +ups.mfr: NUMBER +ups.model: NUMBER +ups.productid: NUMBER +ups.realpower.nominal: NUMBER +ups.serial: NUMBER +ups.status: NUMBER +ups.test.result: NUMBER +ups.timer.shutdown: NUMBER +ups.timer.start: NUMBER +ups.vendorid: NUMBER diff --git a/plugins/inputs/upsd/testcases/CyberPowerSystems_CP900EPFCLCD/variables.dev b/plugins/inputs/upsd/testcases/CyberPowerSystems_CP900EPFCLCD/variables.dev new file mode 100644 index 000000000..1ee4e943f --- /dev/null +++ b/plugins/inputs/upsd/testcases/CyberPowerSystems_CP900EPFCLCD/variables.dev @@ -0,0 +1,49 @@ +battery.charge: 100 +battery.charge.low: 10 +battery.charge.warning: 20 +battery.mfr.date: CPS +battery.runtime: 4020 +battery.runtime.low: 300 +battery.type: PbAcid +battery.voltage: 24.0 +battery.voltage.nominal: 24 +device.mfr: CPS +device.model: CP900EPFCLCD +device.serial: 000000000000 +device.type: ups +driver.debug: 0 +driver.flag.allow_killpower: 0 +driver.name: usbhid-ups +driver.parameter.pollfreq: 30 +driver.parameter.pollinterval: 2 +driver.parameter.port: auto +driver.parameter.product: CP900EPFCLCD +driver.parameter.productid: 0501 +driver.parameter.serial: 000000000000 +driver.parameter.synchronous: auto +driver.parameter.vendor: CPS +driver.parameter.vendorid: 0764 +driver.state: quiet +driver.version: 2.8.1 +driver.version.data: CyberPower HID 0.8 +driver.version.internal: 0.52 +driver.version.usb: libusb-1.0.26 (API: 0x1000109) +input.transfer.high: 260 +input.transfer.low: 170 +input.voltage: 228.0 +input.voltage.nominal: 230 +output.voltage: 228.0 +ups.beeper.status: enabled +ups.delay.shutdown: 20 +ups.delay.start: 30 +ups.load: 13 +ups.mfr: CPS +ups.model: CP900EPFCLCD +ups.productid: 0501 +ups.realpower.nominal: 540 +ups.serial: 000000000000 +ups.status: OL +ups.test.result: No test initiated +ups.timer.shutdown: -60 +ups.timer.start: -60 +ups.vendorid: 0764 diff --git a/plugins/inputs/upsd/testcases/CyberPowerSystems_CP900EPFCLCD_additional/expected.out b/plugins/inputs/upsd/testcases/CyberPowerSystems_CP900EPFCLCD_additional/expected.out new file mode 100644 index 000000000..8abd1a670 --- /dev/null +++ b/plugins/inputs/upsd/testcases/CyberPowerSystems_CP900EPFCLCD_additional/expected.out @@ -0,0 +1 @@ +upsd,model=CP900EPFCLCD,serial=0,status_OL=true,ups_name=fake battery_charge_low=10i,battery_charge_percent=100i,battery_charge_warning=20i,battery_mfr_date="CPS",battery_runtime_low=300i,battery_type="PbAcid",battery_voltage=24,firmware="",input_transfer_high=260i,input_transfer_low=170i,input_voltage=228,load_percent=13i,nominal_battery_voltage=24i,nominal_input_voltage=230i,nominal_power=540i,output_voltage=228,status_flags=8u,time_left_ns=4020000000000i,ups_delay_shutdown=20i,ups_delay_start=30i,ups_status="OL" 1704213086754003102 diff --git a/plugins/inputs/upsd/testcases/CyberPowerSystems_CP900EPFCLCD_additional/telegraf.conf b/plugins/inputs/upsd/testcases/CyberPowerSystems_CP900EPFCLCD_additional/telegraf.conf new file mode 100644 index 000000000..73c758d44 --- /dev/null +++ b/plugins/inputs/upsd/testcases/CyberPowerSystems_CP900EPFCLCD_additional/telegraf.conf @@ -0,0 +1,2 @@ +[[inputs.upsd]] + additional_fields = ["battery.*"] \ No newline at end of file diff --git a/plugins/inputs/upsd/testcases/CyberPowerSystems_CP900EPFCLCD_additional/types.dev b/plugins/inputs/upsd/testcases/CyberPowerSystems_CP900EPFCLCD_additional/types.dev new file mode 100644 index 000000000..2f47bac6d --- /dev/null +++ b/plugins/inputs/upsd/testcases/CyberPowerSystems_CP900EPFCLCD_additional/types.dev @@ -0,0 +1,49 @@ +battery.charge: NUMBER +battery.charge.low: STRING +battery.charge.warning: NUMBER +battery.mfr.date: NUMBER +battery.runtime: NUMBER +battery.runtime.low: STRING +battery.type: NUMBER +battery.voltage: NUMBER +battery.voltage.nominal: NUMBER +device.mfr: NUMBER +device.model: NUMBER +device.serial: NUMBER +device.type: NUMBER +driver.debug: NUMBER +driver.flag.allow_killpower: NUMBER +driver.name: NUMBER +driver.parameter.pollfreq: NUMBER +driver.parameter.pollinterval: NUMBER +driver.parameter.port: NUMBER +driver.parameter.product: NUMBER +driver.parameter.productid: NUMBER +driver.parameter.serial: NUMBER +driver.parameter.synchronous: NUMBER +driver.parameter.vendor: NUMBER +driver.parameter.vendorid: NUMBER +driver.state: NUMBER +driver.version: +driver.version.data: NUMBER +driver.version.internal: NUMBER +driver.version.usb: NUMBER +input.transfer.high: STRING +input.transfer.low: STRING +input.voltage: NUMBER +input.voltage.nominal: NUMBER +output.voltage: NUMBER +ups.beeper.status: NUMBER +ups.delay.shutdown: STRING +ups.delay.start: STRING +ups.load: NUMBER +ups.mfr: NUMBER +ups.model: NUMBER +ups.productid: NUMBER +ups.realpower.nominal: NUMBER +ups.serial: NUMBER +ups.status: NUMBER +ups.test.result: NUMBER +ups.timer.shutdown: NUMBER +ups.timer.start: NUMBER +ups.vendorid: NUMBER diff --git a/plugins/inputs/upsd/testcases/CyberPowerSystems_CP900EPFCLCD_additional/variables.dev b/plugins/inputs/upsd/testcases/CyberPowerSystems_CP900EPFCLCD_additional/variables.dev new file mode 100644 index 000000000..1ee4e943f --- /dev/null +++ b/plugins/inputs/upsd/testcases/CyberPowerSystems_CP900EPFCLCD_additional/variables.dev @@ -0,0 +1,49 @@ +battery.charge: 100 +battery.charge.low: 10 +battery.charge.warning: 20 +battery.mfr.date: CPS +battery.runtime: 4020 +battery.runtime.low: 300 +battery.type: PbAcid +battery.voltage: 24.0 +battery.voltage.nominal: 24 +device.mfr: CPS +device.model: CP900EPFCLCD +device.serial: 000000000000 +device.type: ups +driver.debug: 0 +driver.flag.allow_killpower: 0 +driver.name: usbhid-ups +driver.parameter.pollfreq: 30 +driver.parameter.pollinterval: 2 +driver.parameter.port: auto +driver.parameter.product: CP900EPFCLCD +driver.parameter.productid: 0501 +driver.parameter.serial: 000000000000 +driver.parameter.synchronous: auto +driver.parameter.vendor: CPS +driver.parameter.vendorid: 0764 +driver.state: quiet +driver.version: 2.8.1 +driver.version.data: CyberPower HID 0.8 +driver.version.internal: 0.52 +driver.version.usb: libusb-1.0.26 (API: 0x1000109) +input.transfer.high: 260 +input.transfer.low: 170 +input.voltage: 228.0 +input.voltage.nominal: 230 +output.voltage: 228.0 +ups.beeper.status: enabled +ups.delay.shutdown: 20 +ups.delay.start: 30 +ups.load: 13 +ups.mfr: CPS +ups.model: CP900EPFCLCD +ups.productid: 0501 +ups.realpower.nominal: 540 +ups.serial: 000000000000 +ups.status: OL +ups.test.result: No test initiated +ups.timer.shutdown: -60 +ups.timer.start: -60 +ups.vendorid: 0764 diff --git a/plugins/inputs/upsd/testcases/CyberPowerSystems_CP900EPFCLCD_full/expected.out b/plugins/inputs/upsd/testcases/CyberPowerSystems_CP900EPFCLCD_full/expected.out new file mode 100644 index 000000000..9f0763f28 --- /dev/null +++ b/plugins/inputs/upsd/testcases/CyberPowerSystems_CP900EPFCLCD_full/expected.out @@ -0,0 +1 @@ +upsd,model=CP900EPFCLCD,serial=0,status_OL=true,ups_name=fake battery_charge_low=10i,battery_charge_percent=100i,battery_charge_warning=20i,battery_mfr_date="CPS",battery_runtime_low=300i,battery_type="PbAcid",battery_voltage=24,device_mfr="CPS",device_type="ups",driver_debug=0i,driver_flag_allow_killpower=0i,driver_name="usbhid-ups",driver_parameter_pollfreq=30i,driver_parameter_pollinterval=2i,driver_parameter_port="auto",driver_parameter_product="CP900EPFCLCD",driver_parameter_productid=501i,driver_parameter_serial=0i,driver_parameter_synchronous="auto",driver_parameter_vendor="CPS",driver_parameter_vendorid=764i,driver_state="quiet",driver_version="2.8.1",driver_version_data="CyberPower HID 0.8",driver_version_internal=0.52,driver_version_usb="libusb-1.0.26 (API: 0x1000109)",firmware="",input_transfer_high=260i,input_transfer_low=170i,input_voltage=228,load_percent=13i,nominal_battery_voltage=24i,nominal_input_voltage=230i,nominal_power=540i,output_voltage=228,status_flags=8u,time_left_ns=4020000000000i,ups_beeper_status=true,ups_delay_shutdown=20i,ups_delay_start=30i,ups_mfr="CPS",ups_model="CP900EPFCLCD",ups_productid=501i,ups_serial=0i,ups_status="OL",ups_test_result="No test initiated",ups_timer_shutdown=-60i,ups_timer_start=-60i,ups_vendorid=764i diff --git a/plugins/inputs/upsd/testcases/CyberPowerSystems_CP900EPFCLCD_full/telegraf.conf b/plugins/inputs/upsd/testcases/CyberPowerSystems_CP900EPFCLCD_full/telegraf.conf new file mode 100644 index 000000000..5fed247df --- /dev/null +++ b/plugins/inputs/upsd/testcases/CyberPowerSystems_CP900EPFCLCD_full/telegraf.conf @@ -0,0 +1,2 @@ +[[inputs.upsd]] + additional_fields = ["*"] diff --git a/plugins/inputs/upsd/testcases/CyberPowerSystems_CP900EPFCLCD_full/types.dev b/plugins/inputs/upsd/testcases/CyberPowerSystems_CP900EPFCLCD_full/types.dev new file mode 100644 index 000000000..2f47bac6d --- /dev/null +++ b/plugins/inputs/upsd/testcases/CyberPowerSystems_CP900EPFCLCD_full/types.dev @@ -0,0 +1,49 @@ +battery.charge: NUMBER +battery.charge.low: STRING +battery.charge.warning: NUMBER +battery.mfr.date: NUMBER +battery.runtime: NUMBER +battery.runtime.low: STRING +battery.type: NUMBER +battery.voltage: NUMBER +battery.voltage.nominal: NUMBER +device.mfr: NUMBER +device.model: NUMBER +device.serial: NUMBER +device.type: NUMBER +driver.debug: NUMBER +driver.flag.allow_killpower: NUMBER +driver.name: NUMBER +driver.parameter.pollfreq: NUMBER +driver.parameter.pollinterval: NUMBER +driver.parameter.port: NUMBER +driver.parameter.product: NUMBER +driver.parameter.productid: NUMBER +driver.parameter.serial: NUMBER +driver.parameter.synchronous: NUMBER +driver.parameter.vendor: NUMBER +driver.parameter.vendorid: NUMBER +driver.state: NUMBER +driver.version: +driver.version.data: NUMBER +driver.version.internal: NUMBER +driver.version.usb: NUMBER +input.transfer.high: STRING +input.transfer.low: STRING +input.voltage: NUMBER +input.voltage.nominal: NUMBER +output.voltage: NUMBER +ups.beeper.status: NUMBER +ups.delay.shutdown: STRING +ups.delay.start: STRING +ups.load: NUMBER +ups.mfr: NUMBER +ups.model: NUMBER +ups.productid: NUMBER +ups.realpower.nominal: NUMBER +ups.serial: NUMBER +ups.status: NUMBER +ups.test.result: NUMBER +ups.timer.shutdown: NUMBER +ups.timer.start: NUMBER +ups.vendorid: NUMBER diff --git a/plugins/inputs/upsd/testcases/CyberPowerSystems_CP900EPFCLCD_full/variables.dev b/plugins/inputs/upsd/testcases/CyberPowerSystems_CP900EPFCLCD_full/variables.dev new file mode 100644 index 000000000..1ee4e943f --- /dev/null +++ b/plugins/inputs/upsd/testcases/CyberPowerSystems_CP900EPFCLCD_full/variables.dev @@ -0,0 +1,49 @@ +battery.charge: 100 +battery.charge.low: 10 +battery.charge.warning: 20 +battery.mfr.date: CPS +battery.runtime: 4020 +battery.runtime.low: 300 +battery.type: PbAcid +battery.voltage: 24.0 +battery.voltage.nominal: 24 +device.mfr: CPS +device.model: CP900EPFCLCD +device.serial: 000000000000 +device.type: ups +driver.debug: 0 +driver.flag.allow_killpower: 0 +driver.name: usbhid-ups +driver.parameter.pollfreq: 30 +driver.parameter.pollinterval: 2 +driver.parameter.port: auto +driver.parameter.product: CP900EPFCLCD +driver.parameter.productid: 0501 +driver.parameter.serial: 000000000000 +driver.parameter.synchronous: auto +driver.parameter.vendor: CPS +driver.parameter.vendorid: 0764 +driver.state: quiet +driver.version: 2.8.1 +driver.version.data: CyberPower HID 0.8 +driver.version.internal: 0.52 +driver.version.usb: libusb-1.0.26 (API: 0x1000109) +input.transfer.high: 260 +input.transfer.low: 170 +input.voltage: 228.0 +input.voltage.nominal: 230 +output.voltage: 228.0 +ups.beeper.status: enabled +ups.delay.shutdown: 20 +ups.delay.start: 30 +ups.load: 13 +ups.mfr: CPS +ups.model: CP900EPFCLCD +ups.productid: 0501 +ups.realpower.nominal: 540 +ups.serial: 000000000000 +ups.status: OL +ups.test.result: No test initiated +ups.timer.shutdown: -60 +ups.timer.start: -60 +ups.vendorid: 0764 diff --git a/plugins/inputs/upsd/testcases/fake/expected.out b/plugins/inputs/upsd/testcases/fake/expected.out new file mode 100644 index 000000000..170a63d96 --- /dev/null +++ b/plugins/inputs/upsd/testcases/fake/expected.out @@ -0,0 +1 @@ +upsd,model=Model\ 12345,serial=ABC123,status_OL=true,ups_name=fake battery_charge_percent=100,battery_mfr_date="2016-07-26",battery_voltage=13.4,firmware="CUSTOM_FIRMWARE",input_voltage=242,load_percent=23,nominal_battery_voltage=24,nominal_input_voltage=230,nominal_power=700i,output_voltage=230,real_power=41,status_flags=8u,time_left_ns=600000000000i,ups_status="OL" diff --git a/plugins/inputs/upsd/testcases/fake/telegraf.conf b/plugins/inputs/upsd/testcases/fake/telegraf.conf new file mode 100644 index 000000000..62d77bea7 --- /dev/null +++ b/plugins/inputs/upsd/testcases/fake/telegraf.conf @@ -0,0 +1 @@ +[[inputs.upsd]] diff --git a/plugins/inputs/upsd/testcases/fake/types.dev b/plugins/inputs/upsd/testcases/fake/types.dev new file mode 100644 index 000000000..613685813 --- /dev/null +++ b/plugins/inputs/upsd/testcases/fake/types.dev @@ -0,0 +1,15 @@ +device.serial: STRING:64 +device.model: STRING:64 +input.voltage: NUMBER +ups.load: NUMBER +battery.charge: NUMBER +battery.runtime: NUMBER +output.voltage: NUMBER +battery.voltage: NUMBER +input.voltage.nominal: NUMBER +battery.voltage.nominal: NUMBER +ups.realpower: NUMBER +ups.realpower.nominal: NUMBER +ups.firmware: STRING:64 +battery.mfr.date: STRING:64 +ups.status: STRING:64 \ No newline at end of file diff --git a/plugins/inputs/upsd/testcases/fake/variables.dev b/plugins/inputs/upsd/testcases/fake/variables.dev new file mode 100644 index 000000000..424d96705 --- /dev/null +++ b/plugins/inputs/upsd/testcases/fake/variables.dev @@ -0,0 +1,15 @@ +device.serial: ABC123 +device.model: Model 12345 +input.voltage: 242.0 +ups.load: 23.0 +battery.charge: 100.0 +battery.runtime: 600.00 +output.voltage: 230.0 +battery.voltage: 13.4 +input.voltage.nominal: 230.0 +battery.voltage.nominal: 24.0 +ups.realpower: 41.0 +ups.realpower.nominal: 700 +ups.firmware: CUSTOM_FIRMWARE +battery.mfr.date: 2016-07-26 +ups.status: OL \ No newline at end of file diff --git a/plugins/inputs/upsd/testcases/fake_force_float/expected.out b/plugins/inputs/upsd/testcases/fake_force_float/expected.out new file mode 100644 index 000000000..514ea76ed --- /dev/null +++ b/plugins/inputs/upsd/testcases/fake_force_float/expected.out @@ -0,0 +1 @@ +upsd,model=Model\ 12345,serial=ABC123,status_OL=true,ups_name=fake battery_charge_percent=100,battery_mfr_date="2016-07-26",battery_voltage=13.4,firmware="CUSTOM_FIRMWARE",input_voltage=242,load_percent=23,nominal_battery_voltage=24,nominal_input_voltage=230,nominal_power=700,output_voltage=230,real_power=41,status_flags=8u,time_left_ns=600000000000i,ups_status="OL" diff --git a/plugins/inputs/upsd/testcases/fake_force_float/telegraf.conf b/plugins/inputs/upsd/testcases/fake_force_float/telegraf.conf new file mode 100644 index 000000000..1148e65ed --- /dev/null +++ b/plugins/inputs/upsd/testcases/fake_force_float/telegraf.conf @@ -0,0 +1,2 @@ +[[inputs.upsd]] + force_float = true \ No newline at end of file diff --git a/plugins/inputs/upsd/testcases/fake_force_float/types.dev b/plugins/inputs/upsd/testcases/fake_force_float/types.dev new file mode 100644 index 000000000..613685813 --- /dev/null +++ b/plugins/inputs/upsd/testcases/fake_force_float/types.dev @@ -0,0 +1,15 @@ +device.serial: STRING:64 +device.model: STRING:64 +input.voltage: NUMBER +ups.load: NUMBER +battery.charge: NUMBER +battery.runtime: NUMBER +output.voltage: NUMBER +battery.voltage: NUMBER +input.voltage.nominal: NUMBER +battery.voltage.nominal: NUMBER +ups.realpower: NUMBER +ups.realpower.nominal: NUMBER +ups.firmware: STRING:64 +battery.mfr.date: STRING:64 +ups.status: STRING:64 \ No newline at end of file diff --git a/plugins/inputs/upsd/testcases/fake_force_float/variables.dev b/plugins/inputs/upsd/testcases/fake_force_float/variables.dev new file mode 100644 index 000000000..424d96705 --- /dev/null +++ b/plugins/inputs/upsd/testcases/fake_force_float/variables.dev @@ -0,0 +1,15 @@ +device.serial: ABC123 +device.model: Model 12345 +input.voltage: 242.0 +ups.load: 23.0 +battery.charge: 100.0 +battery.runtime: 600.00 +output.voltage: 230.0 +battery.voltage: 13.4 +input.voltage.nominal: 230.0 +battery.voltage.nominal: 24.0 +ups.realpower: 41.0 +ups.realpower.nominal: 700 +ups.firmware: CUSTOM_FIRMWARE +battery.mfr.date: 2016-07-26 +ups.status: OL \ No newline at end of file diff --git a/plugins/inputs/upsd/testcases/fake_force_float_with_string/expected.out b/plugins/inputs/upsd/testcases/fake_force_float_with_string/expected.out new file mode 100644 index 000000000..d4fac18cb --- /dev/null +++ b/plugins/inputs/upsd/testcases/fake_force_float_with_string/expected.out @@ -0,0 +1 @@ +upsd,model=Model\ 12345,serial=ABC123,status_OL=true,ups_name=fake battery_charge_percent=100,battery_mfr_date="2016-07-26",battery_voltage=13.4,firmware="CUSTOM_FIRMWARE",input_voltage=242,load_percent=23,nominal_battery_voltage=24,nominal_input_voltage=230,nominal_power=700,output_voltage=230,real_power=41,status_flags=8u,time_left_ns=600000000000i,ups_status="OL",device_location="Upper floor" diff --git a/plugins/inputs/upsd/testcases/fake_force_float_with_string/telegraf.conf b/plugins/inputs/upsd/testcases/fake_force_float_with_string/telegraf.conf new file mode 100644 index 000000000..78401a28a --- /dev/null +++ b/plugins/inputs/upsd/testcases/fake_force_float_with_string/telegraf.conf @@ -0,0 +1,3 @@ +[[inputs.upsd]] + force_float = true + additional_fields = ["*"] diff --git a/plugins/inputs/upsd/testcases/fake_force_float_with_string/types.dev b/plugins/inputs/upsd/testcases/fake_force_float_with_string/types.dev new file mode 100644 index 000000000..70335ca3a --- /dev/null +++ b/plugins/inputs/upsd/testcases/fake_force_float_with_string/types.dev @@ -0,0 +1,16 @@ +device.serial: STRING:64 +device.model: STRING:64 +device.location: STRING:64 +input.voltage: NUMBER +ups.load: NUMBER +battery.charge: NUMBER +battery.runtime: NUMBER +output.voltage: NUMBER +battery.voltage: NUMBER +input.voltage.nominal: NUMBER +battery.voltage.nominal: NUMBER +ups.realpower: NUMBER +ups.realpower.nominal: NUMBER +ups.firmware: STRING:64 +battery.mfr.date: STRING:64 +ups.status: STRING:64 \ No newline at end of file diff --git a/plugins/inputs/upsd/testcases/fake_force_float_with_string/variables.dev b/plugins/inputs/upsd/testcases/fake_force_float_with_string/variables.dev new file mode 100644 index 000000000..1e97e73f2 --- /dev/null +++ b/plugins/inputs/upsd/testcases/fake_force_float_with_string/variables.dev @@ -0,0 +1,16 @@ +device.serial: ABC123 +device.model: Model 12345 +device.location: Upper floor +input.voltage: 242.0 +ups.load: 23.0 +battery.charge: 100.0 +battery.runtime: 600.00 +output.voltage: 230.0 +battery.voltage: 13.4 +input.voltage.nominal: 230.0 +battery.voltage.nominal: 24.0 +ups.realpower: 41.0 +ups.realpower.nominal: 700 +ups.firmware: CUSTOM_FIRMWARE +battery.mfr.date: 2016-07-26 +ups.status: OL \ No newline at end of file diff --git a/plugins/inputs/upsd/upsd.go b/plugins/inputs/upsd/upsd.go index 1f467c0f2..4f1796f73 100644 --- a/plugins/inputs/upsd/upsd.go +++ b/plugins/inputs/upsd/upsd.go @@ -9,6 +9,7 @@ import ( nut "github.com/robbiet480/go.nut" "github.com/influxdata/telegraf" + "github.com/influxdata/telegraf/filter" "github.com/influxdata/telegraf/internal" "github.com/influxdata/telegraf/internal/choice" "github.com/influxdata/telegraf/plugins/inputs" @@ -17,37 +18,99 @@ import ( //go:embed sample.conf var sampleConfig string -//See: https://networkupstools.org/docs/developer-guide.chunked/index.html - +// see: https://networkupstools.org/docs/developer-guide.chunked/index.html const defaultAddress = "127.0.0.1" const defaultPort = 3493 -type Upsd struct { - Server string `toml:"server"` - Port int `toml:"port"` - Username string `toml:"username"` - Password string `toml:"password"` - ForceFloat bool `toml:"force_float"` +// Define the set of variables _always_ included in a metric +var mandatoryVariableSet = map[string]bool{ + "battery.date": true, + "battery.mfr.date": true, + "battery.runtime": true, + "device.model": true, + "device.serial": true, + "ups.firmware": true, + "ups.status": true, +} - Log telegraf.Logger `toml:"-"` +// Define the default field set to add if existing +var defaultFieldSet = map[string]string{ + "battery.charge": "battery_charge_percent", + "battery.runtime.low": "battery_runtime_low", + "battery.voltage": "battery_voltage", + "input.frequency": "input_frequency", + "input.transfer.high": "input_transfer_high", + "input.transfer.low": "input_transfer_low", + "input.voltage": "input_voltage", + "ups.temperature": "internal_temp", + "ups.load": "load_percent", + "battery.voltage.nominal": "nominal_battery_voltage", + "input.voltage.nominal": "nominal_input_voltage", + "ups.realpower.nominal": "nominal_power", + "output.voltage": "output_voltage", + "ups.realpower": "real_power", + "ups.delay.shutdown": "ups_delay_shutdown", + "ups.delay.start": "ups_delay_start", +} + +type Upsd struct { + Server string `toml:"server"` + Port int `toml:"port"` + Username string `toml:"username"` + Password string `toml:"password"` + ForceFloat bool `toml:"force_float"` + Additional []string `toml:"additional_fields"` + DumpRaw bool `toml:"dump_raw_variables"` + Log telegraf.Logger `toml:"-"` + + filter filter.Filter + dumped map[string]bool } func (*Upsd) SampleConfig() string { return sampleConfig } +func (u *Upsd) Init() error { + // Compile the additional fields filter + f, err := filter.Compile(u.Additional) + if err != nil { + return fmt.Errorf("compiling additional_fields filter failed: %w", err) + } + u.filter = f + + u.dumped = make(map[string]bool) + + return nil +} + func (u *Upsd) Gather(acc telegraf.Accumulator) error { upsList, err := u.fetchVariables(u.Server, u.Port) if err != nil { return err } + if u.DumpRaw { + for name, variables := range upsList { + // Only dump the information once per UPS + if u.dumped[name] { + continue + } + values := make([]string, 0, len(variables)) + types := make([]string, 0, len(variables)) + for _, v := range variables { + values = append(values, fmt.Sprintf("%s: %v", v.Name, v.Value)) + types = append(types, fmt.Sprintf("%s: %v", v.Name, v.OriginalType)) + } + u.Log.Debugf("Variables dump for UPS %q:\n%s\n-----\n%s", name, strings.Join(values, "\n"), strings.Join(types, "\n")) + } + } for name, variables := range upsList { u.gatherUps(acc, name, variables) } return nil } -func (u *Upsd) gatherUps(acc telegraf.Accumulator, name string, variables []nut.Variable) { +func (u *Upsd) gatherUps(acc telegraf.Accumulator, upsname string, variables []nut.Variable) { metrics := make(map[string]interface{}) for _, variable := range variables { name := variable.Name @@ -57,7 +120,7 @@ func (u *Upsd) gatherUps(acc telegraf.Accumulator, name string, variables []nut. tags := map[string]string{ "serial": fmt.Sprintf("%v", metrics["device.serial"]), - "ups_name": name, + "ups_name": upsname, //"variables": variables.Status not sure if it's a good idea to provide this "model": fmt.Sprintf("%v", metrics["device.model"]), } @@ -75,54 +138,18 @@ func (u *Upsd) gatherUps(acc telegraf.Accumulator, name string, variables []nut. u.Log.Warnf("Converting 'battery.runtime' to 'time_left_ns' failed: %v", err) } + // Add the mandatory information fields := map[string]interface{}{ "battery_date": metrics["battery.date"], "battery_mfr_date": metrics["battery.mfr.date"], "status_flags": status, "ups_status": metrics["ups.status"], - //Compatibility with apcupsd metrics format + // for compatibility with apcupsd metrics format "time_left_ns": timeLeftNS, } - floatValues := map[string]string{ - "battery_charge_percent": "battery.charge", - "battery_runtime_low": "battery.runtime.low", - "battery_voltage": "battery.voltage", - "input_frequency": "input.frequency", - "input_transfer_high": "input.transfer.high", - "input_transfer_low": "input.transfer.low", - "input_voltage": "input.voltage", - "internal_temp": "ups.temperature", - "load_percent": "ups.load", - "nominal_battery_voltage": "battery.voltage.nominal", - "nominal_input_voltage": "input.voltage.nominal", - "nominal_power": "ups.realpower.nominal", - "output_voltage": "output.voltage", - "real_power": "ups.realpower", - "ups_delay_shutdown": "ups.delay.shutdown", - "ups_delay_start": "ups.delay.start", - } - - for key, rawValue := range floatValues { - if metrics[rawValue] == nil { - continue - } - - if !u.ForceFloat { - fields[key] = metrics[rawValue] - continue - } - - // Force expected float values to actually being float (e.g. if delivered as int) - float, err := internal.ToFloat64(metrics[rawValue]) - if err != nil { - acc.AddError(fmt.Errorf("converting %s=%v failed: %w", rawValue, metrics[rawValue], err)) - continue - } - fields[key] = float - } - + // Define the set of mandatory string fields val, err := internal.ToString(metrics["ups.firmware"]) if err != nil { acc.AddError(fmt.Errorf("converting ups.firmware=%q failed: %w", metrics["ups.firmware"], err)) @@ -130,6 +157,36 @@ func (u *Upsd) gatherUps(acc telegraf.Accumulator, name string, variables []nut. fields["firmware"] = val } + // Try to gather all default fields and optional field + for varname, v := range metrics { + // Skip all empty fields and all fields contained in the mandatory set + // of fields added above. + if v == nil || mandatoryVariableSet[varname] { + continue + } + + // Use the name of the default field-set if present and otherwise check + // the additional field-set. If none of them contains the variable, we + // skip over it + var key string + if k, found := defaultFieldSet[varname]; found { + key = k + } else if u.filter != nil && u.filter.Match(varname) { + key = strings.ReplaceAll(varname, ".", "_") + } else { + continue + } + + // Force expected float values to actually being float (e.g. if delivered as int) + if u.ForceFloat { + float, err := internal.ToFloat64(v) + if err == nil { + v = float + } + } + fields[key] = v + } + acc.AddFields("upsd", fields, tags) } diff --git a/plugins/inputs/upsd/upsd_test.go b/plugins/inputs/upsd/upsd_test.go index 532c26af9..e4f741236 100644 --- a/plugins/inputs/upsd/upsd_test.go +++ b/plugins/inputs/upsd/upsd_test.go @@ -1,159 +1,192 @@ package upsd import ( + "bufio" + "bytes" "context" + "fmt" "net" + "os" + "path/filepath" + "strings" "testing" "time" "github.com/stretchr/testify/require" + "github.com/influxdata/telegraf" + "github.com/influxdata/telegraf/config" + "github.com/influxdata/telegraf/plugins/inputs" + "github.com/influxdata/telegraf/plugins/parsers/influx" "github.com/influxdata/telegraf/testutil" ) -func TestUpsdGather(t *testing.T) { - nut := &Upsd{} +func TestBadServer(t *testing.T) { + // Create and start a server without interactions + server := &mockServer{} + ctx, cancel := context.WithCancel(context.Background()) + addr, err := server.listen(ctx) + require.NoError(t, err) + defer cancel() - var ( - tests = []struct { - name string - forceFloat bool - err bool - tags map[string]string - fields map[string]interface{} - out func() []interaction - }{ - { - name: "test listening server with output", - forceFloat: false, - err: false, - tags: map[string]string{ - "serial": "ABC123", - "ups_name": "fake", - "model": "Model 12345", - "status_OL": "true", - }, - fields: map[string]interface{}{ - "battery_charge_percent": float64(100), - "battery_date": nil, - "battery_mfr_date": "2016-07-26", - "battery_voltage": float64(13.4), - "firmware": "CUSTOM_FIRMWARE", - "input_voltage": float64(242), - "load_percent": float64(23), - "nominal_battery_voltage": float64(24), - "nominal_input_voltage": float64(230), - "nominal_power": int64(700), - "output_voltage": float64(230), - "real_power": float64(41), - "status_flags": uint64(8), - "time_left_ns": int64(600000000000), - "ups_status": "OL", - }, - out: genOutput, - }, - { - name: "test listening server with output & force floats", - forceFloat: true, - err: false, - tags: map[string]string{ - "serial": "ABC123", - "ups_name": "fake", - "model": "Model 12345", - "status_OL": "true", - }, - fields: map[string]interface{}{ - "battery_charge_percent": float64(100), - "battery_date": nil, - "battery_mfr_date": "2016-07-26", - "battery_voltage": float64(13.4), - "firmware": "CUSTOM_FIRMWARE", - "input_voltage": float64(242), - "load_percent": float64(23), - "nominal_battery_voltage": float64(24), - "nominal_input_voltage": float64(230), - "nominal_power": int64(700), - "output_voltage": float64(230), - "real_power": float64(41), - "status_flags": uint64(8), - "time_left_ns": int64(600000000000), - "ups_status": "OL", - }, - out: genOutput, - }, + // Setup the plugin + plugin := &Upsd{ + Server: addr.IP.String(), + Port: addr.Port, + } + require.NoError(t, plugin.Init()) + + // Do the query + var acc testutil.Accumulator + require.Error(t, plugin.Gather(&acc)) +} + +func TestCases(t *testing.T) { + // Get all directories in testdata + folders, err := os.ReadDir("testcases") + require.NoError(t, err) + + // Register the plugin + inputs.Add("upsd", func() telegraf.Input { + return &Upsd{} + }) + + for _, f := range folders { + // Only handle folders + if !f.IsDir() { + continue } + testcasePath := filepath.Join("testcases", f.Name()) + configFilename := filepath.Join(testcasePath, "telegraf.conf") + expectedFilename := filepath.Join(testcasePath, "expected.out") - acc testutil.Accumulator - ) + t.Run(f.Name(), func(t *testing.T) { + // Prepare the influx parser for expectations + parser := &influx.Parser{} + require.NoError(t, parser.Init()) - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - ctx, cancel := context.WithCancel(context.Background()) + // Read the expected output if any + var expected []telegraf.Metric + if _, err := os.Stat(expectedFilename); err == nil { + var err error + expected, err = testutil.ParseMetricsFromFile(expectedFilename, parser) + require.NoError(t, err) + } - lAddr, err := listen(ctx, t, tt.out()) + // Setup a server from the input data + server, err := setupServer(testcasePath) require.NoError(t, err) - nut.Server = (lAddr.(*net.TCPAddr)).IP.String() - nut.Port = (lAddr.(*net.TCPAddr)).Port - nut.ForceFloat = tt.forceFloat + // Start the server + ctx, cancel := context.WithCancel(context.Background()) + addr, err := server.listen(ctx) + require.NoError(t, err) + defer cancel() - err = nut.Gather(&acc) - if tt.err { - require.Error(t, err) - } else { - require.NoError(t, err) - acc.AssertContainsFields(t, "upsd", tt.fields) - acc.AssertContainsTaggedFields(t, "upsd", tt.fields, tt.tags) - } - cancel() + // Configure the plugin + cfg := config.NewConfig() + require.NoError(t, cfg.LoadConfig(configFilename)) + require.Len(t, cfg.Inputs, 1) + plugin := cfg.Inputs[0].Input.(*Upsd) + plugin.Server = addr.IP.String() + plugin.Port = addr.Port + require.NoError(t, plugin.Init()) + + var acc testutil.Accumulator + require.NoError(t, plugin.Gather(&acc)) + + // Check the metric nevertheless as we might get some metrics despite errors. + actual := acc.GetTelegrafMetrics() + testutil.RequireMetricsEqual(t, expected, actual, testutil.IgnoreTime()) + acc.Lock() + defer acc.Unlock() + require.Empty(t, acc.Errors) }) } } -func TestUpsdGatherFail(t *testing.T) { - nut := &Upsd{} +type interaction struct { + Expected string + Response string +} - var ( - tests = []struct { - name string - err bool - tags map[string]string - fields map[string]interface{} - out func() []interaction - }{ - { - name: "test with bad output", - err: true, - out: genBadOutput, - }, - } +type variable struct { + Name string + Value string +} - acc testutil.Accumulator - ) +type mockServer struct { + protocol []interaction +} - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - ctx, cancel := context.WithCancel(context.Background()) - - lAddr, err := listen(ctx, t, tt.out()) - require.NoError(t, err) - - nut.Server = (lAddr.(*net.TCPAddr)).IP.String() - nut.Port = (lAddr.(*net.TCPAddr)).Port - - err = nut.Gather(&acc) - if tt.err { - require.Error(t, err) - } else { - require.NoError(t, err) - acc.AssertContainsTaggedFields(t, "upsd", tt.fields, tt.tags) - } - cancel() - }) +func (s *mockServer) init() { + s.protocol = []interaction{ + { + Expected: "VER\n", + Response: "1\n", + }, + { + Expected: "NETVER\n", + Response: "1\n", + }, + { + Expected: "LIST UPS\n", + Response: "BEGIN LIST UPS\nUPS fake \"fake UPS\"\nEND LIST UPS\n", + }, + { + Expected: "LIST CLIENT fake\n", + Response: "BEGIN LIST CLIENT fake\nCLIENT fake 127.0.0.1\nEND LIST CLIENT fake\n", + }, + { + Expected: "LIST CMD fake\n", + Response: "BEGIN LIST CMD fake\nEND LIST CMD fake\n", + }, + { + Expected: "GET UPSDESC fake\n", + Response: "UPSDESC fake \"stub-ups-description\"\n", + }, + { + Expected: "GET NUMLOGINS fake\n", + Response: "NUMLOGINS fake 1\n", + }, } } -func listen(ctx context.Context, t *testing.T, out []interaction) (net.Addr, error) { +func (s *mockServer) addVariables(variables []variable, types map[string]string) error { + // Add a VAR entries for the variables + values := make([]string, 0, len(variables)) + for _, v := range variables { + values = append(values, fmt.Sprintf("VAR fake %s %q", v.Name, v.Value)) + } + + s.protocol = append(s.protocol, interaction{ + Expected: "LIST VAR fake\n", + Response: "BEGIN LIST VAR fake\n" + strings.Join(values, "\n") + "\nEND LIST VAR fake\n", + }) + + // Add a description and type interaction for the variable + for _, v := range variables { + variableType, found := types[v.Name] + if !found { + return fmt.Errorf("type for variable %q not found", v.Name) + } + + s.protocol = append(s.protocol, + interaction{ + Expected: "GET DESC fake " + v.Name + "\n", + Response: "DESC fake" + v.Name + " \"No description here\"\n", + }, + interaction{ + Expected: "GET TYPE fake " + v.Name + "\n", + Response: "TYPE fake " + v.Name + " " + variableType + "\n", + }, + ) + } + + return nil +} + +func (s *mockServer) listen(ctx context.Context) (*net.TCPAddr, error) { lc := net.ListenConfig{} ln, err := lc.Listen(ctx, "tcp4", "127.0.0.1:0") if err != nil { @@ -170,131 +203,90 @@ func listen(ctx context.Context, t *testing.T, out []interaction) (net.Addr, err return } defer conn.Close() - require.NoError(t, conn.SetReadDeadline(time.Now().Add(time.Minute))) + _ = conn.SetReadDeadline(time.Now().Add(time.Minute)) in := make([]byte, 128) - for _, interaction := range out { + for _, interaction := range s.protocol { n, err := conn.Read(in) - require.NoError(t, err, "failed to read from connection") + if err != nil { + fmt.Printf("Failed to read from connection: %v\n", err) + return + } - expectedBytes := []byte(interaction.Expected) - want, got := expectedBytes, in[:n] - require.Equal(t, want, got) + request := in[:n] + if !bytes.Equal([]byte(interaction.Expected), request) { + fmt.Printf("Unexpected request %q, expected %q\n", string(request), interaction.Expected) + return + } - _, err = conn.Write([]byte(interaction.Response)) - require.NoError(t, err, "failed to respond to LIST UPS") + if _, err := conn.Write([]byte(interaction.Response)); err != nil { + fmt.Printf("Cannot write answer for request %q: %v\n", string(request), err) + return + } } // Append EOF to end of output bytes - _, err = conn.Write([]byte{0, 0}) - require.NoError(t, err, "failed to write EOF") + if _, err := conn.Write([]byte{0, 0}); err != nil { + fmt.Printf("Cannot write EOF: %v\n", err) + return + } }() } }() - return ln.Addr(), nil + return ln.Addr().(*net.TCPAddr), nil } -type interaction struct { - Expected string - Response string -} +func setupServer(path string) (*mockServer, error) { + // Read the variables + varbuf, err := os.ReadFile(filepath.Join(path, "variables.dev")) + if err != nil { + return nil, fmt.Errorf("reading variables failed: %w", err) + } -func genOutput() []interaction { - m := make([]interaction, 0) - m = append(m, - interaction{ - Expected: "VER\n", - Response: "1\n", - }, - interaction{ - Expected: "NETVER\n", - Response: "1\n", - }, - interaction{ - Expected: "LIST UPS\n", - Response: `BEGIN LIST UPS -UPS fake "fakescription" -END LIST UPS -`, - }, - interaction{ - Expected: "LIST CLIENT fake\n", - Response: `BEGIN LIST CLIENT fake -CLIENT fake 192.168.1.1 -END LIST CLIENT fake -`, - }, - interaction{ - Expected: "LIST CMD fake\n", - Response: `BEGIN LIST CMD fake -END LIST CMD fake -`, - }, - interaction{ - Expected: "GET UPSDESC fake\n", - Response: "UPSDESC fake \"stub-ups-description\"\n", - }, - interaction{ - Expected: "GET NUMLOGINS fake\n", - Response: "NUMLOGINS fake 1\n", - }, - interaction{ - Expected: "LIST VAR fake\n", - Response: `BEGIN LIST VAR fake -VAR fake device.serial "ABC123" -VAR fake device.model "Model 12345" -VAR fake input.voltage "242.0" -VAR fake ups.load "23.0" -VAR fake battery.charge "100.0" -VAR fake battery.runtime "600.00" -VAR fake output.voltage "230.0" -VAR fake battery.voltage "13.4" -VAR fake input.voltage.nominal "230.0" -VAR fake battery.voltage.nominal "24.0" -VAR fake ups.realpower "41.0" -VAR fake ups.realpower.nominal "700" -VAR fake ups.firmware "CUSTOM_FIRMWARE" -VAR fake battery.mfr.date "2016-07-26" -VAR fake ups.status "OL" -END LIST VAR fake -`, - }, - ) - m = appendVariable(m, "device.serial", "STRING:64") - m = appendVariable(m, "device.model", "STRING:64") - m = appendVariable(m, "input.voltage", "NUMBER") - m = appendVariable(m, "ups.load", "NUMBER") - m = appendVariable(m, "battery.charge", "NUMBER") - m = appendVariable(m, "battery.runtime", "NUMBER") - m = appendVariable(m, "output.voltage", "NUMBER") - m = appendVariable(m, "battery.voltage", "NUMBER") - m = appendVariable(m, "input.voltage.nominal", "NUMBER") - m = appendVariable(m, "battery.voltage.nominal", "NUMBER") - m = appendVariable(m, "ups.realpower", "NUMBER") - m = appendVariable(m, "ups.realpower.nominal", "NUMBER") - m = appendVariable(m, "ups.firmware", "STRING:64") - m = appendVariable(m, "battery.mfr.date", "STRING:64") - m = appendVariable(m, "ups.status", "STRING:64") + // Parse the information into variable names and values (upsc format) + variables := make([]variable, 0) + scanner := bufio.NewScanner(bytes.NewBuffer(varbuf)) + for scanner.Scan() { + line := scanner.Text() + parts := strings.SplitN(line, ":", 2) + if len(parts) != 2 { + return nil, fmt.Errorf("cannot parse line %s", line) + } + name := strings.TrimSpace(parts[0]) + value := strings.TrimSpace(parts[1]) + variables = append(variables, variable{name, value}) + } + if err := scanner.Err(); err != nil { + return nil, fmt.Errorf("processing variables failed: %w", err) + } - return m -} + // Read the variable-type mapping + typebuf, err := os.ReadFile(filepath.Join(path, "types.dev")) + if err != nil { + return nil, fmt.Errorf("reading variables failed: %w", err) + } -func appendVariable(m []interaction, name string, typ string) []interaction { - m = append(m, - interaction{ - Expected: "GET DESC fake " + name + "\n", - Response: "DESC fake" + name + " \"No description here\"\n", - }, - interaction{ - Expected: "GET TYPE fake " + name + "\n", - Response: "TYPE fake " + name + " " + typ + "\n", - }, - ) - return m -} + // Parse the information into variable names and values (upsc format) + types := make(map[string]string, 0) + scanner = bufio.NewScanner(bytes.NewBuffer(typebuf)) + for scanner.Scan() { + line := scanner.Text() + parts := strings.SplitN(line, ":", 2) + if len(parts) != 2 { + return nil, fmt.Errorf("cannot parse line %s", line) + } + name := strings.TrimSpace(parts[0]) + vartype := strings.TrimSpace(parts[1]) + types[name] = vartype + } + if err := scanner.Err(); err != nil { + return nil, fmt.Errorf("processing variables failed: %w", err) + } -func genBadOutput() []interaction { - m := make([]interaction, 0) - return m + // Setup the server and add the device information + server := &mockServer{} + server.init() + err = server.addVariables(variables, types) + return server, err }