containerkeys.go 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213
  1. package costmodel
  2. import (
  3. "errors"
  4. "strings"
  5. "github.com/opencost/opencost/pkg/env"
  6. "github.com/opencost/opencost/pkg/log"
  7. v1 "k8s.io/api/core/v1"
  8. )
  9. var (
  10. // Static KeyTuple Errors
  11. NewKeyTupleErr = errors.New("NewKeyTuple() Provided key not containing exactly 3 components.")
  12. // Static Errors for ContainerMetric creation
  13. InvalidKeyErr error = errors.New("Not a valid key")
  14. NoContainerErr error = errors.New("Prometheus vector does not have container name")
  15. NoContainerNameErr error = errors.New("Prometheus vector does not have string container name")
  16. NoPodErr error = errors.New("Prometheus vector does not have pod name")
  17. NoPodNameErr error = errors.New("Prometheus vector does not have string pod name")
  18. NoNamespaceErr error = errors.New("Prometheus vector does not have namespace")
  19. NoNamespaceNameErr error = errors.New("Prometheus vector does not have string namespace")
  20. NoNodeNameErr error = errors.New("Prometheus vector does not have string node")
  21. NoClusterIDErr error = errors.New("Prometheus vector does not have string cluster id")
  22. )
  23. //--------------------------------------------------------------------------
  24. // KeyTuple
  25. //--------------------------------------------------------------------------
  26. // KeyTuple contains is a utility which parses Namespace, Key, and ClusterID from a
  27. // comma delimitted string.
  28. type KeyTuple struct {
  29. key string
  30. kIndex int
  31. cIndex int
  32. }
  33. // Namespace returns the the namespace from the string key.
  34. func (kt *KeyTuple) Namespace() string {
  35. return kt.key[0 : kt.kIndex-1]
  36. }
  37. // Key returns the identifier from the string key.
  38. func (kt *KeyTuple) Key() string {
  39. return kt.key[kt.kIndex : kt.cIndex-1]
  40. }
  41. // ClusterID returns the cluster identifier from the string key.
  42. func (kt *KeyTuple) ClusterID() string {
  43. return kt.key[kt.cIndex:]
  44. }
  45. // NewKeyTuple creates a new KeyTuple instance by determining the exact indices of each tuple
  46. // entry. When each component is requested, a string slice is returned using the boundaries.
  47. func NewKeyTuple(key string) (*KeyTuple, error) {
  48. kIndex := strings.IndexRune(key, ',')
  49. if kIndex < 0 {
  50. return nil, NewKeyTupleErr
  51. }
  52. kIndex += 1
  53. subIndex := strings.IndexRune(key[kIndex:], ',')
  54. if subIndex < 0 {
  55. return nil, NewKeyTupleErr
  56. }
  57. cIndex := kIndex + subIndex + 1
  58. if strings.ContainsRune(key[cIndex:], ',') {
  59. return nil, NewKeyTupleErr
  60. }
  61. return &KeyTuple{
  62. key: key,
  63. kIndex: kIndex,
  64. cIndex: cIndex,
  65. }, nil
  66. }
  67. //--------------------------------------------------------------------------
  68. // ContainerMetric
  69. //--------------------------------------------------------------------------
  70. // ContainerMetric contains a set of identifiers specific to a kubernetes container including
  71. // a unique string key
  72. type ContainerMetric struct {
  73. Namespace string
  74. PodName string
  75. ContainerName string
  76. NodeName string
  77. ClusterID string
  78. key string
  79. }
  80. // Key returns a unique string key that can be used in map[string]interface{}
  81. func (c *ContainerMetric) Key() string {
  82. return c.key
  83. }
  84. // containerMetricKey creates a unique string key, a comma delimitted list of the provided
  85. // parameters.
  86. func containerMetricKey(ns, podName, containerName, nodeName, clusterID string) string {
  87. return ns + "," + podName + "," + containerName + "," + nodeName + "," + clusterID
  88. }
  89. // NewContainerMetricFromKey creates a new ContainerMetric instance using a provided comma delimitted
  90. // string key.
  91. func NewContainerMetricFromKey(key string) (*ContainerMetric, error) {
  92. s := strings.Split(key, ",")
  93. if len(s) == 5 {
  94. return &ContainerMetric{
  95. Namespace: s[0],
  96. PodName: s[1],
  97. ContainerName: s[2],
  98. NodeName: s[3],
  99. ClusterID: s[4],
  100. key: key,
  101. }, nil
  102. }
  103. return nil, InvalidKeyErr
  104. }
  105. // NewContainerMetricFromValues creates a new ContainerMetric instance using the provided string parameters.
  106. func NewContainerMetricFromValues(ns, podName, containerName, nodeName, clusterId string) *ContainerMetric {
  107. return &ContainerMetric{
  108. Namespace: ns,
  109. PodName: podName,
  110. ContainerName: containerName,
  111. NodeName: nodeName,
  112. ClusterID: clusterId,
  113. key: containerMetricKey(ns, podName, containerName, nodeName, clusterId),
  114. }
  115. }
  116. // NewContainerMetricsFromPod creates a slice of ContainerMetric instances for each container in the
  117. // provided Pod.
  118. func NewContainerMetricsFromPod(pod *v1.Pod, clusterID string) ([]*ContainerMetric, error) {
  119. podName := pod.GetObjectMeta().GetName()
  120. ns := pod.GetObjectMeta().GetNamespace()
  121. node := pod.Spec.NodeName
  122. var cs []*ContainerMetric
  123. for _, container := range pod.Spec.Containers {
  124. containerName := container.Name
  125. cs = append(cs, &ContainerMetric{
  126. Namespace: ns,
  127. PodName: podName,
  128. ContainerName: containerName,
  129. NodeName: node,
  130. ClusterID: clusterID,
  131. key: containerMetricKey(ns, podName, containerName, node, clusterID),
  132. })
  133. }
  134. return cs, nil
  135. }
  136. // NewContainerMetricFromPrometheus accepts the metrics map from a QueryResult and returns a new ContainerMetric
  137. // instance
  138. func NewContainerMetricFromPrometheus(metrics map[string]interface{}, defaultClusterID string) (*ContainerMetric, error) {
  139. // TODO: Can we use *prom.QueryResult.GetString() here?
  140. cName, ok := metrics["container_name"]
  141. if !ok {
  142. return nil, NoContainerErr
  143. }
  144. containerName, ok := cName.(string)
  145. if !ok {
  146. return nil, NoContainerNameErr
  147. }
  148. pName, ok := metrics["pod_name"]
  149. if !ok {
  150. return nil, NoPodErr
  151. }
  152. podName, ok := pName.(string)
  153. if !ok {
  154. return nil, NoPodNameErr
  155. }
  156. ns, ok := metrics["namespace"]
  157. if !ok {
  158. return nil, NoNamespaceErr
  159. }
  160. namespace, ok := ns.(string)
  161. if !ok {
  162. return nil, NoNamespaceNameErr
  163. }
  164. node, ok := metrics["node"]
  165. if !ok {
  166. log.Debugf("Prometheus vector does not have node name")
  167. node = ""
  168. }
  169. nodeName, ok := node.(string)
  170. if !ok {
  171. return nil, NoNodeNameErr
  172. }
  173. cid, ok := metrics[env.GetPromClusterLabel()]
  174. if !ok {
  175. log.Debugf("Prometheus vector does not have cluster id")
  176. cid = defaultClusterID
  177. }
  178. clusterID, ok := cid.(string)
  179. if !ok {
  180. return nil, NoClusterIDErr
  181. }
  182. return &ContainerMetric{
  183. ContainerName: containerName,
  184. PodName: podName,
  185. Namespace: namespace,
  186. NodeName: nodeName,
  187. ClusterID: clusterID,
  188. key: containerMetricKey(namespace, podName, containerName, nodeName, clusterID),
  189. }, nil
  190. }