Просмотр исходного кода

use opencost json marshaler for allocation responses

Signed-off-by: saweber <saweber@gmail.com>
saweber 3 лет назад
Родитель
Сommit
0ddbbb068a
2 измененных файлов с 183 добавлено и 48 удалено
  1. 119 48
      pkg/kubecost/allocation_json.go
  2. 64 0
      pkg/kubecost/allocation_json_test.go

+ 119 - 48
pkg/kubecost/allocation_json.go

@@ -1,59 +1,130 @@
 package kubecost
 
 import (
-	"bytes"
-	"encoding/json"
+	"fmt"
+	"github.com/opencost/opencost/pkg/util/json"
+	"math"
 	"time"
 )
 
+// AllocationJSON  exists because there are expected JSON response fields
+// that are calculated values from methods on an annotation
+type AllocationJSON struct {
+	Name                       string                 `json:"name"`
+	Properties                 *AllocationProperties  `json:"properties"`
+	Window                     Window                 `json:"window"`
+	Start                      string                 `json:"start"`
+	End                        string                 `json:"end"`
+	Minutes                    *float64               `json:"minutes"`
+	CPUCores                   *float64               `json:"cpuCores"`
+	CPUCoreRequestAverage      *float64               `json:"cpuCoreRequestAverage"`
+	CPUCoreUsageAverage        *float64               `json:"cpuCoreUsageAverage"`
+	CPUCoreHours               *float64               `json:"cpuCoreHours"`
+	CPUCost                    *float64               `json:"cpuCost"`
+	CPUCostAdjustment          *float64               `json:"cpuCostAdjustment"`
+	CPUEfficiency              *float64               `json:"cpuEfficiency"`
+	GPUCount                   *float64               `json:"gpuCount"`
+	GPUHours                   *float64               `json:"gpuHours"`
+	GPUCost                    *float64               `json:"gpuCost"`
+	GPUCostAdjustment          *float64               `json:"gpuCostAdjustment"`
+	NetworkTransferBytes       *float64               `json:"networkTransferBytes"`
+	NetworkReceiveBytes        *float64               `json:"networkReceiveBytes"`
+	NetworkCost                *float64               `json:"networkCost"`
+	NetworkCrossZoneCost       *float64               `json:"networkCrossZoneCost"`
+	NetworkCrossRegionCost     *float64               `json:"networkCrossRegionCost"`
+	NetworkInternetCost        *float64               `json:"networkInternetCost"`
+	NetworkCostAdjustment      *float64               `json:"networkCostAdjustment"`
+	LoadBalancerCost           *float64               `json:"loadBalancerCost"`
+	LoadBalancerCostAdjustment *float64               `json:"loadBalancerCostAdjustment"`
+	PVBytes                    *float64               `json:"pvBytes"`
+	PVByteHours                *float64               `json:"pvByteHours"`
+	PVCost                     *float64               `json:"pvCost"`
+	PVs                        PVAllocations          `json:"pvs"`
+	PVCostAdjustment           *float64               `json:"pvCostAdjustment"`
+	RAMBytes                   *float64               `json:"ramBytes"`
+	RAMByteRequestAverage      *float64               `json:"ramByteRequestAverage"`
+	RAMByteUsageAverage        *float64               `json:"ramByteUsageAverage"`
+	RAMByteHours               *float64               `json:"ramByteHours"`
+	RAMCost                    *float64               `json:"ramCost"`
+	RAMCostAdjustment          *float64               `json:"ramCostAdjustment"`
+	RAMEfficiency              *float64               `json:"ramEfficiency"`
+	ExternalCost               *float64               `json:"externalCost"`
+	SharedCost                 *float64               `json:"sharedCost"`
+	TotalCost                  *float64               `json:"totalCost"`
+	TotalEfficiency            *float64               `json:"totalEfficiency"`
+	RawAllocationOnly          *RawAllocationOnlyData `json:"rawAllocationOnly"`
+}
+
+func (aj *AllocationJSON) BuildFromAllocation(a *Allocation) {
+	if aj == nil {
+		return
+	}
+	aj.Name = a.Name
+	aj.Properties = a.Properties
+	aj.Window = a.Window
+	aj.Start = a.Start.Format(time.RFC3339)
+	aj.End = a.End.Format(time.RFC3339)
+	aj.Minutes = formatFloat64ForResponse(a.Minutes())
+	aj.CPUCores = formatFloat64ForResponse(a.CPUCores())
+	aj.CPUCoreRequestAverage = formatFloat64ForResponse(a.CPUCoreRequestAverage)
+	aj.CPUCoreUsageAverage = formatFloat64ForResponse(a.CPUCoreUsageAverage)
+	aj.CPUCoreHours = formatFloat64ForResponse(a.CPUCoreHours)
+	aj.CPUCost = formatFloat64ForResponse(a.CPUCost)
+	aj.CPUCostAdjustment = formatFloat64ForResponse(a.CPUCostAdjustment)
+	aj.CPUEfficiency = formatFloat64ForResponse(a.CPUEfficiency())
+	aj.GPUCount = formatFloat64ForResponse(a.GPUs())
+	aj.GPUHours = formatFloat64ForResponse(a.GPUHours)
+	aj.GPUCost = formatFloat64ForResponse(a.GPUCost)
+	aj.GPUCostAdjustment = formatFloat64ForResponse(a.GPUCostAdjustment)
+	aj.NetworkTransferBytes = formatFloat64ForResponse(a.NetworkTransferBytes)
+	aj.NetworkReceiveBytes = formatFloat64ForResponse(a.NetworkReceiveBytes)
+	aj.NetworkCost = formatFloat64ForResponse(a.NetworkCost)
+	aj.NetworkCrossZoneCost = formatFloat64ForResponse(a.NetworkCrossZoneCost)
+	aj.NetworkCrossRegionCost = formatFloat64ForResponse(a.NetworkCrossRegionCost)
+	aj.NetworkInternetCost = formatFloat64ForResponse(a.NetworkInternetCost)
+	aj.NetworkCostAdjustment = formatFloat64ForResponse(a.NetworkCostAdjustment)
+	aj.LoadBalancerCost = formatFloat64ForResponse(a.LoadBalancerCost)
+	aj.LoadBalancerCostAdjustment = formatFloat64ForResponse(a.LoadBalancerCostAdjustment)
+	aj.PVBytes = formatFloat64ForResponse(a.PVBytes())
+	aj.PVByteHours = formatFloat64ForResponse(a.PVByteHours())
+	aj.PVCost = formatFloat64ForResponse(a.PVCost())
+	aj.PVs = a.PVs
+	aj.PVCostAdjustment = formatFloat64ForResponse(a.PVCostAdjustment)
+	aj.RAMBytes = formatFloat64ForResponse(a.RAMBytes())
+	aj.RAMByteRequestAverage = formatFloat64ForResponse(a.RAMBytesRequestAverage)
+	aj.RAMByteUsageAverage = formatFloat64ForResponse(a.RAMBytesUsageAverage)
+	aj.RAMByteHours = formatFloat64ForResponse(a.RAMByteHours)
+	aj.RAMCost = formatFloat64ForResponse(a.RAMCost)
+	aj.RAMCostAdjustment = formatFloat64ForResponse(a.RAMCostAdjustment)
+	aj.RAMEfficiency = formatFloat64ForResponse(a.RAMEfficiency())
+	aj.SharedCost = formatFloat64ForResponse(a.SharedCost)
+	aj.ExternalCost = formatFloat64ForResponse(a.ExternalCost)
+	aj.TotalCost = formatFloat64ForResponse(a.TotalCost())
+	aj.TotalEfficiency = formatFloat64ForResponse(a.TotalEfficiency())
+	aj.RawAllocationOnly = a.RawAllocationOnly
+}
+
+// formatFloat64ForResponse - take an existing float64, round it to 6 decimal places and return is possible, or return nil if invalid
+func formatFloat64ForResponse(f float64) *float64 {
+	if math.IsNaN(f) || math.IsInf(f, 0) {
+		return nil
+	}
+
+	// 6 digits of precision is the maximum the API should return
+	result := math.Round(f*100000) / 100000.0
+	return &result
+}
+
 // MarshalJSON implements json.Marshaler interface
 func (a *Allocation) MarshalJSON() ([]byte, error) {
-	buffer := bytes.NewBufferString("{")
-	jsonEncodeString(buffer, "name", a.Name, ",")
-	jsonEncode(buffer, "properties", a.Properties, ",")
-	jsonEncode(buffer, "window", a.Window, ",")
-	jsonEncodeString(buffer, "start", a.Start.Format(time.RFC3339), ",")
-	jsonEncodeString(buffer, "end", a.End.Format(time.RFC3339), ",")
-	jsonEncodeFloat64(buffer, "minutes", a.Minutes(), ",")
-	jsonEncodeFloat64(buffer, "cpuCores", a.CPUCores(), ",")
-	jsonEncodeFloat64(buffer, "cpuCoreRequestAverage", a.CPUCoreRequestAverage, ",")
-	jsonEncodeFloat64(buffer, "cpuCoreUsageAverage", a.CPUCoreUsageAverage, ",")
-	jsonEncodeFloat64(buffer, "cpuCoreHours", a.CPUCoreHours, ",")
-	jsonEncodeFloat64(buffer, "cpuCost", a.CPUCost, ",")
-	jsonEncodeFloat64(buffer, "cpuCostAdjustment", a.CPUCostAdjustment, ",")
-	jsonEncodeFloat64(buffer, "cpuEfficiency", a.CPUEfficiency(), ",")
-	jsonEncodeFloat64(buffer, "gpuCount", a.GPUs(), ",")
-	jsonEncodeFloat64(buffer, "gpuHours", a.GPUHours, ",")
-	jsonEncodeFloat64(buffer, "gpuCost", a.GPUCost, ",")
-	jsonEncodeFloat64(buffer, "gpuCostAdjustment", a.GPUCostAdjustment, ",")
-	jsonEncodeFloat64(buffer, "networkTransferBytes", a.NetworkTransferBytes, ",")
-	jsonEncodeFloat64(buffer, "networkReceiveBytes", a.NetworkReceiveBytes, ",")
-	jsonEncodeFloat64(buffer, "networkCost", a.NetworkCost, ",")
-	jsonEncodeFloat64(buffer, "networkCrossZoneCost", a.NetworkCrossZoneCost, ",")
-	jsonEncodeFloat64(buffer, "networkCrossRegionCost", a.NetworkCrossRegionCost, ",")
-	jsonEncodeFloat64(buffer, "networkInternetCost", a.NetworkInternetCost, ",")
-	jsonEncodeFloat64(buffer, "networkCostAdjustment", a.NetworkCostAdjustment, ",")
-	jsonEncodeFloat64(buffer, "loadBalancerCost", a.LoadBalancerCost, ",")
-	jsonEncodeFloat64(buffer, "loadBalancerCostAdjustment", a.LoadBalancerCostAdjustment, ",")
-	jsonEncodeFloat64(buffer, "pvBytes", a.PVBytes(), ",")
-	jsonEncodeFloat64(buffer, "pvByteHours", a.PVByteHours(), ",")
-	jsonEncodeFloat64(buffer, "pvCost", a.PVCost(), ",")
-	jsonEncode(buffer, "pvs", a.PVs, ",")
-	jsonEncodeFloat64(buffer, "pvCostAdjustment", a.PVCostAdjustment, ",")
-	jsonEncodeFloat64(buffer, "ramBytes", a.RAMBytes(), ",")
-	jsonEncodeFloat64(buffer, "ramByteRequestAverage", a.RAMBytesRequestAverage, ",")
-	jsonEncodeFloat64(buffer, "ramByteUsageAverage", a.RAMBytesUsageAverage, ",")
-	jsonEncodeFloat64(buffer, "ramByteHours", a.RAMByteHours, ",")
-	jsonEncodeFloat64(buffer, "ramCost", a.RAMCost, ",")
-	jsonEncodeFloat64(buffer, "ramCostAdjustment", a.RAMCostAdjustment, ",")
-	jsonEncodeFloat64(buffer, "ramEfficiency", a.RAMEfficiency(), ",")
-	jsonEncodeFloat64(buffer, "sharedCost", a.SharedCost, ",")
-	jsonEncodeFloat64(buffer, "externalCost", a.ExternalCost, ",")
-	jsonEncodeFloat64(buffer, "totalCost", a.TotalCost(), ",")
-	jsonEncodeFloat64(buffer, "totalEfficiency", a.TotalEfficiency(), ",")
-	jsonEncode(buffer, "rawAllocationOnly", a.RawAllocationOnly, "")
-	buffer.WriteString("}")
-	return buffer.Bytes(), nil
+
+	aj := &AllocationJSON{}
+	aj.BuildFromAllocation(a)
+	buffer, err := json.Marshal(aj)
+	if err != nil {
+		return nil, fmt.Errorf("unable to marshal allocation %s to JSON: %s", aj.Name, err)
+	}
+	return buffer, nil
 }
 
 // UnmarshalJSON prevent nil pointer on PVAllocations

+ 64 - 0
pkg/kubecost/allocation_json_test.go

@@ -2,6 +2,8 @@ package kubecost
 
 import (
 	"encoding/json"
+	"github.com/opencost/opencost/pkg/util/mathutil"
+	"math"
 	"testing"
 	"time"
 )
@@ -152,3 +154,65 @@ func TestPVAllocations_MarshalJSON(t *testing.T) {
 	}
 
 }
+
+func TestFormatFloat64ForResponse(t *testing.T) {
+	type formatTestCase struct {
+		name          string
+		input         float64
+		expectedNil   bool
+		expectedValue float64
+	}
+	testCases := []formatTestCase{
+		{
+			name:          "zero",
+			input:         0.0,
+			expectedNil:   false,
+			expectedValue: 0.0,
+		},
+		{
+			name:          "round to zero",
+			input:         0.000000001,
+			expectedNil:   false,
+			expectedValue: 0,
+		},
+		{
+			name:          "valid value, no rounding",
+			input:         14.123456,
+			expectedNil:   false,
+			expectedValue: 14.123456,
+		},
+		{
+			name:          "valid value, with rounding",
+			input:         14.1234567,
+			expectedNil:   false,
+			expectedValue: 14.123457,
+		},
+		{
+			name:        "NaN is nil",
+			input:       math.NaN(),
+			expectedNil: true,
+		},
+		{
+			name:        "infinite is nil",
+			input:       math.Inf(1),
+			expectedNil: true,
+		},
+		{
+			name:        "negative infinite is nil",
+			input:       math.Inf(-1),
+			expectedNil: true,
+		},
+	}
+	for _, tc := range testCases {
+		result := formatFloat64ForResponse(tc.input)
+		if result == nil && tc.expectedNil == false {
+			t.Fatalf("test case: %s: expected a value %f, got nil instead", tc.name, tc.expectedValue)
+		}
+		if result != nil && tc.expectedNil == true {
+			t.Fatalf("test case: %s: expected nil, got value %f instead", tc.name, *result)
+		}
+		if result != nil && !mathutil.Approximately(*result, tc.expectedValue) {
+			t.Fatalf("test case: %s: expected %f, got %f", tc.name, tc.expectedValue, *result)
+		}
+	}
+}