router.go 55 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830
  1. package costmodel
  2. import (
  3. "context"
  4. "encoding/base64"
  5. "fmt"
  6. "io"
  7. "net/http"
  8. "os"
  9. "path"
  10. "reflect"
  11. "regexp"
  12. "strconv"
  13. "strings"
  14. "sync"
  15. "time"
  16. "github.com/microcosm-cc/bluemonday"
  17. "github.com/opencost/opencost/pkg/config"
  18. "github.com/opencost/opencost/pkg/kubeconfig"
  19. "github.com/opencost/opencost/pkg/metrics"
  20. "github.com/opencost/opencost/pkg/services"
  21. "github.com/opencost/opencost/pkg/util/httputil"
  22. "github.com/opencost/opencost/pkg/util/timeutil"
  23. "github.com/opencost/opencost/pkg/util/watcher"
  24. "github.com/opencost/opencost/pkg/version"
  25. "github.com/spf13/viper"
  26. v1 "k8s.io/api/core/v1"
  27. "github.com/julienschmidt/httprouter"
  28. sentry "github.com/getsentry/sentry-go"
  29. "github.com/opencost/opencost/pkg/cloud"
  30. "github.com/opencost/opencost/pkg/cloud/azure"
  31. "github.com/opencost/opencost/pkg/cloud/models"
  32. "github.com/opencost/opencost/pkg/cloud/utils"
  33. "github.com/opencost/opencost/pkg/clustercache"
  34. "github.com/opencost/opencost/pkg/costmodel/clusters"
  35. "github.com/opencost/opencost/pkg/env"
  36. "github.com/opencost/opencost/pkg/errors"
  37. "github.com/opencost/opencost/pkg/kubecost"
  38. "github.com/opencost/opencost/pkg/log"
  39. "github.com/opencost/opencost/pkg/prom"
  40. "github.com/opencost/opencost/pkg/thanos"
  41. "github.com/opencost/opencost/pkg/util/json"
  42. prometheus "github.com/prometheus/client_golang/api"
  43. prometheusAPI "github.com/prometheus/client_golang/api/prometheus/v1"
  44. appsv1 "k8s.io/api/apps/v1"
  45. metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  46. "github.com/patrickmn/go-cache"
  47. "k8s.io/client-go/kubernetes"
  48. )
  49. var sanitizePolicy = bluemonday.UGCPolicy()
  50. const (
  51. RFC3339Milli = "2006-01-02T15:04:05.000Z"
  52. maxCacheMinutes1d = 11
  53. maxCacheMinutes2d = 17
  54. maxCacheMinutes7d = 37
  55. maxCacheMinutes30d = 137
  56. CustomPricingSetting = "CustomPricing"
  57. DiscountSetting = "Discount"
  58. epRules = apiPrefix + "/rules"
  59. LogSeparator = "+-------------------------------------------------------------------------------------"
  60. )
  61. var (
  62. // gitCommit is set by the build system
  63. gitCommit string
  64. // ANSIRegex matches ANSI escape and colors https://en.wikipedia.org/wiki/ANSI_escape_code
  65. ANSIRegex = regexp.MustCompile("\x1b\\[[0-9;]*m")
  66. )
  67. // Accesses defines a singleton application instance, providing access to
  68. // Prometheus, Kubernetes, the cloud provider, and caches.
  69. type Accesses struct {
  70. Router *httprouter.Router
  71. PrometheusClient prometheus.Client
  72. ThanosClient prometheus.Client
  73. KubeClientSet kubernetes.Interface
  74. ClusterCache clustercache.ClusterCache
  75. ClusterMap clusters.ClusterMap
  76. CloudProvider models.Provider
  77. ConfigFileManager *config.ConfigFileManager
  78. ClusterInfoProvider clusters.ClusterInfoProvider
  79. Model *CostModel
  80. MetricsEmitter *CostModelMetricsEmitter
  81. OutOfClusterCache *cache.Cache
  82. AggregateCache *cache.Cache
  83. CostDataCache *cache.Cache
  84. ClusterCostsCache *cache.Cache
  85. CacheExpiration map[time.Duration]time.Duration
  86. AggAPI Aggregator
  87. // SettingsCache stores current state of app settings
  88. SettingsCache *cache.Cache
  89. // settingsSubscribers tracks channels through which changes to different
  90. // settings will be published in a pub/sub model
  91. settingsSubscribers map[string][]chan string
  92. settingsMutex sync.Mutex
  93. // registered http service instances
  94. httpServices services.HTTPServices
  95. }
  96. // GetPrometheusClient decides whether the default Prometheus client or the Thanos client
  97. // should be used.
  98. func (a *Accesses) GetPrometheusClient(remote bool) prometheus.Client {
  99. // Use Thanos Client if it exists (enabled) and remote flag set
  100. var pc prometheus.Client
  101. if remote && a.ThanosClient != nil {
  102. pc = a.ThanosClient
  103. } else {
  104. pc = a.PrometheusClient
  105. }
  106. return pc
  107. }
  108. // GetCacheExpiration looks up and returns custom cache expiration for the given duration.
  109. // If one does not exists, it returns the default cache expiration, which is defined by
  110. // the particular cache.
  111. func (a *Accesses) GetCacheExpiration(dur time.Duration) time.Duration {
  112. if expiration, ok := a.CacheExpiration[dur]; ok {
  113. return expiration
  114. }
  115. return cache.DefaultExpiration
  116. }
  117. // GetCacheRefresh determines how long to wait before refreshing the cache for the given duration,
  118. // which is done 1 minute before we expect the cache to expire, or 1 minute if expiration is
  119. // not found or is less than 2 minutes.
  120. func (a *Accesses) GetCacheRefresh(dur time.Duration) time.Duration {
  121. expiry := a.GetCacheExpiration(dur).Minutes()
  122. if expiry <= 2.0 {
  123. return time.Minute
  124. }
  125. mins := time.Duration(expiry/2.0) * time.Minute
  126. return mins
  127. }
  128. func (a *Accesses) ClusterCostsFromCacheHandler(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
  129. w.Header().Set("Content-Type", "application/json")
  130. duration := 24 * time.Hour
  131. offset := time.Minute
  132. durationHrs := "24h"
  133. fmtOffset := "1m"
  134. pClient := a.GetPrometheusClient(true)
  135. key := fmt.Sprintf("%s:%s", durationHrs, fmtOffset)
  136. if data, valid := a.ClusterCostsCache.Get(key); valid {
  137. clusterCosts := data.(map[string]*ClusterCosts)
  138. w.Write(WrapDataWithMessage(clusterCosts, nil, "clusterCosts cache hit"))
  139. } else {
  140. data, err := a.ComputeClusterCosts(pClient, a.CloudProvider, duration, offset, true)
  141. w.Write(WrapDataWithMessage(data, err, fmt.Sprintf("clusterCosts cache miss: %s", key)))
  142. }
  143. }
  144. type Response struct {
  145. Code int `json:"code"`
  146. Status string `json:"status"`
  147. Data interface{} `json:"data"`
  148. Message string `json:"message,omitempty"`
  149. Warning string `json:"warning,omitempty"`
  150. }
  151. // FilterFunc is a filter that returns true iff the given CostData should be filtered out, and the environment that was used as the filter criteria, if it was an aggregate
  152. type FilterFunc func(*CostData) (bool, string)
  153. // FilterCostData allows through only CostData that matches all the given filter functions
  154. func FilterCostData(data map[string]*CostData, retains []FilterFunc, filters []FilterFunc) (map[string]*CostData, int, map[string]int) {
  155. result := make(map[string]*CostData)
  156. filteredEnvironments := make(map[string]int)
  157. filteredContainers := 0
  158. DataLoop:
  159. for key, datum := range data {
  160. for _, rf := range retains {
  161. if ok, _ := rf(datum); ok {
  162. result[key] = datum
  163. // if any retain function passes, the data is retained and move on
  164. continue DataLoop
  165. }
  166. }
  167. for _, ff := range filters {
  168. if ok, environment := ff(datum); !ok {
  169. if environment != "" {
  170. filteredEnvironments[environment]++
  171. }
  172. filteredContainers++
  173. // if any filter function check fails, move on to the next datum
  174. continue DataLoop
  175. }
  176. }
  177. result[key] = datum
  178. }
  179. return result, filteredContainers, filteredEnvironments
  180. }
  181. func filterFields(fields string, data map[string]*CostData) map[string]CostData {
  182. fs := strings.Split(fields, ",")
  183. fmap := make(map[string]bool)
  184. for _, f := range fs {
  185. fieldNameLower := strings.ToLower(f) // convert to go struct name by uppercasing first letter
  186. log.Debugf("to delete: %s", fieldNameLower)
  187. fmap[fieldNameLower] = true
  188. }
  189. filteredData := make(map[string]CostData)
  190. for cname, costdata := range data {
  191. s := reflect.TypeOf(*costdata)
  192. val := reflect.ValueOf(*costdata)
  193. costdata2 := CostData{}
  194. cd2 := reflect.New(reflect.Indirect(reflect.ValueOf(costdata2)).Type()).Elem()
  195. n := s.NumField()
  196. for i := 0; i < n; i++ {
  197. field := s.Field(i)
  198. value := val.Field(i)
  199. value2 := cd2.Field(i)
  200. if _, ok := fmap[strings.ToLower(field.Name)]; !ok {
  201. value2.Set(reflect.Value(value))
  202. }
  203. }
  204. filteredData[cname] = cd2.Interface().(CostData)
  205. }
  206. return filteredData
  207. }
  208. func normalizeTimeParam(param string) (string, error) {
  209. if param == "" {
  210. return "", fmt.Errorf("invalid time param")
  211. }
  212. // convert days to hours
  213. if param[len(param)-1:] == "d" {
  214. count := param[:len(param)-1]
  215. val, err := strconv.ParseInt(count, 10, 64)
  216. if err != nil {
  217. return "", err
  218. }
  219. val = val * 24
  220. param = fmt.Sprintf("%dh", val)
  221. }
  222. return param, nil
  223. }
  224. // ParsePercentString takes a string of expected format "N%" and returns a floating point 0.0N.
  225. // If the "%" symbol is missing, it just returns 0.0N. Empty string is interpreted as "0%" and
  226. // return 0.0.
  227. func ParsePercentString(percentStr string) (float64, error) {
  228. if len(percentStr) == 0 {
  229. return 0.0, nil
  230. }
  231. if percentStr[len(percentStr)-1:] == "%" {
  232. percentStr = percentStr[:len(percentStr)-1]
  233. }
  234. discount, err := strconv.ParseFloat(percentStr, 64)
  235. if err != nil {
  236. return 0.0, err
  237. }
  238. discount *= 0.01
  239. return discount, nil
  240. }
  241. func WrapData(data interface{}, err error) []byte {
  242. var resp []byte
  243. if err != nil {
  244. log.Errorf("Error returned to client: %s", err.Error())
  245. resp, _ = json.Marshal(&Response{
  246. Code: http.StatusInternalServerError,
  247. Status: "error",
  248. Message: err.Error(),
  249. Data: data,
  250. })
  251. } else {
  252. resp, err = json.Marshal(&Response{
  253. Code: http.StatusOK,
  254. Status: "success",
  255. Data: data,
  256. })
  257. if err != nil {
  258. log.Errorf("error marshaling response json: %s", err.Error())
  259. }
  260. }
  261. return resp
  262. }
  263. func WrapDataWithMessage(data interface{}, err error, message string) []byte {
  264. var resp []byte
  265. if err != nil {
  266. log.Errorf("Error returned to client: %s", err.Error())
  267. resp, _ = json.Marshal(&Response{
  268. Code: http.StatusInternalServerError,
  269. Status: "error",
  270. Message: err.Error(),
  271. Data: data,
  272. })
  273. } else {
  274. resp, _ = json.Marshal(&Response{
  275. Code: http.StatusOK,
  276. Status: "success",
  277. Data: data,
  278. Message: message,
  279. })
  280. }
  281. return resp
  282. }
  283. func WrapDataWithWarning(data interface{}, err error, warning string) []byte {
  284. var resp []byte
  285. if err != nil {
  286. log.Errorf("Error returned to client: %s", err.Error())
  287. resp, _ = json.Marshal(&Response{
  288. Code: http.StatusInternalServerError,
  289. Status: "error",
  290. Message: err.Error(),
  291. Warning: warning,
  292. Data: data,
  293. })
  294. } else {
  295. resp, _ = json.Marshal(&Response{
  296. Code: http.StatusOK,
  297. Status: "success",
  298. Data: data,
  299. Warning: warning,
  300. })
  301. }
  302. return resp
  303. }
  304. func WrapDataWithMessageAndWarning(data interface{}, err error, message, warning string) []byte {
  305. var resp []byte
  306. if err != nil {
  307. log.Errorf("Error returned to client: %s", err.Error())
  308. resp, _ = json.Marshal(&Response{
  309. Code: http.StatusInternalServerError,
  310. Status: "error",
  311. Message: err.Error(),
  312. Warning: warning,
  313. Data: data,
  314. })
  315. } else {
  316. resp, _ = json.Marshal(&Response{
  317. Code: http.StatusOK,
  318. Status: "success",
  319. Data: data,
  320. Message: message,
  321. Warning: warning,
  322. })
  323. }
  324. return resp
  325. }
  326. // wrapAsObjectItems wraps a slice of items into an object containing a single items list
  327. // allows our k8s proxy methods to emulate a List() request to k8s API
  328. func wrapAsObjectItems(items interface{}) map[string]interface{} {
  329. return map[string]interface{}{
  330. "items": items,
  331. }
  332. }
  333. // RefreshPricingData needs to be called when a new node joins the fleet, since we cache the relevant subsets of pricing data to avoid storing the whole thing.
  334. func (a *Accesses) RefreshPricingData(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
  335. w.Header().Set("Content-Type", "application/json")
  336. w.Header().Set("Access-Control-Allow-Origin", "*")
  337. err := a.CloudProvider.DownloadPricingData()
  338. if err != nil {
  339. log.Errorf("Error refreshing pricing data: %s", err.Error())
  340. }
  341. w.Write(WrapData(nil, err))
  342. }
  343. func (a *Accesses) CostDataModel(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
  344. w.Header().Set("Content-Type", "application/json")
  345. w.Header().Set("Access-Control-Allow-Origin", "*")
  346. window := r.URL.Query().Get("timeWindow")
  347. offset := r.URL.Query().Get("offset")
  348. fields := r.URL.Query().Get("filterFields")
  349. namespace := r.URL.Query().Get("namespace")
  350. if offset != "" {
  351. offset = "offset " + offset
  352. }
  353. data, err := a.Model.ComputeCostData(a.PrometheusClient, a.CloudProvider, window, offset, namespace)
  354. if fields != "" {
  355. filteredData := filterFields(fields, data)
  356. w.Write(WrapData(filteredData, err))
  357. } else {
  358. w.Write(WrapData(data, err))
  359. }
  360. }
  361. func (a *Accesses) ClusterCosts(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
  362. w.Header().Set("Content-Type", "application/json")
  363. w.Header().Set("Access-Control-Allow-Origin", "*")
  364. window := r.URL.Query().Get("window")
  365. offset := r.URL.Query().Get("offset")
  366. if window == "" {
  367. w.Write(WrapData(nil, fmt.Errorf("missing window argument")))
  368. return
  369. }
  370. windowDur, err := timeutil.ParseDuration(window)
  371. if err != nil {
  372. w.Write(WrapData(nil, fmt.Errorf("error parsing window (%s): %s", window, err)))
  373. return
  374. }
  375. // offset is not a required parameter
  376. var offsetDur time.Duration
  377. if offset != "" {
  378. offsetDur, err = timeutil.ParseDuration(offset)
  379. if err != nil {
  380. w.Write(WrapData(nil, fmt.Errorf("error parsing offset (%s): %s", offset, err)))
  381. return
  382. }
  383. }
  384. useThanos, _ := strconv.ParseBool(r.URL.Query().Get("multi"))
  385. if useThanos && !thanos.IsEnabled() {
  386. w.Write(WrapData(nil, fmt.Errorf("Multi=true while Thanos is not enabled.")))
  387. return
  388. }
  389. var client prometheus.Client
  390. if useThanos {
  391. client = a.ThanosClient
  392. offsetDur = thanos.OffsetDuration()
  393. } else {
  394. client = a.PrometheusClient
  395. }
  396. data, err := a.ComputeClusterCosts(client, a.CloudProvider, windowDur, offsetDur, true)
  397. w.Write(WrapData(data, err))
  398. }
  399. func (a *Accesses) ClusterCostsOverTime(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
  400. w.Header().Set("Content-Type", "application/json")
  401. w.Header().Set("Access-Control-Allow-Origin", "*")
  402. start := r.URL.Query().Get("start")
  403. end := r.URL.Query().Get("end")
  404. window := r.URL.Query().Get("window")
  405. offset := r.URL.Query().Get("offset")
  406. if window == "" {
  407. w.Write(WrapData(nil, fmt.Errorf("missing window argument")))
  408. return
  409. }
  410. windowDur, err := timeutil.ParseDuration(window)
  411. if err != nil {
  412. w.Write(WrapData(nil, fmt.Errorf("error parsing window (%s): %s", window, err)))
  413. return
  414. }
  415. // offset is not a required parameter
  416. var offsetDur time.Duration
  417. if offset != "" {
  418. offsetDur, err = timeutil.ParseDuration(offset)
  419. if err != nil {
  420. w.Write(WrapData(nil, fmt.Errorf("error parsing offset (%s): %s", offset, err)))
  421. return
  422. }
  423. }
  424. data, err := ClusterCostsOverTime(a.PrometheusClient, a.CloudProvider, start, end, windowDur, offsetDur)
  425. w.Write(WrapData(data, err))
  426. }
  427. func (a *Accesses) CostDataModelRange(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
  428. w.Header().Set("Content-Type", "application/json")
  429. w.Header().Set("Access-Control-Allow-Origin", "*")
  430. startStr := r.URL.Query().Get("start")
  431. endStr := r.URL.Query().Get("end")
  432. windowStr := r.URL.Query().Get("window")
  433. fields := r.URL.Query().Get("filterFields")
  434. namespace := r.URL.Query().Get("namespace")
  435. cluster := r.URL.Query().Get("cluster")
  436. remote := r.URL.Query().Get("remote")
  437. remoteEnabled := env.IsRemoteEnabled() && remote != "false"
  438. layout := "2006-01-02T15:04:05.000Z"
  439. start, err := time.Parse(layout, startStr)
  440. if err != nil {
  441. w.Write(WrapDataWithMessage(nil, fmt.Errorf("invalid start date: %s", startStr), fmt.Sprintf("invalid start date: %s", startStr)))
  442. return
  443. }
  444. end, err := time.Parse(layout, endStr)
  445. if err != nil {
  446. w.Write(WrapDataWithMessage(nil, fmt.Errorf("invalid end date: %s", endStr), fmt.Sprintf("invalid end date: %s", endStr)))
  447. return
  448. }
  449. window := kubecost.NewWindow(&start, &end)
  450. if window.IsOpen() || !window.HasDuration() || window.IsNegative() {
  451. w.Write(WrapDataWithMessage(nil, fmt.Errorf("invalid date range: %s", window), fmt.Sprintf("invalid date range: %s", window)))
  452. return
  453. }
  454. resolution := time.Hour
  455. if resDur, err := time.ParseDuration(windowStr); err == nil {
  456. resolution = resDur
  457. }
  458. // Use Thanos Client if it exists (enabled) and remote flag set
  459. var pClient prometheus.Client
  460. if remote != "false" && a.ThanosClient != nil {
  461. pClient = a.ThanosClient
  462. } else {
  463. pClient = a.PrometheusClient
  464. }
  465. data, err := a.Model.ComputeCostDataRange(pClient, a.CloudProvider, window, resolution, namespace, cluster, remoteEnabled)
  466. if err != nil {
  467. w.Write(WrapData(nil, err))
  468. }
  469. if fields != "" {
  470. filteredData := filterFields(fields, data)
  471. w.Write(WrapData(filteredData, err))
  472. } else {
  473. w.Write(WrapData(data, err))
  474. }
  475. }
  476. func parseAggregations(customAggregation, aggregator, filterType string) (string, []string, string) {
  477. var key string
  478. var filter string
  479. var val []string
  480. if customAggregation != "" {
  481. key = customAggregation
  482. filter = filterType
  483. val = strings.Split(customAggregation, ",")
  484. } else {
  485. aggregations := strings.Split(aggregator, ",")
  486. for i, agg := range aggregations {
  487. aggregations[i] = "kubernetes_" + agg
  488. }
  489. key = strings.Join(aggregations, ",")
  490. filter = "kubernetes_" + filterType
  491. val = aggregations
  492. }
  493. return key, val, filter
  494. }
  495. func (a *Accesses) GetAllNodePricing(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
  496. w.Header().Set("Content-Type", "application/json")
  497. w.Header().Set("Access-Control-Allow-Origin", "*")
  498. data, err := a.CloudProvider.AllNodePricing()
  499. w.Write(WrapData(data, err))
  500. }
  501. func (a *Accesses) GetConfigs(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
  502. w.Header().Set("Content-Type", "application/json")
  503. w.Header().Set("Access-Control-Allow-Origin", "*")
  504. data, err := a.CloudProvider.GetConfig()
  505. w.Write(WrapData(data, err))
  506. }
  507. func (a *Accesses) UpdateSpotInfoConfigs(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
  508. w.Header().Set("Content-Type", "application/json")
  509. w.Header().Set("Access-Control-Allow-Origin", "*")
  510. data, err := a.CloudProvider.UpdateConfig(r.Body, cloud.SpotInfoUpdateType)
  511. if err != nil {
  512. w.Write(WrapData(data, err))
  513. return
  514. }
  515. w.Write(WrapData(data, err))
  516. err = a.CloudProvider.DownloadPricingData()
  517. if err != nil {
  518. log.Errorf("Error redownloading data on config update: %s", err.Error())
  519. }
  520. return
  521. }
  522. func (a *Accesses) UpdateAthenaInfoConfigs(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
  523. w.Header().Set("Content-Type", "application/json")
  524. w.Header().Set("Access-Control-Allow-Origin", "*")
  525. data, err := a.CloudProvider.UpdateConfig(r.Body, cloud.AthenaInfoUpdateType)
  526. if err != nil {
  527. w.Write(WrapData(data, err))
  528. return
  529. }
  530. w.Write(WrapData(data, err))
  531. return
  532. }
  533. func (a *Accesses) UpdateBigQueryInfoConfigs(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
  534. w.Header().Set("Content-Type", "application/json")
  535. w.Header().Set("Access-Control-Allow-Origin", "*")
  536. data, err := a.CloudProvider.UpdateConfig(r.Body, cloud.BigqueryUpdateType)
  537. if err != nil {
  538. w.Write(WrapData(data, err))
  539. return
  540. }
  541. w.Write(WrapData(data, err))
  542. return
  543. }
  544. func (a *Accesses) UpdateAzureStorageConfigs(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
  545. w.Header().Set("Content-Type", "application/json")
  546. w.Header().Set("Access-Control-Allow-Origin", "*")
  547. data, err := a.CloudProvider.UpdateConfig(r.Body, azure.AzureStorageUpdateType)
  548. if err != nil {
  549. w.Write(WrapData(data, err))
  550. return
  551. }
  552. w.Write(WrapData(data, err))
  553. return
  554. }
  555. func (a *Accesses) UpdateConfigByKey(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
  556. w.Header().Set("Content-Type", "application/json")
  557. w.Header().Set("Access-Control-Allow-Origin", "*")
  558. data, err := a.CloudProvider.UpdateConfig(r.Body, "")
  559. if err != nil {
  560. w.Write(WrapData(data, err))
  561. return
  562. }
  563. w.Write(WrapData(data, err))
  564. return
  565. }
  566. func (a *Accesses) ManagementPlatform(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
  567. w.Header().Set("Content-Type", "application/json")
  568. w.Header().Set("Access-Control-Allow-Origin", "*")
  569. data, err := a.CloudProvider.GetManagementPlatform()
  570. if err != nil {
  571. w.Write(WrapData(data, err))
  572. return
  573. }
  574. w.Write(WrapData(data, err))
  575. return
  576. }
  577. func (a *Accesses) ClusterInfo(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
  578. w.Header().Set("Content-Type", "application/json")
  579. w.Header().Set("Access-Control-Allow-Origin", "*")
  580. data := a.ClusterInfoProvider.GetClusterInfo()
  581. w.Write(WrapData(data, nil))
  582. }
  583. func (a *Accesses) GetClusterInfoMap(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
  584. w.Header().Set("Content-Type", "application/json")
  585. w.Header().Set("Access-Control-Allow-Origin", "*")
  586. data := a.ClusterMap.AsMap()
  587. w.Write(WrapData(data, nil))
  588. }
  589. func (a *Accesses) GetServiceAccountStatus(w http.ResponseWriter, _ *http.Request, _ httprouter.Params) {
  590. w.Header().Set("Content-Type", "application/json")
  591. w.Header().Set("Access-Control-Allow-Origin", "*")
  592. w.Write(WrapData(a.CloudProvider.ServiceAccountStatus(), nil))
  593. }
  594. func (a *Accesses) GetPricingSourceStatus(w http.ResponseWriter, _ *http.Request, _ httprouter.Params) {
  595. w.Header().Set("Content-Type", "application/json")
  596. w.Header().Set("Access-Control-Allow-Origin", "*")
  597. w.Write(WrapData(a.CloudProvider.PricingSourceStatus(), nil))
  598. }
  599. func (a *Accesses) GetPricingSourceCounts(w http.ResponseWriter, _ *http.Request, _ httprouter.Params) {
  600. w.Header().Set("Content-Type", "application/json")
  601. w.Header().Set("Access-Control-Allow-Origin", "*")
  602. w.Write(WrapData(a.Model.GetPricingSourceCounts()))
  603. }
  604. func (a *Accesses) GetPricingSourceSummary(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
  605. w.Header().Set("Content-Type", "application/json")
  606. w.Header().Set("Access-Control-Allow-Origin", "*")
  607. data := a.CloudProvider.PricingSourceSummary()
  608. w.Write(WrapData(data, nil))
  609. }
  610. func (a *Accesses) GetPrometheusMetadata(w http.ResponseWriter, _ *http.Request, _ httprouter.Params) {
  611. w.Header().Set("Content-Type", "application/json")
  612. w.Header().Set("Access-Control-Allow-Origin", "*")
  613. w.Write(WrapData(prom.Validate(a.PrometheusClient)))
  614. }
  615. func (a *Accesses) PrometheusQuery(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
  616. w.Header().Set("Content-Type", "application/json")
  617. w.Header().Set("Access-Control-Allow-Origin", "*")
  618. qp := httputil.NewQueryParams(r.URL.Query())
  619. query := qp.Get("query", "")
  620. if query == "" {
  621. w.Write(WrapData(nil, fmt.Errorf("Query Parameter 'query' is unset'")))
  622. return
  623. }
  624. // Attempt to parse time as either a unix timestamp or as an RFC3339 value
  625. var timeVal time.Time
  626. timeStr := qp.Get("time", "")
  627. if len(timeStr) > 0 {
  628. if t, err := strconv.ParseInt(timeStr, 10, 64); err == nil {
  629. timeVal = time.Unix(t, 0)
  630. } else if t, err := time.Parse(time.RFC3339, timeStr); err == nil {
  631. timeVal = t
  632. }
  633. // If time is given, but not parse-able, return an error
  634. if timeVal.IsZero() {
  635. http.Error(w, fmt.Sprintf("time must be a unix timestamp or RFC3339 value; illegal value given: %s", timeStr), http.StatusBadRequest)
  636. }
  637. }
  638. ctx := prom.NewNamedContext(a.PrometheusClient, prom.FrontendContextName)
  639. body, err := ctx.RawQuery(query, timeVal)
  640. if err != nil {
  641. w.Write(WrapData(nil, fmt.Errorf("Error running query %s. Error: %s", query, err)))
  642. return
  643. }
  644. w.Write(body)
  645. }
  646. func (a *Accesses) PrometheusQueryRange(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
  647. w.Header().Set("Content-Type", "application/json")
  648. w.Header().Set("Access-Control-Allow-Origin", "*")
  649. qp := httputil.NewQueryParams(r.URL.Query())
  650. query := qp.Get("query", "")
  651. if query == "" {
  652. fmt.Fprintf(w, "Error parsing query from request parameters.")
  653. return
  654. }
  655. start, end, duration, err := toStartEndStep(qp)
  656. if err != nil {
  657. fmt.Fprintf(w, err.Error())
  658. return
  659. }
  660. ctx := prom.NewNamedContext(a.PrometheusClient, prom.FrontendContextName)
  661. body, err := ctx.RawQueryRange(query, start, end, duration)
  662. if err != nil {
  663. fmt.Fprintf(w, "Error running query %s. Error: %s", query, err)
  664. return
  665. }
  666. w.Write(body)
  667. }
  668. func (a *Accesses) ThanosQuery(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
  669. w.Header().Set("Content-Type", "application/json")
  670. w.Header().Set("Access-Control-Allow-Origin", "*")
  671. if !thanos.IsEnabled() {
  672. w.Write(WrapData(nil, fmt.Errorf("ThanosDisabled")))
  673. return
  674. }
  675. qp := httputil.NewQueryParams(r.URL.Query())
  676. query := qp.Get("query", "")
  677. if query == "" {
  678. w.Write(WrapData(nil, fmt.Errorf("Query Parameter 'query' is unset'")))
  679. return
  680. }
  681. // Attempt to parse time as either a unix timestamp or as an RFC3339 value
  682. var timeVal time.Time
  683. timeStr := qp.Get("time", "")
  684. if len(timeStr) > 0 {
  685. if t, err := strconv.ParseInt(timeStr, 10, 64); err == nil {
  686. timeVal = time.Unix(t, 0)
  687. } else if t, err := time.Parse(time.RFC3339, timeStr); err == nil {
  688. timeVal = t
  689. }
  690. // If time is given, but not parse-able, return an error
  691. if timeVal.IsZero() {
  692. http.Error(w, fmt.Sprintf("time must be a unix timestamp or RFC3339 value; illegal value given: %s", timeStr), http.StatusBadRequest)
  693. }
  694. }
  695. ctx := prom.NewNamedContext(a.ThanosClient, prom.FrontendContextName)
  696. body, err := ctx.RawQuery(query, timeVal)
  697. if err != nil {
  698. w.Write(WrapData(nil, fmt.Errorf("Error running query %s. Error: %s", query, err)))
  699. return
  700. }
  701. w.Write(body)
  702. }
  703. func (a *Accesses) ThanosQueryRange(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
  704. w.Header().Set("Content-Type", "application/json")
  705. w.Header().Set("Access-Control-Allow-Origin", "*")
  706. if !thanos.IsEnabled() {
  707. w.Write(WrapData(nil, fmt.Errorf("ThanosDisabled")))
  708. return
  709. }
  710. qp := httputil.NewQueryParams(r.URL.Query())
  711. query := qp.Get("query", "")
  712. if query == "" {
  713. fmt.Fprintf(w, "Error parsing query from request parameters.")
  714. return
  715. }
  716. start, end, duration, err := toStartEndStep(qp)
  717. if err != nil {
  718. fmt.Fprintf(w, err.Error())
  719. return
  720. }
  721. ctx := prom.NewNamedContext(a.ThanosClient, prom.FrontendContextName)
  722. body, err := ctx.RawQueryRange(query, start, end, duration)
  723. if err != nil {
  724. fmt.Fprintf(w, "Error running query %s. Error: %s", query, err)
  725. return
  726. }
  727. w.Write(body)
  728. }
  729. // helper for query range proxy requests
  730. func toStartEndStep(qp httputil.QueryParams) (start, end time.Time, step time.Duration, err error) {
  731. var e error
  732. ss := qp.Get("start", "")
  733. es := qp.Get("end", "")
  734. ds := qp.Get("duration", "")
  735. layout := "2006-01-02T15:04:05.000Z"
  736. start, e = time.Parse(layout, ss)
  737. if e != nil {
  738. err = fmt.Errorf("Error parsing time %s. Error: %s", ss, err)
  739. return
  740. }
  741. end, e = time.Parse(layout, es)
  742. if e != nil {
  743. err = fmt.Errorf("Error parsing time %s. Error: %s", es, err)
  744. return
  745. }
  746. step, e = time.ParseDuration(ds)
  747. if e != nil {
  748. err = fmt.Errorf("Error parsing duration %s. Error: %s", ds, err)
  749. return
  750. }
  751. err = nil
  752. return
  753. }
  754. func (a *Accesses) GetPrometheusQueueState(w http.ResponseWriter, _ *http.Request, _ httprouter.Params) {
  755. w.Header().Set("Content-Type", "application/json")
  756. w.Header().Set("Access-Control-Allow-Origin", "*")
  757. promQueueState, err := prom.GetPrometheusQueueState(a.PrometheusClient)
  758. if err != nil {
  759. w.Write(WrapData(nil, err))
  760. return
  761. }
  762. result := map[string]*prom.PrometheusQueueState{
  763. "prometheus": promQueueState,
  764. }
  765. if thanos.IsEnabled() {
  766. thanosQueueState, err := prom.GetPrometheusQueueState(a.ThanosClient)
  767. if err != nil {
  768. log.Warnf("Error getting Thanos queue state: %s", err)
  769. } else {
  770. result["thanos"] = thanosQueueState
  771. }
  772. }
  773. w.Write(WrapData(result, nil))
  774. }
  775. // GetPrometheusMetrics retrieves availability of Prometheus and Thanos metrics
  776. func (a *Accesses) GetPrometheusMetrics(w http.ResponseWriter, _ *http.Request, _ httprouter.Params) {
  777. w.Header().Set("Content-Type", "application/json")
  778. w.Header().Set("Access-Control-Allow-Origin", "*")
  779. promMetrics := prom.GetPrometheusMetrics(a.PrometheusClient, "")
  780. result := map[string][]*prom.PrometheusDiagnostic{
  781. "prometheus": promMetrics,
  782. }
  783. if thanos.IsEnabled() {
  784. thanosMetrics := prom.GetPrometheusMetrics(a.ThanosClient, thanos.QueryOffset())
  785. result["thanos"] = thanosMetrics
  786. }
  787. w.Write(WrapData(result, nil))
  788. }
  789. func (a *Accesses) GetAllPersistentVolumes(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
  790. w.Header().Set("Content-Type", "application/json")
  791. w.Header().Set("Access-Control-Allow-Origin", "*")
  792. pvList := a.ClusterCache.GetAllPersistentVolumes()
  793. body, err := json.Marshal(wrapAsObjectItems(pvList))
  794. if err != nil {
  795. fmt.Fprintf(w, "Error decoding persistent volumes: "+err.Error())
  796. } else {
  797. w.Write(body)
  798. }
  799. }
  800. func (a *Accesses) GetAllDeployments(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
  801. w.Header().Set("Content-Type", "application/json")
  802. w.Header().Set("Access-Control-Allow-Origin", "*")
  803. qp := httputil.NewQueryParams(r.URL.Query())
  804. namespace := qp.Get("namespace", "")
  805. deploymentsList := a.ClusterCache.GetAllDeployments()
  806. // filter for provided namespace
  807. var deployments []*appsv1.Deployment
  808. if namespace == "" {
  809. deployments = deploymentsList
  810. } else {
  811. deployments = []*appsv1.Deployment{}
  812. for _, d := range deploymentsList {
  813. if d.Namespace == namespace {
  814. deployments = append(deployments, d)
  815. }
  816. }
  817. }
  818. body, err := json.Marshal(wrapAsObjectItems(deployments))
  819. if err != nil {
  820. fmt.Fprintf(w, "Error decoding deployment: "+err.Error())
  821. } else {
  822. w.Write(body)
  823. }
  824. }
  825. func (a *Accesses) GetAllStorageClasses(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
  826. w.Header().Set("Content-Type", "application/json")
  827. w.Header().Set("Access-Control-Allow-Origin", "*")
  828. scList := a.ClusterCache.GetAllStorageClasses()
  829. body, err := json.Marshal(wrapAsObjectItems(scList))
  830. if err != nil {
  831. fmt.Fprintf(w, "Error decoding storageclasses: "+err.Error())
  832. } else {
  833. w.Write(body)
  834. }
  835. }
  836. func (a *Accesses) GetAllStatefulSets(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
  837. w.Header().Set("Content-Type", "application/json")
  838. w.Header().Set("Access-Control-Allow-Origin", "*")
  839. qp := httputil.NewQueryParams(r.URL.Query())
  840. namespace := qp.Get("namespace", "")
  841. statefulSetsList := a.ClusterCache.GetAllStatefulSets()
  842. // filter for provided namespace
  843. var statefulSets []*appsv1.StatefulSet
  844. if namespace == "" {
  845. statefulSets = statefulSetsList
  846. } else {
  847. statefulSets = []*appsv1.StatefulSet{}
  848. for _, ss := range statefulSetsList {
  849. if ss.Namespace == namespace {
  850. statefulSets = append(statefulSets, ss)
  851. }
  852. }
  853. }
  854. body, err := json.Marshal(wrapAsObjectItems(statefulSets))
  855. if err != nil {
  856. fmt.Fprintf(w, "Error decoding deployment: "+err.Error())
  857. } else {
  858. w.Write(body)
  859. }
  860. }
  861. func (a *Accesses) GetAllNodes(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
  862. w.Header().Set("Content-Type", "application/json")
  863. w.Header().Set("Access-Control-Allow-Origin", "*")
  864. nodeList := a.ClusterCache.GetAllNodes()
  865. body, err := json.Marshal(wrapAsObjectItems(nodeList))
  866. if err != nil {
  867. fmt.Fprintf(w, "Error decoding nodes: "+err.Error())
  868. } else {
  869. w.Write(body)
  870. }
  871. }
  872. func (a *Accesses) GetAllPods(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
  873. w.Header().Set("Content-Type", "application/json")
  874. w.Header().Set("Access-Control-Allow-Origin", "*")
  875. podlist := a.ClusterCache.GetAllPods()
  876. body, err := json.Marshal(wrapAsObjectItems(podlist))
  877. if err != nil {
  878. fmt.Fprintf(w, "Error decoding pods: "+err.Error())
  879. } else {
  880. w.Write(body)
  881. }
  882. }
  883. func (a *Accesses) GetAllNamespaces(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
  884. w.Header().Set("Content-Type", "application/json")
  885. w.Header().Set("Access-Control-Allow-Origin", "*")
  886. namespaces := a.ClusterCache.GetAllNamespaces()
  887. body, err := json.Marshal(wrapAsObjectItems(namespaces))
  888. if err != nil {
  889. fmt.Fprintf(w, "Error decoding deployment: "+err.Error())
  890. } else {
  891. w.Write(body)
  892. }
  893. }
  894. func (a *Accesses) GetAllDaemonSets(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
  895. w.Header().Set("Content-Type", "application/json")
  896. w.Header().Set("Access-Control-Allow-Origin", "*")
  897. daemonSets := a.ClusterCache.GetAllDaemonSets()
  898. body, err := json.Marshal(wrapAsObjectItems(daemonSets))
  899. if err != nil {
  900. fmt.Fprintf(w, "Error decoding daemon set: "+err.Error())
  901. } else {
  902. w.Write(body)
  903. }
  904. }
  905. func (a *Accesses) GetPod(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
  906. w.Header().Set("Content-Type", "application/json")
  907. w.Header().Set("Access-Control-Allow-Origin", "*")
  908. podName := ps.ByName("name")
  909. podNamespace := ps.ByName("namespace")
  910. // TODO: ClusterCache API could probably afford to have some better filtering
  911. allPods := a.ClusterCache.GetAllPods()
  912. for _, pod := range allPods {
  913. for _, container := range pod.Spec.Containers {
  914. container.Env = make([]v1.EnvVar, 0)
  915. }
  916. if pod.Namespace == podNamespace && pod.Name == podName {
  917. body, err := json.Marshal(pod)
  918. if err != nil {
  919. fmt.Fprintf(w, "Error decoding pod: "+err.Error())
  920. } else {
  921. w.Write(body)
  922. }
  923. return
  924. }
  925. }
  926. fmt.Fprintf(w, "Pod not found\n")
  927. }
  928. func (a *Accesses) PrometheusRecordingRules(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
  929. w.Header().Set("Content-Type", "application/json")
  930. w.Header().Set("Access-Control-Allow-Origin", "*")
  931. u := a.PrometheusClient.URL(epRules, nil)
  932. req, err := http.NewRequest(http.MethodGet, u.String(), nil)
  933. if err != nil {
  934. fmt.Fprintf(w, "Error creating Prometheus rule request: "+err.Error())
  935. }
  936. _, body, err := a.PrometheusClient.Do(r.Context(), req)
  937. if err != nil {
  938. fmt.Fprintf(w, "Error making Prometheus rule request: "+err.Error())
  939. } else {
  940. w.Write(body)
  941. }
  942. }
  943. func (a *Accesses) PrometheusConfig(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
  944. w.Header().Set("Content-Type", "application/json")
  945. w.Header().Set("Access-Control-Allow-Origin", "*")
  946. pConfig := map[string]string{
  947. "address": env.GetPrometheusServerEndpoint(),
  948. }
  949. body, err := json.Marshal(pConfig)
  950. if err != nil {
  951. fmt.Fprintf(w, "Error marshalling prometheus config")
  952. } else {
  953. w.Write(body)
  954. }
  955. }
  956. func (a *Accesses) PrometheusTargets(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
  957. w.Header().Set("Content-Type", "application/json")
  958. w.Header().Set("Access-Control-Allow-Origin", "*")
  959. u := a.PrometheusClient.URL(epTargets, nil)
  960. req, err := http.NewRequest(http.MethodGet, u.String(), nil)
  961. if err != nil {
  962. fmt.Fprintf(w, "Error creating Prometheus rule request: "+err.Error())
  963. }
  964. _, body, err := a.PrometheusClient.Do(r.Context(), req)
  965. if err != nil {
  966. fmt.Fprintf(w, "Error making Prometheus rule request: "+err.Error())
  967. } else {
  968. w.Write(body)
  969. }
  970. }
  971. func (a *Accesses) GetOrphanedPods(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
  972. w.Header().Set("Content-Type", "application/json")
  973. w.Header().Set("Access-Control-Allow-Origin", "*")
  974. podlist := a.ClusterCache.GetAllPods()
  975. var lonePods []*v1.Pod
  976. for _, pod := range podlist {
  977. if len(pod.OwnerReferences) == 0 {
  978. lonePods = append(lonePods, pod)
  979. }
  980. }
  981. body, err := json.Marshal(lonePods)
  982. if err != nil {
  983. fmt.Fprintf(w, "Error decoding pod: "+err.Error())
  984. } else {
  985. w.Write(body)
  986. }
  987. }
  988. func (a *Accesses) GetInstallNamespace(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
  989. w.Header().Set("Content-Type", "application/json")
  990. w.Header().Set("Access-Control-Allow-Origin", "*")
  991. ns := env.GetKubecostNamespace()
  992. w.Write([]byte(ns))
  993. }
  994. type InstallInfo struct {
  995. Containers []ContainerInfo `json:"containers"`
  996. ClusterInfo map[string]string `json:"clusterInfo"`
  997. Version string `json:"version"`
  998. }
  999. type ContainerInfo struct {
  1000. ContainerName string `json:"containerName"`
  1001. Image string `json:"image"`
  1002. ImageID string `json:"imageID"`
  1003. StartTime string `json:"startTime"`
  1004. Restarts int32 `json:"restarts"`
  1005. }
  1006. func (a *Accesses) GetInstallInfo(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
  1007. w.Header().Set("Content-Type", "application/json")
  1008. w.Header().Set("Access-Control-Allow-Origin", "*")
  1009. pods, err := a.KubeClientSet.CoreV1().Pods(env.GetKubecostNamespace()).List(context.Background(), metav1.ListOptions{
  1010. LabelSelector: "app=cost-analyzer",
  1011. FieldSelector: "status.phase=Running",
  1012. Limit: 1,
  1013. })
  1014. if err != nil {
  1015. writeErrorResponse(w, 500, fmt.Sprintf("Unable to list pods: %s", err.Error()))
  1016. return
  1017. }
  1018. info := InstallInfo{
  1019. ClusterInfo: make(map[string]string),
  1020. Version: version.FriendlyVersion(),
  1021. }
  1022. // If we have zero pods either something is weird with the install since the app selector is not exposed in the helm
  1023. // chart or more likely we are running locally - in either case Images field will return as null
  1024. if len(pods.Items) > 0 {
  1025. for _, pod := range pods.Items {
  1026. for _, container := range pod.Status.ContainerStatuses {
  1027. c := ContainerInfo{
  1028. ContainerName: container.Name,
  1029. Image: container.Image,
  1030. ImageID: container.ImageID,
  1031. StartTime: pod.Status.StartTime.String(),
  1032. Restarts: container.RestartCount,
  1033. }
  1034. info.Containers = append(info.Containers, c)
  1035. }
  1036. }
  1037. }
  1038. nodes := a.ClusterCache.GetAllNodes()
  1039. cachePods := a.ClusterCache.GetAllPods()
  1040. info.ClusterInfo["nodeCount"] = strconv.Itoa(len(nodes))
  1041. info.ClusterInfo["podCount"] = strconv.Itoa(len(cachePods))
  1042. body, err := json.Marshal(info)
  1043. if err != nil {
  1044. writeErrorResponse(w, 500, fmt.Sprintf("Error decoding pod: %s", err.Error()))
  1045. return
  1046. }
  1047. w.Write(body)
  1048. }
  1049. // logsFor pulls the logs for a specific pod, namespace, and container
  1050. func logsFor(c kubernetes.Interface, namespace string, pod string, container string, dur time.Duration, ctx context.Context) (string, error) {
  1051. since := time.Now().UTC().Add(-dur)
  1052. logOpts := v1.PodLogOptions{
  1053. SinceTime: &metav1.Time{Time: since},
  1054. }
  1055. if container != "" {
  1056. logOpts.Container = container
  1057. }
  1058. req := c.CoreV1().Pods(namespace).GetLogs(pod, &logOpts)
  1059. reader, err := req.Stream(ctx)
  1060. if err != nil {
  1061. return "", err
  1062. }
  1063. podLogs, err := io.ReadAll(reader)
  1064. if err != nil {
  1065. return "", err
  1066. }
  1067. // If color is already disabled then we don't need to process the logs
  1068. // to drop ANSI colors
  1069. if !viper.GetBool("disable-log-color") {
  1070. podLogs = ANSIRegex.ReplaceAll(podLogs, []byte{})
  1071. }
  1072. return string(podLogs), nil
  1073. }
  1074. func (a *Accesses) GetPodLogs(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
  1075. w.Header().Set("Content-Type", "application/json")
  1076. w.Header().Set("Access-Control-Allow-Origin", "*")
  1077. qp := httputil.NewQueryParams(r.URL.Query())
  1078. ns := qp.Get("namespace", env.GetKubecostNamespace())
  1079. pod := qp.Get("pod", "")
  1080. selector := qp.Get("selector", "")
  1081. container := qp.Get("container", "")
  1082. since := qp.Get("since", "24h")
  1083. sinceDuration, err := time.ParseDuration(since)
  1084. if err != nil {
  1085. fmt.Fprintf(w, "Invalid Duration String: "+err.Error())
  1086. return
  1087. }
  1088. var logResult string
  1089. appendLog := func(ns string, pod string, container string, l string) {
  1090. if l == "" {
  1091. return
  1092. }
  1093. logResult += fmt.Sprintf("%s\n| %s:%s:%s\n%s\n%s\n\n", LogSeparator, ns, pod, container, LogSeparator, l)
  1094. }
  1095. if pod != "" {
  1096. pd, err := a.KubeClientSet.CoreV1().Pods(ns).Get(r.Context(), pod, metav1.GetOptions{})
  1097. if err != nil {
  1098. fmt.Fprintf(w, "Error Finding Pod: "+err.Error())
  1099. return
  1100. }
  1101. if container != "" {
  1102. var foundContainer bool
  1103. for _, cont := range pd.Spec.Containers {
  1104. if strings.EqualFold(cont.Name, container) {
  1105. foundContainer = true
  1106. break
  1107. }
  1108. }
  1109. if !foundContainer {
  1110. fmt.Fprintf(w, "Could not find container: "+container)
  1111. return
  1112. }
  1113. }
  1114. logs, err := logsFor(a.KubeClientSet, ns, pod, container, sinceDuration, r.Context())
  1115. if err != nil {
  1116. fmt.Fprintf(w, "Error Getting Logs: "+err.Error())
  1117. return
  1118. }
  1119. appendLog(ns, pod, container, logs)
  1120. w.Write([]byte(logResult))
  1121. return
  1122. }
  1123. if selector != "" {
  1124. pods, err := a.KubeClientSet.CoreV1().Pods(ns).List(r.Context(), metav1.ListOptions{LabelSelector: selector})
  1125. if err != nil {
  1126. fmt.Fprintf(w, "Error Finding Pod: "+err.Error())
  1127. return
  1128. }
  1129. for _, pd := range pods.Items {
  1130. for _, cont := range pd.Spec.Containers {
  1131. logs, err := logsFor(a.KubeClientSet, ns, pd.Name, cont.Name, sinceDuration, r.Context())
  1132. if err != nil {
  1133. continue
  1134. }
  1135. appendLog(ns, pd.Name, cont.Name, logs)
  1136. }
  1137. }
  1138. }
  1139. w.Write([]byte(logResult))
  1140. }
  1141. func (a *Accesses) AddServiceKey(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
  1142. w.Header().Set("Content-Type", "application/json")
  1143. w.Header().Set("Access-Control-Allow-Origin", "*")
  1144. r.ParseForm()
  1145. key := r.PostForm.Get("key")
  1146. k := []byte(key)
  1147. err := os.WriteFile(path.Join(env.GetConfigPathWithDefault(env.DefaultConfigMountPath), "key.json"), k, 0644)
  1148. if err != nil {
  1149. fmt.Fprintf(w, "Error writing service key: "+err.Error())
  1150. }
  1151. w.WriteHeader(http.StatusOK)
  1152. }
  1153. func (a *Accesses) GetHelmValues(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
  1154. w.Header().Set("Content-Type", "application/json")
  1155. w.Header().Set("Access-Control-Allow-Origin", "*")
  1156. encodedValues := env.Get("HELM_VALUES", "")
  1157. if encodedValues == "" {
  1158. fmt.Fprintf(w, "Values reporting disabled")
  1159. return
  1160. }
  1161. result, err := base64.StdEncoding.DecodeString(encodedValues)
  1162. if err != nil {
  1163. fmt.Fprintf(w, "Failed to decode encoded values: %s", err)
  1164. return
  1165. }
  1166. w.Write(result)
  1167. }
  1168. func (a *Accesses) Status(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
  1169. w.Header().Set("Content-Type", "application/json")
  1170. w.Header().Set("Access-Control-Allow-Origin", "*")
  1171. promServer := env.GetPrometheusServerEndpoint()
  1172. api := prometheusAPI.NewAPI(a.PrometheusClient)
  1173. result, err := api.Config(r.Context())
  1174. if err != nil {
  1175. fmt.Fprintf(w, "Using Prometheus at "+promServer+". Error: "+err.Error())
  1176. } else {
  1177. fmt.Fprintf(w, "Using Prometheus at "+promServer+". PrometheusConfig: "+result.YAML)
  1178. }
  1179. }
  1180. type LogLevelRequestResponse struct {
  1181. Level string `json:"level"`
  1182. }
  1183. func (a *Accesses) GetLogLevel(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
  1184. w.Header().Set("Content-Type", "application/json")
  1185. w.Header().Set("Access-Control-Allow-Origin", "*")
  1186. level := log.GetLogLevel()
  1187. llrr := LogLevelRequestResponse{
  1188. Level: level,
  1189. }
  1190. body, err := json.Marshal(llrr)
  1191. if err != nil {
  1192. http.Error(w, fmt.Sprintf("unable to retrive log level"), http.StatusInternalServerError)
  1193. return
  1194. }
  1195. _, err = w.Write(body)
  1196. if err != nil {
  1197. http.Error(w, fmt.Sprintf("unable to write response: %s", body), http.StatusInternalServerError)
  1198. return
  1199. }
  1200. }
  1201. func (a *Accesses) SetLogLevel(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
  1202. params := LogLevelRequestResponse{}
  1203. err := json.NewDecoder(r.Body).Decode(&params)
  1204. if err != nil {
  1205. http.Error(w, fmt.Sprintf("unable to decode request body, error: %s", err), http.StatusBadRequest)
  1206. return
  1207. }
  1208. err = log.SetLogLevel(params.Level)
  1209. if err != nil {
  1210. http.Error(w, fmt.Sprintf("level must be a valid log level according to zerolog; level given: %s, error: %s", params.Level, err), http.StatusBadRequest)
  1211. return
  1212. }
  1213. w.WriteHeader(http.StatusOK)
  1214. }
  1215. // captures the panic event in sentry
  1216. func capturePanicEvent(err string, stack string) {
  1217. msg := fmt.Sprintf("Panic: %s\nStackTrace: %s\n", err, stack)
  1218. log.Infof(msg)
  1219. sentry.CurrentHub().CaptureEvent(&sentry.Event{
  1220. Level: sentry.LevelError,
  1221. Message: msg,
  1222. })
  1223. sentry.Flush(5 * time.Second)
  1224. }
  1225. // handle any panics reported by the errors package
  1226. func handlePanic(p errors.Panic) bool {
  1227. err := p.Error
  1228. if err != nil {
  1229. if err, ok := err.(error); ok {
  1230. capturePanicEvent(err.Error(), p.Stack)
  1231. }
  1232. if err, ok := err.(string); ok {
  1233. capturePanicEvent(err, p.Stack)
  1234. }
  1235. }
  1236. // Return true to recover iff the type is http, otherwise allow kubernetes
  1237. // to recover.
  1238. return p.Type == errors.PanicTypeHTTP
  1239. }
  1240. func Initialize(additionalConfigWatchers ...*watcher.ConfigMapWatcher) *Accesses {
  1241. configWatchers := watcher.NewConfigMapWatchers(additionalConfigWatchers...)
  1242. var err error
  1243. if errorReportingEnabled {
  1244. err = sentry.Init(sentry.ClientOptions{Release: version.FriendlyVersion()})
  1245. if err != nil {
  1246. log.Infof("Failed to initialize sentry for error reporting")
  1247. } else {
  1248. err = errors.SetPanicHandler(handlePanic)
  1249. if err != nil {
  1250. log.Infof("Failed to set panic handler: %s", err)
  1251. }
  1252. }
  1253. }
  1254. address := env.GetPrometheusServerEndpoint()
  1255. if address == "" {
  1256. log.Fatalf("No address for prometheus set in $%s. Aborting.", env.PrometheusServerEndpointEnvVar)
  1257. }
  1258. queryConcurrency := env.GetMaxQueryConcurrency()
  1259. log.Infof("Prometheus/Thanos Client Max Concurrency set to %d", queryConcurrency)
  1260. timeout := 120 * time.Second
  1261. keepAlive := 120 * time.Second
  1262. tlsHandshakeTimeout := 10 * time.Second
  1263. scrapeInterval := time.Minute
  1264. var rateLimitRetryOpts *prom.RateLimitRetryOpts = nil
  1265. if env.IsPrometheusRetryOnRateLimitResponse() {
  1266. rateLimitRetryOpts = &prom.RateLimitRetryOpts{
  1267. MaxRetries: env.GetPrometheusRetryOnRateLimitMaxRetries(),
  1268. DefaultRetryWait: env.GetPrometheusRetryOnRateLimitDefaultWait(),
  1269. }
  1270. }
  1271. promCli, err := prom.NewPrometheusClient(address, &prom.PrometheusClientConfig{
  1272. Timeout: timeout,
  1273. KeepAlive: keepAlive,
  1274. TLSHandshakeTimeout: tlsHandshakeTimeout,
  1275. TLSInsecureSkipVerify: env.GetInsecureSkipVerify(),
  1276. RateLimitRetryOpts: rateLimitRetryOpts,
  1277. Auth: &prom.ClientAuth{
  1278. Username: env.GetDBBasicAuthUsername(),
  1279. Password: env.GetDBBasicAuthUserPassword(),
  1280. BearerToken: env.GetDBBearerToken(),
  1281. },
  1282. QueryConcurrency: queryConcurrency,
  1283. QueryLogFile: "",
  1284. })
  1285. if err != nil {
  1286. log.Fatalf("Failed to create prometheus client, Error: %v", err)
  1287. }
  1288. m, err := prom.Validate(promCli)
  1289. if err != nil || !m.Running {
  1290. if err != nil {
  1291. log.Errorf("Failed to query prometheus at %s. Error: %s . Troubleshooting help available at: %s", address, err.Error(), prom.PrometheusTroubleshootingURL)
  1292. } else if !m.Running {
  1293. log.Errorf("Prometheus at %s is not running. Troubleshooting help available at: %s", address, prom.PrometheusTroubleshootingURL)
  1294. }
  1295. } else {
  1296. log.Infof("Success: retrieved the 'up' query against prometheus at: " + address)
  1297. }
  1298. api := prometheusAPI.NewAPI(promCli)
  1299. _, err = api.Config(context.Background())
  1300. if err != nil {
  1301. log.Infof("No valid prometheus config file at %s. Error: %s . Troubleshooting help available at: %s. Ignore if using cortex/thanos here.", address, err.Error(), prom.PrometheusTroubleshootingURL)
  1302. } else {
  1303. log.Infof("Retrieved a prometheus config file from: %s", address)
  1304. }
  1305. // Lookup scrape interval for kubecost job, update if found
  1306. si, err := prom.ScrapeIntervalFor(promCli, env.GetKubecostJobName())
  1307. if err == nil {
  1308. scrapeInterval = si
  1309. }
  1310. log.Infof("Using scrape interval of %f", scrapeInterval.Seconds())
  1311. // Kubernetes API setup
  1312. kubeClientset, err := kubeconfig.LoadKubeClient("")
  1313. if err != nil {
  1314. log.Fatalf("Failed to build Kubernetes client: %s", err.Error())
  1315. }
  1316. // Create ConfigFileManager for synchronization of shared configuration
  1317. confManager := config.NewConfigFileManager(&config.ConfigFileManagerOpts{
  1318. BucketStoreConfig: env.GetKubecostConfigBucket(),
  1319. LocalConfigPath: "/",
  1320. })
  1321. configPrefix := env.GetConfigPathWithDefault("/var/configs/")
  1322. // Create Kubernetes Cluster Cache + Watchers
  1323. var k8sCache clustercache.ClusterCache
  1324. if env.IsClusterCacheFileEnabled() {
  1325. importLocation := confManager.ConfigFileAt(path.Join(configPrefix, "cluster-cache.json"))
  1326. k8sCache = clustercache.NewClusterImporter(importLocation)
  1327. } else {
  1328. k8sCache = clustercache.NewKubernetesClusterCache(kubeClientset)
  1329. }
  1330. k8sCache.Run()
  1331. cloudProviderKey := env.GetCloudProviderAPIKey()
  1332. cloudProvider, err := cloud.NewProvider(k8sCache, cloudProviderKey, confManager)
  1333. if err != nil {
  1334. panic(err.Error())
  1335. }
  1336. // Append the pricing config watcher
  1337. configWatchers.AddWatcher(cloud.ConfigWatcherFor(cloudProvider))
  1338. configWatchers.AddWatcher(metrics.GetMetricsConfigWatcher())
  1339. watchConfigFunc := configWatchers.ToWatchFunc()
  1340. watchedConfigs := configWatchers.GetWatchedConfigs()
  1341. kubecostNamespace := env.GetKubecostNamespace()
  1342. // We need an initial invocation because the init of the cache has happened before we had access to the provider.
  1343. for _, cw := range watchedConfigs {
  1344. configs, err := kubeClientset.CoreV1().ConfigMaps(kubecostNamespace).Get(context.Background(), cw, metav1.GetOptions{})
  1345. if err != nil {
  1346. log.Infof("No %s configmap found at install time, using existing configs: %s", cw, err.Error())
  1347. } else {
  1348. log.Infof("Found configmap %s, watching...", configs.Name)
  1349. watchConfigFunc(configs)
  1350. }
  1351. }
  1352. k8sCache.SetConfigMapUpdateFunc(watchConfigFunc)
  1353. remoteEnabled := env.IsRemoteEnabled()
  1354. if remoteEnabled {
  1355. info, err := cloudProvider.ClusterInfo()
  1356. log.Infof("Saving cluster with id:'%s', and name:'%s' to durable storage", info["id"], info["name"])
  1357. if err != nil {
  1358. log.Infof("Error saving cluster id %s", err.Error())
  1359. }
  1360. _, _, err = utils.GetOrCreateClusterMeta(info["id"], info["name"])
  1361. if err != nil {
  1362. log.Infof("Unable to set cluster id '%s' for cluster '%s', %s", info["id"], info["name"], err.Error())
  1363. }
  1364. }
  1365. // Thanos Client
  1366. var thanosClient prometheus.Client
  1367. if thanos.IsEnabled() {
  1368. thanosAddress := thanos.QueryURL()
  1369. if thanosAddress != "" {
  1370. thanosCli, _ := thanos.NewThanosClient(thanosAddress, &prom.PrometheusClientConfig{
  1371. Timeout: timeout,
  1372. KeepAlive: keepAlive,
  1373. TLSHandshakeTimeout: tlsHandshakeTimeout,
  1374. TLSInsecureSkipVerify: env.GetInsecureSkipVerify(),
  1375. RateLimitRetryOpts: rateLimitRetryOpts,
  1376. Auth: &prom.ClientAuth{
  1377. Username: env.GetMultiClusterBasicAuthUsername(),
  1378. Password: env.GetMultiClusterBasicAuthPassword(),
  1379. BearerToken: env.GetMultiClusterBearerToken(),
  1380. },
  1381. QueryConcurrency: queryConcurrency,
  1382. QueryLogFile: env.GetQueryLoggingFile(),
  1383. })
  1384. _, err = prom.Validate(thanosCli)
  1385. if err != nil {
  1386. log.Warnf("Failed to query Thanos at %s. Error: %s.", thanosAddress, err.Error())
  1387. thanosClient = thanosCli
  1388. } else {
  1389. log.Infof("Success: retrieved the 'up' query against Thanos at: " + thanosAddress)
  1390. thanosClient = thanosCli
  1391. }
  1392. } else {
  1393. log.Infof("Error resolving environment variable: $%s", env.ThanosQueryUrlEnvVar)
  1394. }
  1395. }
  1396. // ClusterInfo Provider to provide the cluster map with local and remote cluster data
  1397. var clusterInfoProvider clusters.ClusterInfoProvider
  1398. if env.IsClusterInfoFileEnabled() {
  1399. clusterInfoFile := confManager.ConfigFileAt(path.Join(configPrefix, "cluster-info.json"))
  1400. clusterInfoProvider = NewConfiguredClusterInfoProvider(clusterInfoFile)
  1401. } else {
  1402. clusterInfoProvider = NewLocalClusterInfoProvider(kubeClientset, cloudProvider)
  1403. }
  1404. // Initialize ClusterMap for maintaining ClusterInfo by ClusterID
  1405. var clusterMap clusters.ClusterMap
  1406. if thanosClient != nil {
  1407. clusterMap = clusters.NewClusterMap(thanosClient, clusterInfoProvider, 10*time.Minute)
  1408. } else {
  1409. clusterMap = clusters.NewClusterMap(promCli, clusterInfoProvider, 5*time.Minute)
  1410. }
  1411. // cache responses from model and aggregation for a default of 10 minutes;
  1412. // clear expired responses every 20 minutes
  1413. aggregateCache := cache.New(time.Minute*10, time.Minute*20)
  1414. costDataCache := cache.New(time.Minute*10, time.Minute*20)
  1415. clusterCostsCache := cache.New(cache.NoExpiration, cache.NoExpiration)
  1416. outOfClusterCache := cache.New(time.Minute*5, time.Minute*10)
  1417. settingsCache := cache.New(cache.NoExpiration, cache.NoExpiration)
  1418. // query durations that should be cached longer should be registered here
  1419. // use relatively prime numbers to minimize likelihood of synchronized
  1420. // attempts at cache warming
  1421. day := 24 * time.Hour
  1422. cacheExpiration := map[time.Duration]time.Duration{
  1423. day: maxCacheMinutes1d * time.Minute,
  1424. 2 * day: maxCacheMinutes2d * time.Minute,
  1425. 7 * day: maxCacheMinutes7d * time.Minute,
  1426. 30 * day: maxCacheMinutes30d * time.Minute,
  1427. }
  1428. var pc prometheus.Client
  1429. if thanosClient != nil {
  1430. pc = thanosClient
  1431. } else {
  1432. pc = promCli
  1433. }
  1434. costModel := NewCostModel(pc, cloudProvider, k8sCache, clusterMap, scrapeInterval)
  1435. metricsEmitter := NewCostModelMetricsEmitter(promCli, k8sCache, cloudProvider, clusterInfoProvider, costModel)
  1436. a := &Accesses{
  1437. Router: httprouter.New(),
  1438. PrometheusClient: promCli,
  1439. ThanosClient: thanosClient,
  1440. KubeClientSet: kubeClientset,
  1441. ClusterCache: k8sCache,
  1442. ClusterMap: clusterMap,
  1443. CloudProvider: cloudProvider,
  1444. ConfigFileManager: confManager,
  1445. ClusterInfoProvider: clusterInfoProvider,
  1446. Model: costModel,
  1447. MetricsEmitter: metricsEmitter,
  1448. AggregateCache: aggregateCache,
  1449. CostDataCache: costDataCache,
  1450. ClusterCostsCache: clusterCostsCache,
  1451. OutOfClusterCache: outOfClusterCache,
  1452. SettingsCache: settingsCache,
  1453. CacheExpiration: cacheExpiration,
  1454. httpServices: services.NewCostModelServices(),
  1455. }
  1456. // Use the Accesses instance, itself, as the CostModelAggregator. This is
  1457. // confusing and unconventional, but necessary so that we can swap it
  1458. // out for the ETL-adapted version elsewhere.
  1459. // TODO clean this up once ETL is open-sourced.
  1460. a.AggAPI = a
  1461. // Initialize mechanism for subscribing to settings changes
  1462. a.InitializeSettingsPubSub()
  1463. err = a.CloudProvider.DownloadPricingData()
  1464. if err != nil {
  1465. log.Infof("Failed to download pricing data: " + err.Error())
  1466. }
  1467. // Warm the aggregate cache unless explicitly set to false
  1468. if env.IsCacheWarmingEnabled() {
  1469. log.Infof("Init: AggregateCostModel cache warming enabled")
  1470. a.warmAggregateCostModelCache()
  1471. } else {
  1472. log.Infof("Init: AggregateCostModel cache warming disabled")
  1473. }
  1474. if !env.IsKubecostMetricsPodEnabled() {
  1475. a.MetricsEmitter.Start()
  1476. }
  1477. a.Router.GET("/costDataModel", a.CostDataModel)
  1478. a.Router.GET("/costDataModelRange", a.CostDataModelRange)
  1479. a.Router.GET("/aggregatedCostModel", a.AggregateCostModelHandler)
  1480. a.Router.GET("/allocation/compute", a.ComputeAllocationHandler)
  1481. a.Router.GET("/allocation/compute/summary", a.ComputeAllocationHandlerSummary)
  1482. a.Router.GET("/allNodePricing", a.GetAllNodePricing)
  1483. a.Router.POST("/refreshPricing", a.RefreshPricingData)
  1484. a.Router.GET("/clusterCostsOverTime", a.ClusterCostsOverTime)
  1485. a.Router.GET("/clusterCosts", a.ClusterCosts)
  1486. a.Router.GET("/clusterCostsFromCache", a.ClusterCostsFromCacheHandler)
  1487. a.Router.GET("/validatePrometheus", a.GetPrometheusMetadata)
  1488. a.Router.GET("/managementPlatform", a.ManagementPlatform)
  1489. a.Router.GET("/clusterInfo", a.ClusterInfo)
  1490. a.Router.GET("/clusterInfoMap", a.GetClusterInfoMap)
  1491. a.Router.GET("/serviceAccountStatus", a.GetServiceAccountStatus)
  1492. a.Router.GET("/pricingSourceStatus", a.GetPricingSourceStatus)
  1493. a.Router.GET("/pricingSourceSummary", a.GetPricingSourceSummary)
  1494. a.Router.GET("/pricingSourceCounts", a.GetPricingSourceCounts)
  1495. // endpoints migrated from server
  1496. a.Router.GET("/allPersistentVolumes", a.GetAllPersistentVolumes)
  1497. a.Router.GET("/allDeployments", a.GetAllDeployments)
  1498. a.Router.GET("/allStorageClasses", a.GetAllStorageClasses)
  1499. a.Router.GET("/allStatefulSets", a.GetAllStatefulSets)
  1500. a.Router.GET("/allNodes", a.GetAllNodes)
  1501. a.Router.GET("/allPods", a.GetAllPods)
  1502. a.Router.GET("/allNamespaces", a.GetAllNamespaces)
  1503. a.Router.GET("/allDaemonSets", a.GetAllDaemonSets)
  1504. a.Router.GET("/pod/:namespace/:name", a.GetPod)
  1505. a.Router.GET("/prometheusRecordingRules", a.PrometheusRecordingRules)
  1506. a.Router.GET("/prometheusConfig", a.PrometheusConfig)
  1507. a.Router.GET("/prometheusTargets", a.PrometheusTargets)
  1508. a.Router.GET("/orphanedPods", a.GetOrphanedPods)
  1509. a.Router.GET("/installNamespace", a.GetInstallNamespace)
  1510. a.Router.GET("/installInfo", a.GetInstallInfo)
  1511. a.Router.GET("/podLogs", a.GetPodLogs)
  1512. a.Router.POST("/serviceKey", a.AddServiceKey)
  1513. a.Router.GET("/helmValues", a.GetHelmValues)
  1514. a.Router.GET("/status", a.Status)
  1515. // prom query proxies
  1516. a.Router.GET("/prometheusQuery", a.PrometheusQuery)
  1517. a.Router.GET("/prometheusQueryRange", a.PrometheusQueryRange)
  1518. a.Router.GET("/thanosQuery", a.ThanosQuery)
  1519. a.Router.GET("/thanosQueryRange", a.ThanosQueryRange)
  1520. // diagnostics
  1521. a.Router.GET("/diagnostics/requestQueue", a.GetPrometheusQueueState)
  1522. a.Router.GET("/diagnostics/prometheusMetrics", a.GetPrometheusMetrics)
  1523. a.Router.GET("/logs/level", a.GetLogLevel)
  1524. a.Router.POST("/logs/level", a.SetLogLevel)
  1525. a.httpServices.RegisterAll(a.Router)
  1526. return a
  1527. }
  1528. func writeErrorResponse(w http.ResponseWriter, code int, message string) {
  1529. out := map[string]string{
  1530. "message": message,
  1531. }
  1532. bytes, err := json.Marshal(out)
  1533. if err != nil {
  1534. w.Header().Set("Content-Type", "text/plain")
  1535. w.WriteHeader(500)
  1536. fmt.Fprint(w, "unable to marshall json for error")
  1537. log.Warnf("Failed to marshall JSON for error response: %s", err.Error())
  1538. return
  1539. }
  1540. w.WriteHeader(code)
  1541. fmt.Fprint(w, string(bytes))
  1542. }