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 1767214 + 954d972 commit 087094e
Show file tree
Hide file tree
Showing 10 changed files with 140 additions and 108 deletions.
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;
}
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;
}
}
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>
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
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 components/jupyter-web-app/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 components/jupyter-web-app/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 087094e

Please sign in to comment.