Skip to content

Commit

Permalink
Support Pod Defaults in Tensorboard Web App (kubeflow/kubeflow#6924)
Browse files Browse the repository at this point in the history
* support Pod Defaults in Tensorboard Web App

* flint files
  • Loading branch information
surajkota authored and Adembc committed Jun 22, 2024
1 parent afb14b8 commit 13b5c94
Show file tree
Hide file tree
Showing 16 changed files with 265 additions and 6 deletions.
22 changes: 22 additions & 0 deletions notebooks/crud-web-apps/tensorboards/backend/app/routes/get.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,25 @@ def get_pvcs(namespace):
content = [pvc.metadata.name for pvc in pvcs.items]

return api.success_response("pvcs", content)


@bp.route("/api/namespaces/<namespace>/poddefaults")
def get_poddefaults(namespace):
pod_defaults = api.list_poddefaults(namespace)

# Return a list of pod defaults adding custom fields (label, desc) for forms
contents = []
for pd in pod_defaults["items"]:
label = list(pd["spec"]["selector"]["matchLabels"].keys())[0]
if "desc" in pd["spec"]:
desc = pd["spec"]["desc"]
else:
desc = pd["metadata"]["name"]

pd["label"] = label
pd["desc"] = desc
contents.append(pd)

log.info("Found poddefaults: %s", contents)

return api.success_response("poddefaults", contents)
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
def post_tensorboard(namespace):

body = request.get_json()
log.info("Got body: ", body)
log.info("Got body: %s", body)

name = body["name"]

Expand Down
25 changes: 22 additions & 3 deletions notebooks/crud-web-apps/tensorboards/backend/app/utils.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from kubeflow.kubeflow.crud_backend import status

from werkzeug.exceptions import BadRequest

def parse_tensorboard(tensorboard):
"""
Expand Down Expand Up @@ -27,12 +27,31 @@ def get_tensorboard_dict(namespace, body):
"""
Create Tensorboard object from request body and format it as a Python dict.
"""
metadata = {
"name": body["name"],
"namespace": namespace,
}
labels = get_tensorboard_configurations(body=body)
if labels:
metadata["labels"] = labels

tensorboard = {
"apiVersion": "tensorboard.kubeflow.org/v1alpha1",
"kind": "Tensorboard",
"metadata": {"name": body["name"], "namespace": namespace, },
"spec": {"logspath": body["logspath"], },
"metadata": metadata,
"spec": {"logspath": body["logspath"]},
}

return tensorboard

def get_tensorboard_configurations(body):
labels = body.get("configurations", None)
cr_labels = {}

if not isinstance(labels, list):
raise BadRequest("Labels for PodDefaults are not list: %s" % labels)

for label in labels:
cr_labels[label] = "true"

return cr_labels
8 changes: 8 additions & 0 deletions notebooks/crud-web-apps/tensorboards/deploy/app-rbac.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,14 @@ rules:
- watch
- create
- delete
- apiGroups:
- kubeflow.org
resources:
- poddefaults
verbs:
- get
- list
- watch
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import {

import { IndexComponent } from './pages/index/index.component';
import { FormComponent } from './pages/form/form.component';

import { FormConfigurationsModule } from './pages/form/form-configurations/form-configurations.module';
import { HttpClientModule, HttpClient } from '@angular/common/http';
import {
MatSnackBarConfig,
Expand Down Expand Up @@ -50,6 +50,7 @@ const TwaSnackBarConfig: MatSnackBarConfig = {
FormModule,
KubeflowModule,
HttpClientModule,
FormConfigurationsModule,
],
providers: [
{ provide: ErrorStateMatcher, useClass: ImmediateErrorStateMatcher },
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<lib-form-section title="Configurations" i18n-title>
<mat-form-field class="wide" appearance="outline">
<mat-label i18n> Configurations </mat-label>
<mat-select [formControl]="parentForm.get('configurations')" multiple>
<mat-option *ngFor="let config of podDefaults" [value]="config.label">
{{ config.desc }}
</mat-option>
</mat-select>
</mat-form-field>
</lib-form-section>
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { CommonModule } from '@angular/common';
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
import { FormControl, FormGroup } from '@angular/forms';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatInputModule } from '@angular/material/input';
import { MatSelectModule } from '@angular/material/select';
import { NamespaceService } from 'kubeflow';
import { of } from 'rxjs';
import { TWABackendService } from 'src/app/services/backend.service';
import { FormModule as KfFormModule } from 'kubeflow';
import { FormConfigurationsComponent } from './form-configurations.component';
import { NoopAnimationsModule } from '@angular/platform-browser/animations';

const TWABackendServiceStub: Partial<TWABackendService> = {
getPodDefaults: () => of(),
};

const NamespaceServiceStub: Partial<NamespaceService> = {
getSelectedNamespace: () => of(),
};

describe('FormConfigurationsComponent', () => {
let component: FormConfigurationsComponent;
let fixture: ComponentFixture<FormConfigurationsComponent>;

beforeEach(
waitForAsync(() => {
TestBed.configureTestingModule({
declarations: [FormConfigurationsComponent],
imports: [
CommonModule,
KfFormModule,
MatFormFieldModule,
MatInputModule,
MatSelectModule,
NoopAnimationsModule,
],
providers: [
{ provide: TWABackendService, useValue: TWABackendServiceStub },
{ provide: NamespaceService, useValue: NamespaceServiceStub },
],
}).compileComponents();
}),
);

beforeEach(() => {
fixture = TestBed.createComponent(FormConfigurationsComponent);
component = fixture.componentInstance;
component.parentForm = new FormGroup({
configurations: new FormControl(),
});

fixture.detectChanges();
});

it('should create', () => {
expect(component).toBeTruthy();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { Component, OnInit, OnDestroy, Input } from '@angular/core';
import { FormGroup } from '@angular/forms';
import { PodDefault } from 'src/app/types';
import { Subscription } from 'rxjs';
import { NamespaceService } from 'kubeflow';
import { TWABackendService } from 'src/app/services/backend.service';

@Component({
selector: 'app-form-configurations',
templateUrl: './form-configurations.component.html',
styleUrls: ['./form-configurations.component.scss'],
})
export class FormConfigurationsComponent implements OnInit, OnDestroy {
podDefaults: PodDefault[];
subscriptions = new Subscription();

@Input() parentForm: FormGroup;

constructor(public ns: NamespaceService, public backend: TWABackendService) {}

ngOnInit() {
// Keep track of the selected namespace
const nsSub = this.ns.getSelectedNamespace().subscribe(namespace => {
// Get the PodDefaults of the new Namespace
this.backend.getPodDefaults(namespace).subscribe(pds => {
this.podDefaults = pds;
});
});

this.subscriptions.add(nsSub);
}

ngOnDestroy() {
this.subscriptions.unsubscribe();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormModule as KfFormModule } from 'kubeflow';
import { FormConfigurationsComponent } from './form-configurations.component';
import { MatFormFieldModule } from '@angular/material/form-field';
import { ReactiveFormsModule } from '@angular/forms';
import { MatInputModule } from '@angular/material/input';
import { MatSelectModule } from '@angular/material/select';

@NgModule({
declarations: [FormConfigurationsComponent],
imports: [
CommonModule,
KfFormModule,
MatFormFieldModule,
ReactiveFormsModule,
MatInputModule,
MatSelectModule,
],
exports: [FormConfigurationsComponent],
})
export class FormConfigurationsModule {}
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,8 @@
<input matInput formControlName="objectStoreLink" type="url" />
</mat-form-field>

<app-form-configurations [parentForm]="formCtrl"></app-form-configurations>

<button
mat-raised-button
color="primary"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { TWABackendService } from 'src/app/services/backend.service';
import { MatRadioModule } from '@angular/material/radio';

import { FormComponent } from './form.component';
import { FormConfigurationsModule } from './form-configurations/form-configurations.module';

const TWABackendServiceStub = {
getTensorBoards: () => of(),
Expand All @@ -25,7 +26,12 @@ describe('FormComponent', () => {
waitForAsync(() => {
TestBed.configureTestingModule({
declarations: [FormComponent],
imports: [KubeflowModule, MatDialogModule, MatRadioModule],
imports: [
KubeflowModule,
MatDialogModule,
MatRadioModule,
FormConfigurationsModule,
],
providers: [
{ provide: TWABackendService, useValue: TWABackendServiceStub },
{ provide: NamespaceService, useValue: NamespaceServiceStub },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ export class FormComponent implements OnInit, OnDestroy {
public tensorboardNames = new Set<string>();
public pvcNames: string[] = [];
public storageType = 'object_store';
public configurations = [];

constructor(
public ns: NamespaceService,
Expand All @@ -39,6 +40,7 @@ export class FormComponent implements OnInit, OnDestroy {
objectStoreLink: ['', [Validators.required]],
pvcName: ['', [Validators.nullValidator]],
pvcMountPath: ['', [Validators.nullValidator]],
configurations: [[], []],
});
}

Expand Down Expand Up @@ -105,6 +107,7 @@ export class FormComponent implements OnInit, OnDestroy {
name: this.formCtrl.get('name').value,
namespace: this.formCtrl.get('namespace').value,
logspath,
configurations: this.formCtrl.get('configurations').value,
}),
);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
TensorboardResponseObject,
TWABackendResponse,
TensorboardPostObject,
PodDefault,
} from '../types';

@Injectable({
Expand Down Expand Up @@ -57,6 +58,16 @@ export class TWABackendService extends BackendService {
);
}

public getPodDefaults(ns: string): Observable<PodDefault[]> {
// Get existing PodDefaults in a namespace
const url = `api/namespaces/${ns}/poddefaults`;

return this.http.get<TWABackendResponse>(url).pipe(
catchError(error => this.handleError(error)),
map((resp: TWABackendResponse) => resp.poddefaults),
);
}

// POST
public createTensorboard(
namespace: string,
Expand Down
36 changes: 36 additions & 0 deletions notebooks/crud-web-apps/tensorboards/frontend/src/app/types.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,19 @@
import { Status, BackendResponse } from 'kubeflow';

import {
V1EnvFromSource,
V1EnvVar,
V1LabelSelector,
V1ObjectMeta,
V1Toleration,
V1Volume,
V1VolumeMount,
} from '@kubernetes/client-node';

export interface TWABackendResponse extends BackendResponse {
tensorboards?: TensorboardResponseObject[];
pvcs?: string[];
poddefaults?: PodDefault[];
}

export interface TensorboardResponseObject {
Expand All @@ -27,4 +38,29 @@ export interface TensorboardProcessedObject extends TensorboardResponseObject {
export interface TensorboardPostObject {
name: string;
logspath: string;
configurations: PodDefault[];
}

export interface PodDefault {
label?: string;
desc?: string;
apiVersion?: string;
kind?: string;
metadata?: V1ObjectMeta;
spec?: PodDefaultSpec;
}

export interface PodDefaultSpec {
annotations?: { [key: string]: string };
labels?: Record<string, string>;

automountServiceAccountToken?: boolean;
desc?: string;
env?: Array<V1EnvVar>;
envFrom?: Array<V1EnvFromSource>;
selector: V1LabelSelector;
serviceAccountName?: string;
volumeMounts?: Array<V1VolumeMount>;
volumes?: Array<V1Volume>;
tolerations?: Array<V1Toleration>;
}
Loading

0 comments on commit 13b5c94

Please sign in to comment.