From 9cbd67af2bd24fa412bd0a9d3d242bdb6c3b5504 Mon Sep 17 00:00:00 2001 From: Jason Ertel Date: Mon, 17 May 2021 18:25:28 -0400 Subject: [PATCH 1/4] Refactor grid interface to support some or all nodes not having metrics enabled --- html/index.html | 15 +- html/js/i18n.js | 1 + html/js/routes/grid.js | 21 +++ html/js/routes/grid.test.js | 26 +++ model/node.go | 12 +- model/node_test.go | 191 ++++++++++----------- server/modules/influxdb/influxdbmetrics.go | 6 +- 7 files changed, 164 insertions(+), 108 deletions(-) diff --git a/html/index.html b/html/index.html index aba0bfb82..dfc7e3144 100644 --- a/html/index.html +++ b/html/index.html @@ -1055,7 +1055,7 @@

{{ i18n.viewJob }}

-

{{ i18n.gridEps }} {{ gridEps | formatCount }}

+

{{ i18n.gridEps }} {{ gridEps | formatCount }}

@@ -1088,7 +1088,10 @@

{{ i18n.gridEps }} {{ gridEps | formatCount }}

- {{ props.item.productionEps | formatCount }} + + {{ props.item.productionEps | formatCount }} + {{ i18n.na }} + {{ props.item.updateTime | formatTimestamp }} {{ props.item.epochTime | formatTimestamp }} {{ props.item.uptimeSeconds | formatDuration }} @@ -1112,15 +1115,15 @@

{{ i18n.gridEps }} {{ gridEps | formatCount }}

{{ i18n.dateOnline }} {{ item.onlineTime | formatDateTime }} -
+
{{ i18n.epsProduction }} {{ item.productionEps | formatCount }}
-
+
{{ i18n.epsConsumption }} {{ item.consumptionEps | formatCount }}
-
+
{{ i18n.nodeStatusProcess }} {{ $root.localizeMessage(item.processStatus) }}
@@ -1128,7 +1131,7 @@

{{ i18n.gridEps }} {{ gridEps | formatCount }}

{{ i18n.nodeStatusConnection }} {{ $root.localizeMessage(item.connectionStatus) }}
-
+
{{ i18n.nodeStatusRaid }} {{ $root.localizeMessage(item.raidStatus) }}
diff --git a/html/js/i18n.js b/html/js/i18n.js index 6c732f94d..af90b2df0 100644 --- a/html/js/i18n.js +++ b/html/js/i18n.js @@ -193,6 +193,7 @@ const i18n = { months: 'months', mruQuery: 'Recently Used', mruQueryHelp: 'This query is a user-defined query and is only available on this browser.', + na: 'N/A', noData: 'No information is currently available.', nodeExpandHelp: 'Show node details', nodeExpand: 'Expand', diff --git a/html/js/routes/grid.js b/html/js/routes/grid.js index 88e7fc413..13410c9e7 100644 --- a/html/js/routes/grid.js +++ b/html/js/routes/grid.js @@ -38,6 +38,7 @@ routes.push({ path: '/grid', name: 'grid', component: { itemsPerPage: 10, footerProps: { 'items-per-page-options': [10,25,50,100,250,1000] }, gridEps: 0, + metricsEnabled: false, }}, created() { Vue.filter('colorNodeStatus', this.colorNodeStatus); @@ -70,6 +71,7 @@ routes.push({ path: '/grid', name: 'grid', component: { this.nodes.forEach(function(node) { route.updateNode(node); }); + this.updateMetricsEnabled(); this.loadLocalSettings(); } catch (error) { this.$root.showError(error); @@ -78,6 +80,21 @@ routes.push({ path: '/grid', name: 'grid', component: { this.$root.subscribe("node", this.updateNode); this.$root.subscribe("status", this.updateStatus); }, + updateMetricsEnabled() { + this.metricsEnabled = !this.nodes.every(function(node) { return !node.metricsEnabled; }); + + const route = this; + const epsColumn = this.headers.find(function(item) { + return item.text == route.i18n.eps + }); + if (epsColumn) { + if (!this.metricsEnabled) { + epsColumn.align = ' d-none'; + } else { + epsColumn.align = ''; + } + } + }, expand(item) { if (this.isExpanded(item)) { this.expanded = []; @@ -101,6 +118,10 @@ routes.push({ path: '/grid', name: 'grid', component: { } }, updateNode(node) { + this.updateNodeDetails(node); + this.updateMetricsEnabled() + }, + updateNodeDetails(node) { var found = false; for (var i = 0; i < this.nodes.length; i++) { if (this.nodes[i].id == node.id) { diff --git a/html/js/routes/grid.test.js b/html/js/routes/grid.test.js index a022147be..3f05a7aca 100644 --- a/html/js/routes/grid.test.js +++ b/html/js/routes/grid.test.js @@ -19,3 +19,29 @@ test('updateStatus', () => { comp.updateStatus(status); expect(comp.gridEps).toBe(12); }); + +test('updateMetricsEnabled', () => { + testUpdateMetricsEnabled(true, false, true); + testUpdateMetricsEnabled(false, false, false); + testUpdateMetricsEnabled(true, true, true); +}); + +function testUpdateMetricsEnabled(node1MetricsEnabled, node2MetricsEnabled, expectedMetricsEnabled) { + const node1 = { metricsEnabled: node1MetricsEnabled }; + const node2 = { metricsEnabled: node2MetricsEnabled }; + comp.nodes = [node1, node2]; + + comp.updateMetricsEnabled(); + + expect(comp.metricsEnabled).toBe(expectedMetricsEnabled); + + const epsColumn = comp.headers.find(function(item) { + return item.text == comp.i18n.eps; + }); + + if (!expectedMetricsEnabled) { + expect(epsColumn.align).toBe(' d-none'); + } else { + expect(epsColumn.align).toBe(''); + } +} diff --git a/model/node.go b/model/node.go index c3b1215ed..8ca3cf0cb 100644 --- a/model/node.go +++ b/model/node.go @@ -38,6 +38,7 @@ type Node struct { ProductionEps int `json:"productionEps"` ConsumptionEps int `json:"consumptionEps"` FailedEvents int `json:"failedEvents"` + MetricsEnabled bool `json:"metricsEnabled"` } func NewNode(id string) *Node { @@ -79,19 +80,22 @@ func (node *Node) updateStatusComponent(currentState string, newState string) st return currentState } -func (node *Node) UpdateOverallStatus() bool { +func (node *Node) UpdateOverallStatus(enhancedStatusEnabled bool) bool { newStatus := NodeStatusUnknown newStatus = node.updateStatusComponent(newStatus, node.ConnectionStatus) - newStatus = node.updateStatusComponent(newStatus, node.RaidStatus) - newStatus = node.updateStatusComponent(newStatus, node.ProcessStatus) + if enhancedStatusEnabled { + newStatus = node.updateStatusComponent(newStatus, node.RaidStatus) + newStatus = node.updateStatusComponent(newStatus, node.ProcessStatus) + } // Special case: If either process or connection status is unknown then show node in error state. - if (node.Role != "so-import" && node.ProcessStatus == NodeStatusUnknown) || + if (enhancedStatusEnabled && node.ProcessStatus == NodeStatusUnknown) || node.ConnectionStatus == NodeStatusUnknown { newStatus = NodeStatusFault } oldStatus := node.Status node.Status = newStatus + node.MetricsEnabled = enhancedStatusEnabled return oldStatus != node.Status } diff --git a/model/node_test.go b/model/node_test.go index 5fcd2bc6b..5feb450d4 100644 --- a/model/node_test.go +++ b/model/node_test.go @@ -43,19 +43,18 @@ func TestSetModel(tester *testing.T) { } func testStatus(tester *testing.T, - role string, + enhancedStatusEnabled bool, nodeStatus string, connectionStatus string, raidStatus string, processStatus string, expectedStatus string) { node := NewNode("") - node.Role = role node.Status = nodeStatus node.ConnectionStatus = connectionStatus node.RaidStatus = raidStatus node.ProcessStatus = processStatus - result := node.UpdateOverallStatus() + result := node.UpdateOverallStatus(enhancedStatusEnabled) shouldChange := nodeStatus != expectedStatus if result != shouldChange { tester.Errorf("Unexpected node status change") @@ -67,115 +66,115 @@ func testStatus(tester *testing.T, func TestUpdateNodeStatusAllUnknown(tester *testing.T) { // If all component statuses are unknown then the node's overall status is fault, regardless of current status. - testStatus(tester, "so-standalone", NodeStatusUnknown, NodeStatusUnknown, NodeStatusUnknown, NodeStatusUnknown, NodeStatusFault) - testStatus(tester, "so-standalone", NodeStatusOk, NodeStatusUnknown, NodeStatusUnknown, NodeStatusUnknown, NodeStatusFault) - testStatus(tester, "so-standalone", NodeStatusFault, NodeStatusUnknown, NodeStatusUnknown, NodeStatusUnknown, NodeStatusFault) + testStatus(tester, true, NodeStatusUnknown, NodeStatusUnknown, NodeStatusUnknown, NodeStatusUnknown, NodeStatusFault) + testStatus(tester, true, NodeStatusOk, NodeStatusUnknown, NodeStatusUnknown, NodeStatusUnknown, NodeStatusFault) + testStatus(tester, true, NodeStatusFault, NodeStatusUnknown, NodeStatusUnknown, NodeStatusUnknown, NodeStatusFault) } func TestUpdateNodeStatusOneNotUnknown(tester *testing.T) { // If only one status is not unknown then must be in fault state. - testStatus(tester, "so-standalone", NodeStatusUnknown, NodeStatusOk, NodeStatusUnknown, NodeStatusUnknown, NodeStatusFault) - testStatus(tester, "so-standalone", NodeStatusUnknown, NodeStatusFault, NodeStatusUnknown, NodeStatusUnknown, NodeStatusFault) - testStatus(tester, "so-standalone", NodeStatusUnknown, NodeStatusUnknown, NodeStatusOk, NodeStatusUnknown, NodeStatusFault) - testStatus(tester, "so-standalone", NodeStatusUnknown, NodeStatusUnknown, NodeStatusFault, NodeStatusUnknown, NodeStatusFault) - testStatus(tester, "so-standalone", NodeStatusUnknown, NodeStatusUnknown, NodeStatusUnknown, NodeStatusOk, NodeStatusFault) - testStatus(tester, "so-standalone", NodeStatusUnknown, NodeStatusUnknown, NodeStatusUnknown, NodeStatusFault, NodeStatusFault) - - testStatus(tester, "so-standalone", NodeStatusOk, NodeStatusOk, NodeStatusUnknown, NodeStatusUnknown, NodeStatusFault) - testStatus(tester, "so-standalone", NodeStatusOk, NodeStatusFault, NodeStatusUnknown, NodeStatusUnknown, NodeStatusFault) - testStatus(tester, "so-standalone", NodeStatusOk, NodeStatusUnknown, NodeStatusOk, NodeStatusUnknown, NodeStatusFault) - testStatus(tester, "so-standalone", NodeStatusOk, NodeStatusUnknown, NodeStatusFault, NodeStatusUnknown, NodeStatusFault) - testStatus(tester, "so-standalone", NodeStatusOk, NodeStatusUnknown, NodeStatusUnknown, NodeStatusOk, NodeStatusFault) - testStatus(tester, "so-standalone", NodeStatusOk, NodeStatusUnknown, NodeStatusUnknown, NodeStatusFault, NodeStatusFault) - - testStatus(tester, "so-standalone", NodeStatusFault, NodeStatusOk, NodeStatusUnknown, NodeStatusUnknown, NodeStatusFault) - testStatus(tester, "so-standalone", NodeStatusFault, NodeStatusFault, NodeStatusUnknown, NodeStatusUnknown, NodeStatusFault) - testStatus(tester, "so-standalone", NodeStatusFault, NodeStatusUnknown, NodeStatusOk, NodeStatusUnknown, NodeStatusFault) - testStatus(tester, "so-standalone", NodeStatusFault, NodeStatusUnknown, NodeStatusFault, NodeStatusUnknown, NodeStatusFault) - testStatus(tester, "so-standalone", NodeStatusFault, NodeStatusUnknown, NodeStatusUnknown, NodeStatusOk, NodeStatusFault) - testStatus(tester, "so-standalone", NodeStatusFault, NodeStatusUnknown, NodeStatusUnknown, NodeStatusFault, NodeStatusFault) + testStatus(tester, true, NodeStatusUnknown, NodeStatusOk, NodeStatusUnknown, NodeStatusUnknown, NodeStatusFault) + testStatus(tester, true, NodeStatusUnknown, NodeStatusFault, NodeStatusUnknown, NodeStatusUnknown, NodeStatusFault) + testStatus(tester, true, NodeStatusUnknown, NodeStatusUnknown, NodeStatusOk, NodeStatusUnknown, NodeStatusFault) + testStatus(tester, true, NodeStatusUnknown, NodeStatusUnknown, NodeStatusFault, NodeStatusUnknown, NodeStatusFault) + testStatus(tester, true, NodeStatusUnknown, NodeStatusUnknown, NodeStatusUnknown, NodeStatusOk, NodeStatusFault) + testStatus(tester, true, NodeStatusUnknown, NodeStatusUnknown, NodeStatusUnknown, NodeStatusFault, NodeStatusFault) + + testStatus(tester, true, NodeStatusOk, NodeStatusOk, NodeStatusUnknown, NodeStatusUnknown, NodeStatusFault) + testStatus(tester, true, NodeStatusOk, NodeStatusFault, NodeStatusUnknown, NodeStatusUnknown, NodeStatusFault) + testStatus(tester, true, NodeStatusOk, NodeStatusUnknown, NodeStatusOk, NodeStatusUnknown, NodeStatusFault) + testStatus(tester, true, NodeStatusOk, NodeStatusUnknown, NodeStatusFault, NodeStatusUnknown, NodeStatusFault) + testStatus(tester, true, NodeStatusOk, NodeStatusUnknown, NodeStatusUnknown, NodeStatusOk, NodeStatusFault) + testStatus(tester, true, NodeStatusOk, NodeStatusUnknown, NodeStatusUnknown, NodeStatusFault, NodeStatusFault) + + testStatus(tester, true, NodeStatusFault, NodeStatusOk, NodeStatusUnknown, NodeStatusUnknown, NodeStatusFault) + testStatus(tester, true, NodeStatusFault, NodeStatusFault, NodeStatusUnknown, NodeStatusUnknown, NodeStatusFault) + testStatus(tester, true, NodeStatusFault, NodeStatusUnknown, NodeStatusOk, NodeStatusUnknown, NodeStatusFault) + testStatus(tester, true, NodeStatusFault, NodeStatusUnknown, NodeStatusFault, NodeStatusUnknown, NodeStatusFault) + testStatus(tester, true, NodeStatusFault, NodeStatusUnknown, NodeStatusUnknown, NodeStatusOk, NodeStatusFault) + testStatus(tester, true, NodeStatusFault, NodeStatusUnknown, NodeStatusUnknown, NodeStatusFault, NodeStatusFault) } func TestUpdateImportNodeStatusOneNotUnknown(tester *testing.T) { // If only one status is not unknown then must be in fault state. - testStatus(tester, "so-import", NodeStatusUnknown, NodeStatusOk, NodeStatusUnknown, NodeStatusUnknown, NodeStatusOk) - testStatus(tester, "so-import", NodeStatusUnknown, NodeStatusFault, NodeStatusUnknown, NodeStatusUnknown, NodeStatusFault) - testStatus(tester, "so-import", NodeStatusUnknown, NodeStatusUnknown, NodeStatusOk, NodeStatusUnknown, NodeStatusFault) - testStatus(tester, "so-import", NodeStatusUnknown, NodeStatusUnknown, NodeStatusFault, NodeStatusUnknown, NodeStatusFault) - testStatus(tester, "so-import", NodeStatusUnknown, NodeStatusUnknown, NodeStatusUnknown, NodeStatusOk, NodeStatusFault) - testStatus(tester, "so-import", NodeStatusUnknown, NodeStatusUnknown, NodeStatusUnknown, NodeStatusFault, NodeStatusFault) - - testStatus(tester, "so-import", NodeStatusOk, NodeStatusOk, NodeStatusUnknown, NodeStatusUnknown, NodeStatusOk) - testStatus(tester, "so-import", NodeStatusOk, NodeStatusFault, NodeStatusUnknown, NodeStatusUnknown, NodeStatusFault) - testStatus(tester, "so-import", NodeStatusOk, NodeStatusUnknown, NodeStatusOk, NodeStatusUnknown, NodeStatusFault) - testStatus(tester, "so-import", NodeStatusOk, NodeStatusUnknown, NodeStatusFault, NodeStatusUnknown, NodeStatusFault) - testStatus(tester, "so-import", NodeStatusOk, NodeStatusUnknown, NodeStatusUnknown, NodeStatusOk, NodeStatusFault) - testStatus(tester, "so-import", NodeStatusOk, NodeStatusUnknown, NodeStatusUnknown, NodeStatusFault, NodeStatusFault) - - testStatus(tester, "so-import", NodeStatusFault, NodeStatusOk, NodeStatusUnknown, NodeStatusUnknown, NodeStatusOk) - testStatus(tester, "so-import", NodeStatusFault, NodeStatusFault, NodeStatusUnknown, NodeStatusUnknown, NodeStatusFault) - testStatus(tester, "so-import", NodeStatusFault, NodeStatusUnknown, NodeStatusOk, NodeStatusUnknown, NodeStatusFault) - testStatus(tester, "so-import", NodeStatusFault, NodeStatusUnknown, NodeStatusFault, NodeStatusUnknown, NodeStatusFault) - testStatus(tester, "so-import", NodeStatusFault, NodeStatusUnknown, NodeStatusUnknown, NodeStatusOk, NodeStatusFault) - testStatus(tester, "so-import", NodeStatusFault, NodeStatusUnknown, NodeStatusUnknown, NodeStatusFault, NodeStatusFault) + testStatus(tester, false, NodeStatusUnknown, NodeStatusOk, NodeStatusUnknown, NodeStatusUnknown, NodeStatusOk) + testStatus(tester, false, NodeStatusUnknown, NodeStatusFault, NodeStatusUnknown, NodeStatusUnknown, NodeStatusFault) + testStatus(tester, false, NodeStatusUnknown, NodeStatusUnknown, NodeStatusOk, NodeStatusUnknown, NodeStatusFault) + testStatus(tester, false, NodeStatusUnknown, NodeStatusUnknown, NodeStatusFault, NodeStatusUnknown, NodeStatusFault) + testStatus(tester, false, NodeStatusUnknown, NodeStatusUnknown, NodeStatusUnknown, NodeStatusOk, NodeStatusFault) + testStatus(tester, false, NodeStatusUnknown, NodeStatusUnknown, NodeStatusUnknown, NodeStatusFault, NodeStatusFault) + + testStatus(tester, false, NodeStatusOk, NodeStatusOk, NodeStatusUnknown, NodeStatusUnknown, NodeStatusOk) + testStatus(tester, false, NodeStatusOk, NodeStatusFault, NodeStatusUnknown, NodeStatusUnknown, NodeStatusFault) + testStatus(tester, false, NodeStatusOk, NodeStatusUnknown, NodeStatusOk, NodeStatusUnknown, NodeStatusFault) + testStatus(tester, false, NodeStatusOk, NodeStatusUnknown, NodeStatusFault, NodeStatusUnknown, NodeStatusFault) + testStatus(tester, false, NodeStatusOk, NodeStatusUnknown, NodeStatusUnknown, NodeStatusOk, NodeStatusFault) + testStatus(tester, false, NodeStatusOk, NodeStatusUnknown, NodeStatusUnknown, NodeStatusFault, NodeStatusFault) + + testStatus(tester, false, NodeStatusFault, NodeStatusOk, NodeStatusUnknown, NodeStatusUnknown, NodeStatusOk) + testStatus(tester, false, NodeStatusFault, NodeStatusFault, NodeStatusUnknown, NodeStatusUnknown, NodeStatusFault) + testStatus(tester, false, NodeStatusFault, NodeStatusUnknown, NodeStatusOk, NodeStatusUnknown, NodeStatusFault) + testStatus(tester, false, NodeStatusFault, NodeStatusUnknown, NodeStatusFault, NodeStatusUnknown, NodeStatusFault) + testStatus(tester, false, NodeStatusFault, NodeStatusUnknown, NodeStatusUnknown, NodeStatusOk, NodeStatusFault) + testStatus(tester, false, NodeStatusFault, NodeStatusUnknown, NodeStatusUnknown, NodeStatusFault, NodeStatusFault) } func TestUpdateNodeStatusMultipleNotUnknownOkFirst(tester *testing.T) { // If an earlier component status is Ok then the subsequent status becomes the overall status, regardless of current status. - testStatus(tester, "so-standalone", NodeStatusUnknown, NodeStatusOk, NodeStatusOk, NodeStatusUnknown, NodeStatusFault) - testStatus(tester, "so-standalone", NodeStatusUnknown, NodeStatusOk, NodeStatusFault, NodeStatusUnknown, NodeStatusFault) - testStatus(tester, "so-standalone", NodeStatusUnknown, NodeStatusOk, NodeStatusOk, NodeStatusOk, NodeStatusOk) - testStatus(tester, "so-standalone", NodeStatusUnknown, NodeStatusOk, NodeStatusOk, NodeStatusFault, NodeStatusFault) - testStatus(tester, "so-standalone", NodeStatusUnknown, NodeStatusOk, NodeStatusUnknown, NodeStatusOk, NodeStatusOk) - testStatus(tester, "so-standalone", NodeStatusUnknown, NodeStatusOk, NodeStatusUnknown, NodeStatusFault, NodeStatusFault) - testStatus(tester, "so-standalone", NodeStatusUnknown, NodeStatusUnknown, NodeStatusOk, NodeStatusOk, NodeStatusFault) - testStatus(tester, "so-standalone", NodeStatusUnknown, NodeStatusUnknown, NodeStatusOk, NodeStatusFault, NodeStatusFault) - - testStatus(tester, "so-standalone", NodeStatusOk, NodeStatusOk, NodeStatusOk, NodeStatusUnknown, NodeStatusFault) - testStatus(tester, "so-standalone", NodeStatusOk, NodeStatusOk, NodeStatusFault, NodeStatusUnknown, NodeStatusFault) - testStatus(tester, "so-standalone", NodeStatusOk, NodeStatusOk, NodeStatusOk, NodeStatusOk, NodeStatusOk) - testStatus(tester, "so-standalone", NodeStatusOk, NodeStatusOk, NodeStatusOk, NodeStatusFault, NodeStatusFault) - testStatus(tester, "so-standalone", NodeStatusOk, NodeStatusOk, NodeStatusUnknown, NodeStatusOk, NodeStatusOk) - testStatus(tester, "so-standalone", NodeStatusOk, NodeStatusOk, NodeStatusUnknown, NodeStatusFault, NodeStatusFault) - testStatus(tester, "so-standalone", NodeStatusOk, NodeStatusUnknown, NodeStatusOk, NodeStatusOk, NodeStatusFault) - testStatus(tester, "so-standalone", NodeStatusOk, NodeStatusUnknown, NodeStatusOk, NodeStatusFault, NodeStatusFault) - - testStatus(tester, "so-standalone", NodeStatusFault, NodeStatusOk, NodeStatusOk, NodeStatusUnknown, NodeStatusFault) - testStatus(tester, "so-standalone", NodeStatusFault, NodeStatusOk, NodeStatusFault, NodeStatusUnknown, NodeStatusFault) - testStatus(tester, "so-standalone", NodeStatusFault, NodeStatusOk, NodeStatusOk, NodeStatusOk, NodeStatusOk) - testStatus(tester, "so-standalone", NodeStatusFault, NodeStatusOk, NodeStatusOk, NodeStatusFault, NodeStatusFault) - testStatus(tester, "so-standalone", NodeStatusFault, NodeStatusOk, NodeStatusUnknown, NodeStatusOk, NodeStatusOk) - testStatus(tester, "so-standalone", NodeStatusFault, NodeStatusOk, NodeStatusUnknown, NodeStatusFault, NodeStatusFault) - testStatus(tester, "so-standalone", NodeStatusFault, NodeStatusUnknown, NodeStatusOk, NodeStatusOk, NodeStatusFault) - testStatus(tester, "so-standalone", NodeStatusFault, NodeStatusUnknown, NodeStatusOk, NodeStatusFault, NodeStatusFault) + testStatus(tester, true, NodeStatusUnknown, NodeStatusOk, NodeStatusOk, NodeStatusUnknown, NodeStatusFault) + testStatus(tester, true, NodeStatusUnknown, NodeStatusOk, NodeStatusFault, NodeStatusUnknown, NodeStatusFault) + testStatus(tester, true, NodeStatusUnknown, NodeStatusOk, NodeStatusOk, NodeStatusOk, NodeStatusOk) + testStatus(tester, true, NodeStatusUnknown, NodeStatusOk, NodeStatusOk, NodeStatusFault, NodeStatusFault) + testStatus(tester, true, NodeStatusUnknown, NodeStatusOk, NodeStatusUnknown, NodeStatusOk, NodeStatusOk) + testStatus(tester, true, NodeStatusUnknown, NodeStatusOk, NodeStatusUnknown, NodeStatusFault, NodeStatusFault) + testStatus(tester, true, NodeStatusUnknown, NodeStatusUnknown, NodeStatusOk, NodeStatusOk, NodeStatusFault) + testStatus(tester, true, NodeStatusUnknown, NodeStatusUnknown, NodeStatusOk, NodeStatusFault, NodeStatusFault) + + testStatus(tester, true, NodeStatusOk, NodeStatusOk, NodeStatusOk, NodeStatusUnknown, NodeStatusFault) + testStatus(tester, true, NodeStatusOk, NodeStatusOk, NodeStatusFault, NodeStatusUnknown, NodeStatusFault) + testStatus(tester, true, NodeStatusOk, NodeStatusOk, NodeStatusOk, NodeStatusOk, NodeStatusOk) + testStatus(tester, true, NodeStatusOk, NodeStatusOk, NodeStatusOk, NodeStatusFault, NodeStatusFault) + testStatus(tester, true, NodeStatusOk, NodeStatusOk, NodeStatusUnknown, NodeStatusOk, NodeStatusOk) + testStatus(tester, true, NodeStatusOk, NodeStatusOk, NodeStatusUnknown, NodeStatusFault, NodeStatusFault) + testStatus(tester, true, NodeStatusOk, NodeStatusUnknown, NodeStatusOk, NodeStatusOk, NodeStatusFault) + testStatus(tester, true, NodeStatusOk, NodeStatusUnknown, NodeStatusOk, NodeStatusFault, NodeStatusFault) + + testStatus(tester, true, NodeStatusFault, NodeStatusOk, NodeStatusOk, NodeStatusUnknown, NodeStatusFault) + testStatus(tester, true, NodeStatusFault, NodeStatusOk, NodeStatusFault, NodeStatusUnknown, NodeStatusFault) + testStatus(tester, true, NodeStatusFault, NodeStatusOk, NodeStatusOk, NodeStatusOk, NodeStatusOk) + testStatus(tester, true, NodeStatusFault, NodeStatusOk, NodeStatusOk, NodeStatusFault, NodeStatusFault) + testStatus(tester, true, NodeStatusFault, NodeStatusOk, NodeStatusUnknown, NodeStatusOk, NodeStatusOk) + testStatus(tester, true, NodeStatusFault, NodeStatusOk, NodeStatusUnknown, NodeStatusFault, NodeStatusFault) + testStatus(tester, true, NodeStatusFault, NodeStatusUnknown, NodeStatusOk, NodeStatusOk, NodeStatusFault) + testStatus(tester, true, NodeStatusFault, NodeStatusUnknown, NodeStatusOk, NodeStatusFault, NodeStatusFault) } func TestUpdateNodeStatusMultipleNotUnknownFaultFirst(tester *testing.T) { // If an earlier component status is Fault then the subsequent status remains Fault, regardless of current status. - testStatus(tester, "so-standalone", NodeStatusUnknown, NodeStatusFault, NodeStatusOk, NodeStatusUnknown, NodeStatusFault) - testStatus(tester, "so-standalone", NodeStatusUnknown, NodeStatusFault, NodeStatusFault, NodeStatusUnknown, NodeStatusFault) - testStatus(tester, "so-standalone", NodeStatusUnknown, NodeStatusFault, NodeStatusOk, NodeStatusOk, NodeStatusFault) - testStatus(tester, "so-standalone", NodeStatusUnknown, NodeStatusFault, NodeStatusOk, NodeStatusFault, NodeStatusFault) - testStatus(tester, "so-standalone", NodeStatusUnknown, NodeStatusFault, NodeStatusUnknown, NodeStatusOk, NodeStatusFault) - testStatus(tester, "so-standalone", NodeStatusUnknown, NodeStatusFault, NodeStatusUnknown, NodeStatusFault, NodeStatusFault) - testStatus(tester, "so-standalone", NodeStatusUnknown, NodeStatusUnknown, NodeStatusFault, NodeStatusOk, NodeStatusFault) - testStatus(tester, "so-standalone", NodeStatusUnknown, NodeStatusUnknown, NodeStatusFault, NodeStatusFault, NodeStatusFault) + testStatus(tester, true, NodeStatusUnknown, NodeStatusFault, NodeStatusOk, NodeStatusUnknown, NodeStatusFault) + testStatus(tester, true, NodeStatusUnknown, NodeStatusFault, NodeStatusFault, NodeStatusUnknown, NodeStatusFault) + testStatus(tester, true, NodeStatusUnknown, NodeStatusFault, NodeStatusOk, NodeStatusOk, NodeStatusFault) + testStatus(tester, true, NodeStatusUnknown, NodeStatusFault, NodeStatusOk, NodeStatusFault, NodeStatusFault) + testStatus(tester, true, NodeStatusUnknown, NodeStatusFault, NodeStatusUnknown, NodeStatusOk, NodeStatusFault) + testStatus(tester, true, NodeStatusUnknown, NodeStatusFault, NodeStatusUnknown, NodeStatusFault, NodeStatusFault) + testStatus(tester, true, NodeStatusUnknown, NodeStatusUnknown, NodeStatusFault, NodeStatusOk, NodeStatusFault) + testStatus(tester, true, NodeStatusUnknown, NodeStatusUnknown, NodeStatusFault, NodeStatusFault, NodeStatusFault) - testStatus(tester, "so-standalone", NodeStatusOk, NodeStatusFault, NodeStatusOk, NodeStatusUnknown, NodeStatusFault) - testStatus(tester, "so-standalone", NodeStatusOk, NodeStatusFault, NodeStatusFault, NodeStatusUnknown, NodeStatusFault) - testStatus(tester, "so-standalone", NodeStatusOk, NodeStatusFault, NodeStatusOk, NodeStatusOk, NodeStatusFault) - testStatus(tester, "so-standalone", NodeStatusOk, NodeStatusFault, NodeStatusOk, NodeStatusFault, NodeStatusFault) - testStatus(tester, "so-standalone", NodeStatusOk, NodeStatusFault, NodeStatusUnknown, NodeStatusOk, NodeStatusFault) - testStatus(tester, "so-standalone", NodeStatusOk, NodeStatusFault, NodeStatusUnknown, NodeStatusFault, NodeStatusFault) - testStatus(tester, "so-standalone", NodeStatusOk, NodeStatusUnknown, NodeStatusFault, NodeStatusOk, NodeStatusFault) - testStatus(tester, "so-standalone", NodeStatusOk, NodeStatusUnknown, NodeStatusFault, NodeStatusFault, NodeStatusFault) + testStatus(tester, true, NodeStatusOk, NodeStatusFault, NodeStatusOk, NodeStatusUnknown, NodeStatusFault) + testStatus(tester, true, NodeStatusOk, NodeStatusFault, NodeStatusFault, NodeStatusUnknown, NodeStatusFault) + testStatus(tester, true, NodeStatusOk, NodeStatusFault, NodeStatusOk, NodeStatusOk, NodeStatusFault) + testStatus(tester, true, NodeStatusOk, NodeStatusFault, NodeStatusOk, NodeStatusFault, NodeStatusFault) + testStatus(tester, true, NodeStatusOk, NodeStatusFault, NodeStatusUnknown, NodeStatusOk, NodeStatusFault) + testStatus(tester, true, NodeStatusOk, NodeStatusFault, NodeStatusUnknown, NodeStatusFault, NodeStatusFault) + testStatus(tester, true, NodeStatusOk, NodeStatusUnknown, NodeStatusFault, NodeStatusOk, NodeStatusFault) + testStatus(tester, true, NodeStatusOk, NodeStatusUnknown, NodeStatusFault, NodeStatusFault, NodeStatusFault) - testStatus(tester, "so-standalone", NodeStatusFault, NodeStatusFault, NodeStatusOk, NodeStatusUnknown, NodeStatusFault) - testStatus(tester, "so-standalone", NodeStatusFault, NodeStatusFault, NodeStatusFault, NodeStatusUnknown, NodeStatusFault) - testStatus(tester, "so-standalone", NodeStatusFault, NodeStatusFault, NodeStatusOk, NodeStatusOk, NodeStatusFault) - testStatus(tester, "so-standalone", NodeStatusFault, NodeStatusFault, NodeStatusOk, NodeStatusFault, NodeStatusFault) - testStatus(tester, "so-standalone", NodeStatusFault, NodeStatusFault, NodeStatusUnknown, NodeStatusOk, NodeStatusFault) - testStatus(tester, "so-standalone", NodeStatusFault, NodeStatusFault, NodeStatusUnknown, NodeStatusFault, NodeStatusFault) - testStatus(tester, "so-standalone", NodeStatusFault, NodeStatusUnknown, NodeStatusFault, NodeStatusOk, NodeStatusFault) - testStatus(tester, "so-standalone", NodeStatusFault, NodeStatusUnknown, NodeStatusFault, NodeStatusFault, NodeStatusFault) + testStatus(tester, true, NodeStatusFault, NodeStatusFault, NodeStatusOk, NodeStatusUnknown, NodeStatusFault) + testStatus(tester, true, NodeStatusFault, NodeStatusFault, NodeStatusFault, NodeStatusUnknown, NodeStatusFault) + testStatus(tester, true, NodeStatusFault, NodeStatusFault, NodeStatusOk, NodeStatusOk, NodeStatusFault) + testStatus(tester, true, NodeStatusFault, NodeStatusFault, NodeStatusOk, NodeStatusFault, NodeStatusFault) + testStatus(tester, true, NodeStatusFault, NodeStatusFault, NodeStatusUnknown, NodeStatusOk, NodeStatusFault) + testStatus(tester, true, NodeStatusFault, NodeStatusFault, NodeStatusUnknown, NodeStatusFault, NodeStatusFault) + testStatus(tester, true, NodeStatusFault, NodeStatusUnknown, NodeStatusFault, NodeStatusOk, NodeStatusFault) + testStatus(tester, true, NodeStatusFault, NodeStatusUnknown, NodeStatusFault, NodeStatusFault, NodeStatusFault) } \ No newline at end of file diff --git a/server/modules/influxdb/influxdbmetrics.go b/server/modules/influxdb/influxdbmetrics.go index 4cd6b2ab4..389a64d1d 100644 --- a/server/modules/influxdb/influxdbmetrics.go +++ b/server/modules/influxdb/influxdbmetrics.go @@ -111,7 +111,7 @@ func (metrics *InfluxDBMetrics) fetchLatestValuesByHost(measurement string, fiel log.WithError(err).Error("Unable to determine latest value") } } else { - log.Info("Skipping InfluxDB fetch due to disconnected InfluxDB client") + log.Debug("Skipping InfluxDB fetch due to disconnected InfluxDB client") } return values } @@ -249,5 +249,7 @@ func (metrics *InfluxDBMetrics) UpdateNodeMetrics(node *model.Node) bool { node.ProductionEps = metrics.getProductionEps(node.Id) node.ConsumptionEps = metrics.getConsumptionEps(node.Id) node.FailedEvents = metrics.getFailedEvents(node.Id) - return node.UpdateOverallStatus() + + enhancedStatusEnabled := (metrics.client != nil) + return node.UpdateOverallStatus(enhancedStatusEnabled) } \ No newline at end of file From 7190e27399ab4aeb685a59fb6edb7e354be89f51 Mon Sep 17 00:00:00 2001 From: Jason Ertel Date: Tue, 18 May 2021 11:22:10 -0400 Subject: [PATCH 2/4] Force Job node IDs to lowercase to be consistent with sensoroni.json config files which now require lowercase node IDs --- agent/modules/importer/importer.go | 3 +- agent/modules/stenoquery/stenoquery.go | 3 +- model/job.go | 12 ++++++ model/job_test.go | 39 +++++++++++++++++++ server/modules/elastic/elasticeventstore.go | 2 +- .../filedatastore/filedatastoreimpl.go | 22 +++++------ .../filedatastore/filedatastoreimpl_test.go | 2 +- 7 files changed, 68 insertions(+), 15 deletions(-) diff --git a/agent/modules/importer/importer.go b/agent/modules/importer/importer.go index e489a0f88..6fd041372 100644 --- a/agent/modules/importer/importer.go +++ b/agent/modules/importer/importer.go @@ -18,6 +18,7 @@ import ( "os/exec" "time" "github.com/apex/log" + "github.com/kennygrant/sanitize" "github.com/security-onion-solutions/securityonion-soc/agent" "github.com/security-onion-solutions/securityonion-soc/model" "github.com/security-onion-solutions/securityonion-soc/module" @@ -117,7 +118,7 @@ func (importer *Importer) ProcessJob(job *model.Job, reader io.ReadCloser) (io.R } func (importer *Importer) CleanupJob(job *model.Job) { - pcapOutputFilepath := fmt.Sprintf("%s/%d.%s", importer.pcapOutputPath, job.Id, job.FileExtension) + pcapOutputFilepath := fmt.Sprintf("%s/%d.%s", importer.pcapOutputPath, job.Id, sanitize.Name(job.FileExtension)) os.Remove(pcapOutputFilepath) } diff --git a/agent/modules/stenoquery/stenoquery.go b/agent/modules/stenoquery/stenoquery.go index d1958bb72..caa24a10d 100644 --- a/agent/modules/stenoquery/stenoquery.go +++ b/agent/modules/stenoquery/stenoquery.go @@ -20,6 +20,7 @@ import ( "time" "github.com/apex/log" + "github.com/kennygrant/sanitize" "github.com/security-onion-solutions/securityonion-soc/agent" "github.com/security-onion-solutions/securityonion-soc/model" "github.com/security-onion-solutions/securityonion-soc/module" @@ -139,7 +140,7 @@ func (steno *StenoQuery) ProcessJob(job *model.Job, reader io.ReadCloser) (io.Re } func (steno *StenoQuery) CleanupJob(job *model.Job) { - pcapOutputFilepath := fmt.Sprintf("%s/%d.%s", steno.pcapOutputPath, job.Id, job.FileExtension) + pcapOutputFilepath := fmt.Sprintf("%s/%d.%s", steno.pcapOutputPath, job.Id, sanitize.Name(job.FileExtension)) os.Remove(pcapOutputFilepath) } diff --git a/model/job.go b/model/job.go index b1c5f34f5..312d1611c 100644 --- a/model/job.go +++ b/model/job.go @@ -11,6 +11,7 @@ package model import ( + "strings" "time" ) @@ -45,6 +46,17 @@ func NewJob() *Job { } } +func (job *Job) SetNodeId(nodeId string) { + job.NodeId = strings.ToLower(nodeId) +} + +func (job *Job) GetNodeId() string { + // Lower case on the Getter as well since the property could have been + // manipulated directly. Consider json.Unmarshall(). + job.NodeId = strings.ToLower(job.NodeId) + return job.NodeId +} + func (job *Job) Complete() { job.Status = JobStatusCompleted job.CompleteTime = time.Now() diff --git a/model/job_test.go b/model/job_test.go index 480210da9..6f645f818 100644 --- a/model/job_test.go +++ b/model/job_test.go @@ -41,3 +41,42 @@ func TestVerifyJob(tester *testing.T) { tester.Errorf("expected Status %d but got %d", JobStatusCompleted, job.Status) } } + +func TestSetNodeId(tester *testing.T) { + job := NewJob() + if job.NodeId != "" { + tester.Errorf("expected new jobs to have an empty node ID") + } + + job.NodeId = "test" + if job.NodeId != "test" { + tester.Errorf("expected unmodified Node ID but got %s", job.NodeId) + } + + job.SetNodeId("testing") + if job.NodeId != "testing" { + tester.Errorf("expected unmodified Node ID but got %s", job.NodeId) + } + if job.GetNodeId() != "testing" { + tester.Errorf("expected unmodified Node ID via getter but got %s", job.GetNodeId()) + } + + job.SetNodeId("TestingThis") + if job.NodeId != "testingthis" { + tester.Errorf("expected lowercased Node ID but got %s", job.NodeId) + } + if job.GetNodeId() != "testingthis" { + tester.Errorf("expected lowercased Node ID via getter but got %s", job.GetNodeId()) + } + + job.NodeId = "TestingThis2" + if job.NodeId != "TestingThis2" { + tester.Errorf("expected unmodified Node ID but got %s", job.NodeId) + } + if job.GetNodeId() != "testingthis2" { + tester.Errorf("expected lowercased Node ID via getter but got %s", job.GetNodeId()) + } + if job.NodeId != "testingthis2" { + tester.Errorf("expected lowercased Node ID after getter but got %s", job.NodeId) + } +} \ No newline at end of file diff --git a/server/modules/elastic/elasticeventstore.go b/server/modules/elastic/elasticeventstore.go index afa0d474a..f340453a1 100644 --- a/server/modules/elastic/elasticeventstore.go +++ b/server/modules/elastic/elasticeventstore.go @@ -632,7 +632,7 @@ func (store *ElasticEventstore) PopulateJobFromDocQuery(query string, job *model filter.BeginTime = timestamp.Add(time.Duration(-duration - int64(store.timeShiftMs)) * time.Millisecond) filter.EndTime = timestamp.Add(time.Duration(duration + int64(store.timeShiftMs)) * time.Millisecond) - job.NodeId = outputSensorId + job.SetNodeId(outputSensorId) job.Filter = filter return nil diff --git a/server/modules/filedatastore/filedatastoreimpl.go b/server/modules/filedatastore/filedatastoreimpl.go index 4e78eda77..33794da1c 100644 --- a/server/modules/filedatastore/filedatastoreimpl.go +++ b/server/modules/filedatastore/filedatastoreimpl.go @@ -129,7 +129,7 @@ func (datastore *FileDatastoreImpl) GetNextJob(nodeId string) *model.Job { defer datastore.lock.RUnlock() var nextJob *model.Job now := time.Now() - jobs := datastore.jobsByNodeId[nodeId] + jobs := datastore.jobsByNodeId[strings.ToLower(nodeId)] for _, job := range jobs { retryTime := job.FailTime.Add(time.Millisecond * time.Duration(datastore.retryFailureIntervalMs)) if job.Status != model.JobStatusCompleted && @@ -203,7 +203,7 @@ func (datastore *FileDatastoreImpl) DeleteJob(job *model.Job) error { if err == nil { job.Status = model.JobStatusDeleted filename := fmt.Sprintf("%d.json", job.Id) - folder := filepath.Join(datastore.jobDir, sanitize.Name(job.NodeId)) + folder := filepath.Join(datastore.jobDir, sanitize.Name(job.GetNodeId())) err = os.Remove(filepath.Join(folder, filename)) if err == nil { filename = fmt.Sprintf("%d.bin", job.Id) @@ -227,18 +227,18 @@ func (datastore *FileDatastoreImpl) deleteJob(job *model.Job) error { if existingJob == nil { err = errors.New("Job does not exist") } else { - jobs := datastore.jobsByNodeId[job.NodeId] + jobs := datastore.jobsByNodeId[job.GetNodeId()] newJobs := make([]*model.Job, 0) for _, currentJob := range jobs { if currentJob.Id != job.Id { newJobs = append(newJobs, currentJob) } } - datastore.jobsByNodeId[job.NodeId] = newJobs + datastore.jobsByNodeId[job.GetNodeId()] = newJobs delete(datastore.jobsById, job.Id) log.WithFields(log.Fields { "id": job.Id, - "node": job.NodeId, + "node": job.GetNodeId(), }).Debug("Deleted job from list") } return err @@ -250,16 +250,16 @@ func (datastore *FileDatastoreImpl) addJob(job *model.Job) error { if existingJob != nil { err = errors.New("Job already exists") } else { - jobs := datastore.jobsByNodeId[job.NodeId] + jobs := datastore.jobsByNodeId[job.GetNodeId()] if jobs == nil { jobs = make([]*model.Job, 0) } - datastore.jobsByNodeId[job.NodeId] = append(jobs, job) + datastore.jobsByNodeId[job.GetNodeId()] = append(jobs, job) datastore.jobsById[job.Id] = job datastore.incrementJobId(job.Id) log.WithFields(log.Fields { "id": job.Id, - "node": job.NodeId, + "node": job.GetNodeId(), }).Debug("Added job") } return err @@ -273,7 +273,7 @@ func (datastore *FileDatastoreImpl) incrementJobId(id int) { func (datastore *FileDatastoreImpl) saveJob(job *model.Job) error { filename := fmt.Sprintf("%d.json", job.Id) - folder := filepath.Join(datastore.jobDir, sanitize.Name(job.NodeId)) + folder := filepath.Join(datastore.jobDir, sanitize.Name(job.GetNodeId())) log.WithFields(log.Fields { "id": job.Id, "folder": folder, @@ -359,7 +359,7 @@ func (datastore *FileDatastoreImpl) GetPacketStream(jobId int, unwrap bool) (io. job := datastore.GetJob(jobId) if job != nil { if job.Status == model.JobStatusCompleted { - filename = fmt.Sprintf("nodeoni_%s_%d.%s", sanitize.Name(job.NodeId), job.Id, job.FileExtension); + filename = fmt.Sprintf("sensoroni_%s_%d.%s", sanitize.Name(job.GetNodeId()), job.Id, sanitize.Name(job.FileExtension)); file, err := os.Open(datastore.getModifiedStreamFilename(job, unwrap)) if err != nil { log.WithError(err).WithField("jobId", job.Id).Error("Failed to open packet stream") @@ -384,7 +384,7 @@ func (datastore *FileDatastoreImpl) GetPacketStream(jobId int, unwrap bool) (io. func (datastore *FileDatastoreImpl) getStreamFilename(job *model.Job) string { filename := fmt.Sprintf("%d.bin", job.Id) - folder := filepath.Join(datastore.jobDir, sanitize.Name(job.NodeId)) + folder := filepath.Join(datastore.jobDir, sanitize.Name(job.GetNodeId())) return filepath.Join(folder, filename) } diff --git a/server/modules/filedatastore/filedatastoreimpl_test.go b/server/modules/filedatastore/filedatastoreimpl_test.go index a119e1c5b..743e7156d 100644 --- a/server/modules/filedatastore/filedatastoreimpl_test.go +++ b/server/modules/filedatastore/filedatastoreimpl_test.go @@ -24,7 +24,7 @@ func TestFileDatastoreInit(tester *testing.T) { tester.Errorf("expected Init error") } - jobDir := "/tmp/nodeoni.jobs" + jobDir := "/tmp/sensoroni.jobs" cfg["jobDir"] = jobDir defer os.Remove(jobDir) os.Mkdir(jobDir, 0777) From 0a1de1843ba3717476daed45b35dff51d90ff24a Mon Sep 17 00:00:00 2001 From: Jason Ertel Date: Tue, 18 May 2021 15:47:49 -0400 Subject: [PATCH 3/4] Prevent panic when pcap binary file does not exist on host --- .../filedatastore/filedatastoreimpl.go | 24 ++++++++++--------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/server/modules/filedatastore/filedatastoreimpl.go b/server/modules/filedatastore/filedatastoreimpl.go index 33794da1c..f9a421193 100644 --- a/server/modules/filedatastore/filedatastoreimpl.go +++ b/server/modules/filedatastore/filedatastoreimpl.go @@ -360,19 +360,21 @@ func (datastore *FileDatastoreImpl) GetPacketStream(jobId int, unwrap bool) (io. if job != nil { if job.Status == model.JobStatusCompleted { filename = fmt.Sprintf("sensoroni_%s_%d.%s", sanitize.Name(job.GetNodeId()), job.Id, sanitize.Name(job.FileExtension)); - file, err := os.Open(datastore.getModifiedStreamFilename(job, unwrap)) + var file *os.File + file, err = os.Open(datastore.getModifiedStreamFilename(job, unwrap)) if err != nil { log.WithError(err).WithField("jobId", job.Id).Error("Failed to open packet stream") - } - reader = file - info, err := file.Stat() - length = info.Size() - log.WithFields(log.Fields { - "size": length, - "name": info.Name(), - }).Info("Streaming file") - if err != nil { - log.WithError(err).WithField("jobId", job.Id).Error("Failed to open file stats") + } else { + reader = file + info, err := file.Stat() + length = info.Size() + log.WithFields(log.Fields { + "size": length, + "name": info.Name(), + }).Info("Streaming file") + if err != nil { + log.WithError(err).WithField("jobId", job.Id).Error("Failed to open file stats") + } } } } else { From 682b783981eb3aad875f7688b0537905e6efdb09 Mon Sep 17 00:00:00 2001 From: Jason Ertel Date: Wed, 19 May 2021 13:31:20 -0400 Subject: [PATCH 4/4] Correct downloaded PCAP filename --- server/streamhandler.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/server/streamhandler.go b/server/streamhandler.go index 86fa7484d..52962741a 100644 --- a/server/streamhandler.go +++ b/server/streamhandler.go @@ -59,7 +59,10 @@ func (streamHandler *StreamHandler) get(writer http.ResponseWriter, request *htt if !safe { return http.StatusBadRequest, nil, errors.New("Invalid extension") } - filename = strings.TrimSuffix(filename, ".bin") + "." + extension + extension = "." + extension + if !strings.HasSuffix(filename, extension) { + filename = strings.TrimSuffix(filename, ".bin") + extension + } } if err == nil {