<groupId>org.o-ran-sc.ric.plt.a1med.client</groupId>
<artifactId>a1-med-client</artifactId>
<name>RIC A1 Mediator client</name>
- <version>0.10.0-SNAPSHOT</version>
+ <version>0.10.3-SNAPSHOT</version>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<goal>generate</goal>
</goals>
<configuration>
- <inputSpec>${project.basedir}/src/main/resources/a1_mediator_0.10.0.yaml</inputSpec>
+ <inputSpec>${project.basedir}/src/main/resources/a1_mediator_0.10.3.yaml</inputSpec>
<language>java</language>
<packageName>${client.base.package.name}</packageName>
<modelPackage>${client.base.package.name}.model</modelPackage>
# ==================================================================================
openapi: 3.0.0
info:
- version: 0.10.0
+ version: 0.10.3
title: RIC A1
paths:
'/a1-p/healthcheck':
description: >
Replace the current operation of policyname with the new parameters (replaces the current policy with the new one specified here).
-
Until there are standard policy definitions that are defined OUTSIDE of the scope of xapps, this API will be *very underspecified*.
This is a known gap, do not despair.
The PUT body is specified, *currently* in the xapp manifest that implements this policy; the caller should refer to the message_receives_payload_schema field to make this request.
The return content is also specified as above (in the xapp manifest) in the message_sends_payload_schema field.
-
Eventually, we need concrete policy defintions that are decoupled from xapp, and then this API description will become more fully specified.
tags:
- A1 Mediator
'200':
description: >
The downstream component responsible for implementing this policy replied with a good response. Check the manifest for response details.
+ content:
+ application/json:
+ schema:
+ type: object
'400':
description: >
The downstream component for implementing this policy does not support policy fetching.
/**
* Demonstrates use of the generated A1 mediator client.
*
- * The test fails because no server is available.
+ * The tests fail because no server is available.
*/
public class A1MediatorClientTest {
apiClient.setBasePath("http://localhost:30099/");
A1MediatorApi a1Api = new A1MediatorApi(apiClient);
try {
- a1Api.a1ControllerGetHandler("policy");
- System.out.println("getPolicy answered: " + apiClient.getStatusCode().toString());
+ Object o = a1Api.a1ControllerGetHandler("policy");
+ System.out.println(
+ "getPolicy answered code {} " + apiClient.getStatusCode().toString() + ", content " + o.toString());
+ Assertions.assertTrue(apiClient.getStatusCode().is2xxSuccessful());
+ } catch (RestClientException e) {
+ System.err.println("getPolicy failed: " + e.toString());
+ }
+ try {
+ String policy = "{}";
+ a1Api.a1ControllerPutHandler("policy", policy);
+ System.out.println("putPolicy answered: " + apiClient.getStatusCode().toString());
Assertions.assertTrue(apiClient.getStatusCode().is2xxSuccessful());
} catch (RestClientException e) {
System.err.println("getPolicy failed: " + e.toString());
-------------------------
* Add EPSDK-FW user management and Portal security
-Version 1.2.0, 21 Aug 2019
+Version 1.2.0, 23 Aug 2019
--------------------------
* Split URL properties into prefix/suffix parts
* Add jacoco plugin to back-end for code coverage
* Drop mock RAN names feature that supported R1 testing
* Extend mock endpoints to simulate delay seen in tests
* Move mock configuration classes into test area
-* Update A1 mediator client to spec version 0.10.0
* Update App manager client to spec version 0.1.7
* Update E2 manager client to spec version 20190815
* Add controller for page refresh of Angular routes
* Display AC xAPP metrics data via Kibana source (metrics.url.ac) on dashboard
* Pass AC policy parameter without parsing as JSON
* Use snake_case (not camelCase) names in AC policy front end
+* Update A1 mediator client to spec version 0.10.3
+* Extend AC control screen to read policy from A1
Version 1.0.5, 5 July 2019
--------------------------
<dependency>
<groupId>org.o-ran-sc.ric.plt.a1med.client</groupId>
<artifactId>a1-med-client</artifactId>
- <version>0.10.0-SNAPSHOT</version>
+ <version>0.10.3-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.o-ran-sc.ric.plt.appmgr.client</groupId>
import org.onap.portalsdk.core.onboarding.util.PortalApiConstants;
import org.oransc.ric.portal.dashboard.DashboardConstants;
import org.oransc.ric.portal.dashboard.LoginServlet;
-import org.oransc.ric.portal.dashboard.controller.AcXappController;
+import org.oransc.ric.portal.dashboard.controller.A1MediatorController;
import org.oransc.ric.portal.dashboard.controller.AdminController;
import org.oransc.ric.portal.dashboard.controller.AnrXappController;
import org.oransc.ric.portal.dashboard.controller.AppManagerController;
"/swagger-ui.html", //
"/webjars/**", //
PortalApiConstants.API_PREFIX + "/**", //
- AcXappController.CONTROLLER_PATH + "/" + AcXappController.VERSION_METHOD, //
+ A1MediatorController.CONTROLLER_PATH + "/" + A1MediatorController.VERSION_METHOD, //
AdminController.CONTROLLER_PATH + "/" + AdminController.HEALTH_METHOD, //
AdminController.CONTROLLER_PATH + "/" + AdminController.VERSION_METHOD, //
AnrXappController.CONTROLLER_PATH + "/" + AnrXappController.HEALTH_ALIVE_METHOD, //
import org.springframework.security.access.annotation.Secured;
import org.springframework.util.Assert;
import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import io.swagger.annotations.ApiParam;
/**
- * Proxies calls from the front end to the AC xApp via the A1 Mediator API.
+ * Proxies calls from the front end to the A1 Mediator API to get and put
+ * policies. The first application managed via this path is Admission Control.
*
* If a method throws RestClientResponseException, it is handled by
* {@link CustomResponseEntityExceptionHandler#handleProxyMethodException(Exception, org.springframework.web.context.request.WebRequest)}
* returns status 500.
*/
@RestController
-@RequestMapping(value = AcXappController.CONTROLLER_PATH, produces = MediaType.APPLICATION_JSON_VALUE)
-public class AcXappController {
+@RequestMapping(value = A1MediatorController.CONTROLLER_PATH, produces = MediaType.APPLICATION_JSON_VALUE)
+public class A1MediatorController {
private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
// Publish paths in constants so tests are easy to write
- public static final String CONTROLLER_PATH = DashboardConstants.ENDPOINT_PREFIX + "/xapp/admctl";
+ public static final String CONTROLLER_PATH = DashboardConstants.ENDPOINT_PREFIX + "/a1-p";
// Endpoints
public static final String VERSION_METHOD = DashboardConstants.VERSION_METHOD;
- public static final String POLICY_METHOD = "policy";
-
- // A "control" is an element in the XApp descriptor
- private static final String AC_CONTROL_NAME = "admission_control_policy";
+ // Path parameters
+ public static final String PP_POLICIES = "policies";
// Populated by the autowired constructor
private final A1MediatorApi a1MediatorApi;
@Autowired
- public AcXappController(final A1MediatorApi a1MediatorApi) {
+ public A1MediatorController(final A1MediatorApi a1MediatorApi) {
Assert.notNull(a1MediatorApi, "API must not be null");
this.a1MediatorApi = a1MediatorApi;
if (logger.isDebugEnabled())
}
/*
- * This controller is deliberately kept ignorant of the data expected by AC. The
- * fields are defined in the ACAdmissionIntervalControl Typescript interface.
+ * This method is deliberately kept ignorant of the data passing thru.
*/
- @ApiOperation(value = "Gets the admission control policy for AC xApp via the A1 Mediator")
- @GetMapping(POLICY_METHOD)
+ @ApiOperation(value = "Gets the specified policy from the A1 Mediator")
+ @GetMapping(PP_POLICIES + "/{" + PP_POLICIES + "}")
@Secured({ DashboardConstants.ROLE_ADMIN, DashboardConstants.ROLE_STANDARD })
- public Object getAdmissionControlPolicy(HttpServletResponse response) {
- logger.debug("getAdmissionControlPolicy");
- response.setStatus(HttpServletResponse.SC_NOT_IMPLEMENTED);
- return null;
+ public Object getPolicy(@PathVariable(PP_POLICIES) String policyName) {
+ logger.debug("getPolicy {}", policyName);
+ return a1MediatorApi.a1ControllerGetHandler(policyName);
}
/*
- * This controller is deliberately kept ignorant of the data expected by AC. The
- * fields are defined in the ACAdmissionIntervalControl Typescript interface. AC
- * uses snake_case keys but Jackson automatically converts to CamelCase on
- * parse. To avoid this conversion, specify the request parameter as String.
+ * This method is deliberately kept ignorant of the data passing thru.
*/
- @ApiOperation(value = "Sets the admission control policy for AC xApp via the A1 Mediator")
- @PutMapping(POLICY_METHOD)
+ @ApiOperation(value = "Puts the specified policy to the A1 Mediator")
+ @PutMapping(PP_POLICIES + "/{" + PP_POLICIES + "}")
@Secured({ DashboardConstants.ROLE_ADMIN })
- public void putAdmissionControlPolicy(@ApiParam(value = "Admission control policy") @RequestBody String acPolicy, //
+ public void putPolicy(@PathVariable(PP_POLICIES) String policyName,
+ @ApiParam(value = "Policy body") @RequestBody String policy, //
HttpServletResponse response) {
- logger.debug("putAdmissionControlPolicy {}", acPolicy);
- a1MediatorApi.a1ControllerPutHandler(AC_CONTROL_NAME, acPolicy);
+ logger.debug("putPolicy name {} value {}", policyName, policy);
+ a1MediatorApi.a1ControllerPutHandler(policyName, policy);
response.setStatus(a1MediatorApi.getApiClient().getStatusCode().value());
}
import static org.mockito.Mockito.when;
import java.lang.invoke.MethodHandles;
+import java.util.HashMap;
+import java.util.Map;
import org.oransc.ric.a1med.client.api.A1MediatorApi;
import org.oransc.ric.a1med.client.invoker.ApiClient;
import org.springframework.context.annotation.Profile;
import org.springframework.http.HttpStatus;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+
/**
* Creates a mock implementation of the A1 mediator client API.
*/
private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
+ // A "control" is an element in the XApp descriptor
+ public static final String AC_CONTROL_NAME = "admission_control_policy";
+
// Simulate remote method delay for UI testing
@Value("${mock.config.delay:0}")
private int delayMs;
+ private final Map<String, String> appPolicyMap;
+
public A1MediatorMockConfiguration() {
logger.info("Configuring mock A1 Mediator");
+ appPolicyMap = new HashMap<>();
+ // Define a mock AC policy
+ ObjectMapper mapper = new ObjectMapper();
+ ObjectNode node = mapper.createObjectNode();
+ // These fields are defined in the ACAdmissionIntervalControl
+ // Typescript interface, but are otherwise unknown to this backend.
+ node.put("enforce", Boolean.TRUE);
+ node.put("window_length", 0);
+ node.put("blocking_rate", 0);
+ node.put("trigger_threshold", 0);
+ appPolicyMap.put(AC_CONTROL_NAME, node.toString());
}
private ApiClient apiClient() {
logger.debug("a1ControllerGetHandler sleeping {}", delayMs);
Thread.sleep(delayMs);
}
- return null;
+ String appName = inv.<String>getArgument(0);
+ return appPolicyMap.get(appName);
}).when(mockApi).a1ControllerGetHandler(any(String.class));
doAnswer(inv -> {
if (delayMs > 0) {
logger.debug("a1ControllerPutHandler sleeping {}", delayMs);
Thread.sleep(delayMs);
}
+ String appName = inv.<String>getArgument(0);
+ String policy = inv.<String>getArgument(1);
+ appPolicyMap.put(appName, policy);
return null;
}).when(mockApi).a1ControllerPutHandler(any(String.class), any(Object.class));
return mockApi;
import java.lang.invoke.MethodHandles;
import java.net.URI;
+import org.junit.Assert;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
+import org.oransc.ric.portal.dashboard.config.A1MediatorMockConfiguration;
import org.oransc.ric.portal.dashboard.model.SuccessTransport;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
-public class AcXappControllerTest extends AbstractControllerTest {
+public class A1MediatorControllerTest extends AbstractControllerTest {
private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
@Test
public void versionTest() {
- URI uri = buildUri(null, AcXappController.CONTROLLER_PATH, AcXappController.VERSION_METHOD);
+ URI uri = buildUri(null, A1MediatorController.CONTROLLER_PATH, A1MediatorController.VERSION_METHOD);
logger.info("Invoking {}", uri);
SuccessTransport st = restTemplate.getForObject(uri, SuccessTransport.class);
Assertions.assertFalse(st.getData().toString().isEmpty());
@Test
public void getTest() throws IOException {
- // Always returns 501; surprised that no exception is thrown.
- URI uri = buildUri(null, AcXappController.CONTROLLER_PATH, AcXappController.POLICY_METHOD);
+ URI uri = buildUri(null, A1MediatorController.CONTROLLER_PATH, A1MediatorController.PP_POLICIES,
+ A1MediatorMockConfiguration.AC_CONTROL_NAME);
logger.info("Invoking {}", uri);
ResponseEntity<String> response = testRestTemplateStandardRole().exchange(uri, HttpMethod.GET, null,
String.class);
- Assertions.assertTrue(response.getStatusCode().is5xxServerError());
+ Assertions.assertTrue(response.getStatusCode().is2xxSuccessful());
+ Assert.assertFalse(response.getBody().isEmpty());
}
@Test
public void putTest() throws IOException {
ObjectMapper mapper = new ObjectMapper();
JsonNode body = mapper.readTree("{ \"policy\" : true }");
- URI uri = buildUri(null, AcXappController.CONTROLLER_PATH, AcXappController.POLICY_METHOD);
+ URI uri = buildUri(null, A1MediatorController.CONTROLLER_PATH, A1MediatorController.PP_POLICIES,
+ A1MediatorMockConfiguration.AC_CONTROL_NAME);
HttpEntity<JsonNode> entity = new HttpEntity<>(body);
logger.info("Invoking {}", uri);
ResponseEntity<Void> voidResponse = testRestTemplateAdminRole().exchange(uri, HttpMethod.PUT, entity,
import org.oransc.ric.portal.dashboard.DashboardConstants;
import org.oransc.ric.portal.dashboard.model.DashboardUser;
import org.oransc.ric.portal.dashboard.model.ErrorTransport;
-import org.oransc.ric.portal.dashboard.model.IDashboardResponse;
import org.oransc.ric.portal.dashboard.model.SuccessTransport;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
metricsQueryParms.put("app", DashboardConstants.APP_NAME_AC);
URI uri = buildUri(metricsQueryParms, AdminController.CONTROLLER_PATH, AdminController.XAPPMETRICS_METHOD);
logger.debug("Invoking {}", uri);
- ResponseEntity<SuccessTransport> successResponse = testRestTemplateStandardRole().exchange(uri, HttpMethod.GET, null,
- SuccessTransport.class);
+ ResponseEntity<SuccessTransport> successResponse = testRestTemplateStandardRole().exchange(uri, HttpMethod.GET,
+ null, SuccessTransport.class);
Assertions.assertFalse(successResponse.getBody().getData().toString().isEmpty());
Assertions.assertTrue(successResponse.getStatusCode().is2xxSuccessful());
}
@Test
public void getxAppMetricsUrlTestFail() {
- Map<String, String> metricsQueryParms = new HashMap<String, String>();
- //Providing a bogus value for application name in query parameter to test failure
+ Map<String, String> metricsQueryParms = new HashMap<String, String>();
+ // Providing a bogus value for application name in query parameter to test
+ // failure
metricsQueryParms.put("app", "ABCD");
URI uri = buildUri(metricsQueryParms, AdminController.CONTROLLER_PATH, AdminController.XAPPMETRICS_METHOD);
logger.debug("Invoking {}", uri);
- ResponseEntity<ErrorTransport> errorResponse = testRestTemplateStandardRole().exchange(uri, HttpMethod.GET, null,
- ErrorTransport.class);
+ ResponseEntity<ErrorTransport> errorResponse = testRestTemplateStandardRole().exchange(uri, HttpMethod.GET,
+ null, ErrorTransport.class);
logger.debug("{}", errorResponse.getBody().getError().toString());
Assertions.assertTrue(errorResponse.getStatusCode().is4xxClientError());
}
<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
import { ACXappService } from '../services/ac-xapp/ac-xapp.service';
import { ErrorDialogService } from '../services/ui/error-dialog.service';
import { NotificationService } from './../services/ui/notification.service';
+import { HttpErrorResponse } from '@angular/common/http';
@Component({
selector: 'rd-ac-xapp',
private acForm: FormGroup;
- // this is probably the A1 version string
- acVersion: string;
-
constructor(
private acXappService: ACXappService,
private errorDialogService: ErrorDialogService,
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({
// Names must match the ACAdmissionIntervalControl interface
- enforce: new FormControl(true, [Validators.required]),
+ enforce: new FormControl(true, [Validators.required]),
window_length: new FormControl('', [Validators.required, Validators.pattern(windowLengthPattern)]),
blocking_rate: new FormControl('', [Validators.required, Validators.pattern(blockingRatePattern)]),
trigger_threshold: new FormControl('', [Validators.required, Validators.pattern(triggerPattern)])
});
- this.acXappService.getVersion().subscribe((res: string) => this.acVersion = res);
+ // TODO: show pending action indicator
+ this.acXappService.getPolicy().subscribe((res: ACAdmissionIntervalControl) => {
+ this.acForm.controls['enforce'].setValue(res.enforce);
+ this.acForm.controls['window_length'].setValue(res.window_length);
+ this.acForm.controls['blocking_rate'].setValue(res.blocking_rate);
+ this.acForm.controls['trigger_threshold'].setValue(res.trigger_threshold);
+ // TODO: clear pending action indicator
+ },
+ (error: HttpErrorResponse) => {
+ // TODO: clear pending action indicator
+ this.errorDialogService.displayError(error.message);
+ });
}
updateAc = (acFormValue: ACAdmissionIntervalControl) => {
if (this.acForm.valid) {
- this.acXappService.putPolicy(acFormValue).subscribe(
+ // convert strings to numbers using the plus operator
+ const acFormValueConverted = {
+ enforce: acFormValue.enforce,
+ window_length: +acFormValue.window_length,
+ blocking_rate: +acFormValue.blocking_rate,
+ trigger_threshold: +acFormValue.trigger_threshold
+ };
+ this.acXappService.putPolicy(acFormValueConverted).subscribe(
response => {
- if (response.status === 200 ) {
+ if (response.status === 200) {
this.notificationService.success('AC update policy succeeded!');
}
},
import { DashboardSuccessTransport } from '../../interfaces/dashboard.types';
/**
- * Services for calling the Dashboard's AC endpoints.
+ * Services for calling the Dashboard's A1 endpoints to get/put AC policies.
*/
@Injectable({
providedIn: 'root'
})
export class ACXappService {
- private basePath = 'api/xapp/admctl';
- private policyPath = 'policy';
+ private basePath = 'api/a1-p';
+ private policyPath = 'policies';
+ private acPolicyName = 'admission_control_policy';
private buildPath(...args: any[]) {
let result = this.basePath;
}
/**
- * Gets admission control parameters.
+ * Gets admission control policy.
* @returns Observable that should yield an ACAdmissionIntervalControl
*/
getPolicy(): Observable<ACAdmissionIntervalControl> {
- const url = this.buildPath(this.policyPath);
+ const url = this.buildPath(this.policyPath, this.acPolicyName);
return this.httpClient.get<ACAdmissionIntervalControl>(url);
}
/**
- * Puts admission control parameters.
+ * Puts admission control policy.
* @param policy an instance of ACAdmissionIntervalControl
* @returns Observable that should yield a response code, no data
*/
putPolicy(policy: ACAdmissionIntervalControl): Observable<any> {
- const url = this.buildPath(this.policyPath);
+ const url = this.buildPath(this.policyPath, this.acPolicyName);
return this.httpClient.put<ACAdmissionIntervalControlAck>(url, policy, { observe: 'response' });
}