containerkeys.go 6.6 KB

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