| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263 |
- package costmodel
- import (
- "errors"
- "fmt"
- "regexp"
- "strings"
- "github.com/opencost/opencost/core/pkg/clustercache"
- "github.com/opencost/opencost/core/pkg/log"
- "github.com/opencost/opencost/core/pkg/source"
- )
- var (
- // Static KeyTuple Errors
- ErrNewKeyTuple = errors.New("new-key-tuple: key not containing exactly 3 components")
- // Static Errors for ContainerMetric creation
- ErrInvalidKey error = errors.New("not a valid key")
- ErrNoContainer error = errors.New("vector does not have container name")
- ErrNoContainerName error = errors.New("vector does not have string container name")
- ErrNoPod error = errors.New("vector does not have pod name")
- ErrNoPodName error = errors.New("vector does not have string pod name")
- ErrNoNamespace error = errors.New("vector does not have namespace")
- ErrNoNamespaceName error = errors.New("vector does not have string namespace")
- ErrNoNodeName error = errors.New("vector does not have string node")
- ErrNoClusterID error = errors.New("vector does not have string cluster id")
- )
- const invalidNodeNameErrFmt = `invalid node name: %s was likely set from "instance" label. cAdvisor scrape configs require the following:
- relabel_configs:
- - action: labelmap
- regex: __meta_kubernetes_node_name
- replacement: node`
- //--------------------------------------------------------------------------
- // KeyTuple
- //--------------------------------------------------------------------------
- // KeyTuple contains is a utility which parses Namespace, Key, and ClusterID from a
- // comma delimitted string.
- type KeyTuple struct {
- key string
- kIndex int
- cIndex int
- }
- // Namespace returns the the namespace from the string key.
- func (kt *KeyTuple) Namespace() string {
- return kt.key[0 : kt.kIndex-1]
- }
- // Key returns the identifier from the string key.
- func (kt *KeyTuple) Key() string {
- return kt.key[kt.kIndex : kt.cIndex-1]
- }
- // ClusterID returns the cluster identifier from the string key.
- func (kt *KeyTuple) ClusterID() string {
- return kt.key[kt.cIndex:]
- }
- // NewKeyTuple creates a new KeyTuple instance by determining the exact indices of each tuple
- // entry. When each component is requested, a string slice is returned using the boundaries.
- func NewKeyTuple(key string) (*KeyTuple, error) {
- kIndex := strings.IndexRune(key, ',')
- if kIndex < 0 {
- return nil, ErrNewKeyTuple
- }
- kIndex += 1
- subIndex := strings.IndexRune(key[kIndex:], ',')
- if subIndex < 0 {
- return nil, ErrNewKeyTuple
- }
- cIndex := kIndex + subIndex + 1
- if strings.ContainsRune(key[cIndex:], ',') {
- return nil, ErrNewKeyTuple
- }
- return &KeyTuple{
- key: key,
- kIndex: kIndex,
- cIndex: cIndex,
- }, nil
- }
- //--------------------------------------------------------------------------
- // ContainerMetric
- //--------------------------------------------------------------------------
- // ContainerMetric contains a set of identifiers specific to a kubernetes container including
- // a unique string key
- type ContainerMetric struct {
- Namespace string
- PodName string
- ContainerName string
- NodeName string
- ClusterID string
- key string
- }
- // Key returns a unique string key that can be used in map[string]interface{}
- func (c *ContainerMetric) Key() string {
- return c.key
- }
- // containerMetricKey creates a unique string key, a comma delimitted list of the provided
- // parameters.
- func containerMetricKey(ns, podName, containerName, nodeName, clusterID string) string {
- return ns + "," + podName + "," + containerName + "," + nodeName + "," + clusterID
- }
- // NewContainerMetricFromKey creates a new ContainerMetric instance using a provided comma delimitted
- // string key.
- func NewContainerMetricFromKey(key string) (*ContainerMetric, error) {
- s := strings.Split(key, ",")
- if len(s) == 5 {
- return &ContainerMetric{
- Namespace: s[0],
- PodName: s[1],
- ContainerName: s[2],
- NodeName: s[3],
- ClusterID: s[4],
- key: key,
- }, nil
- }
- return nil, ErrInvalidKey
- }
- // NewContainerMetricFromValues creates a new ContainerMetric instance using the provided string parameters.
- func NewContainerMetricFromValues(ns, podName, containerName, nodeName, clusterId string) *ContainerMetric {
- return &ContainerMetric{
- Namespace: ns,
- PodName: podName,
- ContainerName: containerName,
- NodeName: nodeName,
- ClusterID: clusterId,
- key: containerMetricKey(ns, podName, containerName, nodeName, clusterId),
- }
- }
- // NewContainerMetricsFromPod creates a slice of ContainerMetric instances for each container in the
- // provided Pod.
- func NewContainerMetricsFromPod(pod *clustercache.Pod, clusterID string) ([]*ContainerMetric, error) {
- podName := pod.Name
- ns := pod.Namespace
- node := pod.Spec.NodeName
- var cs []*ContainerMetric
- for _, container := range pod.Spec.Containers {
- containerName := container.Name
- cs = append(cs, &ContainerMetric{
- Namespace: ns,
- PodName: podName,
- ContainerName: containerName,
- NodeName: node,
- ClusterID: clusterID,
- key: containerMetricKey(ns, podName, containerName, node, clusterID),
- })
- }
- return cs, nil
- }
- // NewContainerMetricFromResult accepts the metrics map from a QueryResult and returns a new ContainerMetric
- // instance
- func NewContainerMetricFromResult(result *source.QueryResult, defaultClusterID string) (*ContainerMetric, error) {
- containerName, err := result.GetContainer()
- if err != nil {
- return nil, ErrNoContainer
- }
- podName, err := result.GetPod()
- if err != nil {
- return nil, ErrNoPodName
- }
- namespace, err := result.GetNamespace()
- if err != nil {
- return nil, ErrNoNamespaceName
- }
- nodeName, err := result.GetNode()
- if err != nil {
- log.Debugf("Prometheus vector does not have node name")
- nodeName = ""
- }
- clusterID, err := result.GetCluster()
- if err != nil {
- log.Debugf("Prometheus vector does not have cluster id")
- clusterID = defaultClusterID
- }
- return &ContainerMetric{
- ContainerName: containerName,
- PodName: podName,
- Namespace: namespace,
- NodeName: nodeName,
- ClusterID: clusterID,
- key: containerMetricKey(namespace, podName, containerName, nodeName, clusterID),
- }, nil
- }
- func NewContainerMetricFrom(result *source.ContainerMetricResult, defaultClusterID string) (*ContainerMetric, error) {
- containerName := result.Container
- if containerName == "" {
- return nil, ErrNoContainer
- }
- podName := result.Pod
- if podName == "" {
- return nil, ErrNoPodName
- }
- namespace := result.Namespace
- if namespace == "" {
- return nil, ErrNoNamespaceName
- }
- nodeName := result.Node
- if !isValidNodeName(nodeName) {
- return nil, fmt.Errorf(invalidNodeNameErrFmt, nodeName)
- }
- if nodeName == "" {
- log.Debugf("metric vector does not have node name")
- nodeName = ""
- }
- clusterID := result.Cluster
- if clusterID == "" {
- clusterID = defaultClusterID
- }
- return &ContainerMetric{
- ContainerName: containerName,
- PodName: podName,
- Namespace: namespace,
- NodeName: nodeName,
- ClusterID: clusterID,
- key: containerMetricKey(namespace, podName, containerName, nodeName, clusterID),
- }, nil
- }
- /*
- - contain no more than 253 characters
- - contain only lowercase alphanumeric characters, '-' or '.'
- - start with an alphanumeric character
- - end with an alphanumeric character
- */
- var nodeNameRegex = regexp.MustCompile(`^[a-z0-9][a-z0-9\-\.]+[a-z0-9]$`)
- // isValidNodeName determines if the nodeName provided is valid according to DNS subdomain
- // specifications: RFC 1123
- func isValidNodeName(nodeName string) bool {
- if len(nodeName) > 253 {
- return false
- }
- return nodeNameRegex.Match([]byte(nodeName))
- }
|