2
0

containerkeys.go 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263
  1. package costmodel
  2. import (
  3. "errors"
  4. "fmt"
  5. "regexp"
  6. "strings"
  7. "github.com/opencost/opencost/core/pkg/clustercache"
  8. "github.com/opencost/opencost/core/pkg/log"
  9. "github.com/opencost/opencost/core/pkg/source"
  10. )
  11. var (
  12. // Static KeyTuple Errors
  13. ErrNewKeyTuple = errors.New("new-key-tuple: key not containing exactly 3 components")
  14. // Static Errors for ContainerMetric creation
  15. ErrInvalidKey error = errors.New("not a valid key")
  16. ErrNoContainer error = errors.New("vector does not have container name")
  17. ErrNoContainerName error = errors.New("vector does not have string container name")
  18. ErrNoPod error = errors.New("vector does not have pod name")
  19. ErrNoPodName error = errors.New("vector does not have string pod name")
  20. ErrNoNamespace error = errors.New("vector does not have namespace")
  21. ErrNoNamespaceName error = errors.New("vector does not have string namespace")
  22. ErrNoNodeName error = errors.New("vector does not have string node")
  23. ErrNoClusterID error = errors.New("vector does not have string cluster id")
  24. )
  25. const invalidNodeNameErrFmt = `invalid node name: %s was likely set from "instance" label. cAdvisor scrape configs require the following:
  26. relabel_configs:
  27. - action: labelmap
  28. regex: __meta_kubernetes_node_name
  29. replacement: node`
  30. //--------------------------------------------------------------------------
  31. // KeyTuple
  32. //--------------------------------------------------------------------------
  33. // KeyTuple contains is a utility which parses Namespace, Key, and ClusterID from a
  34. // comma delimitted string.
  35. type KeyTuple struct {
  36. key string
  37. kIndex int
  38. cIndex int
  39. }
  40. // Namespace returns the the namespace from the string key.
  41. func (kt *KeyTuple) Namespace() string {
  42. return kt.key[0 : kt.kIndex-1]
  43. }
  44. // Key returns the identifier from the string key.
  45. func (kt *KeyTuple) Key() string {
  46. return kt.key[kt.kIndex : kt.cIndex-1]
  47. }
  48. // ClusterID returns the cluster identifier from the string key.
  49. func (kt *KeyTuple) ClusterID() string {
  50. return kt.key[kt.cIndex:]
  51. }
  52. // NewKeyTuple creates a new KeyTuple instance by determining the exact indices of each tuple
  53. // entry. When each component is requested, a string slice is returned using the boundaries.
  54. func NewKeyTuple(key string) (*KeyTuple, error) {
  55. kIndex := strings.IndexRune(key, ',')
  56. if kIndex < 0 {
  57. return nil, ErrNewKeyTuple
  58. }
  59. kIndex += 1
  60. subIndex := strings.IndexRune(key[kIndex:], ',')
  61. if subIndex < 0 {
  62. return nil, ErrNewKeyTuple
  63. }
  64. cIndex := kIndex + subIndex + 1
  65. if strings.ContainsRune(key[cIndex:], ',') {
  66. return nil, ErrNewKeyTuple
  67. }
  68. return &KeyTuple{
  69. key: key,
  70. kIndex: kIndex,
  71. cIndex: cIndex,
  72. }, nil
  73. }
  74. //--------------------------------------------------------------------------
  75. // ContainerMetric
  76. //--------------------------------------------------------------------------
  77. // ContainerMetric contains a set of identifiers specific to a kubernetes container including
  78. // a unique string key
  79. type ContainerMetric struct {
  80. Namespace string
  81. PodName string
  82. ContainerName string
  83. NodeName string
  84. ClusterID string
  85. key string
  86. }
  87. // Key returns a unique string key that can be used in map[string]interface{}
  88. func (c *ContainerMetric) Key() string {
  89. return c.key
  90. }
  91. // containerMetricKey creates a unique string key, a comma delimitted list of the provided
  92. // parameters.
  93. func containerMetricKey(ns, podName, containerName, nodeName, clusterID string) string {
  94. return ns + "," + podName + "," + containerName + "," + nodeName + "," + clusterID
  95. }
  96. // NewContainerMetricFromKey creates a new ContainerMetric instance using a provided comma delimitted
  97. // string key.
  98. func NewContainerMetricFromKey(key string) (*ContainerMetric, error) {
  99. s := strings.Split(key, ",")
  100. if len(s) == 5 {
  101. return &ContainerMetric{
  102. Namespace: s[0],
  103. PodName: s[1],
  104. ContainerName: s[2],
  105. NodeName: s[3],
  106. ClusterID: s[4],
  107. key: key,
  108. }, nil
  109. }
  110. return nil, ErrInvalidKey
  111. }
  112. // NewContainerMetricFromValues creates a new ContainerMetric instance using the provided string parameters.
  113. func NewContainerMetricFromValues(ns, podName, containerName, nodeName, clusterId string) *ContainerMetric {
  114. return &ContainerMetric{
  115. Namespace: ns,
  116. PodName: podName,
  117. ContainerName: containerName,
  118. NodeName: nodeName,
  119. ClusterID: clusterId,
  120. key: containerMetricKey(ns, podName, containerName, nodeName, clusterId),
  121. }
  122. }
  123. // NewContainerMetricsFromPod creates a slice of ContainerMetric instances for each container in the
  124. // provided Pod.
  125. func NewContainerMetricsFromPod(pod *clustercache.Pod, clusterID string) ([]*ContainerMetric, error) {
  126. podName := pod.Name
  127. ns := pod.Namespace
  128. node := pod.Spec.NodeName
  129. var cs []*ContainerMetric
  130. for _, container := range pod.Spec.Containers {
  131. containerName := container.Name
  132. cs = append(cs, &ContainerMetric{
  133. Namespace: ns,
  134. PodName: podName,
  135. ContainerName: containerName,
  136. NodeName: node,
  137. ClusterID: clusterID,
  138. key: containerMetricKey(ns, podName, containerName, node, clusterID),
  139. })
  140. }
  141. return cs, nil
  142. }
  143. // NewContainerMetricFromResult accepts the metrics map from a QueryResult and returns a new ContainerMetric
  144. // instance
  145. func NewContainerMetricFromResult(result *source.QueryResult, defaultClusterID string) (*ContainerMetric, error) {
  146. containerName, err := result.GetContainer()
  147. if err != nil {
  148. return nil, ErrNoContainer
  149. }
  150. podName, err := result.GetPod()
  151. if err != nil {
  152. return nil, ErrNoPodName
  153. }
  154. namespace, err := result.GetNamespace()
  155. if err != nil {
  156. return nil, ErrNoNamespaceName
  157. }
  158. nodeName, err := result.GetNode()
  159. if err != nil {
  160. log.Debugf("Prometheus vector does not have node name")
  161. nodeName = ""
  162. }
  163. clusterID, err := result.GetCluster()
  164. if err != nil {
  165. log.Debugf("Prometheus vector does not have cluster id")
  166. clusterID = defaultClusterID
  167. }
  168. return &ContainerMetric{
  169. ContainerName: containerName,
  170. PodName: podName,
  171. Namespace: namespace,
  172. NodeName: nodeName,
  173. ClusterID: clusterID,
  174. key: containerMetricKey(namespace, podName, containerName, nodeName, clusterID),
  175. }, nil
  176. }
  177. func NewContainerMetricFrom(result *source.ContainerMetricResult, defaultClusterID string) (*ContainerMetric, error) {
  178. containerName := result.Container
  179. if containerName == "" {
  180. return nil, ErrNoContainer
  181. }
  182. podName := result.Pod
  183. if podName == "" {
  184. return nil, ErrNoPodName
  185. }
  186. namespace := result.Namespace
  187. if namespace == "" {
  188. return nil, ErrNoNamespaceName
  189. }
  190. nodeName := result.Node
  191. if !isValidNodeName(nodeName) {
  192. return nil, fmt.Errorf(invalidNodeNameErrFmt, nodeName)
  193. }
  194. if nodeName == "" {
  195. log.Debugf("metric vector does not have node name")
  196. nodeName = ""
  197. }
  198. clusterID := result.Cluster
  199. if clusterID == "" {
  200. clusterID = defaultClusterID
  201. }
  202. return &ContainerMetric{
  203. ContainerName: containerName,
  204. PodName: podName,
  205. Namespace: namespace,
  206. NodeName: nodeName,
  207. ClusterID: clusterID,
  208. key: containerMetricKey(namespace, podName, containerName, nodeName, clusterID),
  209. }, nil
  210. }
  211. /*
  212. - contain no more than 253 characters
  213. - contain only lowercase alphanumeric characters, '-' or '.'
  214. - start with an alphanumeric character
  215. - end with an alphanumeric character
  216. */
  217. var nodeNameRegex = regexp.MustCompile(`^[a-z0-9][a-z0-9\-\.]+[a-z0-9]$`)
  218. // isValidNodeName determines if the nodeName provided is valid according to DNS subdomain
  219. // specifications: RFC 1123
  220. func isValidNodeName(nodeName string) bool {
  221. if len(nodeName) > 253 {
  222. return false
  223. }
  224. return nodeNameRegex.Match([]byte(nodeName))
  225. }