router.go 55 KB

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