Răsfoiți Sursa

Merge pull request #2317 from mattray/cloudclost-k8sless

Cloud Costs Kubernetesless
Matt Ray 2 ani în urmă
părinte
comite
e5dfa18da9

+ 8 - 2
pkg/cloud/config/controller.go

@@ -9,6 +9,7 @@ import (
 	"github.com/opencost/opencost/pkg/cloud"
 	"github.com/opencost/opencost/pkg/cloud/models"
 	"github.com/opencost/opencost/pkg/cloud/provider"
+	"github.com/opencost/opencost/pkg/env"
 )
 
 // configID identifies the source and the ID of a configuration to handle duplicate configs from multiple sources
@@ -47,8 +48,13 @@ type Controller struct {
 
 // NewController initializes an Config Controller
 func NewController(cp models.Provider) *Controller {
-	providerConfig := provider.ExtractConfigFromProviders(cp)
-	watchers := GetCloudBillingWatchers(providerConfig)
+	var watchers map[ConfigSource]cloud.KeyedConfigWatcher
+	if env.IsKubernetesEnabled() {
+		providerConfig := provider.ExtractConfigFromProviders(cp)
+		watchers = GetCloudBillingWatchers(providerConfig)
+	} else {
+		watchers = GetCloudBillingWatchers(nil)
+	}
 	ic := &Controller{
 		statuses: make(map[configID]*Status),
 		watchers: watchers,

+ 9 - 1
pkg/cloud/config/watcher.go

@@ -239,7 +239,13 @@ type MultiCloudWatcher struct {
 }
 
 func (mcw *MultiCloudWatcher) GetConfigs() []cloud.KeyedConfig {
-	multiConfigPath := path.Join(env.GetConfigPathWithDefault("/var/configs"), cloudIntegrationSecretPath)
+	var multiConfigPath string
+
+	if env.IsKubernetesEnabled() {
+		multiConfigPath = path.Join(env.GetConfigPathWithDefault("/var/configs"), cloudIntegrationSecretPath)
+	} else {
+		multiConfigPath = env.GetCloudCostConfigPath()
+	}
 	exists, err := fileutil.FileExists(multiConfigPath)
 	if err != nil {
 		log.Errorf("MultiCloudWatcher:  error checking file at '%s': %s", multiConfigPath, err.Error())
@@ -260,6 +266,8 @@ func (mcw *MultiCloudWatcher) GetConfigs() []cloud.KeyedConfig {
 		}
 	}
 
+	log.Debugf("MultiCloudWatcher GetConfigs: multiConfigPath: %s", multiConfigPath)
+
 	configurations := &Configurations{}
 	err = loadFile(multiConfigPath, configurations)
 	if err != nil {

+ 19 - 7
pkg/cmd/costmodel/costmodel.go

@@ -34,13 +34,22 @@ func Healthz(w http.ResponseWriter, _ *http.Request, _ httprouter.Params) {
 
 func Execute(opts *CostModelOpts) error {
 	log.Infof("Starting cost-model version %s", version.FriendlyVersion())
-	a := costmodel.Initialize()
+	log.Infof("Kubernetes enabled: %t", env.IsKubernetesEnabled())
 
-	err := StartExportWorker(context.Background(), a.Model)
-	if err != nil {
-		log.Errorf("couldn't start CSV export worker: %v", err)
+	var a *costmodel.Accesses
+
+	if env.IsKubernetesEnabled() {
+		a = costmodel.Initialize()
+		err := StartExportWorker(context.Background(), a.Model)
+		if err != nil {
+			log.Errorf("couldn't start CSV export worker: %v", err)
+		}
+	} else {
+		a = costmodel.InitializeWithoutKubernetes()
+		log.Debugf("Cloud Cost config path: %s", env.GetCloudCostConfigPath())
 	}
 
+	log.Infof("Cloud Costs enabled: %t", env.IsCloudCostEnabled())
 	if env.IsCloudCostEnabled() {
 		repo := cloudcost.NewMemoryRepository()
 		a.CloudCostPipelineService = cloudcost.NewPipelineService(repo, a.CloudConfigController, cloudcost.DefaultIngestorConfiguration())
@@ -50,9 +59,12 @@ func Execute(opts *CostModelOpts) error {
 
 	rootMux := http.NewServeMux()
 	a.Router.GET("/healthz", Healthz)
-	a.Router.GET("/allocation", a.ComputeAllocationHandler)
-	a.Router.GET("/allocation/summary", a.ComputeAllocationHandlerSummary)
-	a.Router.GET("/assets", a.ComputeAssetsHandler)
+
+	if env.IsKubernetesEnabled() {
+		a.Router.GET("/allocation", a.ComputeAllocationHandler)
+		a.Router.GET("/allocation/summary", a.ComputeAllocationHandlerSummary)
+		a.Router.GET("/assets", a.ComputeAssetsHandler)
+	}
 
 	a.Router.GET("/cloudCost", a.CloudCostQueryService.GetCloudCostHandler())
 	a.Router.GET("/cloudCost/view/graph", a.CloudCostQueryService.GetCloudCostViewGraphHandler())

+ 28 - 0
pkg/costmodel/router.go

@@ -1830,6 +1830,34 @@ func Initialize(additionalConfigWatchers ...*watcher.ConfigMapWatcher) *Accesses
 	return a
 }
 
+func InitializeWithoutKubernetes() *Accesses {
+	var err error
+	if errorReportingEnabled {
+		err = sentry.Init(sentry.ClientOptions{Release: version.FriendlyVersion()})
+		if err != nil {
+			log.Infof("Failed to initialize sentry for error reporting")
+		} else {
+			err = errors.SetPanicHandler(handlePanic)
+			if err != nil {
+				log.Infof("Failed to set panic handler: %s", err)
+			}
+		}
+	}
+
+	a := &Accesses{
+		Router:                httprouter.New(),
+		CloudConfigController: cloudconfig.NewController(nil),
+		httpServices:          services.NewCostModelServices(),
+	}
+
+	a.Router.GET("/logs/level", a.GetLogLevel)
+	a.Router.POST("/logs/level", a.SetLogLevel)
+
+	a.httpServices.RegisterAll(a.Router)
+
+	return a
+}
+
 func writeErrorResponse(w http.ResponseWriter, code int, message string) {
 	out := map[string]string{
 		"message": message,

+ 11 - 0
pkg/env/costmodelenv.go

@@ -113,7 +113,10 @@ const (
 
 	DataRetentionDailyResolutionDaysEnvVar = "DATA_RETENTION_DAILY_RESOLUTION_DAYS"
 
+	// We assume that Kubernetes is enabled if there is a KUBERNETES_PORT environment variable present
+	KubernetesEnabledEnvVar         = "KUBERNETES_PORT"
 	CloudCostEnabledEnvVar          = "CLOUD_COST_ENABLED"
+	CloudCostConfigPath             = "CLOUD_COST_CONFIG_PATH"
 	CloudCostMonthToDateIntervalVar = "CLOUD_COST_MONTH_TO_DATE_INTERVAL"
 	CloudCostRefreshRateHoursEnvVar = "CLOUD_COST_REFRESH_RATE_HOURS"
 	CloudCostQueryWindowDaysEnvVar  = "CLOUD_COST_QUERY_WINDOW_DAYS"
@@ -614,10 +617,18 @@ func GetDataRetentionDailyResolutionDays() int64 {
 	return env.GetInt64(DataRetentionDailyResolutionDaysEnvVar, 15)
 }
 
+func IsKubernetesEnabled() bool {
+	return env.Get(KubernetesEnabledEnvVar, "") != ""
+}
+
 func IsCloudCostEnabled() bool {
 	return env.GetBool(CloudCostEnabledEnvVar, false)
 }
 
+func GetCloudCostConfigPath() string {
+	return env.Get(CloudCostConfigPath, "cloud-integration.json")
+}
+
 func GetCloudCostMonthToDateInterval() int {
 	return env.GetInt(CloudCostMonthToDateIntervalVar, 6)
 }

+ 69 - 0
pkg/env/costmodelenv_test.go

@@ -123,3 +123,72 @@ func TestGetExportCSVMaxDays(t *testing.T) {
 		})
 	}
 }
+
+func TestGetKubernetesEnabled(t *testing.T) {
+	tests := []struct {
+		name string
+		want bool
+		pre  func()
+	}{
+		{
+			name: "Ensure the default value is false",
+			want: false,
+		},
+		{
+			name: "Ensure the value is true when KUBERNETES_PORT has a value",
+			want: true,
+			pre: func() {
+				os.Setenv("KUBERNETES_PORT", "tcp://10.43.0.1:443")
+			},
+		},
+	}
+	for _, tt := range tests {
+		if tt.pre != nil {
+			tt.pre()
+		}
+		t.Run(tt.name, func(t *testing.T) {
+			if got := IsKubernetesEnabled(); got != tt.want {
+				t.Errorf("IsKubernetesEnabled() = %v, want %v", got, tt.want)
+			}
+		})
+	}
+
+}
+
+func TestGetCloudCostConfigPath(t *testing.T) {
+	tests := []struct {
+		name string
+		want string
+		pre  func()
+	}{
+		{
+			name: "Ensure the default value is 'cloud-integration.json'",
+			want: "cloud-integration.json",
+		},
+		{
+			name: "Ensure the value is 'cloud-integration.json' when CLOUD_COST_CONFIG_PATH is set to ''",
+			want: "cloud-integration.json",
+			pre: func() {
+				os.Setenv("CLOUD_COST_CONFIG_PATH", "")
+			},
+		},
+		{
+			name: "Ensure the value is 'flying-pig.json' when CLOUD_COST_CONFIG_PATH is set to 'flying-pig.json'",
+			want: "flying-pig.json",
+			pre: func() {
+				os.Setenv("CLOUD_COST_CONFIG_PATH", "flying-pig.json")
+			},
+		},
+	}
+	for _, tt := range tests {
+		if tt.pre != nil {
+			tt.pre()
+		}
+		t.Run(tt.name, func(t *testing.T) {
+			if got := GetCloudCostConfigPath(); got != tt.want {
+				t.Errorf("GetCloudCostConfigPath() = %v, want %v", got, tt.want)
+			}
+		})
+	}
+
+}