Skip to content

Commit

Permalink
Merge pull request #34 from StatCan/fix-kubecost-error
Browse files Browse the repository at this point in the history
fix: add check for empty response
  • Loading branch information
brendangadd authored Nov 6, 2020
2 parents f938db9 + 460f113 commit d412a39
Show file tree
Hide file tree
Showing 10 changed files with 140 additions and 108 deletions.
19 changes: 16 additions & 3 deletions frontend/src/app/main-table/cost-table/cost-table.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,10 @@
</p>

<mat-divider></mat-divider>
<table style="width: auto">
<table
*ngIf="getStatus() == AsyncStatus.SUCCESS; else error"
style="width: auto"
>
<tr class="mat-header-row" style="text-align: left">
<th class="mat-header-cell">{{ "costTable.thCompute" | translate }}</th>
<th class="mat-header-cell">{{ "costTable.thGpus" | translate }}</th>
Expand All @@ -18,13 +21,23 @@
{{ "costTable.thTotal" | translate }}
</th>
</tr>
<tr *ngIf="aggregatedCost?.data[currNamespace]; let cost" class="mat-row" style="text-align: left;">
<tr
*ngIf="aggregatedCost.data[currNamespace]; let cost"
class="mat-row"
style="text-align: left"
>
<td class="mat-cell">{{ formatCost(cost.cpuCost + cost.ramCost) }}</td>
<td class="mat-cell">{{ formatCost(cost.gpuCost) }}</td>
<td class="mat-cell">{{ formatCost(cost.pvCost) }}</td>
<td class="mat-cell" style="font-weight: 500;">
<td class="mat-cell" style="font-weight: 500">
{{ formatCost(cost.totalCost) }}
</td>
</tr>
</table>

<ng-template #error>
<div *ngIf="getStatus() == AsyncStatus.FAILURE" class="costerror">
{{ "costTable.errMessage" | translate }}
</div>
</ng-template>
</div>
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,21 @@ p {
padding-right: 16px;
}

.costerror {
padding: 20px;
padding-left: 24px;
color: #cc0033;
}

.header p {
padding: 12px 0 0px;
font-weight: 400;
font-size: 20px;
}

.mat-cell, .mat-header-cell, .mat-footer-cell {
.mat-cell,
.mat-header-cell,
.mat-footer-cell {
padding-left: 12px;
padding-right: 195px;
}
31 changes: 25 additions & 6 deletions frontend/src/app/main-table/cost-table/cost-table.component.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,36 @@
import { Component, Input } from "@angular/core";
import { AggregateCostResponse } from 'src/app/services/kubecost.service';
import {Component, Input} from "@angular/core";
import {AggregateCostResponse} from "src/app/services/kubecost.service";

enum AsyncStatus {
PENDING,
SUCCESS,
FAILURE
}

@Component({
selector: "app-cost-table",
templateUrl: "./cost-table.component.html",
styleUrls: ["./cost-table.component.scss", "../main-table.component.scss"]
})
export class CostTableComponent{
@Input() aggregatedCost: AggregateCostResponse;
@Input() currNamespace:string;
export class CostTableComponent {
@Input() aggregatedCost: AggregateCostResponse;
@Input() currNamespace: string;

AsyncStatus = AsyncStatus;

formatCost(value: number): string {
return '$' + (value > 0 ? Math.max(value, 0.01) : 0).toFixed(2)
return "$" + (value > 0 ? Math.max(value, 0.01) : 0).toFixed(2);
}

getStatus(): AsyncStatus {
if (this.aggregatedCost == null) {
return AsyncStatus.PENDING;
}

if (this.aggregatedCost instanceof Error) {
return AsyncStatus.FAILURE;
}

return AsyncStatus.SUCCESS;
}
}
38 changes: 18 additions & 20 deletions frontend/src/app/main-table/main-table.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -16,24 +16,22 @@
</div>

<!-- The Table showing the persistent volume claims -->
<div class="parent spacing">
<div class="spacer"></div>
<app-volume-table
[pvcProperties]="pvcProperties"
(deletePvcEvent)="deletePvc($event)"
>
</app-volume-table>
<div class="spacer"></div>
</div>

<!-- The Table showing our Costs-->
<div class="parent spacing">
<div class="spacer"></div>
<app-cost-table
[aggregatedCost]="aggregatedCost"
[currNamespace]="currNamespace"
></app-cost-table>
<div class="spacer"></div>
</div>

<div class="parent spacing">
<div class="spacer"></div>
<app-volume-table
[pvcProperties]="pvcProperties"
(deletePvcEvent)="deletePvc($event)"
>
</app-volume-table>
<div class="spacer"></div>
</div>

<!-- The Table showing our Costs-->
<div class="parent spacing">
<div class="spacer"></div>
<app-cost-table
[aggregatedCost]="aggregatedCost"
[currNamespace]="currNamespace"
></app-cost-table>
<div class="spacer"></div>
</div>
19 changes: 11 additions & 8 deletions frontend/src/app/main-table/main-table.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@ import {KubernetesService} from "src/app/services/kubernetes.service";
import {Subscription} from "rxjs";
import {isEqual} from "lodash";
import {first} from "rxjs/operators";
import { KubecostService, AggregateCostResponse } from 'src/app/services/kubecost.service';
import {AggregateCostResponse, KubecostService} from "src/app/services/kubecost.service";

import {ExponentialBackoff} from "src/app/utils/polling";
import {Volume, Resource} from "../utils/types";
import {PvcWithStatus} from "./volumes-table/volume-table.component"
import {PvcWithStatus} from "./volumes-table/volume-table.component";

@Component({
selector: "app-main-table",
Expand All @@ -24,15 +24,14 @@ export class MainTableComponent implements OnInit {
pvcs: Volume[] = [];
pvcProperties: PvcWithStatus[] = [];

aggregatedCost: AggregateCostResponse = null;

subscriptions = new Subscription();
poller: ExponentialBackoff;
aggregatedCost: AggregateCostResponse = null;

constructor(
public ns: NamespaceService,
private k8s: KubernetesService,
private kubecostService: KubecostService,
private kubecostService: KubecostService
) {}

ngOnInit() {
Expand All @@ -46,7 +45,10 @@ export class MainTableComponent implements OnInit {
this.k8s.getResource(this.currNamespace).toPromise(),
this.k8s.getVolumes(this.currNamespace).toPromise()
]).then(([notebooks, volumes]) => {
if (!isEqual(notebooks, this.resources) || !isEqual(volumes, this.pvcs)) {
if (
!isEqual(notebooks, this.resources) ||
!isEqual(volumes, this.pvcs)
) {
this.poller.reset();
}
this.resources = notebooks;
Expand Down Expand Up @@ -92,7 +94,8 @@ export class MainTableComponent implements OnInit {

getAggregatedCost() {
this.kubecostService.getAggregateCost(this.currNamespace).subscribe(
aggCost => this.aggregatedCost = aggCost
)
aggCost => (this.aggregatedCost = aggCost),
error => (this.aggregatedCost = error)
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<mat-icon>storage</mat-icon>
<p>{{ "volumeTable.title" | translate }}</p>
</div>

<mat-divider></mat-divider>
<table mat-table [dataSource]="pvcProperties" matSort>
<!-- Status Column -->
<ng-container matColumnDef="status">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {TranslateService} from "@ngx-translate/core";
export type PvcWithStatus = {
pvc: Volume;
mountedBy: string | null;
}
};

enum PvcStatus {
DELETING,
Expand Down
121 changes: 55 additions & 66 deletions frontend/src/app/services/kubecost.service.ts
Original file line number Diff line number Diff line change
@@ -1,84 +1,73 @@
import { Injectable } from "@angular/core";
import { HttpClient, HttpErrorResponse } from "@angular/common/http";
import {Injectable} from "@angular/core";
import {HttpClient, HttpErrorResponse} from "@angular/common/http";

import { Observable, throwError } from "rxjs";
import { tap, catchError } from "rxjs/operators";
import { environment } from "src/environments/environment";

import {
Resp,
SnackType
} from "../utils/types";
import { SnackBarService } from "./snack-bar.service";
import {Observable, throwError} from "rxjs";
import {tap, catchError} from "rxjs/operators";
import {environment} from "src/environments/environment";
import {Resp} from "../utils/types";

export type AggregateCostResponse = {
code: number,
code: number;
data: {
[namespace: string]: {
aggregation: string,
environment: string,
cpuAllocationAverage: number,
cpuCost: number,
cpuEfficiency: number,
efficiency: number,
gpuAllocationAverage: number,
gpuCost: number,
ramAllocationAverage: number,
ramCost: number,
ramEfficiency: number,
pvAllocationAverage: number,
pvCost: number,
networkCost: number,
sharedCost: number,
totalCost: number
}
},
message: string
}
aggregation: string;
environment: string;
cpuAllocationAverage: number;
cpuCost: number;
cpuEfficiency: number;
efficiency: number;
gpuAllocationAverage: number;
gpuCost: number;
ramAllocationAverage: number;
ramCost: number;
ramEfficiency: number;
pvAllocationAverage: number;
pvCost: number;
networkCost: number;
sharedCost: number;
totalCost: number;
};
};
message: string;
};

@Injectable()
export class KubecostService {

constructor(private http: HttpClient, private snackBar: SnackBarService) { }
constructor(private http: HttpClient) {}

getAggregateCost(ns: string): Observable<AggregateCostResponse> {
const url = environment.apiUrl + `/api/namespaces/${ns}/cost/aggregated`

return this.http.get<AggregateCostResponse>(url, {
params: {
aggregation: 'namespace',
namespace: ns,
window: '1d'
}
}).pipe(
tap(res => this.handleBackendError(res)),
catchError(err => this.handleError(err))
);
}
const url = environment.apiUrl + `/api/namespaces/${ns}/cost/aggregated`;

return this.http
.get<AggregateCostResponse | Resp>(url, {
params: {
aggregation: "namespace",
namespace: ns,
window: "1d"
}
})
.pipe(
tap(res => this.handleBackendError(res)),
catchError(err => this.handleError(err))
) as Observable<AggregateCostResponse>;
}

// ---------------------------Error Handling----------------------------------
private handleBackendError(response: { code: number }) {
if (response.code < 200 || response.code >= 300) {
private handleBackendError(response: AggregateCostResponse | Resp) {
if (this.isResp(response) || response.code < 200 || response.code >= 300) {
throw response;
}
}

private handleError(error: HttpErrorResponse | Resp): Observable<never> {
// The backend returned an unsuccessful response code.
// The response body may contain clues as to what went wrong,
if (error instanceof HttpErrorResponse) {
this.snackBar.show(
`${error.status}: There was an error trying to connect ` +
`to the backend API. ${error.message}`,
SnackType.Error
);
return throwError(error.message);
} else {
// Backend error thrown from handleBackendError
const backendError = error as Resp;
this.snackBar.show(backendError.log, SnackType.Error);
return throwError(backendError.log);
}
private handleError(
error: HttpErrorResponse | AggregateCostResponse | Resp
): Observable<never> {
const message = this.isResp(error) ? error.log : error.message;
return throwError(new Error(message));
}

private isResp(
obj: HttpErrorResponse | AggregateCostResponse | Resp
): obj is Resp {
return (obj as Resp).success !== undefined;
}
}
3 changes: 2 additions & 1 deletion frontend/src/assets/i18n/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@
"thCompute": "Compute",
"thGpus": "GPUs",
"thStorage": "Storage",
"thTotal": "Total"
"thTotal": "Total",
"errMessage": "Failed to retrieve cost information"
},
"namespaceSelect": {
"lblSelectNamespace": "Select Namespace",
Expand Down
3 changes: 2 additions & 1 deletion frontend/src/assets/i18n/fr.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@
"thCompute": "Calculé",
"thGpus": "GPUs",
"thStorage": "Stockage",
"thTotal": "Total"
"thTotal": "Total",
"errMessage": "Échec de la récupération des informations des coûts."
},
"namespaceSelect": {
"lblSelectNamespace": "Sélectionner l'espace de noms",
Expand Down

0 comments on commit d412a39

Please sign in to comment.