pricingset.go 3.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110
  1. package pricing
  2. import (
  3. "cmp"
  4. "encoding/hex"
  5. "fmt"
  6. "hash/fnv"
  7. "slices"
  8. )
  9. type PricingSet struct {
  10. ClusterPricing []*ClusterPricing `json:"clusterPricing" yaml:"clusterPricing"`
  11. NetworkPricing []*NetworkPricing `json:"networkPricing" yaml:"networkPricing"`
  12. NodePricing []*NodePricing `json:"nodePricing" yaml:"nodePricing"`
  13. PersistentVolumePricing []*PersistentVolumePricing `json:"persistentVolumePricing" yaml:"persistentVolumePricing"`
  14. ServicePricing []*ServicePricing `json:"servicePricing" yaml:"servicePricing"`
  15. }
  16. func (ps *PricingSet) IsEmpty() bool {
  17. if ps == nil {
  18. return true
  19. }
  20. return len(ps.ClusterPricing) == 0 &&
  21. len(ps.NetworkPricing) == 0 &&
  22. len(ps.NodePricing) == 0 &&
  23. len(ps.PersistentVolumePricing) == 0 &&
  24. len(ps.ServicePricing) == 0
  25. }
  26. // Checksum returns a hash that is stable across map and slice ordering and
  27. // sensitive to both pricing properties and price values.
  28. //
  29. // TODO: Consider commutative hash folding via a multiset hashing algorithm
  30. // if the string-based Checksum() implementation is too resource intensive
  31. // for large pricing sets. For now, this string version is more readable.
  32. func (ps *PricingSet) Checksum() (string, error) {
  33. if ps == nil {
  34. ps = &PricingSet{}
  35. }
  36. // Each item's String() is prefixed with its kind so that items of
  37. // different kinds cannot collide, then all keys are sorted to make the
  38. // hash independent of input ordering.
  39. keys := make([]string, 0,
  40. len(ps.ClusterPricing)+
  41. len(ps.NetworkPricing)+
  42. len(ps.NodePricing)+
  43. len(ps.PersistentVolumePricing)+
  44. len(ps.ServicePricing))
  45. for _, cp := range ps.ClusterPricing {
  46. keys = append(keys, "cluster:"+cp.String())
  47. }
  48. for _, np := range ps.NetworkPricing {
  49. keys = append(keys, "network:"+np.String())
  50. }
  51. for _, np := range ps.NodePricing {
  52. keys = append(keys, "node:"+np.String())
  53. }
  54. for _, pvp := range ps.PersistentVolumePricing {
  55. keys = append(keys, "persistentvolume:"+pvp.String())
  56. }
  57. for _, sp := range ps.ServicePricing {
  58. keys = append(keys, "service:"+sp.String())
  59. }
  60. slices.Sort(keys)
  61. hasher := fnv.New64a()
  62. for _, key := range keys {
  63. if _, err := hasher.Write([]byte(key)); err != nil {
  64. return "", fmt.Errorf("fnv hash: %w", err)
  65. }
  66. }
  67. return hex.EncodeToString(hasher.Sum(nil)), nil
  68. }
  69. // Sort sorts the pricing data to ensure deterministic serialization.
  70. func (ps *PricingSet) Sort() {
  71. if ps == nil {
  72. return
  73. }
  74. // Sort clusters
  75. slices.SortFunc(ps.ClusterPricing, func(a, b *ClusterPricing) int {
  76. return cmp.Compare(a.String(), b.String())
  77. })
  78. // Sort network
  79. slices.SortFunc(ps.NetworkPricing, func(a, b *NetworkPricing) int {
  80. return cmp.Compare(a.String(), b.String())
  81. })
  82. // Sort nodes
  83. slices.SortFunc(ps.NodePricing, func(a, b *NodePricing) int {
  84. return cmp.Compare(a.String(), b.String())
  85. })
  86. // Sort persistent volumes
  87. slices.SortFunc(ps.PersistentVolumePricing, func(a, b *PersistentVolumePricing) int {
  88. return cmp.Compare(a.String(), b.String())
  89. })
  90. // Sort services
  91. slices.SortFunc(ps.ServicePricing, func(a, b *ServicePricing) int {
  92. return cmp.Compare(a.String(), b.String())
  93. })
  94. }