router.go 58 KB

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