Version 1.0.4, 4 June 2019
--------------------------
+* Add AC xApp neighbor control screen
* Add ANR xApp neighbor cell relation table
* Drop the pendulum xApp control screen
* Add column sorting on xApp catalog, xApp control, ANR
/**
* Provides methods to manage policies of the Admission Control xApp, which
- * initially defines just one. All requests go via the A1 Mediatior.
+ * initially defines just one. All requests go via the A1 Mediator.
*/
@RestController
@RequestMapping(value = DashboardConstants.ENDPOINT_PREFIX + "/xapp/ac", produces = MediaType.APPLICATION_JSON_VALUE)
*/
@ApiOperation(value = "Gets the admission control policy for AC xApp via the A1 Mediator")
@RequestMapping(value = "admctrl", method = RequestMethod.GET)
- public Object getAdmissionControlPolicy() {
+ public Object getAdmissionControlPolicy(HttpServletResponse response) {
logger.debug("getAdmissionControlPolicy");
- a1MediatorApi.a1ControllerGetHandler(AC_CONTROL_NAME);
+ response.setStatus(HttpServletResponse.SC_NOT_IMPLEMENTED);
return null;
}
/*
- * This controller is deliberately kept ignorant of the
- * ACAdmissionIntervalControl Typescript interface.
+ * This controller is deliberately kept ignorant of the data expected by AC. The
+ * fields are defined in the ACAdmissionIntervalControl Typescript interface.
*/
@ApiOperation(value = "Sets the admission control policy for AC xApp via the A1 Mediator")
- @RequestMapping(value = "admctrl", method = RequestMethod.PUT)
+ @RequestMapping(value = "catime", method = RequestMethod.PUT)
public void setAdmissionControlPolicy(@ApiParam(value = "Admission control policy") @RequestBody JsonNode acPolicy, //
HttpServletResponse response) {
logger.debug("setAdmissionControlPolicy {}", acPolicy);
@ApiOperation(value = "Returns neighbor cell relation table for all gNodeBs or based on query parameters", response = NeighborCellRelationTable.class)
@RequestMapping(value = "/ncrt", method = RequestMethod.GET)
- public NeighborCellRelationTable getNcrtInfo( //
+ public NeighborCellRelationTable getNcrt( //
@RequestParam(name = QP_NODEB, required = false) String ggnbId, //
@RequestParam(name = QP_SERVING, required = false) String servingCellNrcgi, //
@RequestParam(name = QP_NEIGHBOR, required = false) String neighborCellNrpci) {
- logger.debug("getNcrtInfo: ggnbid {}, servingCellNrpci {}, neighborCellNrcgi {}", ggnbId, servingCellNrcgi,
+ logger.debug("getNcrt: ggnbid {}, servingCellNrpci {}, neighborCellNrcgi {}", ggnbId, servingCellNrcgi,
neighborCellNrpci);
return ncrtApi.getNcrt(ggnbId, servingCellNrcgi, neighborCellNrpci);
}
--- /dev/null
+<!--
+ ========================LICENSE_START=================================
+ O-RAN-SC
+ %%
+ Copyright (C) 2019 AT&T Intellectual Property and Nokia
+ %%
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ ========================LICENSE_END===================================
+ -->
+<div class="ac__section">
+ <h3 class="ac__header">AC xApp Policy</h3>
+
+ <form [formGroup]="acForm" novalidate autocomplete="off" (ngSubmit)="updateAc(acForm.value)">
+ <div name="enforce">
+ <mat-checkbox formControlName="enforce">Enforce</mat-checkbox>
+ </div>
+ <mat-form-field class="input-display-block">
+ <input matInput type="text" placeholder="Window length" formControlName="windowLength">
+ <mat-hint align="end">Sliding window length in minutes for measurement, range 1..60.</mat-hint>
+ <mat-error *ngIf="validateControl('windowLength') && hasError('windowLength', 'required')">Number is required
+ </mat-error>
+ <mat-error *ngIf="hasError('windowLength', 'pattern')">Valid number is required</mat-error>
+ </mat-form-field>
+ <mat-form-field class="input-display-block">
+ <input matInput type="text" placeholder="Blocking rate" formControlName="blockingRate">
+ <mat-hint align="end">Connections to block if above trigger threshold, range 1..100.</mat-hint>
+ <mat-error *ngIf="validateControl('blockingRate') && hasError('blockingRate', 'required')">Number is required
+ </mat-error>
+ <mat-error *ngIf="hasError('blockingRate', 'pattern')">Valid number is required</mat-error>
+ </mat-form-field>
+ <mat-form-field class="input-display-block">
+ <input matInput type="text" placeholder="Trigger threshold" formControlName="triggerThreshold">
+ <mat-hint align="end">Number of events in window to trigger blocking, minimum 1.</mat-hint>
+ <mat-error *ngIf="validateControl('triggerThreshold') && hasError('triggerThreshold', 'required')">Number is
+ required</mat-error>
+ <mat-error *ngIf="hasError('triggerThreshold', 'pattern')">Valid number is required</mat-error>
+ </mat-form-field>
+ <div class="input-display-block">
+ <button class="mat-raised-button mat-primary update-button" [disabled]="!acForm.valid">Update</button>
+ </div>
+ <div class="input-display-block">
+ <span class="version__text">AC API version {{acVersion}}</span>
+ </div>
+</form>
+
+</div>
\ No newline at end of file
--- /dev/null
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 AT&T Intellectual Property and Nokia
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ========================LICENSE_END===================================
+ */
+
+ .ac__section {
+ position: relative;
+ top: -50px;
+}
+
+.ac__header {
+ text-align: center;
+ color: #432c85;
+ font-size: 50px;
+ font-weight: 200;
+ letter-spacing: .1em;
+ transform: translate(149 56);
+}
+
+ /* used to place form fields on separate lines/rows */
+.input-display-block {
+ display: block;
+ width: 400px;
+}
+
+.version__text {
+ color: gray;
+ font-size: 10px;
+}
+
+.update-button {
+ margin-top: 10px;
+ float: right;
+}
--- /dev/null
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 AT&T Intellectual Property and Nokia
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ========================LICENSE_END===================================
+ */
+
+ import { async, ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { AcXappComponent } from './ac-xapp.component';
+
+describe('AcXappComponent', () => {
+ let component: AcXappComponent;
+ let fixture: ComponentFixture<AcXappComponent>;
+
+ beforeEach(async(() => {
+ TestBed.configureTestingModule({
+ declarations: [ AcXappComponent ]
+ })
+ .compileComponents();
+ }));
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(AcXappComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
--- /dev/null
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 AT&T Intellectual Property and Nokia
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ========================LICENSE_END===================================
+ */
+
+import { Component, OnInit } from '@angular/core';
+import { FormGroup, FormControl, Validators } from '@angular/forms';
+import { ACAdmissionIntervalControl, ACAdmissionIntervalControlAck } from '../interfaces/ac-xapp.types';
+import { ACXappService } from '../services/ac-xapp/ac-xapp.service';
+import { ErrorDialogService } from '../services/ui/error-dialog.service';
+import { NotificationService } from './../services/ui/notification.service';
+
+@Component({
+ selector: 'app-ac-xapp',
+ templateUrl: './ac-xapp.component.html',
+ styleUrls: ['./ac-xapp.component.scss']
+})
+export class AcXappComponent implements OnInit {
+
+ private acForm: FormGroup;
+
+ // this is probably the A1 version string
+ acVersion: string;
+
+ constructor(
+ private acXappService: ACXappService,
+ private errorDialogService: ErrorDialogService,
+ private notificationService: NotificationService) { }
+
+ ngOnInit() {
+ const windowLengthPattern = /^([0-9]{1}|[1-5][0-9]{1}|60)$/;
+ const blockingRatePattern = /^([0-9]{1,2}|100)$/;
+ const triggerPattern = /^([0-9]+)$/;
+ // No way to fetch current settings via A1 at present
+ this.acForm = new FormGroup({
+ enforce: new FormControl(true, [Validators.required]),
+ windowLength: new FormControl('', [Validators.required, Validators.pattern(windowLengthPattern)]),
+ blockingRate: new FormControl('', [Validators.required, Validators.pattern(blockingRatePattern)]),
+ triggerThreshold: new FormControl('', [Validators.required, Validators.pattern(triggerPattern)])
+ });
+ this.acXappService.getVersion().subscribe((res: string) => this.acVersion = res);
+ }
+
+ updateAc = (acFormValue: ACAdmissionIntervalControl) => {
+ if (this.acForm.valid) {
+ this.acXappService.putPolicy(acFormValue).subscribe(
+ response => {
+ if (response.status === 200 ) {
+ this.notificationService.success('AC update policy succeeded!');
+ }
+ },
+ (error => {
+ this.errorDialogService.displayError('AC update policy failed: ' + error.message);
+ })
+ );
+ }
+ }
+
+ hasError(controlName: string, errorName: string) {
+ if (this.acForm.controls[controlName].hasError(errorName)) {
+ return true;
+ }
+ return false;
+ }
+
+ validateControl(controlName: string) {
+ if (this.acForm.controls[controlName].invalid && this.acForm.controls[controlName].touched) {
+ return true;
+ }
+ return false;
+ }
+
+}
/* used to place form fields on separate lines/rows in dialog */
.input-display-block {
- display: block;
- }
\ No newline at end of file
+ display: block;
+}
import { ANRXappService } from '../services/anr-xapp/anr-xapp.service';
import { ErrorDialogService } from '../services/ui/error-dialog.service';
import { ANRNeighborCellRelation, ANRNeighborCellRelationMod } from '../interfaces/anr-xapp.types';
-import { modelGroupProvider } from '@angular/forms/src/directives/ng_model_group';
@Component({
selector: 'app-ncr-edit-dialog',
constructor(
private dialogRef: MatDialogRef<ANREditNCRDialogComponent>,
- private dataService: ANRXappService, private errorService: ErrorDialogService,
- @Inject(MAT_DIALOG_DATA) private data: ANRNeighborCellRelation) {
- console.log('constructed with data ' + data);
- }
+ private dataService: ANRXappService,
+ private errorService: ErrorDialogService,
+ @Inject(MAT_DIALOG_DATA) private data: ANRNeighborCellRelation) { }
ngOnInit() {
this.ncrDialogForm = new FormGroup({
}
modifyNcr = (ncrFormValue: ANRNeighborCellRelation) => {
- if (this.ncrDialogForm.valid) {
- const ncrm = {} as ANRNeighborCellRelationMod;
- // there must be a btter way
- ncrm.neighborCellNrcgi = ncrFormValue.neighborCellNrcgi;
- ncrm.neighborCellNrpci = ncrFormValue.neighborCellNrpci;
- ncrm.flagNoHo = ncrFormValue.flagNoHo;
- ncrm.flagNoXn = ncrFormValue.flagNoXn;
- ncrm.flagNoRemove = ncrFormValue.flagNoRemove;
- this.dataService.modifyNcr(ncrFormValue.servingCellNrcgi, ncrFormValue.neighborCellNrpci, ncrm).subscribe((val: any[]) => {},
- (error => {
- this.errorService.displayError('NCR update failed: ' + error.message);
- })
- );
- this.dialogRef.close();
- }
+ if (this.ncrDialogForm.valid) {
+ const ncrm = {} as ANRNeighborCellRelationMod;
+ // there must be a better way
+ ncrm.neighborCellNrcgi = ncrFormValue.neighborCellNrcgi;
+ ncrm.neighborCellNrpci = ncrFormValue.neighborCellNrpci;
+ ncrm.flagNoHo = ncrFormValue.flagNoHo;
+ ncrm.flagNoXn = ncrFormValue.flagNoXn;
+ ncrm.flagNoRemove = ncrFormValue.flagNoRemove;
+ this.dataService.modifyNcr(ncrFormValue.servingCellNrcgi, ncrFormValue.neighborCellNrpci, ncrm).subscribe((val: any[]) => { },
+ (error => {
+ this.errorService.displayError('NCR update failed: ' + error.message);
+ })
+ );
+ this.dialogRef.close();
+ }
}
- public hasError(controlName: string, errorName: string) {
+ hasError(controlName: string, errorName: string) {
if (this.ncrDialogForm.controls[controlName].hasError(errorName)) {
- return true;
+ return true;
}
return false;
}
- public validateControl(controlName: string) {
+ validateControl(controlName: string) {
if (this.ncrDialogForm.controls[controlName].invalid && this.ncrDialogForm.controls[controlName].touched) {
return true;
}
import { RANConnectionComponent } from './ran-connection/ran-connection.component';
import { StatsComponent } from './stats/stats.component';
import { AdminComponent } from './admin/admin.component';
+import { AcXappComponent } from './ac-xapp/ac-xapp.component';
import { AnrXappComponent } from './anr-xapp/anr-xapp.component';
const routes: Routes = [
{path: 'ran-connection', component: RANConnectionComponent},
{path: 'stats', component: StatsComponent},
{path: 'admin', component: AdminComponent},
+ {path: 'ac', component: AcXappComponent},
{path: 'anr', component: AnrXappComponent},
];
import { AnrXappComponent } from './anr-xapp/anr-xapp.component';
import { ErrorDialogComponent } from './ui/error-dialog/error-dialog.component';
import { ErrorDialogService } from './services/ui/error-dialog.service';
+import { AcXappComponent } from './ac-xapp/ac-xapp.component';
@NgModule({
declarations: [
+ AcXappComponent,
+ AdminComponent,
+ ANREditNCRDialogComponent,
+ AnrXappComponent,
AppComponent,
- LoginComponent,
CatalogComponent,
- SidenavListComponent,
CatalogCardComponent,
+ ConfigEventComponent,
+ ConfirmDialogComponent,
ControlCardComponent,
- StatCardComponent,
ControlComponent,
- RANConnectionComponent,
- StatsComponent,
- AdminComponent,
+ ErrorDialogComponent,
+ FooterComponent,
+ LoginComponent,
ModalEventComponent,
- ConfigEventComponent,
- AnrXappComponent,
+ RANConnectionComponent,
RANConnectionDialogComponent,
- ANREditNCRDialogComponent,
- ConfirmDialogComponent,
- FooterComponent,
- ErrorDialogComponent
+ SidenavListComponent,
+ StatCardComponent,
+ StatsComponent
],
imports: [
AppRoutingModule,
}
controlApp(app: XappControlRow): void {
+ const acXappPattern = /[Aa][Dd][Mm][Ii][Ss]{2}[Ii][Oo][Nn]/;
const anrXappPattern = /[Aa][Nn][Rr]/;
- if (anrXappPattern.test(app.xapp)) {
+ if (acXappPattern.test(app.xapp)) {
+ this.router.navigate(['/ac']);
+ } else if (anrXappPattern.test(app.xapp)) {
this.router.navigate(['/anr']);
} else {
this.errorDialogService.displayError('No control available for ' + app.xapp + ' (yet)');
import { ErrorDialogService } from '../services/ui/error-dialog.service';
import { ConfirmDialogService } from './../services/ui/confirm-dialog.service';
import { NotificationService } from './../services/ui/notification.service';
-import { E2SetupRequest } from '../interfaces/e2-mgr.types';
import { RANConnectionDataSource } from './ran-connection.datasource';
import { HttpErrorResponse } from '@angular/common/http';
-import { Observable } from 'rxjs';
-
@Component({
selector: 'app-ran-connection',
displayedColumns: string[] = ['requestType', 'ranName', 'ranIp', 'ranPort', 'responseCode', 'timeStamp'];
dataSource: RANConnectionDataSource;
- constructor(private e2MgrSvc: E2ManagerService, private errorSvc: ErrorDialogService,
- private confirmDialogService: ConfirmDialogService, private notification: NotificationService,
+ constructor(private e2MgrSvc: E2ManagerService,
+ private errorSvc: ErrorDialogService,
+ private confirmDialogService: ConfirmDialogService,
+ private notification: NotificationService,
public dialog: MatDialog) { }
ngOnInit() {
*/
import { TestBed } from '@angular/core/testing';
-import { AcXappService } from './ac-xapp.service';
+import { ACXappService } from './ac-xapp.service';
-describe('AcXappService', () => {
+describe('ACXappService', () => {
beforeEach(() => TestBed.configureTestingModule({}));
it('should be created', () => {
- const service: AcXappService = TestBed.get(AcXappService);
+ const service: ACXappService = TestBed.get(ACXappService);
expect(service).toBeTruthy();
});
});
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';
+ import { map } from 'rxjs/operators';
import { ACAdmissionIntervalControl, ACAdmissionIntervalControlAck } from '../../interfaces/ac-xapp.types';
import { DashboardSuccessTransport } from '../../interfaces/dashboard.types';
+/**
+ * Services for calling the Dashboard's AC endpoints.
+ */
@Injectable({
providedIn: 'root'
})
-export class AcXappService {
+export class ACXappService {
+
+ private basePath = 'api/xapp/ac';
- private basePath = 'api/xapp/ac/';
+ private buildPath(...args: any[]) {
+ let result = this.basePath;
+ args.forEach(part => {
+ result = result + '/' + part;
+ });
+ return result;
+ }
constructor(private httpClient: HttpClient) {
// injects to variable httpClient
}
/**
- * Gets A1 Mediator client version details
- * @returns Observable that should yield a SuccessTransport object
+ * Gets version details
+ * @returns Observable that should yield a String
*/
- getVersion() {
- // Remember that AC traffic goes via A1!
- return this.httpClient.get<DashboardSuccessTransport>(this.basePath + 'version');
+ getVersion(): Observable<string> {
+ const url = this.buildPath('version');
+ return this.httpClient.get<DashboardSuccessTransport>(url).pipe(
+ // Extract the string here
+ map(res => res['data'])
+ );
}
/**
- * Puts control admission time parameters to AC via A1
+ * Puts admission control parameters.
* @param policy an instance of ACAdmissionIntervalControl
- * @returns Observable that should yield an ACAdmissionIntervalControlAck
+ * @returns Observable that should yield a response code, no data
*/
- putCaTime(policy: ACAdmissionIntervalControl): Observable<ACAdmissionIntervalControlAck> {
- return this.httpClient.put<ACAdmissionIntervalControlAck>(this.basePath + 'catime', policy);
+ putPolicy(policy: ACAdmissionIntervalControl): Observable<any> {
+ const url = this.buildPath('catime');
+ return this.httpClient.put<ACAdmissionIntervalControlAck>(url, policy, { observe: 'response' });
}
}
import { TestBed } from '@angular/core/testing';
-import { AnrXappService } from './anr-xapp.service';
+import { ANRXappService } from './anr-xapp.service';
-describe('AnrXappService', () => {
+describe('ANRXappService', () => {
beforeEach(() => TestBed.configureTestingModule({}));
it('should be created', () => {
- const service: AnrXappService = TestBed.get(AnrXappService);
+ const service: ANRXappService = TestBed.get(ANRXappService);
expect(service).toBeTruthy();
});
});
/**
* Gets ANR xApp client version details
- * @returns Observable that should yield a DashboardSuccessTransport
+ * @returns Observable that should yield a String
*/
getVersion(): Observable<string> {
const url = this.buildPath('version');
*/
getHealthAlive(): Observable<any> {
const url = this.buildPath('health/alive');
- return this.httpClient.get(url);
+ return this.httpClient.get(url, { observe: 'response' });
}
/**
*/
getHealthReady(): Observable<any> {
const url = this.buildPath('health/ready');
- return this.httpClient.get(url);
+ return this.httpClient.get(url, { observe: 'response' });
}
- /**
- * Gets ANR xApp client version details
- * @returns Observable that should yield a DashboardSuccessTransport
- */
+/**
+ * Gets array of gNodeB IDs
+ * @returns Observable that should yield a string array
+ */
getgNodeBs(): Observable<string[]> {
const url = this.buildPath('gnodebs');
return this.httpClient.get<string[]>(url).pipe(