Merge remote-tracking branch 'origin/master' into merge-master

Change-Id: I4608093c4400a313b253508ac6bc8a84ecba5c7e
This commit is contained in:
Ray Milkey 2018-10-04 15:13:33 -07:00
commit df521294ce
196 changed files with 27123 additions and 7999 deletions

View File

@ -1134,7 +1134,8 @@ public class Dhcp4HandlerImpl implements DhcpHandler, HostProvider {
DhcpServerInfo foundServerInfo = findServerInfoFromServer(directConnFlag, inPort);
if (foundServerInfo == null) {
log.warn("Cannot find server info");
log.warn("Cannot find server info for {} server, inPort {}",
directConnFlag ? "direct" : "indirect", inPort);
return null;
} else {
if (Dhcp4HandlerUtil.isServerIpEmpty(foundServerInfo)) {
@ -1517,7 +1518,14 @@ public class Dhcp4HandlerImpl implements DhcpHandler, HostProvider {
// sent by ONOS or circuit Id can't be parsed
// TODO: remove relay store from this method
MacAddress dstMac = valueOf(dhcpPayload.getClientHardwareAddress());
Optional<DhcpRecord> dhcpRecord = dhcpRelayStore.getDhcpRecord(HostId.hostId(dstMac, originalPacketVlanId));
VlanId filteredVlanId = getVlanIdFromDhcpRecord(dstMac, originalPacketVlanId);
// Get the vlan from the dhcp record
if (filteredVlanId == null) {
log.debug("not find the matching DHCP record for mac: {} and vlan: {}", dstMac, originalPacketVlanId);
return Optional.empty();
}
Optional<DhcpRecord> dhcpRecord = dhcpRelayStore.getDhcpRecord(HostId.hostId(dstMac, filteredVlanId));
ConnectPoint clientConnectPoint = dhcpRecord
.map(DhcpRecord::locations)
.orElse(Collections.emptySet())
@ -1534,12 +1542,51 @@ public class Dhcp4HandlerImpl implements DhcpHandler, HostProvider {
if (clientConnectPoint != null) {
return interfaceService.getInterfacesByPort(clientConnectPoint)
.stream()
.filter(iface -> interfaceContainsVlan(iface, originalPacketVlanId))
.filter(iface -> interfaceContainsVlan(iface, filteredVlanId))
.findFirst();
}
return Optional.empty();
}
/**
* Get the required vlanId in case the DCHP record has more than one vlanId for a given MAC.
*
* @param mac MAC address of the DHCP client
* @param vlan Expected vlan of the DHCP client
*/
private VlanId getVlanIdFromDhcpRecord(MacAddress mac, VlanId vlan) {
// Get all the DHCP records matching with the mac address
// If only one entry is present then pick the vlan of that entry
// If more then one entry is present then look for an entry with matching vlan
// else return null
Collection<DhcpRecord> records = dhcpRelayStore.getDhcpRecords();
List<DhcpRecord> filteredRecords = new ArrayList<>();
for (DhcpRecord e: records) {
if (e.macAddress().equals(mac)) {
filteredRecords.add(e);
}
}
log.debug("getVlanIdFromDhcpRecord mac: {} vlan: {}", mac, vlan);
log.debug("filteredRecords are: {}", filteredRecords);
if (filteredRecords.size() == 1) {
log.debug("Only one DHCP record entry. Returning back the vlan of that DHCP record: {}", filteredRecords);
return filteredRecords.get(0).vlanId();
}
// Check in the DHCP filtered record for matching vlan
for (DhcpRecord e: filteredRecords) {
if (e.vlanId().equals(vlan)) {
log.debug("Found a matching vlan entry in the DHCP record:{}", e);
return vlan;
}
}
// Found nothing return null
log.debug("Returning null as no matching or more than one matching entry found");
return null;
}
/**
* Send the response DHCP to the requester host.
*
@ -1924,9 +1971,6 @@ public class Dhcp4HandlerImpl implements DhcpHandler, HostProvider {
log.debug("ServerInfo found for Rcv port {} Server Connect Point {} for {}",
inPort, serverInfo.getDhcpServerConnectPoint(), directConnFlag ? "direct" : "indirect");
break;
} else {
log.warn("Rcv port {} not the same as Server Connect Point {} for {}",
inPort, serverInfo.getDhcpServerConnectPoint(), directConnFlag ? "direct" : "indirect");
}
}
return foundServerInfo;

View File

@ -1211,7 +1211,8 @@ public class Dhcp6HandlerImpl implements DhcpHandler, HostProvider {
DhcpServerInfo foundServerInfo = findServerInfoFromServer(directConnFlag, inPort);
if (foundServerInfo == null) {
log.warn("Cannot find server info");
log.warn("Cannot find server info for {} server, inPort {}",
directConnFlag ? "direct" : "indirect", inPort);
dhcpRelayCountersStore.incrementCounter(gCount, DhcpRelayCounters.NO_SERVER_INFO);
return null;
} else {
@ -1846,9 +1847,6 @@ public class Dhcp6HandlerImpl implements DhcpHandler, HostProvider {
log.debug("ServerInfo found for Rcv port {} Server Connect Point {} for {}",
inPort, serverInfo.getDhcpServerConnectPoint(), directConnFlag ? "direct" : "indirect");
break;
} else {
log.warn("Rcv port {} not the same as Server Connect Point {} for {}",
inPort, serverInfo.getDhcpServerConnectPoint(), directConnFlag ? "direct" : "indirect");
}
}
return foundServerInfo;

View File

@ -0,0 +1,13 @@
# Editor configuration, see http://editorconfig.org
root = true
[*]
charset = utf-8
indent_style = space
indent_size = 2
insert_final_newline = true
trim_trailing_whitespace = true
[*.md]
max_line_length = off
trim_trailing_whitespace = false

View File

@ -0,0 +1,42 @@
# See http://help.github.com/ignore-files/ for more about ignoring files.
# compiled output
/dist
/tmp
/out-tsc
# dependencies
/node_modules
# IDEs and editors
/.idea
.project
.classpath
.c9/
*.launch
.settings/
*.sublime-workspace
# IDE - VSCode
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
# misc
/.sass-cache
/connect.lock
/coverage
/libpeerconnection.log
npm-debug.log
yarn-error.log
testem.log
/typings
# System Files
.DS_Store
Thumbs.db
# ONOS temp CSS files
/projects/fm-gui2-lib/fw

View File

@ -0,0 +1,291 @@
"""
Copyright 2018-present Open Networking Foundation
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.
"""
"""
Rules to build the Fault Management (FM) GUI app for GUI 2
"""
COMPILE_DEPS = CORE_DEPS + JACKSON + KRYO + [
"@javax_ws_rs_api//jar",
"@servlet_api//jar",
"@jetty_websocket//jar",
"@jetty_util//jar",
"@jersey_media_multipart//jar",
"@jersey_server//jar",
"//utils/rest:onlab-rest",
"//core/store/serializers:onos-core-serializers",
]
TEST_DEPS = TEST + [
"//core/api:onos-api-tests",
"//drivers/default:onos-drivers-default",
]
"""
Files that get put at the top level of the tar ball
"""
filegroup(
name = "_root_level_files",
srcs =
[
":angular.json",
":package.json",
":package-lock.json",
":tsconfig.json",
":tslint.json",
],
)
filegroup(
name = "_e2e_test_files",
srcs = [
":e2e/protractor.conf.js",
":e2e/src/app.e2e-spec.ts",
":e2e/src/app.po.ts",
":e2e/tsconfig.e2e.json",
],
)
"""
Run ng build to create FM GUI 2 library in production mode
The output file fm-gui2-lib-ver.tgz is in the form that can be uploaded directly to https://www.npmjs.com/
See bazel-genfiles/apps/faultmanagement/fm-gui2-lib/fm-gui2-lib.log for details of the Angular CLI output
"""
genrule(
name = "fm-gui2-lib-build",
srcs = [
"@nodejs//:bin/npm",
"@nodejs//:bin/node",
"@nodejs//:bin/node.js",
"@nodejs//:bin/nodejs/bin/node",
"@nodejs//:bin/nodejs/bin/npm",
"//web/gui2-fw-lib:onos-gui2-fw-npm-install",
"//web/gui2-fw-lib:onos-gui2-fw-ng-build",
"//web/gui2-fw-lib:gui2_fw_lib_ext_css",
":_root_level_files",
":_fm_gui2_lib_src",
":_fm-gui2_app_files",
],
outs = [
"fm-gui2-lib.log",
"fm-gui2-lib-ver.tgz",
],
cmd = "ROOT=`pwd` &&" +
" export HOME=. &&" +
" export XDG_CONFIG_HOME=$(@D)/config &&" +
" NODE=$(location @nodejs//:bin/node) &&" +
" INSTALL_FILES=($(locations //web/gui2-fw-lib:onos-gui2-fw-npm-install)) &&" + # An array of filenames - sorted by time created
" FWLIB_FILES=($(locations //web/gui2-fw-lib:onos-gui2-fw-ng-build)) &&" + # An array of filenames - sorted by time created
" mkdir -p apps/faultmanagement/fm-gui2-lib &&" +
" cd apps/faultmanagement/fm-gui2-lib &&" +
" jar xf $$ROOT/$${INSTALL_FILES[0]} &&" +
" tar xf $$ROOT/$${FWLIB_FILES[0]} &&" +
" mv package/ node_modules/gui2-fw-lib/ &&" +
" mkdir -p projects/fm-gui2-lib/fw &&" +
" (cd projects/fm-gui2-lib/fw &&" +
" jar xf $$ROOT/$(location //web/gui2-fw-lib:gui2_fw_lib_ext_css)) &&" +
" chmod +x node_modules/@angular/cli/bin/ng &&" +
" export PATH=$$ROOT/$$(dirname $${NODE}):$$ROOT/apps/faultmanagement/fm-gui2-lib/node_modules/@angular/cli/bin:$$PATH &&" +
" ng build --prod fm-gui2-lib >> $$ROOT/$(location fm-gui2-lib.log) 2>&1 ||" +
" if [ $$? -eq 0 ]; then echo 'Successfully ran build';" +
" else " +
" echo 'Error running \'ng build fm-gui2-lib\' on \'//apps/faultmanagement/fm-gui2-lib:fm-gui2-lib-build\'. \\\n" +
" See bazel-genfiles/apps/faultmanagement/fm-gui2-lib/fm-gui2-lib.log for more details' >&2;" +
#" tail -n 100 ../../$(location onos-gui2-ng-test.log) >&2;" +
" exit 1;" +
" fi;" +
" cd dist/fm-gui2-lib && " +
" npm pack >> $$ROOT/$(location fm-gui2-lib.log) 2>&1 &&" +
" mv fm-gui2-lib-*.tgz $$ROOT/$(location fm-gui2-lib-ver.tgz) &&" +
" touch $$ROOT/$(location fm-gui2-lib.log)", # to get the log always as the 2nd file,
message = "Angular FM GUI2 build",
visibility = ["//visibility:public"],
)
"""
Run 'ng test' to run Angular test and 'ng lint' for checkstyle
See bazel-genfiles/apps/faultmanagement/fm-gui2-lib/fm-gui2-lib-lint.log or
bazel-genfiles/apps/faultmanagement/fm-gui2-lib/fm-gui2-lib-test.log for details of the Angular CLI output
"""
genrule(
name = "_fm-gui2-lib-test-genrule",
srcs = [
"@nodejs//:bin/npm",
"@nodejs//:bin/node",
"@nodejs//:bin/node.js",
"@nodejs//:bin/nodejs/bin/node",
"@nodejs//:bin/nodejs/bin/npm",
"//web/gui2-fw-lib:onos-gui2-fw-npm-install",
"//web/gui2-fw-lib:onos-gui2-fw-ng-build",
"//web/gui2-fw-lib:gui2_fw_lib_ext_css",
":_root_level_files",
":_fm_gui2_lib_src",
":_fm_gui2_lib_src_tests",
],
outs = [
"fm-gui2-lib-ver.log",
"fm-gui2-lib-lint.log",
"fm-gui2-lib-test.log",
],
cmd = " ROOT=`pwd` &&" +
" export HOME=. &&" +
" export XDG_CONFIG_HOME=$(@D)/config &&" +
" NODE=$(location @nodejs//:bin/node) &&" +
" INSTALL_FILES=($(locations //web/gui2-fw-lib:onos-gui2-fw-npm-install)) &&" + # An array of filenames - sorted by time created
" FWLIB_FILES=($(locations //web/gui2-fw-lib:onos-gui2-fw-ng-build)) &&" + # An array of filenames - sorted by time created
" mkdir -p apps/faultmanagement/fm-gui2-lib &&" +
" cd apps/faultmanagement/fm-gui2-lib &&" +
" jar xf ../../../$(location :_fm_gui2_lib_src_tests) &&" +
" jar xf $$ROOT/$${INSTALL_FILES[0]} &&" +
" tar xf $$ROOT/$${FWLIB_FILES[0]} &&" +
" mv package/ node_modules/gui2-fw-lib/ &&" +
" mkdir -p projects/fm-gui2-lib/fw &&" +
" (cd projects/fm-gui2-lib/fw &&" +
" jar xf $$ROOT/$(location //web/gui2-fw-lib:gui2_fw_lib_ext_css)) &&" +
" chmod +x $$ROOT/apps/faultmanagement/fm-gui2-lib/node_modules/@angular/cli/bin/ng &&" +
" export PATH=$$ROOT/$$(dirname $${NODE}):node_modules/@angular/cli/bin:$$PATH &&" +
" node -v > ../../../$(location fm-gui2-lib-ver.log) &&" +
" npm -v >> ../../../$(location fm-gui2-lib-ver.log) &&" +
" ng -v >> ../../../$(location fm-gui2-lib-ver.log) &&" +
" ng lint fm-gui2-lib > ../../../$(location fm-gui2-lib-lint.log);" +
" if [ -f /usr/bin/chromium-browser ]; then " + # Add to this for Mac and Chrome
" export CHROME_BIN=/usr/bin/chromium-browser; " +
" elif [ -f /opt/google/chrome/chrome ]; then " +
" export CHROME_BIN=/opt/google/chrome/chrome; " +
" else " +
" MSG='Warning: Step fm-gui2-lib-tests skipped because \\n" +
" no binary for ChromeHeadless browser was found at /usr/bin/chromium-browser. \\n" +
" Install Google Chrome or Chromium Browser to allow this step to run.';" +
" echo -e $$MSG >&2;" +
" echo -e $$MSG > ../../../$(location fm-gui2-lib-test.log);" +
" exit 0;" +
" fi;" +
" ng test --preserve-symlinks --code-coverage --browsers=ChromeHeadless" +
" --watch=false fm-gui2-lib > ../../../$(location fm-gui2-lib-test.log) 2>&1 ||" +
" if [ $$? -eq 0 ]; then echo 'Successfully ran tests';" +
" else " +
" echo 'Error running \'ng test fm-gui2-lib\' on \'//apps/faultmanagement/fm-gui2-lib:_fm-gui2-lib-test-genrule\'. \\\n" +
" See bazel-genfiles/apps/faultmanagement/fm-gui2-lib/fm-gui2-lib-test.log for more details' >&2;" +
" exit 1;" +
" fi;",
message = "Angular FM GUI2 Lib lint and test",
)
"""
Make a group of all the webapp files.
"""
filegroup(
name = "_fm_gui2_lib_src",
srcs = glob(
[
"projects/fm-gui2-lib/**/*",
],
exclude = [
"projects/fm-gui2-lib/**/*.spec.*", # Don't track tests here
"projects/fm-gui2-lib/karma.conf.js",
"projects/fm-gui2-lib/src/test.ts",
"projects/fm-gui2-lib/fw/**/*",
],
),
)
"""
Make a group of all the webapp qpp files.
"""
filegroup(
name = "_fm-gui2_app_files",
srcs = glob(
[
"src/**/*",
],
),
)
"""
Make a jar file of all the webapp test (*.spec.ts) files.
"""
genrule(
name = "_fm_gui2_lib_src_tests",
srcs = glob(
[
"projects/fm-gui2-lib/karma.conf.js",
"projects/fm-gui2-lib/src/test.ts",
"projects/fm-gui2-lib/tsconfig.spec.json",
"projects/fm-gui2-lib/**/*.spec.ts",
],
exclude = [
"projects/fm-gui2-lib/ng-package.json",
"projects/fm-gui2-lib/ng-package.prod.json",
"projects/fm-gui2-lib/package.json",
"projects/fm-gui2-lib/tsconfig.lib.json",
"projects/fm-gui2-lib/tslint.json",
"projects/fm-gui2-lib/src/public_api.ts",
],
),
outs = ["fm_gui2_lib_src_tests.jar"],
cmd = "cd apps/faultmanagement/fm-gui2-lib &&" +
" jar Mcf ../../../$@ .",
)
"""
Make a jar file of all the webapp test (*.spec.ts) files.
"""
genrule(
name = "_fm_gui2_lib_tests",
srcs = glob(
[
"projects/fm-gui2-lib/**/*.spec.ts",
"projects/fm-gui2-lib/**/*.spec.json",
],
exclude = [
"src/main/webapp/tests/**",
"src/main/webapp/node_modules/**",
"src/main/webapp/dist/**",
"src/main/webapp/doc/**",
],
),
outs = ["fm_gui2_lib_tests.jar"],
cmd = "cd apps/faultmanagement/fm-gui2-lib &&" +
" find projects/fm-gui2-lib/src/lib -type f -exec touch -t 201808280000 {} \; &&" +
" jar Mcf ../../../$@ projects/fm-gui2-lib/src/lib",
)
"""
Wrap the genrule for testing in a test
"""
sh_test(
name = "fm-gui2-lib-tests",
size = "small",
srcs = [
":ng-test.sh",
],
data = [
":_fm-gui2-lib-test-genrule",
],
deps = [
"@bazel_tools//tools/bash/runfiles",
],
)

View File

@ -0,0 +1,85 @@
# FmGui2LibApp
This project contains the FM GUI2 web application as an NPM library module
It is a new approach to GUI extensions in GUI2, where each extension is implemented in its own
library module as an Angular module containing one or more Angular components, services or directives.
It benefits from the lazy loading approach of the ONOS GUI2 application, and so does not have injected
js and css files as the old approach had.
It contains the component AlarmTableComponent and AlarmDetailsComponent, and is built on top of
classes from the GUI2 framework library (gui2-fw-lib).
This is integrated in to the main ONOS GUI2 web application by adding it to the
"onos-routing.module.ts" in web/gui2/src/main/webapp/app and in to nav.html
```
===============================
== ONOS GUI2 Web Application ==
===============================
||
|| Lazy loads
\/
===================================
== FM GUI2 LIB module (incl ...) ==
== Alarm Table Component ==
== Alarm Details Component ==
===================================
||
|| Depends
\/
=============================
== GUI2 FW Lib ==
== Web Sockets, LION etc ==
=============================
```
This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 6.0.0.
A couple of good articles on the creation and use of __libraries__ in Angular 6 is given in:
[The Angular Library Series - Creating a Library with the Angular CLI](https://blog.angularindepth.com/creating-a-library-in-angular-6-87799552e7e5)
and
[The Angular Library Series - Building and Packaging](https://blog.angularindepth.com/creating-a-library-in-angular-6-part-2-6e2bc1e14121)
The Bazel build of this library handles the building and packaging of the library
so that other projects and libraries can use it.
## Development server
To build the library project using Angular CLI run 'ng build --prod fm-gui2-lib'
inside the ~/onos/apps/faultmanagement/fm-gui2-lib folder
To make the library in to an NPM package use 'npm pack' inside the dist/fm-gui2-lib folder
To build the app that surrounds the library run 'ng build'. This app is not
part of the ONOS GUI and is there as a placeholder for testing the library
Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`.
The app will automatically reload if you change any of the source files.
__NOTE__ If you make changes to files in the library, the app will not pick them up until you build the library again
## Code scaffolding
Run `ng generate component <component-name> --project=fm-gui2-lib` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`.
## Build
Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory. Use the `--prod` flag for a production build.
## Running unit tests
Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io).
## Running end-to-end tests
Run `ng e2e` to execute the end-to-end tests via [Protractor](http://www.protractortest.org/).
## Further help
To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI README](https://github.com/angular/angular-cli/blob/master/README.md).

View File

@ -0,0 +1,162 @@
{
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
"version": 1,
"newProjectRoot": "projects",
"projects": {
"fm-gui2-lib-app": {
"root": "",
"sourceRoot": "src",
"projectType": "application",
"prefix": "app",
"schematics": {},
"architect": {
"build": {
"builder": "@angular-devkit/build-angular:browser",
"options": {
"outputPath": "dist/fm-gui2-lib-app",
"index": "src/index.html",
"main": "src/main.ts",
"polyfills": "src/polyfills.ts",
"tsConfig": "src/tsconfig.app.json",
"assets": [
"src/favicon.ico",
"src/assets"
],
"styles": [
"src/styles.css"
],
"scripts": []
},
"configurations": {
"production": {
"fileReplacements": [
{
"replace": "src/environments/environment.ts",
"with": "src/environments/environment.prod.ts"
}
],
"optimization": true,
"outputHashing": "all",
"sourceMap": false,
"extractCss": true,
"namedChunks": false,
"aot": true,
"extractLicenses": true,
"vendorChunk": false,
"buildOptimizer": true
}
}
},
"serve": {
"builder": "@angular-devkit/build-angular:dev-server",
"options": {
"browserTarget": "fm-gui2-lib-app:build"
},
"configurations": {
"production": {
"browserTarget": "fm-gui2-lib-app:build:production"
}
}
},
"extract-i18n": {
"builder": "@angular-devkit/build-angular:extract-i18n",
"options": {
"browserTarget": "fm-gui2-lib-app:build"
}
},
"test": {
"builder": "@angular-devkit/build-angular:karma",
"options": {
"main": "src/test.ts",
"polyfills": "src/polyfills.ts",
"tsConfig": "src/tsconfig.spec.json",
"karmaConfig": "src/karma.conf.js",
"styles": [
"styles.css"
],
"scripts": [],
"assets": [
"src/favicon.ico",
"src/assets"
]
}
},
"lint": {
"builder": "@angular-devkit/build-angular:tslint",
"options": {
"tsConfig": [
"src/tsconfig.app.json",
"src/tsconfig.spec.json"
],
"exclude": [
"**/node_modules/**"
]
}
}
}
},
"fm-gui2-lib-app-e2e": {
"root": "e2e/",
"projectType": "application",
"architect": {
"e2e": {
"builder": "@angular-devkit/build-angular:protractor",
"options": {
"protractorConfig": "e2e/protractor.conf.js",
"devServerTarget": "fm-gui2-lib-app:serve"
}
},
"lint": {
"builder": "@angular-devkit/build-angular:tslint",
"options": {
"tsConfig": "e2e/tsconfig.e2e.json",
"exclude": [
"**/node_modules/**"
]
}
}
}
},
"fm-gui2-lib": {
"root": "projects/fm-gui2-lib",
"sourceRoot": "projects/fm-gui2-lib/src",
"projectType": "library",
"prefix": "onos",
"architect": {
"build": {
"builder": "@angular-devkit/build-ng-packagr:build",
"options": {
"tsConfig": "projects/fm-gui2-lib/tsconfig.lib.json",
"project": "projects/fm-gui2-lib/ng-package.json"
},
"configurations": {
"production": {
"project": "projects/fm-gui2-lib/ng-package.prod.json"
}
}
},
"test": {
"builder": "@angular-devkit/build-angular:karma",
"options": {
"main": "projects/fm-gui2-lib/src/test.ts",
"tsConfig": "projects/fm-gui2-lib/tsconfig.spec.json",
"karmaConfig": "projects/fm-gui2-lib/karma.conf.js"
}
},
"lint": {
"builder": "@angular-devkit/build-angular:tslint",
"options": {
"tsConfig": [
"projects/fm-gui2-lib/tsconfig.lib.json",
"projects/fm-gui2-lib/tsconfig.spec.json"
],
"exclude": [
"**/node_modules/**"
]
}
}
}
}
},
"defaultProject": "fm-gui2-lib-app"
}

View File

@ -0,0 +1,28 @@
// Protractor configuration file, see link for more information
// https://github.com/angular/protractor/blob/master/lib/config.ts
const { SpecReporter } = require('jasmine-spec-reporter');
exports.config = {
allScriptsTimeout: 11000,
specs: [
'./src/**/*.e2e-spec.ts'
],
capabilities: {
'browserName': 'chrome'
},
directConnect: true,
baseUrl: 'http://localhost:4200/',
framework: 'jasmine',
jasmineNodeOpts: {
showColors: true,
defaultTimeoutInterval: 30000,
print: function() {}
},
onPrepare() {
require('ts-node').register({
project: require('path').join(__dirname, './tsconfig.e2e.json')
});
jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } }));
}
};

View File

@ -0,0 +1,14 @@
import { AppPage } from './app.po';
describe('workspace-project App', () => {
let page: AppPage;
beforeEach(() => {
page = new AppPage();
});
it('should display welcome message', () => {
page.navigateTo();
expect(page.getParagraphText()).toEqual('Welcome to app!');
});
});

View File

@ -0,0 +1,11 @@
import { browser, by, element } from 'protractor';
export class AppPage {
navigateTo() {
return browser.get('/');
}
getParagraphText() {
return element(by.css('app-root h1')).getText();
}
}

View File

@ -0,0 +1,13 @@
{
"extends": "../tsconfig.json",
"compilerOptions": {
"outDir": "../out-tsc/app",
"module": "commonjs",
"target": "es5",
"types": [
"jasmine",
"jasminewd2",
"node"
]
}
}

View File

@ -0,0 +1,29 @@
#!/bin/bash
set -euo pipefail
# --- begin runfiles.bash initialization ---
if [[ ! -d "${RUNFILES_DIR:-/dev/null}" && ! -f "${RUNFILES_MANIFEST_FILE:-/dev/null}" ]]; then
if [[ -f "$0.runfiles_manifest" ]]; then
export RUNFILES_MANIFEST_FILE="$0.runfiles_manifest"
elif [[ -f "$0.runfiles/MANIFEST" ]]; then
export RUNFILES_MANIFEST_FILE="$0.runfiles/MANIFEST"
elif [[ -f "$0.runfiles/bazel_tools/tools/bash/runfiles/runfiles.bash" ]]; then
export RUNFILES_DIR="$0.runfiles"
fi
fi
if [[ -f "${RUNFILES_DIR:-/dev/null}/bazel_tools/tools/bash/runfiles/runfiles.bash" ]]; then
source "${RUNFILES_DIR}/bazel_tools/tools/bash/runfiles/runfiles.bash"
elif [[ -f "${RUNFILES_MANIFEST_FILE:-/dev/null}" ]]; then
source "$(grep -m1 "^bazel_tools/tools/bash/runfiles/runfiles.bash " \
"$RUNFILES_MANIFEST_FILE" | cut -d ' ' -f 2-)"
else
echo >&2 "ERROR: cannot find @bazel_tools//tools/bash/runfiles:runfiles.bash"
exit 1
fi
# --- end runfiles.bash initialization ---
echo "Test results for Angular CLI commands run in fm-gui2-lib-tests outputs at:"
ls $(rlocation org_onosproject_onos/apps/faultmanagement/fm-gui2-lib/fw-gui2-lib-ver.log)
ls $(rlocation org_onosproject_onos/apps/faultmanagement/fm-gui2-lib/fw-gui2-lib-lint.log)
ls $(rlocation org_onosproject_onos/apps/faultmanagement/fm-gui2-lib/fw-gui2-lib-test.log)

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,47 @@
{
"name": "fm-gui2-lib-app",
"version": "2.0.0",
"private": true,
"dependencies": {
"@angular/animations": "^6.0.0",
"@angular/common": "^6.0.0",
"@angular/compiler": "^6.0.0",
"@angular/core": "^6.0.0",
"@angular/forms": "^6.0.0",
"@angular/http": "^6.0.0",
"@angular/platform-browser": "^6.0.0",
"@angular/platform-browser-dynamic": "^6.0.0",
"@angular/router": "^6.0.0",
"core-js": "^2.5.4",
"d3": "^5.2.0",
"gui2-fw-lib": "file:../../../web/gui2-fw-lib/dist/gui2-fw-lib/gui2-fw-lib-0.14.0.tgz",
"rxjs": "^6.0.0",
"zone.js": "^0.8.26"
},
"devDependencies": {
"@angular/compiler-cli": "^6.0.0",
"@angular-devkit/build-ng-packagr": "~0.6.8",
"@angular-devkit/build-angular": "~0.6.0",
"ng-packagr": "^3.0.0-rc.2",
"tsickle": ">=0.25.5",
"tslib": "^1.7.1",
"typescript": "~2.7.2",
"@angular/cli": "~6.0.0",
"@angular/language-service": "^6.0.0",
"@types/jasmine": "~2.8.6",
"@types/jasminewd2": "~2.0.3",
"@types/node": "~8.9.4",
"codelyzer": "~4.2.1",
"jasmine-core": "~2.99.1",
"jasmine-spec-reporter": "~4.2.1",
"karma": "~1.7.1",
"karma-chrome-launcher": "~2.2.0",
"karma-coverage-istanbul-reporter": "~1.4.2",
"karma-firefox-launcher": "^1.1.0",
"karma-jasmine": "~1.1.1",
"karma-jasmine-html-reporter": "^0.2.2",
"protractor": "~5.3.0",
"ts-node": "~5.0.1",
"tslint": "~5.9.1"
}
}

View File

@ -0,0 +1,48 @@
/*
* Copyright 2018-present Open Networking Foundation
*
* 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.
*/
// Karma configuration file, see link for more information
// https://karma-runner.github.io/1.0/config/configuration-file.html
module.exports = function (config) {
config.set({
basePath: '',
frameworks: ['jasmine', '@angular-devkit/build-angular'],
plugins: [
require('karma-jasmine'),
require('karma-chrome-launcher'),
require('karma-firefox-launcher'),
require('karma-jasmine-html-reporter'),
require('karma-coverage-istanbul-reporter'),
require('@angular-devkit/build-angular/plugins/karma')
],
client: {
clearContext: false // leave Jasmine Spec Runner output visible in browser
},
coverageIstanbulReporter: {
dir: require('path').join(__dirname, '../../coverage'),
reports: ['html', 'lcovonly'],
fixWebpackSourcePaths: true
},
reporters: ['progress', 'kjhtml'],
port: 9876,
colors: true,
logLevel: config.LOG_INFO,
autoWatch: true,
browsers: ['Chrome', 'Firefox'],
singleRun: false
});
};

View File

@ -0,0 +1,8 @@
{
"$schema": "../../node_modules/ng-packagr/ng-package.schema.json",
"dest": "../../dist/fm-gui2-lib",
"deleteDestPath": false,
"lib": {
"entryFile": "src/public_api.ts"
}
}

View File

@ -0,0 +1,7 @@
{
"$schema": "../../node_modules/ng-packagr/ng-package.schema.json",
"dest": "../../dist/fm-gui2-lib",
"lib": {
"entryFile": "src/public_api.ts"
}
}

View File

@ -0,0 +1,8 @@
{
"name": "fm-gui2-lib",
"version": "1.15.0",
"peerDependencies": {
"@angular/common": "^6.0.0-rc.0 || ^6.0.0",
"@angular/core": "^6.0.0-rc.0 || ^6.0.0"
}
}

View File

@ -0,0 +1,67 @@
/*
* Copyright 2018-present Open Networking Foundation
*
* 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.
*/
/* css for alarm details table view */
/* Panel Styling */
#ov-alarm-table-item-details-panel.floatpanel {
z-index: 0;
font-size: 10pt;
top: 185px;
}
#ov-alarm-table-item-details-panel .container {
padding: 0 30px;
}
#ov-alarm-table-item-details-panel .close-btn {
position: absolute;
right:5px;
top: 5px;
cursor: pointer;
}
.light #ov-alarm-table-item-details-panel.floatpanel {
background-color: rgb(229, 234, 237);
}
.dark #ov-alarm-table-item-details-panel.floatpanel {
background-color: #3A4042;
}
#ov-alarm-table-item-details-panel h2 {
display: inline-block;
margin: 8px 0;
font-weight: bold;
font-size: 16pt;
}
#ov-alarm-table-item-details-panel h3 {
margin: 0;
font-size: large;
}
#ov-alarm-table-item-details-panel h4 {
margin: 0;
}
#ov-alarm-table-item-details-panel td {
padding: 5px;
}
#ov-alarm-table-item-details-panel td.label {
font-weight: bold;
text-align: right;
padding-right: 6px;
}

View File

@ -0,0 +1,82 @@
<!--
~ Copyright 2018-present Open Networking Foundation
~
~ 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.
-->
<div id="ov-alarm-table-item-details-panel" class="floatpanel" [@alarmDetailsState]="id!=='' && !closed">
<div class="container">
<div class="top">
<div class="close-btn">
<onos-icon class="close-btn" classes="active-close" iconId="close" iconSize="20" (click)="close()"></onos-icon>
</div>
<h2 class="editable clickable">{{detailsData.id}}</h2>
<div class="top-content">
<div class="top-tables">
<div class="left">
<table>
<tbody>
<tr>
<td class="label" width="110">Description:</td>
<td class="value" width="80">{{detailsData.alarmDesc}}</td>
</tr>
<tr>
<td class="label" width="110">Device ID :</td>
<td class="value" width="80">{{detailsData.alarmDeviceId}}</td>
</tr>
<tr>
<td class="label" width="110">Alarm source :</td>
<td class="value" width="80">{{detailsData.alarmSource}}</td>
</tr>
<tr>
<td class="label" width="110">Time raised :</td>
<td class="value" width="80">{{detailsData.alarmTimeRaised}}</td>
</tr>
<tr>
<td class="label" width="110">Time updated :</td>
<td class="value" width="80">{{detailsData.alarmTimeUpdated}}</td>
</tr>
<tr>
<td class="label" width="110">Cleared :</td>
<td class="value" width="80">{{detailsData.alarmCleared}}</td>
</tr>
<tr>
<td class="label" width="110">Time cleared :</td>
<td class="value" width="80">{{detailsData.alarmTimeCleared}}</td>
</tr>
<tr>
<td class="label" width="110">Severity :</td>
<td class="value" width="80">{{detailsData.alarmSeverity}}</td>
</tr>
<hr>
<tr>
<td class="label" width="110">Service affecting :</td>
<td class="value" width="80">{{detailsData.alarmServiceAffecting}}</td>
</tr>
<tr>
<td class="label" width="110">Acknowledged :</td>
<td class="value" width="80">{{detailsData.alarmAcknowledged}}</td>
</tr>
<tr>
<td class="label" width="110">Assigned User :</td>
<td class="value" width="80">{{detailsData.alarmAssignedUser}}</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<hr>
</div>
</div>
</div>

View File

@ -0,0 +1,100 @@
/*
* Copyright 2018-present Open Networking Foundation
*
* 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.
*/
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { AlarmDetailsComponent } from './alarmdetails.component';
import { ActivatedRoute, Params } from '@angular/router';
import { of } from 'rxjs/index';
import {
FnService,
IconService,
LogService,
WebSocketService,
IconComponent
} from 'gui2-fw-lib';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { DebugElement } from '@angular/core';
import { By } from '@angular/platform-browser';
import { } from 'jasmine';
class MockActivatedRoute extends ActivatedRoute {
constructor(params: Params) {
super();
this.queryParams = of(params);
}
}
class MockIconService {
classes = 'active-close';
loadIconDef() { }
}
class MockWebSocketService {
createWebSocket() { }
isConnected() { return false; }
unbindHandlers() { }
bindHandlers() { }
}
describe('AlarmDetailsComponent', () => {
let fs: FnService;
let ar: MockActivatedRoute;
let windowMock: Window;
let logServiceSpy: jasmine.SpyObj<LogService>;
let component: AlarmDetailsComponent;
let fixture: ComponentFixture<AlarmDetailsComponent>;
beforeEach(async(() => {
const logSpy = jasmine.createSpyObj('LogService', ['info', 'debug', 'warn', 'error']);
ar = new MockActivatedRoute({ 'debug': 'panel' });
windowMock = <any>{
location: <any>{
hostname: 'foo',
host: 'foo',
port: '80',
protocol: 'http',
search: { debug: 'true' },
href: 'ws://foo:123/onos/ui/websock/path',
absUrl: 'ws://foo:123/onos/ui/websock/path'
}
};
fs = new FnService(ar, logSpy, windowMock);
TestBed.configureTestingModule({
imports: [BrowserAnimationsModule],
declarations: [ AlarmDetailsComponent, IconComponent ],
providers: [
{ provide: FnService, useValue: fs },
{ provide: IconService, useClass: MockIconService },
{ provide: LogService, useValue: logSpy },
{ provide: WebSocketService, useClass: MockWebSocketService },
{ provide: 'Window', useValue: windowMock },
]
})
.compileComponents();
logServiceSpy = TestBed.get(LogService);
}));
beforeEach(() => {
fixture = TestBed.createComponent(AlarmDetailsComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,102 @@
/*
* Copyright 2018-present Open Networking Foundation
*
* 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.
*/
import { Component, Input, OnInit, OnDestroy, OnChanges } from '@angular/core';
import { trigger, state, style, animate, transition } from '@angular/animations';
import {
FnService,
IconService,
LoadingService,
LogService,
DetailsPanelBaseImpl,
WebSocketService,
} from 'gui2-fw-lib';
/**
* ONOS GUI -- Alarm Details Component extends TableBaseImpl
* The details view when an alarm row is clicked from the Alarm view
*
* This is expected to be passed an 'id' and it makes a call
* to the WebSocket with an alarmDetailsRequest, and gets back an
* alarmDetailsResponse.
*
* The animated fly-in is controlled by the animation below
* The alarmDetailsState is attached to alarm-details-panel
* and is false (flies out) when id='' and true (flies in) when
* id has a value
*/
@Component({
selector: 'onos-alarmdetails',
templateUrl: './alarmdetails.component.html',
styleUrls: ['./alarmdetails.component.css',
'../../../fw/widget/panel.css', '../../../fw/widget/panel-theme.css'
],
animations: [
trigger('alarmDetailsState', [
state('true', style({
transform: 'translateX(-100%)',
opacity: '100'
})),
state('false', style({
transform: 'translateX(0%)',
opacity: '0'
})),
transition('0 => 1', animate('100ms ease-in')),
transition('1 => 0', animate('100ms ease-out'))
])
]
})
export class AlarmDetailsComponent extends DetailsPanelBaseImpl implements OnInit, OnDestroy, OnChanges {
@Input() id: string;
constructor(
protected fs: FnService,
protected ls: LoadingService,
protected log: LogService,
protected is: IconService,
protected wss: WebSocketService
) {
super(fs, ls, log, wss, 'alarmTable');
}
ngOnInit() {
this.init();
this.log.debug('Alarm Table Details Component initialized:', this.id);
}
/**
* Stop listening to alarmTableDetailsResponse on WebSocket
*/
ngOnDestroy() {
this.destroy();
this.log.debug('Alarm Table Details Component destroyed');
}
/**
* Details Panel Data Request on row selection changes
* Should be called whenever id changes
* If id is empty, no request is made
*/
ngOnChanges() {
if (this.id === '') {
return '';
} else {
const query = {
'id': this.id
};
this.requestDetailsPanelData(query);
}
}
}

View File

@ -0,0 +1,21 @@
/*
* Copyright 2018-present Open Networking Foundation
*
* 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.
*/
/* css for alarm table view */
#ov-alarm-table h2 {
display: inline-block;
}

View File

@ -0,0 +1,87 @@
<!--
~ Copyright 2018-present Open Networking Foundation
~
~ 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.
-->
<div id="ov-alarm-table">
<div class="tabular-header">
<h2>Alarms for {{ "all devices."}} ({{ tableData.length }} total)</h2>
<div class="ctrl-btns">
<div class="refresh" (click)="toggleRefresh()">
<!-- See icon.theme.css for the defintions of the classes active and refresh-->
<onos-icon classes="{{ autoRefresh?'active refresh':'refresh' }}" iconId="refresh" iconSize="36" toolTip="{{ autoRefreshTip }}"></onos-icon>
</div>
<!--div class="separator"></div>
<div (click)="(!!selId) ? confirmAction(AlarmActionEnum.ACKNOWLEDGE) : ''">
<onos-icon classes="{{ ctrlBtnState.acknowledged?'active-rect play':'play' }}"
iconId="play" iconSize="42" toolTip="{{ acknowledgeTip }}"></onos-icon>
</div>
<div (click)="(!!selId) ? confirmAction(AlarmActionEnum.CLEAR) : ''">
<onos-icon classes="{{ ctrlBtnState.cleared?'active-rect stop':'stop' }}"
iconId="stop" iconSize="42" toolTip="{{ clearTip }}"></onos-icon>
</div-->
</div>
</div>
<div class="summary-list" class="summary-list" onosTableResize>
<div class="table-header">
<table>
<tr>
<td colId="alarmId" (click)="onSort('id')">Id
<onos-icon classes="active-sort" [iconSize]="10" [iconId]="sortIcon('id')"></onos-icon>
</td>
<td colId="alarmDeviceId" (click)="onSort('alarmDeviceId')">Device
<onos-icon classes="active-sort" [iconSize]="10" [iconId]="sortIcon('alarmDeviceId')"></onos-icon>
</td>
<td colId="alarmDesc" (click)="onSort('alarmDesc')">Description
<onos-icon classes="active-sort" [iconSize]="10" [iconId]="sortIcon('alarmDesc')"></onos-icon>
</td>
<td colId="alarmSource" (click)="onSort('alarmSource')">Source
<onos-icon classes="active-sort" [iconSize]="10" [iconId]="sortIcon('alarmSource')"></onos-icon>
</td>
<td colId="alarmTimeRaised" (click)="onSort('alarmTimeRaised')">Time Raised
<onos-icon classes="active-sort" [iconSize]="10" [iconId]="sortIcon('alarmTimeRaised')"></onos-icon>
</td>
<td colId="alarmSeverity" (click)="onSort('alarmSeverity')">Severity
<onos-icon classes="active-sort" [iconSize]="10" [iconId]="sortIcon('alarmSeverity')"></onos-icon>
</td>
<td colId="alarmAcknowledged" (click)="onSort('alarmAcknowledged')">Acknowledged
<onos-icon classes="active-sort" [iconSize]="10" [iconId]="sortIcon('alarmAcknowledged')"></onos-icon>
</td>
</tr>
</table>
</div>
<div class="table-body">
<table>
<tr class="table-body" *ngIf="tableData.length === 0" class="no-data">
<td colspan="9">{{ annots.noRowsMsg }}</td>
</tr>
<tr *ngFor="let item of tableData | filter : tableDataFilter" (click)="selectCallback($event, item)" [ngClass]="{selected: item.id === selId, 'data-change': isChanged(item.id)}">
<td>{{ item.id }}</td>
<td>{{ item.alarmDeviceId }}</td>
<td>{{ item.alarmDesc }}</td>
<td>{{ item.alarmSource }}</td>
<td>{{ item.alarmTimeRaised }}</td>
<td>{{ item.alarmSeverity }}</td>
<td class="table-icon">
<onos-icon [iconId]="ackIcon(item.alarmAcknowledged)"></onos-icon>
</td>
</tr>
</table>
</div>
</div>
<onos-alarmdetails class="floatpanels" id="{{ selId }}" (closeEvent)="deselectRow($event)"></onos-alarmdetails>
</div>

View File

@ -0,0 +1,111 @@
/*
* Copyright 2018-present Open Networking Foundation
*
* 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.
*/
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { ActivatedRoute, Params } from '@angular/router';
import { DebugElement } from '@angular/core';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { FormsModule } from '@angular/forms';
import { By } from '@angular/platform-browser';
import { AlarmTableComponent } from './alarmtable.component';
import { AlarmDetailsComponent } from '../alarmdetails/alarmdetails.component';
import {
FnService,
IconService,
GlyphService,
IconComponent,
LoadingService,
LogService,
NavService,
MastService,
TableFilterPipe,
ThemeService,
WebSocketService
} from 'gui2-fw-lib';
import { of } from 'rxjs';
import { } from 'jasmine';
import { RouterTestingModule } from '@angular/router/testing';
class MockActivatedRoute extends ActivatedRoute {
constructor(params: Params) {
super();
this.queryParams = of(params);
}
}
class MockIconService {
loadIconDef() { }
}
class MockLoadingService {
startAnim() { }
stop() { }
waiting() { }
}
describe('AlarmTableComponent', () => {
let fs: FnService;
let ar: MockActivatedRoute;
let windowMock: Window;
let logServiceSpy: jasmine.SpyObj<LogService>;
let component: AlarmTableComponent;
let fixture: ComponentFixture<AlarmTableComponent>;
beforeEach(async(() => {
const logSpy = jasmine.createSpyObj('LogService', ['info', 'debug', 'warn', 'error']);
ar = new MockActivatedRoute({ 'debug': 'txrx' });
windowMock = <any>{
location: <any>{
hostname: 'foo',
host: 'foo',
port: '80',
protocol: 'http',
search: { debug: 'true' },
href: 'ws://foo:123/onos/ui/websock/path',
absUrl: 'ws://foo:123/onos/ui/websock/path'
}
};
fs = new FnService(ar, logSpy, windowMock);
TestBed.configureTestingModule({
imports: [BrowserAnimationsModule, FormsModule, RouterTestingModule],
declarations: [
AlarmTableComponent, AlarmDetailsComponent,
IconComponent, TableFilterPipe
],
providers: [
{ provide: FnService, useValue: fs },
{ provide: LogService, useValue: logSpy },
{ provide: LoadingService, useClass: MockLoadingService },
{ provide: IconService, useClass: MockIconService },
{ provide: 'Window', useValue: windowMock },
]
})
.compileComponents();
logServiceSpy = TestBed.get(LogService);
}));
beforeEach(() => {
fixture = TestBed.createComponent(AlarmTableComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,185 @@
/*
* Copyright 2018-present Open Networking Foundation
*
* 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.
*/
import { Component, Input, Inject, OnInit, OnDestroy } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import {
FnService,
LoadingService,
LogService,
WebSocketService,
SortDir, TableBaseImpl, TableResponse
} from 'gui2-fw-lib';
/**
* Model of the response from the WebSocket
*/
export interface AlarmTableResponse extends TableResponse {
alarmTables: Alarm[];
}
/**
* Model of the alarms returned from the WebSocket
*/
export interface Alarm {
id: string;
alarmDesc: string;
alarmDeviceId: string;
alarmSource: string;
alarmTimeRaised: string;
alarmTimeUpdated: string;
alarmTimeCleared: string;
alarmSeverity: string;
}
export enum AlarmAction {
NONE = 0,
ACKNOWLEDGE = 1,
CLEAR = 2,
}
/**
* Model of the Control Button
*/
export interface CtrlBtnState {
acknowledged: boolean;
cleared: boolean;
selection: string;
}
const ACKNOWLEDGED = 'ACKNOWLEDGED';
const CLEARED = 'CLEARED';
/**
* ONOS GUI -- Alarm Table Component extends TableBaseImpl
*/
@Component({
selector: 'onos-alarmtable',
templateUrl: './alarmtable.component.html',
styleUrls: ['./alarmtable.component.css', '../../../fw/widget/table.css', '../../../fw/widget/table.theme.css']
})
export class AlarmTableComponent extends TableBaseImpl implements OnInit, OnDestroy {
devId: string;
selRowAlarmId: string;
// TODO Add in LION translations
acknowledgeTip: string = 'Acknowledge';
clearTip: string = 'Clear';
AlarmActionEnum: any = AlarmAction;
alarmAction: AlarmAction = AlarmAction.NONE;
confirmMsg: string = '';
ctrlBtnState: CtrlBtnState;
constructor(
private route: ActivatedRoute,
@Inject('Window') private w: any,
protected log: LogService,
protected ls: LoadingService,
protected fs: FnService,
protected wss: WebSocketService,
) {
super(fs, ls, log, wss, 'alarmTable');
this.route.queryParams.subscribe(params => {
this.devId = params['devId'];
});
this.payloadParams = {
devId: this.devId
};
this.responseCallback = this.alarmResponseCb;
this.sortParams = {
firstCol: 'alarmTimeRaised',
firstDir: SortDir.desc,
secondCol: 'alarmDeviceId',
secondDir: SortDir.asc,
};
this.ctrlBtnState = <CtrlBtnState>{
acknowledged: false,
cleared: false
};
this.log.debug('AlarmTableComponent constructed');
}
ngOnInit() {
this.init();
this.log.debug('AlarmTableComponent initialized');
}
ngOnDestroy() {
this.destroy();
this.log.debug('AlarmTableComponent destroyed');
}
alarmResponseCb(data: AlarmTableResponse) {
this.log.debug('Alarm response received for ', data.alarmTables.length, 'alarm');
}
rowSelection(event: any, selRow: any) {
this.ctrlBtnState.acknowledged = this.selId && selRow && selRow.state === ACKNOWLEDGED;
this.ctrlBtnState.cleared = this.selId && selRow && selRow.cleared === CLEARED;
this.ctrlBtnState.selection = this.selId;
this.selRowAlarmId = selRow.appId;
this.log.debug('Row ', this.selId, 'selected', this.ctrlBtnState);
}
/**
* Perform one of the alarm actions - acknowledge or clear
* Raises a dialog which calls back the dOk() below
*/
confirmAction(action: AlarmAction): void {
this.alarmAction = action;
const alarmActionLc = (<string>AlarmAction[this.alarmAction]).toLowerCase();
this.confirmMsg = alarmActionLc + ' ' + this.selId + '?';
this.log.debug('Initiating', this.alarmAction, 'of', this.selId);
}
/**
* Callback when the Confirm dialog is shown and a choice is made
*/
dOk(choice: boolean) {
const alarmActionLc = (<string>AlarmAction[this.alarmAction]).toLowerCase();
if (choice) {
this.log.debug('Confirmed', alarmActionLc, 'on', this.selId);
/** commented out until backend is implemented
this.wss.sendEvent(APPMGMTREQ, {
action: alarmActionLc,
name: this.selId,
sortCol: this.sortParams.firstCol,
sortDir: SortDir[this.sortParams.firstDir],
});
this.wss.sendEvent(DETAILSREQ, { id: this.selId });
*/
} else {
this.log.debug('Cancelled', alarmActionLc, 'on', this.selId);
}
this.confirmMsg = '';
}
ackIcon(acknowledged: string): string {
if (acknowledged === 'true') {
return 'active';
} else {
return 'appInactive';
}
}
}

View File

@ -0,0 +1,37 @@
/*
* Copyright 2018-present Open Networking Foundation
*
* 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.
*/
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { AlarmTableComponent } from './alarmtable/alarmtable.component';
const fmRoutes: Routes = [
{
path: '',
component: AlarmTableComponent
}
];
/**
* ONOS GUI -- FM GUI Routing Module - allows it to be lazy loaded
*
* See https://angular.io/guide/lazy-loading-ngmodules
*/
@NgModule({
imports: [RouterModule.forChild(fmRoutes)],
exports: [RouterModule]
})
export class FmGui2LibRoutingModule { }

View File

@ -0,0 +1,34 @@
/*
* Copyright 2018-present Open Networking Foundation
*
* 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.
*/
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';
import { FmGui2LibRoutingModule } from './fm-gui2-lib-routing.module';
import { Gui2FwLibModule, ConsoleLoggerService, LogService } from 'gui2-fw-lib';
import { AlarmTableComponent } from './alarmtable/alarmtable.component';
import { AlarmDetailsComponent } from './alarmdetails/alarmdetails.component';
@NgModule({
imports: [
CommonModule,
FormsModule,
FmGui2LibRoutingModule,
Gui2FwLibModule
],
declarations: [ AlarmTableComponent, AlarmDetailsComponent ],
exports: [ AlarmTableComponent, AlarmDetailsComponent ],
})
export class FmGui2LibModule { }

View File

@ -0,0 +1,24 @@
/*
* Copyright 2018-present Open Networking Foundation
*
* 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.
*/
/*
* Public API Surface of fm-gui2-lib
*/
export * from './lib/alarmtable/alarmtable.component';
export * from './lib/alarmdetails/alarmdetails.component';
export * from './lib/fm-gui2-lib.module';
export * from './lib/fm-gui2-lib-routing.module';

View File

@ -0,0 +1,22 @@
// This file is required by karma.conf.js and loads recursively all the .spec and framework files
import 'core-js/es7/reflect';
import 'zone.js/dist/zone';
import 'zone.js/dist/zone-testing';
import { getTestBed } from '@angular/core/testing';
import {
BrowserDynamicTestingModule,
platformBrowserDynamicTesting
} from '@angular/platform-browser-dynamic/testing';
declare const require: any;
// First, initialize the Angular testing environment.
getTestBed().initTestEnvironment(
BrowserDynamicTestingModule,
platformBrowserDynamicTesting()
);
// Then we find all the tests.
const context = require.context('./', true, /\.spec\.ts$/);
// And load the modules.
context.keys().map(context);

View File

@ -0,0 +1,33 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"outDir": "../../out-tsc/lib",
"target": "es2015",
"module": "es2015",
"moduleResolution": "node",
"declaration": true,
"sourceMap": true,
"inlineSources": true,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"importHelpers": true,
"types": [],
"lib": [
"dom",
"es2015"
]
},
"angularCompilerOptions": {
"annotateForClosureCompiler": true,
"skipTemplateCodegen": true,
"strictMetadataEmit": true,
"fullTemplateTypeCheck": true,
"strictInjectionParameters": true,
"flatModuleId": "AUTOGENERATED",
"flatModuleOutFile": "AUTOGENERATED"
},
"exclude": [
"src/test.ts",
"**/*.spec.ts"
]
}

View File

@ -0,0 +1,17 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"outDir": "../../out-tsc/spec",
"types": [
"jasmine",
"node"
]
},
"files": [
"src/test.ts"
],
"include": [
"**/*.spec.ts",
"**/*.d.ts"
]
}

View File

@ -0,0 +1,17 @@
{
"extends": "../../tslint.json",
"rules": {
"directive-selector": [
true,
"attribute",
"onos",
"camelCase"
],
"component-selector": [
true,
"element",
"onos",
"kebab-case"
]
}
}

View File

@ -0,0 +1,47 @@
<!--
~ Copyright 2018-present Open Networking Foundation
~
~ 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.
-->
<div style="text-align:center">
<onos-icon iconId="nav_apps" iconSize="200" toolTip="Test app"></onos-icon>
<h1>
Welcome to {{ title }}!
</h1>
</div>
<h2>Test App - Do not use or extend</h2>
<p>This app is just a wrapper around the FM GUI2 Library and is
necessary only to provide a base for that library.
It has a use in validating that the library can be loaded.
The library is linked in here by the addition of the paths statement in
tsconfig.json. This is fine for accessing a local library, but for another
project it should be added in through package.json, using the 'file' locator.
Then run "npm install" in that target project and the tar will be expanded in
to it's node_modules folder.
A good article on the creation and use of libraries in Angular 6 is given in<br />
<a href="https://blog.angularindepth.com/creating-a-library-in-angular-6-87799552e7e5">The Angular Library Series - Creating a Library with the Angular CLI</a><br />
and <br />
<a href="https://blog.angularindepth.com/creating-a-library-in-angular-6-part-2-6e2bc1e14121">The Angular Library Series - Building and Packaging</a><br />
This "app" component is not built by Bazel - it's only the library that is built
by bazel which in turn calls "ng build --prod fm-gui2-lib" and then "npm pack"
resulting in a tar file that can be used as an NPM package anywhere.
<br />
Note: Please remember that in Angular 6 rebuilding of libraries is not automatic.
If you change anything in the library, you will have to build it again before
it is picked up in this app.
</p>
<onos-alarmtable></onos-alarmtable>

View File

@ -0,0 +1,43 @@
/*
* Copyright 2018-present Open Networking Foundation
*
* 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.
*/
import { TestBed, async } from '@angular/core/testing';
import { AppComponent } from './app.component';
describe('AppComponent', () => {
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [
AppComponent
],
}).compileComponents();
}));
it('should create the app', async(() => {
const fixture = TestBed.createComponent(AppComponent);
const app = fixture.debugElement.componentInstance;
expect(app).toBeTruthy();
}));
it(`should have as title 'app'`, async(() => {
const fixture = TestBed.createComponent(AppComponent);
const app = fixture.debugElement.componentInstance;
expect(app.title).toEqual('app');
}));
it('should render title in a h1 tag', async(() => {
const fixture = TestBed.createComponent(AppComponent);
fixture.detectChanges();
const compiled = fixture.debugElement.nativeElement;
expect(compiled.querySelector('h1').textContent).toContain('Welcome to app!');
}));
});

View File

@ -0,0 +1,26 @@
/*
* Copyright 2018-present Open Networking Foundation
*
* 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.
*/
import { Component } from '@angular/core';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
title = 'app';
}

View File

@ -0,0 +1,44 @@
/*
* Copyright 2018-present Open Networking Foundation
*
* 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.
*/
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { FmGui2LibModule } from 'fm-gui2-lib';
import { AppComponent } from './app.component';
import { Gui2FwLibModule, ConsoleLoggerService, LogService } from 'gui2-fw-lib';
const appRoutes: Routes = [
{ path: '**', component: AppComponent }
]
@NgModule({
declarations: [
AppComponent
],
imports: [
RouterModule.forRoot(appRoutes),
BrowserModule,
FmGui2LibModule,
Gui2FwLibModule
],
providers: [
{ provide: 'Window', useValue: window },
{ provide: LogService, useClass: ConsoleLoggerService },
],
bootstrap: [AppComponent]
})
export class AppModule { }

View File

@ -0,0 +1,9 @@
# This file is currently used by autoprefixer to adjust CSS to support the below specified browsers
# For additional information regarding the format and rule options, please see:
# https://github.com/browserslist/browserslist#queries
# For IE 9-11 support, please uncomment the last line of the file and adjust as needed
> 0.5%
last 2 versions
Firefox ESR
not dead
# IE 9-11

View File

@ -0,0 +1,3 @@
export const environment = {
production: true
};

View File

@ -0,0 +1,15 @@
// This file can be replaced during build by using the `fileReplacements` array.
// `ng build ---prod` replaces `environment.ts` with `environment.prod.ts`.
// The list of file replacements can be found in `angular.json`.
export const environment = {
production: false
};
/*
* In development mode, to ignore zone related error stack frames such as
* `zone.run`, `zoneDelegate.invokeTask` for easier debugging, you can
* import the following file, but please comment it out in production mode
* because it will have performance impact when throw error
*/
// import 'zone.js/dist/zone-error'; // Included with Angular CLI.

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

View File

@ -0,0 +1,14 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>FmGui2LibApp</title>
<base href="/">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" type="image/x-icon" href="favicon.ico">
</head>
<body>
<app-root></app-root>
</body>
</html>

View File

@ -0,0 +1,31 @@
// Karma configuration file, see link for more information
// https://karma-runner.github.io/1.0/config/configuration-file.html
module.exports = function (config) {
config.set({
basePath: '',
frameworks: ['jasmine', '@angular-devkit/build-angular'],
plugins: [
require('karma-jasmine'),
require('karma-chrome-launcher'),
require('karma-jasmine-html-reporter'),
require('karma-coverage-istanbul-reporter'),
require('@angular-devkit/build-angular/plugins/karma')
],
client: {
clearContext: false // leave Jasmine Spec Runner output visible in browser
},
coverageIstanbulReporter: {
dir: require('path').join(__dirname, '../coverage'),
reports: ['html', 'lcovonly'],
fixWebpackSourcePaths: true
},
reporters: ['progress', 'kjhtml'],
port: 9876,
colors: true,
logLevel: config.LOG_INFO,
autoWatch: true,
browsers: ['Chrome'],
singleRun: false
});
};

View File

@ -0,0 +1,12 @@
import { enableProdMode } from '@angular/core';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppModule } from './app/app.module';
import { environment } from './environments/environment';
if (environment.production) {
enableProdMode();
}
platformBrowserDynamic().bootstrapModule(AppModule)
.catch(err => console.log(err));

View File

@ -0,0 +1,80 @@
/**
* This file includes polyfills needed by Angular and is loaded before the app.
* You can add your own extra polyfills to this file.
*
* This file is divided into 2 sections:
* 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers.
* 2. Application imports. Files imported after ZoneJS that should be loaded before your main
* file.
*
* The current setup is for so-called "evergreen" browsers; the last versions of browsers that
* automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera),
* Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile.
*
* Learn more in https://angular.io/docs/ts/latest/guide/browser-support.html
*/
/***************************************************************************************************
* BROWSER POLYFILLS
*/
/** IE9, IE10 and IE11 requires all of the following polyfills. **/
// import 'core-js/es6/symbol';
// import 'core-js/es6/object';
// import 'core-js/es6/function';
// import 'core-js/es6/parse-int';
// import 'core-js/es6/parse-float';
// import 'core-js/es6/number';
// import 'core-js/es6/math';
// import 'core-js/es6/string';
// import 'core-js/es6/date';
// import 'core-js/es6/array';
// import 'core-js/es6/regexp';
// import 'core-js/es6/map';
// import 'core-js/es6/weak-map';
// import 'core-js/es6/set';
/** IE10 and IE11 requires the following for NgClass support on SVG elements */
// import 'classlist.js'; // Run `npm install --save classlist.js`.
/** IE10 and IE11 requires the following for the Reflect API. */
// import 'core-js/es6/reflect';
/** Evergreen browsers require these. **/
// Used for reflect-metadata in JIT. If you use AOT (and only Angular decorators), you can remove.
import 'core-js/es7/reflect';
/**
* Web Animations `@angular/platform-browser/animations`
* Only required if AnimationBuilder is used within the application and using IE/Edge or Safari.
* Standard animation support in Angular DOES NOT require any polyfills (as of Angular 6.0).
**/
// import 'web-animations-js'; // Run `npm install --save web-animations-js`.
/**
* By default, zone.js will patch all possible macroTask and DomEvents
* user can disable parts of macroTask/DomEvents patch by setting following flags
*/
// (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame
// (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick
// (window as any).__zone_symbol__BLACK_LISTED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames
/*
* in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js
* with the following flag, it will bypass `zone.js` patch for IE/Edge
*/
// (window as any).__Zone_enable_cross_context_check = true;
/***************************************************************************************************
* Zone JS is required by default for Angular itself.
*/
import 'zone.js/dist/zone'; // Included with Angular CLI.
/***************************************************************************************************
* APPLICATION IMPORTS
*/

View File

@ -0,0 +1 @@
/* You can add global styles to this file, and also import other style files */

View File

@ -0,0 +1,20 @@
// This file is required by karma.conf.js and loads recursively all the .spec and framework files
import 'zone.js/dist/zone-testing';
import { getTestBed } from '@angular/core/testing';
import {
BrowserDynamicTestingModule,
platformBrowserDynamicTesting
} from '@angular/platform-browser-dynamic/testing';
declare const require: any;
// First, initialize the Angular testing environment.
getTestBed().initTestEnvironment(
BrowserDynamicTestingModule,
platformBrowserDynamicTesting()
);
// Then we find all the tests.
const context = require.context('./', true, /\.spec\.ts$/);
// And load the modules.
context.keys().map(context);

View File

@ -0,0 +1,12 @@
{
"extends": "../tsconfig.json",
"compilerOptions": {
"outDir": "../out-tsc/app",
"module": "es2015",
"types": []
},
"exclude": [
"src/test.ts",
"**/*.spec.ts"
]
}

View File

@ -0,0 +1,19 @@
{
"extends": "../tsconfig.json",
"compilerOptions": {
"outDir": "../out-tsc/spec",
"module": "commonjs",
"types": [
"jasmine",
"node"
]
},
"files": [
"test.ts",
"polyfills.ts"
],
"include": [
"**/*.spec.ts",
"**/*.d.ts"
]
}

View File

@ -0,0 +1,17 @@
{
"extends": "../tslint.json",
"rules": {
"directive-selector": [
true,
"attribute",
"app",
"camelCase"
],
"component-selector": [
true,
"element",
"app",
"kebab-case"
]
}
}

View File

@ -0,0 +1,28 @@
{
"compileOnSave": false,
"compilerOptions": {
"baseUrl": "./",
"outDir": "./dist/out-tsc",
"sourceMap": true,
"declaration": false,
"moduleResolution": "node",
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"target": "es6",
"typeRoots": [
"node_modules/@types"
],
"lib": [
"es2017",
"dom"
],
"paths": {
"fm-gui2-lib": [
"dist/fm-gui2-lib"
],
"fm-gui2-lib/*": [
"dist/fm-gui2-lib/*"
]
}
}
}

View File

@ -0,0 +1,130 @@
{
"rulesDirectory": [
"node_modules/codelyzer"
],
"rules": {
"arrow-return-shorthand": true,
"callable-types": true,
"class-name": true,
"comment-format": [
true,
"check-space"
],
"curly": true,
"deprecation": {
"severity": "warn"
},
"eofline": true,
"forin": true,
"import-blacklist": [
true,
"rxjs/Rx"
],
"import-spacing": true,
"indent": [
true,
"spaces"
],
"interface-over-type-literal": true,
"label-position": true,
"max-line-length": [
true,
140
],
"member-access": false,
"member-ordering": [
true,
{
"order": [
"static-field",
"instance-field",
"static-method",
"instance-method"
]
}
],
"no-arg": true,
"no-bitwise": true,
"no-console": [
true,
"debug",
"info",
"time",
"timeEnd",
"trace"
],
"no-construct": true,
"no-debugger": true,
"no-duplicate-super": true,
"no-empty": false,
"no-empty-interface": true,
"no-eval": true,
"no-inferrable-types": [
false,
"ignore-params"
],
"no-misused-new": true,
"no-non-null-assertion": true,
"no-shadowed-variable": true,
"no-string-literal": false,
"no-string-throw": true,
"no-switch-case-fall-through": true,
"no-trailing-whitespace": true,
"no-unnecessary-initializer": true,
"no-unused-expression": true,
"no-use-before-declare": true,
"no-var-keyword": true,
"object-literal-sort-keys": false,
"one-line": [
true,
"check-open-brace",
"check-catch",
"check-else",
"check-whitespace"
],
"prefer-const": true,
"quotemark": [
true,
"single"
],
"radix": true,
"semicolon": [
true,
"always"
],
"triple-equals": [
true,
"allow-null-check"
],
"typedef-whitespace": [
true,
{
"call-signature": "nospace",
"index-signature": "nospace",
"parameter": "nospace",
"property-declaration": "nospace",
"variable-declaration": "nospace"
}
],
"unified-signatures": true,
"variable-name": false,
"whitespace": [
true,
"check-branch",
"check-decl",
"check-operator",
"check-separator",
"check-type"
],
"no-output-on-prefix": true,
"use-input-property-decorator": true,
"use-output-property-decorator": true,
"use-host-property-decorator": true,
"no-input-rename": true,
"no-output-rename": true,
"use-life-cycle-interface": true,
"use-pipe-transform-interface": true,
"component-class-suffix": true,
"directive-class-suffix": true
}
}

View File

@ -7,6 +7,7 @@ COMPILE_DEPS = [
'//lib:javax.ws.rs-api',
'//utils/osgi:onlab-osgi',
'//core/store/serializers:onos-core-serializers',
'//apps/faultmanagement/fmmgr:onos-apps-faultmanagement-fmmgr'
]

View File

@ -0,0 +1,78 @@
/*
* Copyright 2018-present Open Networking Foundation
*
* 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.
*/
package org.onosproject.faultmanagement.alarms.cli;
import org.apache.karaf.shell.api.action.Argument;
import org.apache.karaf.shell.api.action.Command;
import org.apache.karaf.shell.api.action.lifecycle.Service;
import org.onosproject.cli.AbstractShellCommand;
import org.onosproject.faultmanagement.api.AlarmStore;
import org.onosproject.incubator.net.faultmanagement.alarm.Alarm;
import org.onosproject.incubator.net.faultmanagement.alarm.AlarmId;
import org.onosproject.incubator.net.faultmanagement.alarm.DefaultAlarm;
import org.onosproject.net.DeviceId;
import org.onosproject.net.device.DeviceService;
import java.time.Instant;
/**
* Creates a default alarm on a device.
*/
@Service
@Command(scope = "onos", name = "alarm-create",
description = "Creates an alarm")
public class CreateAlarm extends AbstractShellCommand {
@Argument(index = 0, name = "deviceId", description = "Device identity",
required = true, multiValued = false)
String deviceIdStr = null;
@Argument(index = 1, name = "severity", description = "Severity level",
required = true, multiValued = false)
String severityStr = null;
@Argument(index = 2, name = "alarmId", description = "Unique alarm id",
required = true, multiValued = false)
String alarmId = null;
@Argument(index = 3, name = "desc", description = "Alarm description",
required = true, multiValued = false)
String desc = null;
private AlarmStore alarmStore = AbstractShellCommand.get(AlarmStore.class);
private DeviceService deviceManager = AbstractShellCommand.get(DeviceService.class);
@Override
protected void doExecute() {
DeviceId deviceId = DeviceId.deviceId(deviceIdStr);
if (!deviceManager.isAvailable(deviceId)) {
throw new IllegalArgumentException("Device " + deviceIdStr + " is not available");
}
Alarm.SeverityLevel severityLevel = Alarm.SeverityLevel.valueOf(severityStr);
Alarm newAlarm = new DefaultAlarm.Builder(
AlarmId.alarmId(deviceId, alarmId),
deviceId,
desc,
severityLevel,
Instant.now().toEpochMilli())
.build();
alarmStore.createOrUpdateAlarm(newAlarm);
}
}

View File

@ -0,0 +1,41 @@
/*
* Copyright 2018-present Open Networking Foundation
*
* 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.
*/
package org.onosproject.faultmanagement.alarms.cli;
import org.apache.karaf.shell.api.action.Argument;
import org.apache.karaf.shell.api.action.Command;
import org.onosproject.cli.AbstractShellCommand;
import org.onosproject.faultmanagement.api.AlarmStore;
import org.onosproject.incubator.net.faultmanagement.alarm.AlarmId;
/**
* Remove an alarm from the Alarm Store.
*/
@Command(scope = "onos", name = "alarm-remove",
description = "Removes an alarm")
public class RemoveAlarm extends AbstractShellCommand {
@Argument(index = 0, name = "alarmId", description = "Unique alarm id",
required = true, multiValued = false)
String alarmId = null;
private AlarmStore alarmStore = AbstractShellCommand.get(AlarmStore.class);
@Override
protected void doExecute() {
alarmStore.removeAlarm(AlarmId.alarmId(alarmId));
}
}

View File

@ -0,0 +1,97 @@
/*
* Copyright 2018-present Open Networking Foundation
*
* 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.
*/
package org.onosproject.faultmanagement.alarms.cli;
import org.apache.karaf.shell.api.action.Argument;
import org.apache.karaf.shell.api.action.Command;
import org.apache.karaf.shell.api.action.lifecycle.Service;
import org.onosproject.cli.AbstractShellCommand;
import org.onosproject.faultmanagement.api.AlarmStore;
import org.onosproject.incubator.net.faultmanagement.alarm.Alarm;
import org.onosproject.incubator.net.faultmanagement.alarm.AlarmEntityId;
import org.onosproject.incubator.net.faultmanagement.alarm.AlarmId;
import org.onosproject.incubator.net.faultmanagement.alarm.DefaultAlarm;
import java.time.Instant;
/**
* Updates an existing alarm.
*/
@Command(scope = "onos", name = "alarm-update",
description = "Updates an alarm")
@Service
public class UpdateAlarm extends AbstractShellCommand {
@Argument(index = 0, name = "alarmId", description = "Unique alarm id",
required = true, multiValued = false)
String alarmId = null;
@Argument(index = 1, name = "desc", description = "Alarm field",
required = true, multiValued = false)
String alarmField = null;
@Argument(index = 2, name = "value", description = "The new value of the chosen Alarm field.",
required = true, multiValued = false)
String alarmFieldValue = null;
private AlarmStore alarmStore = AbstractShellCommand.get(AlarmStore.class);
@Override
protected void doExecute() {
Alarm existing = alarmStore.getAlarm(AlarmId.alarmId(alarmId));
DefaultAlarm.Builder newAlarmBuilder = new DefaultAlarm.Builder(existing);
UpdateAlarm.AlarmField field = UpdateAlarm.AlarmField.valueOf(alarmField);
switch (field) {
case SOURCE:
AlarmEntityId sourceId = AlarmEntityId.alarmEntityId(alarmFieldValue);
newAlarmBuilder.forSource(sourceId);
break;
case ASSIGNED_USER:
newAlarmBuilder.withAssignedUser(alarmFieldValue);
break;
case ACKNOWLEDGED:
newAlarmBuilder.withAcknowledged("TRUE".equalsIgnoreCase(alarmFieldValue));
break;
case MANUALLY_CLEARABLE:
newAlarmBuilder.withManuallyClearable("TRUE".equalsIgnoreCase(alarmFieldValue));
break;
case SERVICE_AFFECTING:
newAlarmBuilder.withServiceAffecting("TRUE".equalsIgnoreCase(alarmFieldValue));
break;
case TIME_CLEARED:
newAlarmBuilder.clear();
newAlarmBuilder.withTimeCleared(Instant.parse(alarmFieldValue).toEpochMilli());
break;
case TIME_UPDATED:
default:
newAlarmBuilder.withTimeUpdated(Instant.parse(alarmFieldValue).toEpochMilli());
break;
}
alarmStore.createOrUpdateAlarm(newAlarmBuilder.build());
}
public enum AlarmField {
SOURCE,
ASSIGNED_USER,
ACKNOWLEDGED,
MANUALLY_CLEARABLE,
SERVICE_AFFECTING,
TIME_CLEARED,
TIME_UPDATED
}
}

View File

@ -0,0 +1,39 @@
/*
* Copyright 2018-present Open Networking Foundation
*
* 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.
*/
package org.onosproject.faultmanagement.alarms.cli.completer;
import org.onosproject.cli.AbstractChoicesCompleter;
import org.onosproject.faultmanagement.alarms.cli.UpdateAlarm;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* CLI completer for Alarm Severity.
*/
public class AlarmFieldCompleter extends AbstractChoicesCompleter {
@Override
public List<String> choices() {
List<String> choices = new ArrayList<>();
Arrays.asList(UpdateAlarm.AlarmField.values()).forEach(field -> choices.add(field.toString()));
return choices;
}
}

View File

@ -0,0 +1,52 @@
/*
* Copyright 2018-present Open Networking Foundation
*
* 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.
*/
package org.onosproject.faultmanagement.alarms.cli.completer;
import org.apache.karaf.shell.api.action.lifecycle.Service;
import org.onosproject.cli.AbstractChoicesCompleter;
import org.onosproject.faultmanagement.alarms.cli.UpdateAlarm;
import java.time.Instant;
import java.util.ArrayList;
import java.util.List;
/**
* CLI completer for Alarm Field values.
*/
@Service
public class AlarmFieldValueCompleter extends AbstractChoicesCompleter {
@Override
protected List<String> choices() {
List<String> choices = new ArrayList<>();
UpdateAlarm.AlarmField field = UpdateAlarm.AlarmField.valueOf(commandLine.getArguments()[2]);
switch (field) {
case ACKNOWLEDGED:
case MANUALLY_CLEARABLE:
case SERVICE_AFFECTING:
choices.add("TRUE");
choices.add("FALSE");
return choices;
case TIME_CLEARED:
case TIME_UPDATED:
choices.add(Instant.now().toString());
default:
return choices;
}
}
}

View File

@ -0,0 +1,40 @@
/*
* Copyright 2018-present Open Networking Foundation
*
* 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.
*/
package org.onosproject.faultmanagement.alarms.cli.completer;
import org.onosproject.cli.AbstractChoicesCompleter;
import static org.onosproject.cli.AbstractShellCommand.get;
import org.onosproject.faultmanagement.api.AlarmStore;
import java.util.ArrayList;
import java.util.List;
/**
* CLI completer for Alarm Id.
*/
public class AlarmIdCompleter extends AbstractChoicesCompleter {
@Override
public List<String> choices() {
List<String> choices = new ArrayList<>();
AlarmStore alarmStore = get(AlarmStore.class);
alarmStore.getAlarms().forEach((alarm -> choices.add(alarm.id().toString())));
return choices;
}
}

View File

@ -0,0 +1,37 @@
/*
* Copyright 2018-present Open Networking Foundation
*
* 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.
*/
package org.onosproject.faultmanagement.alarms.cli.completer;
import org.onosproject.cli.AbstractChoicesCompleter;
import org.onosproject.incubator.net.faultmanagement.alarm.Alarm;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* CLI completer for Alarm Severity.
*/
public class AlarmSeverityCompleter extends AbstractChoicesCompleter {
@Override
public List<String> choices() {
List<String> severityList = new ArrayList<>();
Arrays.asList(Alarm.SeverityLevel.values()).forEach(s -> severityList.add(s.toString()));
return severityList;
}
}

View File

@ -0,0 +1,20 @@
/*
* Copyright 2018-present Open Networking Foundation
*
* 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.
*/
/**
* CLI completers for alarms.
*/
package org.onosproject.faultmanagement.alarms.cli.completer;

View File

@ -29,8 +29,33 @@
<ref component-id="deviceIdCompleter"/>
</completers>
</command>
<command>
<action class="org.onosproject.faultmanagement.alarms.cli.CreateAlarm"/>
<completers>
<ref component-id="deviceIdCompleter"/>
<ref component-id="alarmSeverityCompleter"/>
</completers>
</command>
<command>
<action class="org.onosproject.faultmanagement.alarms.cli.UpdateAlarm"/>
<completers>
<ref component-id="alarmIdCompleter"/>
<ref component-id="alarmFieldCompleter"/>
<ref component-id="alarmFieldValueCompleter"/>
</completers>
</command>
<command>
<action class="org.onosproject.faultmanagement.alarms.cli.RemoveAlarm"/>
<completers>
<ref component-id="alarmIdCompleter"/>
</completers>
</command>
</command-bundle>
<bean id="deviceIdCompleter" class="org.onosproject.cli.net.DeviceIdCompleter"/>
<bean id="alarmIdCompleter" class="org.onosproject.faultmanagement.alarms.cli.completer.AlarmIdCompleter"/>
<bean id="alarmSeverityCompleter" class="org.onosproject.faultmanagement.alarms.cli.completer.AlarmSeverityCompleter"/>
<bean id="alarmFieldCompleter" class="org.onosproject.faultmanagement.alarms.cli.completer.AlarmFieldCompleter"/>
<bean id="alarmFieldValueCompleter" class="org.onosproject.faultmanagement.alarms.cli.completer.AlarmFieldValueCompleter"/>
</blueprint>

View File

@ -55,6 +55,11 @@ public class AlarmTableMessageHandler extends UiMessageHandler {
private static final String TIME_UPDATED = "alarmTimeUpdated";
private static final String TIME_CLEARED = "alarmTimeCleared";
private static final String SEVERITY = "alarmSeverity";
private static final String SERVICE_AFFECTING = "alarmServiceAffecting";
private static final String ACKNOWLEDGED = "alarmAcknowledged";
private static final String CLEARED = "alarmCleared";
private static final String MANUALLY_CLEARABLE = "alarmManuallyClearable";
private static final String ASSIGNED_USER = "alarmAssignedUser";
private static final String RESULT = "result";
// TODO No need to show id column in ONOS-GUI
@ -63,7 +68,8 @@ public class AlarmTableMessageHandler extends UiMessageHandler {
// e.g. red=critical, green=cleared etc
private static final String[] COLUMN_IDS = {
ID, DEVICE_ID_STR, DESCRIPTION, SOURCE, TIME_RAISED, SEVERITY
ID, DEVICE_ID_STR, DESCRIPTION, SOURCE, TIME_RAISED, SEVERITY,
MANUALLY_CLEARABLE, CLEARED, ACKNOWLEDGED
};
private final Logger log = LoggerFactory.getLogger(getClass());
@ -127,7 +133,10 @@ public class AlarmTableMessageHandler extends UiMessageHandler {
.cell(DESCRIPTION, alarm.description())
.cell(SOURCE, alarm.source())
.cell(TIME_RAISED, Instant.ofEpochMilli(alarm.timeRaised()))
.cell(SEVERITY, alarm.severity());
.cell(SEVERITY, alarm.severity())
.cell(MANUALLY_CLEARABLE, alarm.manuallyClearable())
.cell(CLEARED, alarm.cleared())
.cell(ACKNOWLEDGED, alarm.acknowledged());
}
}
@ -166,6 +175,14 @@ public class AlarmTableMessageHandler extends UiMessageHandler {
data.put(TIME_UPDATED, formatTime(alarm.timeUpdated()));
data.put(TIME_CLEARED, formatTime(alarm.timeCleared()));
data.put(SEVERITY, alarm.severity().toString());
// TODO: The following should be change to %yes% and %no% if LION is added to Alarm View
data.put(SERVICE_AFFECTING,
alarm.serviceAffecting() ? "Yes" : "No");
data.put(ACKNOWLEDGED,
alarm.acknowledged() ? "Yes" : "No");
data.put(CLEARED,
alarm.cleared() ? "Yes" : "No");
data.put(ASSIGNED_USER, alarm.assignedUser());
}
log.debug("send = {}", rootNode);

View File

@ -0,0 +1,76 @@
/*
* Copyright 2018-present Open Networking Foundation
*
* 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.
*/
package org.onosproject.openstacknetworking.cli;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.google.common.collect.Lists;
import org.apache.karaf.shell.api.action.Command;
import org.apache.karaf.shell.api.action.lifecycle.Service;
import org.onosproject.cli.AbstractShellCommand;
import org.onosproject.openstacknetworking.api.OpenstackNetworkService;
import org.openstack4j.model.network.Subnet;
import org.openstack4j.openstack.networking.domain.NeutronSubnet;
import java.util.Comparator;
import java.util.List;
import static org.onosproject.openstacknetworking.util.OpenstackNetworkingUtil.modelEntityToJson;
import static org.onosproject.openstacknetworking.util.OpenstackNetworkingUtil.prettyJson;
/**
* Lists OpenStack subnets.
*/
@Service
@Command(scope = "onos", name = "openstack-subnets",
description = "Lists all OpenStack subnets")
public class OpenstackSubnetListCommand extends AbstractShellCommand {
private static final String FORMAT = "%-40s%-20s%-20s%-20s%-40s%-20s%-8s";
@Override
protected void doExecute() {
OpenstackNetworkService service = AbstractShellCommand.get(OpenstackNetworkService.class);
List<Subnet> subnets = Lists.newArrayList(service.subnets());
subnets.sort(Comparator.comparing(Subnet::getName));
if (outputJson()) {
print("%s", json(subnets));
} else {
print(FORMAT, "ID", "Name", "CIDR", "GatewayIp", "NetworkId", "NetworkName", "HostRoutes");
for (Subnet subnet: subnets) {
print(FORMAT,
subnet.getId(),
subnet.getName(),
subnet.getCidr(),
subnet.getGateway(),
subnet.getNetworkId(),
service.network(subnet.getNetworkId()).getName(),
subnet.getHostRoutes());
}
}
}
private String json(List<Subnet> subnets) {
ObjectMapper mapper = new ObjectMapper();
ArrayNode result = mapper.createArrayNode();
for (Subnet net: subnets) {
result.add(modelEntityToJson(net, NeutronSubnet.class));
}
return prettyJson(mapper, result.toString());
}
}

View File

@ -216,7 +216,8 @@ public final class OpenstackNetworkingUtil {
if (Strings.isNullOrEmpty(fip.getFloatingIpAddress())) {
continue;
}
if (fip.getFixedIpAddress().equals(port.ipAddress().toString())) {
if (fip.getFixedIpAddress().equals(port.ipAddress().toString()) &&
fip.getPortId().equals(port.portId())) {
return fip;
}
}

View File

@ -18,6 +18,9 @@
<command>
<action class="org.onosproject.openstacknetworking.cli.OpenstackNetworkListCommand"/>
</command>
<command>
<action class="org.onosproject.openstacknetworking.cli.OpenstackSubnetListCommand"/>
</command>
<command>
<action class="org.onosproject.openstacknetworking.cli.OpenstackPortListCommand"/>
</command>

View File

@ -3,6 +3,19 @@ GRPC_VER = '1.3.1'
BUNDLES = [
'//lib:kafka-clients',
'//lib:influxdb-java',
'//lib:simpleclient',
'//lib:simpleclient_common',
'//lib:simpleclient_hotspot',
'//lib:simpleclient_servlet',
'//lib:jetty-http',
'//lib:jetty-servlet',
'//lib:jetty-server',
'//lib:jetty-security',
'//lib:jetty-util',
'//lib:jetty-io',
'//lib:servlet-api',
'//lib:javax.ws.rs-api',
'//lib:jetty-websocket',
'//lib:commons-codec',
'//lib:retrofit',
'//lib:okhttp',

View File

@ -42,6 +42,10 @@ public final class Constants {
public static final String DEFAULT_INFLUXDB_MEASUREMENT = "sonaflow";
public static final boolean DEFAULT_INFLUXDB_ENABLE_BATCH = true;
// default configuration variables for Promethetus exporter
public static final String DEFAULT_PROMETHEUS_EXPORTER_IP = "0.0.0.0";
public static final int DEFAULT_PROMETHEUS_EXPORTER_PORT = 9501;
// default configuration variables for Kafka
public static final String DEFAULT_KAFKA_SERVER_IP = DEFAULT_SERVER_IP;
public static final int DEFAULT_KAFKA_SERVER_PORT = 9092;

View File

@ -0,0 +1,23 @@
/*
* Copyright 2018-present Open Networking Foundation
*
* 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.
*/
package org.onosproject.openstacktelemetry.api;
/**
* Admin service API for publishing openstack telemetry through Prometheus producer.
*/
public interface PrometheusTelemetryAdminService
extends TelemetryAdminService, PrometheusTelemetryService {
}

View File

@ -0,0 +1,22 @@
/*
* Copyright 2018-present Open Networking Foundation
*
* 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.
*/
package org.onosproject.openstacktelemetry.api;
/**
* Configuration service API for publishing openstack telemetry through Prometheus producer.
*/
public interface PrometheusTelemetryConfigService extends TelemetryConfigService {
}

View File

@ -0,0 +1,30 @@
/*
* Copyright 2018-present Open Networking Foundation
*
* 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.
*/
package org.onosproject.openstacktelemetry.api;
import java.util.Set;
/**
* Service API for publishing openstack telemetry through Prometheus producer.
*/
public interface PrometheusTelemetryService extends TelemetryService {
/**
* Publishes openstack telemetry to Prometheus server.
*
* @param flowInfos a network metric to be published
*/
void publish(Set<FlowInfo> flowInfos);
}

View File

@ -0,0 +1,82 @@
/*
* Copyright 2018-present Open Networking Foundation
*
* 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.
*/
package org.onosproject.openstacktelemetry.api.config;
import java.util.Map;
public interface PrometheusTelemetryConfig extends TelemetryConfig {
/**
* Obtains prometheus exporter IP address.
*
* @return IP address which prometheus exporter binds
*/
String address();
/**
* Obtains prometheus exporter port number.
*
* @return prometheus exporter port number
*/
int port();
/**
* Obtains prometheus config maps.
*
* @return prometheus config map
*/
Map<String, Object> configMap();
/**
* Builder class of PrometheusTelemetryConfig.
*/
interface Builder extends TelemetryConfig.Builder {
/**
* Sets prometheus exporter IP address.
*
* @param address prometheus exporter IP
* @return builder instance
*/
Builder withAddress(String address);
/**
* Sets prometheus exporter port number.
*
* @param port prometheus exporter port
* @return builder instance
*/
Builder withPort(int port);
// TODO add authentication.
/**
* Sets other prometheus configuration map.
*
* @param configMap prometheus configuration map
* @return builder instance
*/
Builder withConfigMap(Map<String, Object> configMap);
/**
* Creates a prometheus telemetry config instance.
*
* @return prometheus telemetry config instance
*/
PrometheusTelemetryConfig build();
}
}

View File

@ -15,6 +15,18 @@ COMPILE_DEPS = [
'//apps/openstacktelemetry/api:onos-apps-openstacktelemetry-api',
'//lib:kafka-clients',
'//lib:influxdb-java',
'//lib:simpleclient',
'//lib:simpleclient_common',
'//lib:simpleclient_hotspot',
'//lib:simpleclient_servlet',
'//lib:jetty-http',
'//lib:jetty-servlet',
'//lib:jetty-server',
'//lib:jetty-security',
'//lib:jetty-websocket',
'//lib:jetty-util',
'//lib:jetty-io',
'//lib:servlet-api',
'//lib:protobuf-java-3.2.0',
'//lib:GRPC_1.3',
'//incubator/grpc-dependencies:grpc-core-repkg-' + GRPC_VER,

View File

@ -2,6 +2,16 @@ COMPILE_DEPS = CORE_DEPS + JACKSON + KRYO + REST + CLI + [
"@kafka_clients//jar",
"@jersey_client//jar",
"@influxdb_java//jar",
"@simpleclient//jar",
"@simpleclient_common//jar",
"@simpleclient_hotspot//jar",
"@simpleclient_servlet//jar",
"@jetty_servlet//jar",
"@jetty_http//jar",
"@jetty_server//jar",
"@jetty_util//jar",
"@jetty_websocket//jar",
"@servlet_api//jar",
"@io_grpc_grpc_java//core",
"@io_grpc_grpc_java//protobuf-lite",
"//core/store/serializers:onos-core-serializers",

View File

@ -0,0 +1,131 @@
/*
* Copyright 2018-present Open Networking Foundation
*
* 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.
*/
package org.onosproject.openstacktelemetry.config;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Maps;
import org.onosproject.openstacktelemetry.api.config.PrometheusTelemetryConfig;
import org.onosproject.openstacktelemetry.api.config.TelemetryConfig;
import java.util.Map;
import java.util.Objects;
import static com.google.common.base.MoreObjects.toStringHelper;
import static com.google.common.base.Preconditions.checkNotNull;
/**
* A configuration file contains Prometheus telemetry parameters.
*/
public final class DefaultPrometheusTelemetryConfig implements PrometheusTelemetryConfig {
private final String address;
private final int port;
private final Map<String, Object> configMap;
private DefaultPrometheusTelemetryConfig(String address,
int port,
Map<String, Object> configMap) {
this.address = address;
this.port = port;
this.configMap = configMap;
}
@Override
public String address() {
return address;
}
@Override
public int port() {
return port;
}
@Override
public Map<String, Object> configMap() {
if (configMap != null) {
return ImmutableMap.copyOf(configMap);
} else {
return Maps.newConcurrentMap();
}
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj instanceof DefaultPrometheusTelemetryConfig) {
final DefaultPrometheusTelemetryConfig other = (DefaultPrometheusTelemetryConfig) obj;
return Objects.equals(this.address, other.address) &&
Objects.equals(this.port, other.port) &&
Objects.equals(this.configMap, other.configMap);
}
return false;
}
@Override
public int hashCode() {
return Objects.hash(address, port, configMap);
}
@Override
public String toString() {
return toStringHelper(this)
.add("address", address)
.add("port", port)
.add("configMap", configMap)
.toString();
}
@Override
public TelemetryConfig.Builder createBuilder() {
return new DefaultBuilder();
}
/**
* Builder class of DefaultPrometheusTelemetryConfig.
*/
public static final class DefaultBuilder implements Builder {
private String address;
private int port;
private Map<String, Object> configMap;
@Override
public Builder withAddress(String address) {
this.address = address;
return this;
}
@Override
public Builder withPort(int port) {
this.port = port;
return this;
}
@Override
public Builder withConfigMap(Map<String, Object> configMap) {
this.configMap = configMap;
return this;
}
@Override
public PrometheusTelemetryConfig build() {
checkNotNull(address, "Prometheus exporter binding address cannot be null");
checkNotNull(configMap, "Config map cannot be null");
return new DefaultPrometheusTelemetryConfig(address, port, configMap);
}
}
}

View File

@ -56,8 +56,6 @@ public class InfluxDbTelemetryManager implements InfluxDbTelemetryAdminService {
private static final String SRC_PORT = "srcPort";
private static final String DST_PORT = "dstPort";
private static final String PROTOCOL = "protocol";
private static final String SRC_MAC = "srcMac";
private static final String DST_MAC = "dstMac";
private static final String STARTUP_TIME = "startupTime";
private static final String FST_PKT_ARR_TIME = "fstPktArrTime";
@ -128,7 +126,7 @@ public class InfluxDbTelemetryManager implements InfluxDbTelemetryAdminService {
producer = null;
}
log.info("InfluxDB producer has Stopped");
log.info("InfluxDB producer has stopped");
}
@Override

View File

@ -24,6 +24,7 @@ import org.onosproject.openstacktelemetry.api.GrpcTelemetryService;
import org.onosproject.openstacktelemetry.api.InfluxDbTelemetryService;
import org.onosproject.openstacktelemetry.api.KafkaTelemetryService;
import org.onosproject.openstacktelemetry.api.OpenstackTelemetryService;
import org.onosproject.openstacktelemetry.api.PrometheusTelemetryService;
import org.onosproject.openstacktelemetry.api.RestTelemetryService;
import org.onosproject.openstacktelemetry.api.TelemetryService;
import org.onosproject.openstacktelemetry.codec.TinaMessageByteBufferCodec;
@ -94,6 +95,12 @@ public class OpenstackTelemetryManager implements OpenstackTelemetryService {
invokeInfluxDbPublisher((InfluxDbTelemetryService) service, flowInfos);
}
if (service instanceof PrometheusTelemetryManager &&
getPropertyValueAsBoolean(componentConfigService.getProperties(
PrometheusTelemetryConfigManager.class.getName()), ENABLE_SERVICE)) {
invokePrometheusPublisher((PrometheusTelemetryService) service, flowInfos);
}
if (service instanceof KafkaTelemetryManager &&
getPropertyValueAsBoolean(componentConfigService.getProperties(
KafkaTelemetryConfigManager.class.getName()), ENABLE_SERVICE)) {
@ -125,6 +132,10 @@ public class OpenstackTelemetryManager implements OpenstackTelemetryService {
service.publish(influxRecord);
}
private void invokePrometheusPublisher(PrometheusTelemetryService service, Set<FlowInfo> flowInfos) {
service.publish(flowInfos);
}
private void invokeKafkaPublisher(KafkaTelemetryService service, Set<FlowInfo> flowInfos) {
TinaMessageByteBufferCodec codec = new TinaMessageByteBufferCodec();
ByteBuffer buffer = codec.encode(flowInfos);

View File

@ -0,0 +1,136 @@
/*
* Copyright 2018-present Open Networking Foundation
*
* 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.
*/
package org.onosproject.openstacktelemetry.impl;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Deactivate;
import org.osgi.service.component.annotations.Modified;
import org.osgi.service.component.annotations.Reference;
import org.osgi.service.component.annotations.ReferenceCardinality;
import org.onlab.util.Tools;
import org.onosproject.cfg.ComponentConfigService;
import org.onosproject.openstacktelemetry.api.PrometheusTelemetryAdminService;
import org.onosproject.openstacktelemetry.api.PrometheusTelemetryConfigService;
import org.onosproject.openstacktelemetry.api.config.TelemetryConfig;
import org.onosproject.openstacktelemetry.config.DefaultPrometheusTelemetryConfig;
import org.osgi.service.component.ComponentContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Dictionary;
import static org.onosproject.openstacktelemetry.api.Constants.DEFAULT_DISABLE;
import static org.onosproject.openstacktelemetry.api.Constants.DEFAULT_ENABLE;
import static org.onosproject.openstacktelemetry.api.Constants.DEFAULT_PROMETHEUS_EXPORTER_IP;
import static org.onosproject.openstacktelemetry.api.Constants.DEFAULT_PROMETHEUS_EXPORTER_PORT;
import static org.onosproject.openstacktelemetry.util.OpenstackTelemetryUtil.getBooleanProperty;
import static org.onosproject.openstacktelemetry.util.OpenstackTelemetryUtil.initTelemetryService;
/**
* Prometheus exporter configuration manager for publishing openstack telemetry.
*/
@Component(immediate = true, service = PrometheusTelemetryConfigService.class)
public class PrometheusTelemetryConfigManager implements PrometheusTelemetryConfigService {
private final Logger log = LoggerFactory.getLogger(getClass());
private static final String ENABLE_SERVICE = "enableService";
private static final String ADDRESS = "address";
private static final String PORT = "port";
@Reference(cardinality = ReferenceCardinality.MANDATORY)
protected ComponentConfigService componentConfigService;
@Reference(cardinality = ReferenceCardinality.MANDATORY)
protected PrometheusTelemetryAdminService prometheusTelemetryAdminService;
//@Property(name = ADDRESS, value = DEFAULT_PROMETHEUS_EXPORTER_IP,
// label = "Default IP address of prometheus exporter")
protected String address = DEFAULT_PROMETHEUS_EXPORTER_IP;
//@Property(name = PORT, intValue = DEFAULT_PROMETHEUS_EXPORTER_PORT,
// label = "Default port number of prometheus exporter")
protected Integer port = DEFAULT_PROMETHEUS_EXPORTER_PORT;
//@Property(name = ENABLE_SERVICE, boolValue = DEFAULT_ENABLE,
// label = "Specify the default behavior of telemetry service")
protected Boolean enableService = DEFAULT_ENABLE;
@Activate
protected void activate(ComponentContext context) {
componentConfigService.registerProperties(getClass());
if (enableService) {
prometheusTelemetryAdminService.start(getConfig());
}
log.info("Started");
}
@Deactivate
protected void deactivate() {
componentConfigService.unregisterProperties(getClass(), false);
if (enableService) {
prometheusTelemetryAdminService.stop();
}
log.info("Stopped");
}
@Modified
private void modified(ComponentContext context) {
readComponentConfiguration(context);
initTelemetryService(prometheusTelemetryAdminService, getConfig(), enableService);
log.info("Modified");
}
@Override
public TelemetryConfig getConfig() {
return new DefaultPrometheusTelemetryConfig.DefaultBuilder()
.withAddress(address)
.withPort(port)
.build();
}
/**
* Extracts properties from the component configuration context.
*
* @param context the component context
*/
private void readComponentConfiguration(ComponentContext context) {
Dictionary<?, ?> properties = context.getProperties();
String addressStr = Tools.get(properties, ADDRESS);
address = addressStr != null ? addressStr : DEFAULT_PROMETHEUS_EXPORTER_IP;
log.info("Configured. Prometheus exporter address is {}", address);
Integer portConfigured = Tools.getIntegerProperty(properties, PORT);
if (portConfigured == null) {
port = DEFAULT_PROMETHEUS_EXPORTER_PORT;
log.info("Prometheus exporter port is NOT configured, default value is {}", port);
} else {
port = portConfigured;
log.info("Configured. Prometheus exporter port is {}", port);
}
Boolean enableServiceConfigured = getBooleanProperty(properties, ENABLE_SERVICE);
if (enableServiceConfigured == null) {
enableService = DEFAULT_DISABLE;
log.info("Prometheus service enable flag is NOT " +
"configured, default value is {}", enableService);
} else {
enableService = enableServiceConfigured;
log.info("Configured. Prometheus service enable flag is {}", enableService);
}
}
}

View File

@ -0,0 +1,195 @@
/*
* Copyright 2018-present Open Networking Foundation
*
* 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.
*/
package org.onosproject.openstacktelemetry.impl;
import org.onosproject.openstacktelemetry.api.FlowInfo;
import org.onosproject.openstacktelemetry.api.OpenstackTelemetryService;
import org.onosproject.openstacktelemetry.api.PrometheusTelemetryAdminService;
import org.onosproject.openstacktelemetry.api.PrometheusTelemetryService;
import org.onosproject.openstacktelemetry.api.config.PrometheusTelemetryConfig;
import org.onosproject.openstacktelemetry.api.config.TelemetryConfig;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Deactivate;
import org.osgi.service.component.annotations.Reference;
import org.osgi.service.component.annotations.ReferenceCardinality;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import io.prometheus.client.Counter;
import io.prometheus.client.exporter.MetricsServlet;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import java.util.Set;
/**
* Prometheus telemetry manager.
*/
@Component(immediate = true, service = PrometheusTelemetryService.class)
public class PrometheusTelemetryManager implements PrometheusTelemetryAdminService {
private final Logger log = LoggerFactory.getLogger(getClass());
private Server prometheusExporter;
private static final String BYTE_VM2VM = "byte_vm2vm";
private static final String BYTE_DEVICE = "byte_device";
private static final String BYTE_SRC_IP = "byte_src_ip";
private static final String BYTE_DST_IP = "byte_dst_ip";
private static final String PKT_VM2VM = "pkt_vm2vm";
private static final String PKT_DEVICE = "pkt_device";
private static final String PKT_SRC_IP = "pkt_src_ip";
private static final String PKT_DST_IP = "pkt_dst_ip";
private static final String PKT_ERROR = "pkt_error";
private static final String PKT_DROP = "pkt_drop";
private static final String LABEL_IP_5_TUPLE = "IP_5_TUPLE";
private static final String LABEL_DEV_ID = "DEVICE_ID";
private static final String LABEL_SRC_IP = "SOURCE_IP";
private static final String LABEL_DST_IP = "DESTINATION_IP";
private static final String HELP_MSG = "SONA Flow statistics";
private static Counter byteVM2VM = Counter.build().name(BYTE_VM2VM)
.help(HELP_MSG)
.labelNames(LABEL_IP_5_TUPLE).register();
private static Counter byteDevice = Counter.build().name(BYTE_DEVICE)
.help(HELP_MSG)
.labelNames(LABEL_DEV_ID).register();
private static Counter byteSrcIp = Counter.build().name(BYTE_SRC_IP)
.help(HELP_MSG)
.labelNames(LABEL_SRC_IP).register();
private static Counter byteDstIp = Counter.build().name(BYTE_DST_IP)
.help(HELP_MSG)
.labelNames(LABEL_DST_IP).register();
private static Counter pktVM2VM = Counter.build().name(PKT_VM2VM)
.help(HELP_MSG)
.labelNames(LABEL_IP_5_TUPLE).register();
private static Counter pktDevice = Counter.build().name(PKT_DEVICE)
.help(HELP_MSG)
.labelNames(LABEL_DEV_ID).register();
private static Counter pktSrcIp = Counter.build().name(PKT_SRC_IP)
.help(HELP_MSG)
.labelNames(LABEL_SRC_IP).register();
private static Counter pktDstIp = Counter.build().name(PKT_DST_IP)
.help(HELP_MSG)
.labelNames(LABEL_DST_IP).register();
private static Counter pktError = Counter.build().name(PKT_ERROR)
.help(HELP_MSG)
.register();
private static Counter pktDrop = Counter.build().name(PKT_DROP)
.help(HELP_MSG)
.register();
@Reference(cardinality = ReferenceCardinality.MANDATORY)
protected OpenstackTelemetryService openstackTelemetryService;
@Activate
protected void activate() {
openstackTelemetryService.addTelemetryService(this);
log.info("Started");
}
@Deactivate
protected void deactivate() {
stop();
openstackTelemetryService.removeTelemetryService(this);
log.info("Stopped");
}
@Override
public void start(TelemetryConfig config) {
log.info("Prometheus exporter starts.");
PrometheusTelemetryConfig prometheusConfig = (PrometheusTelemetryConfig) config;
try {
// TODO Offer a 'Authentication'
prometheusExporter = new Server(prometheusConfig.port());
ServletContextHandler context = new ServletContextHandler();
context.setContextPath("/");
prometheusExporter.setHandler(context);
context.addServlet(new ServletHolder(new MetricsServlet()), "/metrics");
log.info("Prometeus server start");
prometheusExporter.start();
} catch (Exception ex) {
log.warn("Exception: {}", ex.toString());
}
}
@Override
public void stop() {
try {
prometheusExporter.stop();
} catch (Exception ex) {
log.warn("Exception: {}", ex.toString());
}
log.info("Prometheus exporter has stopped");
}
@Override
public void restart(TelemetryConfig config) {
stop();
start(config);
}
@Override
public void publish(Set<FlowInfo> flowInfos) {
if (flowInfos.size() == 0) {
log.debug("No record to publish");
return;
}
long flowByte;
int flowPkt;
for (FlowInfo flowInfo: flowInfos) {
flowByte = flowInfo.statsInfo().currAccBytes() - flowInfo.statsInfo().prevAccBytes();
flowPkt = flowInfo.statsInfo().currAccPkts() - flowInfo.statsInfo().prevAccPkts();
byteVM2VM.labels(flowInfo.uniqueFlowInfoKey()).inc(flowByte);
byteDevice.labels(flowInfo.deviceId().toString()).inc(flowByte);
byteSrcIp.labels(flowInfo.srcIp().toString()).inc(flowByte);
byteDstIp.labels(flowInfo.dstIp().toString()).inc(flowByte);
pktVM2VM.labels(flowInfo.uniqueFlowInfoKey()).inc(flowPkt);
pktDevice.labels(flowInfo.deviceId().toString()).inc(flowPkt);
pktSrcIp.labels(flowInfo.srcIp().toString()).inc(flowPkt);
pktDstIp.labels(flowInfo.dstIp().toString()).inc(flowPkt);
pktError.inc(flowInfo.statsInfo().errorPkts());
pktDrop.inc(flowInfo.statsInfo().dropPkts());
}
}
@Override
public boolean isRunning() {
log.info("Prometheus Exporter State: {}", prometheusExporter.isRunning());
return prometheusExporter.isRunning();
}
}

View File

@ -0,0 +1,113 @@
/*
* Copyright 2018-present Open Networking Foundation
*
* 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.
*/
package org.onosproject.openstacktelemetry.config;
import com.google.common.collect.ImmutableMap;
import com.google.common.testing.EqualsTester;
import org.junit.Before;
import org.junit.Test;
import org.onosproject.openstacktelemetry.api.config.PrometheusTelemetryConfig;
import java.util.Map;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
import static org.onlab.junit.ImmutableClassChecker.assertThatClassIsImmutable;
import static org.onosproject.openstacktelemetry.api.Constants.DEFAULT_PROMETHEUS_EXPORTER_IP;
import static org.onosproject.openstacktelemetry.api.Constants.DEFAULT_PROMETHEUS_EXPORTER_PORT;
/**
* Unit tests for DefaultPrometheusTelemetryConfig class.
*/
public class DefaultPrometheusTelemetryConfigTest {
private static final String IP_ADDRESS_1 = DEFAULT_PROMETHEUS_EXPORTER_IP;
private static final String IP_ADDRESS_2 = "10.10.1.2";
private static final int PORT_1 = DEFAULT_PROMETHEUS_EXPORTER_PORT;
private static final int PORT_2 = DEFAULT_PROMETHEUS_EXPORTER_PORT + 1;
private static final Map<String, Object> CONFIG_MAP_1 =
ImmutableMap.of("key1", "value1");
private static final Map<String, Object> CONFIG_MAP_2 =
ImmutableMap.of("key2", "value2");
private PrometheusTelemetryConfig config1;
private PrometheusTelemetryConfig sameAsConfig1;
private PrometheusTelemetryConfig config2;
/**
* Initial setup for this unit test.
*/
@Before
public void setup() {
PrometheusTelemetryConfig.Builder builder1 =
new DefaultPrometheusTelemetryConfig.DefaultBuilder();
PrometheusTelemetryConfig.Builder builder2 =
new DefaultPrometheusTelemetryConfig.DefaultBuilder();
PrometheusTelemetryConfig.Builder builder3 =
new DefaultPrometheusTelemetryConfig.DefaultBuilder();
config1 = builder1
.withAddress(IP_ADDRESS_1)
.withPort(PORT_1)
.withConfigMap(CONFIG_MAP_1)
.build();
sameAsConfig1 = builder2
.withAddress(IP_ADDRESS_1)
.withPort(PORT_1)
.withConfigMap(CONFIG_MAP_1)
.build();
config2 = builder3
.withAddress(IP_ADDRESS_2)
.withPort(PORT_2)
.withConfigMap(CONFIG_MAP_2)
.build();
}
/**
* Tests class immutability.
*/
@Test
public void testImmutability() {
assertThatClassIsImmutable(DefaultPrometheusTelemetryConfig.class);
}
/**
* Tests object equality.
*/
@Test
public void testEquality() {
new EqualsTester()
.addEqualityGroup(config1, sameAsConfig1)
.addEqualityGroup(config2).testEquals();
}
/**
* Tests object construction.
*/
@Test
public void testConstruction() {
PrometheusTelemetryConfig config = config1;
assertThat(config.address(), is(IP_ADDRESS_1));
assertThat(config.port(), is(PORT_1));
assertThat(config.configMap(), is(CONFIG_MAP_1));
}
}

View File

@ -23,10 +23,10 @@ import java.util.concurrent.ExecutorService;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;
import org.onlab.packet.IpAddress;
import org.onlab.packet.IpPrefix;
import org.onosproject.cluster.NodeId;
import org.onlab.util.KryoNamespace;
import org.onosproject.routeservice.InternalRouteEvent;
import org.onosproject.routeservice.Route;
@ -50,7 +50,12 @@ import static com.google.common.base.Preconditions.checkNotNull;
public class DefaultRouteTable implements RouteTable {
private final RouteTableId id;
private final ConsistentMultimap<IpPrefix, Route> routes;
// The route map stores RawRoute instead of Route to translate the polymorphic IpPrefix and IpAddress types
// into monomorphic types (specifically String). Using strings in the stored RawRoute is necessary to ensure
// the serialized bytes are consistent whether e.g. IpAddress or Ip4Address is used when storing a route.
private final ConsistentMultimap<String, RawRoute> routes;
private final RouteStoreDelegate delegate;
private final ExecutorService executor;
private final RouteTableListener listener = new RouteTableListener();
@ -89,13 +94,14 @@ public class DefaultRouteTable implements RouteTable {
new InternalRouteEvent(InternalRouteEvent.Type.ROUTE_ADDED, routeSet)));
}
private ConsistentMultimap<IpPrefix, Route> buildRouteMap(StorageService storageService) {
private ConsistentMultimap<String, RawRoute> buildRouteMap(StorageService storageService) {
KryoNamespace routeTableSerializer = KryoNamespace.newBuilder()
.register(KryoNamespaces.API)
.register(Route.class)
.register(Route.Source.class)
.register(RawRoute.class)
.build();
return storageService.<IpPrefix, Route>consistentMultimapBuilder()
return storageService.<String, RawRoute>consistentMultimapBuilder()
.withName("onos-routes-" + id.name())
.withRelaxedReadConsistency()
.withSerializer(Serializer.using(routeTableSerializer))
@ -121,36 +127,37 @@ public class DefaultRouteTable implements RouteTable {
@Override
public void update(Route route) {
routes.put(route.prefix(), route);
routes.put(route.prefix().toString(), new RawRoute(route));
}
@Override
public void remove(Route route) {
routes.remove(route.prefix(), route);
routes.remove(route.prefix().toString(), new RawRoute(route));
}
@Override
public void replace(Route route) {
routes.replaceValues(route.prefix(), Sets.newHashSet(route));
routes.replaceValues(route.prefix().toString(), Sets.newHashSet(new RawRoute(route)));
}
@Override
public Collection<RouteSet> getRoutes() {
return routes.stream()
.map(Map.Entry::getValue)
.collect(Collectors.groupingBy(Route::prefix))
.collect(Collectors.groupingBy(RawRoute::prefix))
.entrySet()
.stream()
.map(entry -> new RouteSet(id, entry.getKey(), ImmutableSet.copyOf(entry.getValue())))
.map(entry -> new RouteSet(id,
IpPrefix.valueOf(entry.getKey()),
entry.getValue().stream().map(RawRoute::route).collect(Collectors.toSet())))
.collect(Collectors.toList());
}
@Override
public RouteSet getRoutes(IpPrefix prefix) {
Versioned<Collection<? extends Route>> routeSet = routes.get(prefix);
Versioned<Collection<? extends RawRoute>> routeSet = routes.get(prefix.toString());
if (routeSet != null) {
return new RouteSet(id, prefix, ImmutableSet.copyOf(routeSet.value()));
return new RouteSet(id, prefix, routeSet.value().stream().map(RawRoute::route).collect(Collectors.toSet()));
}
return null;
}
@ -159,22 +166,25 @@ public class DefaultRouteTable implements RouteTable {
public Collection<Route> getRoutesForNextHop(IpAddress nextHop) {
return routes.stream()
.map(Map.Entry::getValue)
.filter(r -> r.nextHop().equals(nextHop))
.filter(r -> IpAddress.valueOf(r.nextHop()).equals(nextHop))
.map(RawRoute::route)
.collect(Collectors.toSet());
}
private class RouteTableListener
implements MultimapEventListener<IpPrefix, Route> {
implements MultimapEventListener<String, RawRoute> {
private InternalRouteEvent createRouteEvent(
InternalRouteEvent.Type type, MultimapEvent<IpPrefix, Route> event) {
Collection<? extends Route> currentRoutes = Versioned.valueOrNull(routes.get(event.key()));
InternalRouteEvent.Type type, MultimapEvent<String, RawRoute> event) {
Collection<? extends RawRoute> currentRoutes = Versioned.valueOrNull(routes.get(event.key()));
return new InternalRouteEvent(type, new RouteSet(
id, event.key(), currentRoutes != null ? ImmutableSet.copyOf(currentRoutes) : Collections.emptySet()));
id, IpPrefix.valueOf(event.key()), currentRoutes != null ?
currentRoutes.stream().map(RawRoute::route).collect(Collectors.toSet())
: Collections.emptySet()));
}
@Override
public void event(MultimapEvent<IpPrefix, Route> event) {
public void event(MultimapEvent<String, RawRoute> event) {
InternalRouteEvent ire = null;
switch (event.type()) {
case INSERT:
@ -190,4 +200,32 @@ public class DefaultRouteTable implements RouteTable {
}
}
/**
* Represents a route object stored in the underlying ConsistentMultimap.
*/
private static class RawRoute {
private Route.Source source;
private String prefix;
private String nextHop;
private NodeId sourceNode;
RawRoute(Route route) {
this.source = route.source();
this.prefix = route.prefix().toString();
this.nextHop = route.nextHop().toString();
this.sourceNode = route.sourceNode();
}
String prefix() {
return prefix;
}
String nextHop() {
return nextHop;
}
Route route() {
return new Route(source, IpPrefix.valueOf(prefix), IpAddress.valueOf(nextHop), sourceNode);
}
}
}

View File

@ -991,6 +991,11 @@ public class SegmentRoutingManager implements SegmentRoutingService {
ImmutableMap.copyOf(defaultRoutingHandler.shouldProgramCache);
}
@Override
public boolean shouldProgram(DeviceId deviceId) {
return defaultRoutingHandler.shouldProgram(deviceId);
}
@Override
public ApplicationId appId() {
return appId;

View File

@ -352,6 +352,14 @@ public interface SegmentRoutingService {
*/
Map<DeviceId, Boolean> getShouldProgramCache();
/**
* Returns whether instance should program device or not.
*
* @param deviceId .
* @return boolean status saying instance should program device or not.
*/
boolean shouldProgram(DeviceId deviceId);
/**
* Gets application id.
*

View File

@ -67,8 +67,8 @@ public class DeviceConfiguration implements DeviceProperties {
private SegmentRoutingManager srManager;
private class SegmentRouterInfo {
int ipv4NodeSid;
int ipv6NodeSid;
int ipv4NodeSid = -1;
int ipv6NodeSid = -1;
DeviceId deviceId;
Ip4Address ipv4Loopback;
Ip6Address ipv6Loopback;
@ -271,9 +271,15 @@ public class DeviceConfiguration implements DeviceProperties {
* @return node segment id, or -1 if not found in config
*/
public int getIPv4SegmentId(Ip4Address routerAddress) {
for (Map.Entry<DeviceId, SegmentRouterInfo> entry:
deviceConfigMap.entrySet()) {
for (Map.Entry<DeviceId, SegmentRouterInfo> entry: deviceConfigMap.entrySet()) {
Ip4Address ipv4Loopback = entry.getValue().ipv4Loopback;
if (ipv4Loopback == null) {
continue;
}
if (entry.getValue().ipv4Loopback.equals(routerAddress)) {
if (entry.getValue().ipv4NodeSid == -1) {
continue;
}
return entry.getValue().ipv4NodeSid;
}
}
@ -288,9 +294,15 @@ public class DeviceConfiguration implements DeviceProperties {
* @return node segment id, or -1 if not found in config
*/
public int getIPv6SegmentId(Ip6Address routerAddress) {
for (Map.Entry<DeviceId, SegmentRouterInfo> entry:
deviceConfigMap.entrySet()) {
for (Map.Entry<DeviceId, SegmentRouterInfo> entry: deviceConfigMap.entrySet()) {
Ip6Address ipv6Loopback = entry.getValue().ipv6Loopback;
if (ipv6Loopback == null) {
continue;
}
if (entry.getValue().ipv6Loopback.equals(routerAddress)) {
if (entry.getValue().ipv6NodeSid == -1) {
continue;
}
return entry.getValue().ipv6NodeSid;
}
}

View File

@ -21,6 +21,7 @@ import org.onosproject.net.ConnectPoint;
import org.onosproject.net.DeviceId;
import org.onosproject.net.PortNumber;
import java.util.List;
import java.util.Set;
/**
@ -70,6 +71,24 @@ public interface XconnectService {
*/
boolean hasXconnect(ConnectPoint cp);
/**
* Gives xconnect VLAN of given port of a device.
*
* @param deviceId Device ID
* @param port Port number
* @return true if given VLAN vlanId is XConnect VLAN on device deviceId.
*/
List<VlanId> getXconnectVlans(DeviceId deviceId, PortNumber port);
/**
* Checks given VLAN is XConnect VLAN in given device.
*
* @param deviceId Device ID
* @param vlanId VLAN ID
* @return true if given VLAN vlanId is XConnect VLAN on device deviceId.
*/
boolean isXconnectVlan(DeviceId deviceId, VlanId vlanId);
/**
* Returns the Xconnect next objective store.
*
@ -84,4 +103,12 @@ public interface XconnectService {
*/
void removeNextId(int nextId);
/**
* Returns Xconnect next objective ID associated with group device + vlan.
*
* @param deviceId - Device ID
* @param vlanId - VLAN ID
* @return Current associated group ID
*/
int getNextId(DeviceId deviceId, VlanId vlanId);
}

View File

@ -18,6 +18,7 @@ package org.onosproject.segmentrouting.xconnect.impl;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;
import org.onlab.packet.Ethernet;
import org.onlab.packet.MacAddress;
import org.onlab.packet.VlanId;
import org.onlab.util.KryoNamespace;
@ -27,6 +28,8 @@ import org.onosproject.core.CoreService;
import org.onosproject.mastership.MastershipService;
import org.onosproject.net.ConnectPoint;
import org.onosproject.net.DeviceId;
import org.onosproject.net.Host;
import org.onosproject.net.HostLocation;
import org.onosproject.net.PortNumber;
import org.onosproject.net.config.NetworkConfigService;
import org.onosproject.net.device.DeviceEvent;
@ -48,7 +51,12 @@ import org.onosproject.net.flowobjective.NextObjective;
import org.onosproject.net.flowobjective.Objective;
import org.onosproject.net.flowobjective.ObjectiveContext;
import org.onosproject.net.flowobjective.ObjectiveError;
import org.onosproject.net.host.HostEvent;
import org.onosproject.net.host.HostListener;
import org.onosproject.net.host.HostService;
import org.onosproject.net.intf.InterfaceService;
import org.onosproject.segmentrouting.SegmentRoutingService;
import org.onosproject.segmentrouting.storekey.VlanNextObjectiveStoreKey;
import org.onosproject.segmentrouting.xconnect.api.XconnectCodec;
import org.onosproject.segmentrouting.xconnect.api.XconnectDesc;
import org.onosproject.segmentrouting.xconnect.api.XconnectKey;
@ -69,6 +77,11 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
@ -105,6 +118,12 @@ public class XconnectManager implements XconnectService {
@Reference(cardinality = ReferenceCardinality.OPTIONAL)
public SegmentRoutingService srService;
@Reference(cardinality = ReferenceCardinality.MANDATORY)
public InterfaceService interfaceService;
@Reference(cardinality = ReferenceCardinality.MANDATORY)
HostService hostService;
private static final String APP_NAME = "org.onosproject.xconnect";
private static final String ERROR_NOT_MASTER = "Not master controller";
@ -114,11 +133,18 @@ public class XconnectManager implements XconnectService {
private ConsistentMap<XconnectKey, Set<PortNumber>> xconnectStore;
private ConsistentMap<XconnectKey, Integer> xconnectNextObjStore;
private ConsistentMap<VlanNextObjectiveStoreKey, Integer> xconnectMulticastNextStore;
private ConsistentMap<VlanNextObjectiveStoreKey, List<PortNumber>> xconnectMulticastPortsStore;
private final MapEventListener<XconnectKey, Set<PortNumber>> xconnectListener = new XconnectMapListener();
private final DeviceListener deviceListener = new InternalDeviceListener();
private ExecutorService deviceEventExecutor;
private final HostListener hostListener = new InternalHostListener();
private ExecutorService hostEventExecutor;
@Activate
void activate() {
appId = coreService.registerApplication(APP_NAME);
@ -127,7 +153,8 @@ public class XconnectManager implements XconnectService {
KryoNamespace.Builder serializer = KryoNamespace.newBuilder()
.register(KryoNamespaces.API)
.register(XconnectManager.class)
.register(XconnectKey.class);
.register(XconnectKey.class)
.register(VlanNextObjectiveStoreKey.class);
xconnectStore = storageService.<XconnectKey, Set<PortNumber>>consistentMapBuilder()
.withName("onos-sr-xconnect")
@ -142,11 +169,25 @@ public class XconnectManager implements XconnectService {
.withSerializer(Serializer.using(serializer.build()))
.build();
xconnectMulticastNextStore = storageService.<VlanNextObjectiveStoreKey, Integer>consistentMapBuilder()
.withName("onos-sr-xconnect-l2multicast-next")
.withSerializer(Serializer.using(serializer.build()))
.build();
xconnectMulticastPortsStore = storageService.<VlanNextObjectiveStoreKey, List<PortNumber>>consistentMapBuilder()
.withName("onos-sr-xconnect-l2multicast-ports")
.withSerializer(Serializer.using(serializer.build()))
.build();
deviceEventExecutor = Executors.newSingleThreadScheduledExecutor(
groupedThreads("sr-xconnect-device-event", "%d", log));
deviceService.addListener(deviceListener);
hostEventExecutor = Executors.newSingleThreadExecutor(
groupedThreads("sr-xconnect-host-event", "%d", log));
hostService.addListener(hostListener);
log.info("Started");
}
@ -154,9 +195,11 @@ public class XconnectManager implements XconnectService {
void deactivate() {
xconnectStore.removeListener(xconnectListener);
deviceService.removeListener(deviceListener);
hostService.removeListener(hostListener);
codecService.unregisterCodec(XconnectDesc.class);
deviceEventExecutor.shutdown();
hostEventExecutor.shutdown();
log.info("Stopped");
}
@ -164,7 +207,7 @@ public class XconnectManager implements XconnectService {
@Override
public void addOrUpdateXconnect(DeviceId deviceId, VlanId vlanId, Set<PortNumber> ports) {
log.info("Adding or updating xconnect. deviceId={}, vlanId={}, ports={}",
deviceId, vlanId, ports);
deviceId, vlanId, ports);
final XconnectKey key = new XconnectKey(deviceId, vlanId);
xconnectStore.put(key, ports);
}
@ -172,9 +215,15 @@ public class XconnectManager implements XconnectService {
@Override
public void removeXonnect(DeviceId deviceId, VlanId vlanId) {
log.info("Removing xconnect. deviceId={}, vlanId={}",
deviceId, vlanId);
deviceId, vlanId);
final XconnectKey key = new XconnectKey(deviceId, vlanId);
xconnectStore.remove(key);
// Cleanup multicasting support, if any.
srService.getPairDeviceId(deviceId).ifPresent(pairDeviceId -> {
cleanupL2MulticastRule(pairDeviceId, srService.getPairLocalPort(pairDeviceId).get(), vlanId, true);
});
}
@Override
@ -187,10 +236,26 @@ public class XconnectManager implements XconnectService {
@Override
public boolean hasXconnect(ConnectPoint cp) {
return getXconnects().stream().anyMatch(desc ->
desc.key().deviceId().equals(cp.deviceId()) && desc.ports().contains(cp.port())
desc.key().deviceId().equals(cp.deviceId())
&& desc.ports().contains(cp.port())
);
}
@Override
public List<VlanId> getXconnectVlans(DeviceId deviceId, PortNumber port) {
return getXconnects().stream()
.filter(desc -> desc.key().deviceId().equals(deviceId) && desc.ports().contains(port))
.map(XconnectDesc::key)
.map(XconnectKey::vlanId)
.collect(Collectors.toList());
}
@Override
public boolean isXconnectVlan(DeviceId deviceId, VlanId vlanId) {
return getXconnects().stream()
.anyMatch(desc -> desc.key().deviceId().equals(deviceId) && desc.key().vlanId().equals(vlanId));
}
@Override
public ImmutableMap<XconnectKey, Integer> getNext() {
if (xconnectNextObjStore != null) {
@ -200,6 +265,15 @@ public class XconnectManager implements XconnectService {
}
}
@Override
public int getNextId(final DeviceId deviceId, final VlanId vlanId) {
Optional<Integer> nextObjective = getNext().entrySet().stream()
.filter(d -> d.getKey().deviceId().equals(deviceId) && d.getKey().vlanId().equals(vlanId))
.findFirst()
.map(Map.Entry::getValue);
return nextObjective.isPresent() ? nextObjective.get() : -1;
}
@Override
public void removeNextId(int nextId) {
xconnectNextObjStore.entrySet().forEach(e -> {
@ -258,6 +332,98 @@ public class XconnectManager implements XconnectService {
}
}
private class InternalHostListener implements HostListener {
@Override
public void event(HostEvent event) {
hostEventExecutor.execute(() -> {
switch (event.type()) {
case HOST_MOVED:
log.trace("Processing host event {}", event);
Host host = event.subject();
Set<HostLocation> prevLocations = event.prevSubject().locations();
Set<HostLocation> newLocations = host.locations();
// Dual-home host port failure
// For each old location, in failed and paired devices update L2 vlan groups
Sets.difference(prevLocations, newLocations).forEach(prevLocation -> {
Optional<DeviceId> pairDeviceId = srService.getPairDeviceId(prevLocation.deviceId());
Optional<PortNumber> pairLocalPort = srService.getPairLocalPort(prevLocation.deviceId());
if (pairDeviceId.isPresent() && pairLocalPort.isPresent() && newLocations.stream()
.anyMatch(location -> location.deviceId().equals(pairDeviceId.get())) &&
hasXconnect(new ConnectPoint(prevLocation.deviceId(), prevLocation.port()))) {
List<VlanId> xconnectVlans = getXconnectVlans(prevLocation.deviceId(),
prevLocation.port());
xconnectVlans.forEach(xconnectVlan -> {
// Add single-home host into L2 multicast group at paired device side.
// Also append ACL rule to forward traffic from paired port to L2 multicast group.
newLocations.stream()
.filter(location -> location.deviceId().equals(pairDeviceId.get()))
.forEach(location -> populateL2Multicast(location.deviceId(),
srService.getPairLocalPort(
location.deviceId()).get(),
xconnectVlan,
Collections.singletonList(
location.port())));
// Ensure pair-port attached to xconnect vlan flooding group at dual home failed device.
updateL2Flooding(prevLocation.deviceId(), pairLocalPort.get(), xconnectVlan, true);
});
}
});
// Dual-home host port restoration
// For each new location, reverse xconnect loop prevention groups.
Sets.difference(newLocations, prevLocations).forEach(newLocation -> {
final Optional<DeviceId> pairDeviceId = srService.getPairDeviceId(newLocation.deviceId());
Optional<PortNumber> pairLocalPort = srService.getPairLocalPort(newLocation.deviceId());
if (pairDeviceId.isPresent() && pairLocalPort.isPresent() &&
hasXconnect((new ConnectPoint(newLocation.deviceId(), newLocation.port())))) {
List<VlanId> xconnectVlans = getXconnectVlans(newLocation.deviceId(),
newLocation.port());
xconnectVlans.forEach(xconnectVlan -> {
// Remove recovered dual homed port from vlan L2 multicast group
prevLocations.stream()
.filter(prevLocation -> prevLocation.deviceId().equals(pairDeviceId.get()))
.forEach(prevLocation -> revokeL2Multicast(prevLocation.deviceId(),
srService.getPairLocalPort(
prevLocation.deviceId()).get(),
xconnectVlan,
Collections.singletonList(newLocation.port()))
);
// Remove pair-port from vlan's flooding group at dual home restored device,if needed.
if (!hasAccessPortInMulticastGroup(newLocation.deviceId(),
xconnectVlan,
pairLocalPort.get())) {
updateL2Flooding(newLocation.deviceId(),
pairLocalPort.get(),
xconnectVlan,
false);
// Clean L2 multicast group at pair-device; also update store.
cleanupL2MulticastRule(pairDeviceId.get(),
srService.getPairLocalPort(pairDeviceId.get()).get(),
xconnectVlan,
false);
}
});
}
});
break;
default:
log.warn("Unsupported host event type: {} received. Ignoring.", event.type());
break;
}
});
}
}
private void init(DeviceId deviceId) {
getXconnects().stream()
.filter(desc -> desc.key().deviceId().equals(deviceId))
@ -274,7 +440,7 @@ public class XconnectManager implements XconnectService {
/**
* Populates XConnect groups and flows for given key.
*
* @param key XConnect key
* @param key XConnect key
* @param ports a set of ports to be cross-connected
*/
private void populateXConnect(XconnectKey key, Set<PortNumber> ports) {
@ -283,7 +449,6 @@ public class XconnectManager implements XconnectService {
return;
}
ports = addPairPort(key.deviceId(), ports);
populateFilter(key, ports);
populateFwd(key, populateNext(key, ports));
populateAcl(key);
@ -292,7 +457,7 @@ public class XconnectManager implements XconnectService {
/**
* Populates filtering objectives for given XConnect.
*
* @param key XConnect store key
* @param key XConnect store key
* @param ports XConnect ports
*/
private void populateFilter(XconnectKey key, Set<PortNumber> ports) {
@ -300,10 +465,10 @@ public class XconnectManager implements XconnectService {
FilteringObjective.Builder filtObjBuilder = filterObjBuilder(key, port);
ObjectiveContext context = new DefaultObjectiveContext(
(objective) -> log.debug("XConnect FilterObj for {} on port {} populated",
key, port),
key, port),
(objective, error) ->
log.warn("Failed to populate XConnect FilterObj for {} on port {}: {}",
key, port, error));
key, port, error));
flowObjectiveService.filter(key.deviceId(), filtObjBuilder.add(context));
});
}
@ -311,7 +476,7 @@ public class XconnectManager implements XconnectService {
/**
* Populates next objectives for given XConnect.
*
* @param key XConnect store key
* @param key XConnect store key
* @param ports XConnect ports
*/
private int populateNext(XconnectKey key, Set<PortNumber> ports) {
@ -340,7 +505,7 @@ public class XconnectManager implements XconnectService {
/**
* Populates bridging forwarding objectives for given XConnect.
*
* @param key XConnect store key
* @param key XConnect store key
* @param nextId next objective id
*/
private void populateFwd(XconnectKey key, int nextId) {
@ -369,7 +534,7 @@ public class XconnectManager implements XconnectService {
/**
* Revokes XConnect groups and flows for given key.
*
* @param key XConnect key
* @param key XConnect key
* @param ports XConnect ports
*/
private void revokeXConnect(XconnectKey key, Set<PortNumber> ports) {
@ -378,7 +543,6 @@ public class XconnectManager implements XconnectService {
return;
}
ports = addPairPort(key.deviceId(), ports);
revokeFilter(key, ports);
if (xconnectNextObjStore.containsKey(key)) {
int nextId = xconnectNextObjStore.get(key).value();
@ -393,7 +557,7 @@ public class XconnectManager implements XconnectService {
/**
* Revokes filtering objectives for given XConnect.
*
* @param key XConnect store key
* @param key XConnect store key
* @param ports XConnect ports
*/
private void revokeFilter(XconnectKey key, Set<PortNumber> ports) {
@ -401,10 +565,10 @@ public class XconnectManager implements XconnectService {
FilteringObjective.Builder filtObjBuilder = filterObjBuilder(key, port);
ObjectiveContext context = new DefaultObjectiveContext(
(objective) -> log.debug("XConnect FilterObj for {} on port {} revoked",
key, port),
key, port),
(objective, error) ->
log.warn("Failed to revoke XConnect FilterObj for {} on port {}: {}",
key, port, error));
key, port, error));
flowObjectiveService.filter(key.deviceId(), filtObjBuilder.remove(context));
});
}
@ -412,9 +576,9 @@ public class XconnectManager implements XconnectService {
/**
* Revokes next objectives for given XConnect.
*
* @param key XConnect store key
* @param ports ports in the XConnect
* @param nextId next objective id
* @param key XConnect store key
* @param ports ports in the XConnect
* @param nextId next objective id
* @param nextFuture completable future for this next objective operation
*/
private void revokeNext(XconnectKey key, Set<PortNumber> ports, int nextId,
@ -444,8 +608,8 @@ public class XconnectManager implements XconnectService {
/**
* Revokes bridging forwarding objectives for given XConnect.
*
* @param key XConnect store key
* @param nextId next objective id
* @param key XConnect store key
* @param nextId next objective id
* @param fwdFuture completable future for this forwarding objective operation
*/
private void revokeFwd(XconnectKey key, int nextId, CompletableFuture<ObjectiveError> fwdFuture) {
@ -487,9 +651,9 @@ public class XconnectManager implements XconnectService {
/**
* Updates XConnect groups and flows for given key.
*
* @param key XConnect key
* @param key XConnect key
* @param prevPorts previous XConnect ports
* @param ports new XConnect ports
* @param ports new XConnect ports
*/
private void updateXConnect(XconnectKey key, Set<PortNumber> prevPorts,
Set<PortNumber> ports) {
@ -498,10 +662,12 @@ public class XconnectManager implements XconnectService {
// remove old filter
prevPorts.stream().filter(port -> !ports.contains(port)).forEach(port ->
revokeFilter(key, ImmutableSet.of(port)));
revokeFilter(key,
ImmutableSet.of(port)));
// install new filter
ports.stream().filter(port -> !prevPorts.contains(port)).forEach(port ->
populateFilter(key, ImmutableSet.of(port)));
populateFilter(key,
ImmutableSet.of(port)));
CompletableFuture<ObjectiveError> fwdFuture = new CompletableFuture<>();
CompletableFuture<ObjectiveError> nextFuture = new CompletableFuture<>();
@ -531,8 +697,8 @@ public class XconnectManager implements XconnectService {
/**
* Creates a next objective builder for XConnect with given nextId.
*
* @param key XConnect key
* @param ports set of XConnect ports
* @param key XConnect key
* @param ports set of XConnect ports
* @param nextId next objective id
* @return next objective builder
*/
@ -554,7 +720,7 @@ public class XconnectManager implements XconnectService {
/**
* Creates a next objective builder for XConnect.
*
* @param key XConnect key
* @param key XConnect key
* @param ports set of XConnect ports
* @return next objective builder
*/
@ -567,7 +733,7 @@ public class XconnectManager implements XconnectService {
/**
* Creates a bridging forwarding objective builder for XConnect.
*
* @param key XConnect key
* @param key XConnect key
* @param nextId next ID of the broadcast group for this XConnect key
* @return forwarding objective builder
*/
@ -615,7 +781,7 @@ public class XconnectManager implements XconnectService {
/**
* Creates a filtering objective builder for XConnect.
*
* @param key XConnect key
* @param key XConnect key
* @param port XConnect ports
* @return next objective builder
*/
@ -629,18 +795,320 @@ public class XconnectManager implements XconnectService {
}
/**
* Add pair port to the given set of port.
* Updates L2 flooding groups; add pair link into L2 flooding group of given xconnect vlan.
*
* @param deviceId device Id
* @param ports ports specified in the xconnect config
* @return port specified in the xconnect config plus the pair port (if configured)
* @param deviceId Device ID
* @param port Port details
* @param vlanId VLAN ID
* @param install Whether to add or revoke pair link addition to flooding group
*/
private Set<PortNumber> addPairPort(DeviceId deviceId, Set<PortNumber> ports) {
if (srService == null) {
return ports;
private void updateL2Flooding(DeviceId deviceId, final PortNumber port, VlanId vlanId, boolean install) {
// Ensure mastership on device
if (!mastershipService.isLocalMaster(deviceId)) {
return;
}
Set<PortNumber> newPorts = Sets.newHashSet(ports);
srService.getPairLocalPort(deviceId).ifPresent(newPorts::add);
return newPorts;
// Locate L2 flooding group details for given xconnect vlan
int nextId = getNextId(deviceId, vlanId);
if (nextId == -1) {
log.debug("XConnect vlan {} broadcast group for device {} doesn't exists. " +
"Aborting pair group linking.", vlanId, deviceId);
return;
}
// Add pairing-port group to flooding group
TrafficTreatment.Builder treatment = DefaultTrafficTreatment.builder();
// treatment.popVlan();
treatment.setOutput(port);
ObjectiveContext context = new DefaultObjectiveContext(
(objective) ->
log.debug("Pair port added/removed to vlan {} next objective {} on {}",
vlanId, nextId, deviceId),
(objective, error) ->
log.warn("Failed adding/removing pair port to vlan {} next objective {} on {}." +
"Error : {}", vlanId, nextId, deviceId, error)
);
NextObjective.Builder vlanNextObjectiveBuilder = DefaultNextObjective.builder()
.withId(nextId)
.withType(NextObjective.Type.BROADCAST)
.fromApp(srService.appId())
.withMeta(DefaultTrafficSelector.builder().matchVlanId(vlanId).build())
.addTreatment(treatment.build());
if (install) {
flowObjectiveService.next(deviceId, vlanNextObjectiveBuilder.addToExisting(context));
} else {
flowObjectiveService.next(deviceId, vlanNextObjectiveBuilder.removeFromExisting(context));
}
log.debug("Submitted next objective {} for vlan: {} in device {}",
nextId, vlanId, deviceId);
}
/**
* Populate L2 multicast rule on given deviceId that matches given mac, given vlan and
* output to given port's L2 mulitcast group.
*
* @param deviceId Device ID
* @param pairPort Pair port number
* @param vlanId VLAN ID
* @param accessPorts List of access ports to be added into L2 multicast group
*/
private void populateL2Multicast(DeviceId deviceId, final PortNumber pairPort,
VlanId vlanId, List<PortNumber> accessPorts) {
boolean multicastGroupExists = true;
int vlanMulticastNextId;
// Ensure enough rights to program pair device
if (!srService.shouldProgram(deviceId)) {
return;
}
// Step 1 : Populate single homed access ports into vlan's L2 multicast group
NextObjective.Builder vlanMulticastNextObjBuilder = DefaultNextObjective
.builder()
.withType(NextObjective.Type.BROADCAST)
.fromApp(srService.appId())
.withMeta(DefaultTrafficSelector.builder().matchVlanId(vlanId)
.matchEthDst(MacAddress.IPV4_MULTICAST).build());
vlanMulticastNextId = getMulticastGroupNextObjectiveId(deviceId, vlanId);
if (vlanMulticastNextId == -1) {
// Vlan's L2 multicast group doesn't exist; create it, update store and add pair port as sub-group
multicastGroupExists = false;
vlanMulticastNextId = flowObjectiveService.allocateNextId();
addMulticastGroupNextObjectiveId(deviceId, vlanId, vlanMulticastNextId);
vlanMulticastNextObjBuilder.addTreatment(
DefaultTrafficTreatment.builder().popVlan().setOutput(pairPort).build()
);
}
vlanMulticastNextObjBuilder.withId(vlanMulticastNextId);
final int nextId = vlanMulticastNextId;
accessPorts.forEach(p -> {
TrafficTreatment.Builder egressAction = DefaultTrafficTreatment.builder();
// Do vlan popup action based on interface configuration
if (interfaceService.getInterfacesByPort(new ConnectPoint(deviceId, p))
.stream().noneMatch(i -> i.vlanTagged().contains(vlanId))) {
egressAction.popVlan();
}
egressAction.setOutput(p);
vlanMulticastNextObjBuilder.addTreatment(egressAction.build());
addMulticastGroupPort(deviceId, vlanId, p);
});
ObjectiveContext context = new DefaultObjectiveContext(
(objective) ->
log.debug("L2 multicast group installed/updated. "
+ "NextObject Id {} on {} for subnet {} ",
nextId, deviceId, vlanId),
(objective, error) ->
log.warn("L2 multicast group failed to install/update. "
+ " NextObject Id {} on {} for subnet {} : {}",
nextId, deviceId, vlanId, error)
);
if (!multicastGroupExists) {
flowObjectiveService.next(deviceId, vlanMulticastNextObjBuilder.add(context));
// Step 2 : Populate ACL rule; selector = vlan + pair-port, output = vlan L2 multicast group
TrafficSelector.Builder multicastSelector = DefaultTrafficSelector.builder();
multicastSelector.matchEthType(Ethernet.TYPE_VLAN);
multicastSelector.matchInPort(pairPort);
multicastSelector.matchVlanId(vlanId);
ForwardingObjective.Builder vlanMulticastForwardingObj = DefaultForwardingObjective.builder()
.withFlag(ForwardingObjective.Flag.VERSATILE)
.nextStep(vlanMulticastNextId)
.withSelector(multicastSelector.build())
.withPriority(100)
.fromApp(srService.appId())
.makePermanent();
context = new DefaultObjectiveContext(
(objective) -> log.debug("L2 multicasting versatile rule for device {}, port/vlan {}/{} populated",
deviceId,
pairPort,
vlanId),
(objective, error) -> log.warn("Failed to populate L2 multicasting versatile rule for device {}, " +
"ports/vlan {}/{}: {}", deviceId, pairPort, vlanId, error));
flowObjectiveService.forward(deviceId, vlanMulticastForwardingObj.add(context));
} else {
// L2_MULTICAST & BROADCAST are similar structure in subgroups; so going with BROADCAST type.
vlanMulticastNextObjBuilder.withType(NextObjective.Type.BROADCAST);
flowObjectiveService.next(deviceId, vlanMulticastNextObjBuilder.addToExisting(context));
}
}
/**
* Removes access ports from VLAN L2 multicast group on given deviceId.
*
* @param deviceId Device ID
* @param pairPort Pair port number
* @param vlanId VLAN ID
* @param accessPorts List of access ports to be added into L2 multicast group
*/
private void revokeL2Multicast(DeviceId deviceId, final PortNumber pairPort,
VlanId vlanId, List<PortNumber> accessPorts) {
// Ensure enough rights to program pair device
if (!srService.shouldProgram(deviceId)) {
return;
}
int vlanMulticastNextId = getMulticastGroupNextObjectiveId(deviceId, vlanId);
if (vlanMulticastNextId == -1) {
return;
}
NextObjective.Builder vlanMulticastNextObjBuilder = DefaultNextObjective
.builder()
.withType(NextObjective.Type.BROADCAST)
.fromApp(srService.appId())
.withMeta(DefaultTrafficSelector.builder().matchVlanId(vlanId).build())
.withId(vlanMulticastNextId);
accessPorts.forEach(p -> {
TrafficTreatment.Builder egressAction = DefaultTrafficTreatment.builder();
// Do vlan popup action based on interface configuration
if (interfaceService.getInterfacesByPort(new ConnectPoint(deviceId, p))
.stream().noneMatch(i -> i.vlanTagged().contains(vlanId))) {
egressAction.popVlan();
}
egressAction.setOutput(p);
vlanMulticastNextObjBuilder.addTreatment(egressAction.build());
removeMulticastGroupPort(deviceId, vlanId, p);
});
ObjectiveContext context = new DefaultObjectiveContext(
(objective) ->
log.debug("L2 multicast group installed/updated. "
+ "NextObject Id {} on {} for subnet {} ",
vlanMulticastNextId, deviceId, vlanId),
(objective, error) ->
log.warn("L2 multicast group failed to install/update. "
+ " NextObject Id {} on {} for subnet {} : {}",
vlanMulticastNextId, deviceId, vlanId, error)
);
flowObjectiveService.next(deviceId, vlanMulticastNextObjBuilder.removeFromExisting(context));
}
/**
* Cleans up VLAN L2 multicast group on given deviceId. ACL rules for the group will also be deleted.
* Normally multicast group is not removed if it contains access ports; which can be forced
* by "force" flag
*
* @param deviceId Device ID
* @param pairPort Pair port number
* @param vlanId VLAN ID
* @param force Forceful removal
*/
private void cleanupL2MulticastRule(DeviceId deviceId, PortNumber pairPort, VlanId vlanId, boolean force) {
// Ensure enough rights to program pair device
if (!srService.shouldProgram(deviceId)) {
return;
}
// Ensure L2 multicast group doesn't contain access ports
if (hasAccessPortInMulticastGroup(deviceId, vlanId, pairPort) && !force) {
return;
}
// Load L2 multicast group details
int vlanMulticastNextId = getMulticastGroupNextObjectiveId(deviceId, vlanId);
if (vlanMulticastNextId == -1) {
return;
}
// Step 1 : Clear ACL rule; selector = vlan + pair-port, output = vlan L2 multicast group
TrafficSelector.Builder l2MulticastSelector = DefaultTrafficSelector.builder();
l2MulticastSelector.matchEthType(Ethernet.TYPE_VLAN);
l2MulticastSelector.matchInPort(pairPort);
l2MulticastSelector.matchVlanId(vlanId);
ForwardingObjective.Builder vlanMulticastForwardingObj = DefaultForwardingObjective.builder()
.withFlag(ForwardingObjective.Flag.VERSATILE)
.nextStep(vlanMulticastNextId)
.withSelector(l2MulticastSelector.build())
.withPriority(100)
.fromApp(srService.appId())
.makePermanent();
ObjectiveContext context = new DefaultObjectiveContext(
(objective) -> log.debug("L2 multicasting rule for device {}, port/vlan {}/{} deleted", deviceId,
pairPort, vlanId),
(objective, error) -> log.warn("Failed to delete L2 multicasting rule for device {}, " +
"ports/vlan {}/{}: {}", deviceId, pairPort, vlanId, error));
flowObjectiveService.forward(deviceId, vlanMulticastForwardingObj.remove(context));
// Step 2 : Clear L2 multicast group associated with vlan
NextObjective.Builder l2MulticastGroupBuilder = DefaultNextObjective
.builder()
.withId(vlanMulticastNextId)
.withType(NextObjective.Type.BROADCAST)
.fromApp(srService.appId())
.withMeta(DefaultTrafficSelector.builder()
.matchVlanId(vlanId)
.matchEthDst(MacAddress.IPV4_MULTICAST).build())
.addTreatment(DefaultTrafficTreatment.builder().popVlan().setOutput(pairPort).build());
context = new DefaultObjectiveContext(
(objective) ->
log.debug("L2 multicast group with NextObject Id {} deleted on {} for subnet {} ",
vlanMulticastNextId, deviceId, vlanId),
(objective, error) ->
log.warn("L2 multicast group with NextObject Id {} failed to delete on {} for subnet {} : {}",
vlanMulticastNextId, deviceId, vlanId, error)
);
flowObjectiveService.next(deviceId, l2MulticastGroupBuilder.remove(context));
// Finally clear store.
removeMulticastGroup(deviceId, vlanId);
}
private boolean isMulticastGroupExists(DeviceId deviceId, VlanId vlanId) {
return xconnectMulticastNextStore.asJavaMap().entrySet().stream()
.anyMatch(e -> e.getKey().deviceId().equals(deviceId) &&
e.getKey().vlanId().equals(vlanId));
}
private int getMulticastGroupNextObjectiveId(DeviceId deviceId, VlanId vlanId) {
Optional<Integer> nextId
= xconnectMulticastNextStore.asJavaMap().entrySet().stream()
.filter(e -> e.getKey().deviceId().equals(deviceId) &&
e.getKey().vlanId().equals(vlanId))
.findFirst()
.map(Map.Entry::getValue);
return nextId.orElse(-1);
}
private void addMulticastGroupNextObjectiveId(DeviceId deviceId, VlanId vlanId, int nextId) {
if (nextId == -1) {
return;
}
VlanNextObjectiveStoreKey key = new VlanNextObjectiveStoreKey(deviceId, vlanId);
xconnectMulticastNextStore.put(key, nextId);
// Update port store with empty entry.
xconnectMulticastPortsStore.put(key, new ArrayList<PortNumber>());
}
private void addMulticastGroupPort(DeviceId deviceId, VlanId vlanId, PortNumber port) {
VlanNextObjectiveStoreKey key = new VlanNextObjectiveStoreKey(deviceId, vlanId);
List<PortNumber> ports = xconnectMulticastPortsStore.get(key).value();
ports.add(port);
xconnectMulticastPortsStore.put(key, ports);
}
private void removeMulticastGroupPort(DeviceId deviceId, VlanId vlanId, PortNumber port) {
VlanNextObjectiveStoreKey key = new VlanNextObjectiveStoreKey(deviceId, vlanId);
List<PortNumber> ports = xconnectMulticastPortsStore.get(key).value();
ports.remove(port);
xconnectMulticastPortsStore.put(key, ports);
}
private void removeMulticastGroup(DeviceId deviceId, VlanId vlanId) {
VlanNextObjectiveStoreKey key = new VlanNextObjectiveStoreKey(deviceId, vlanId);
xconnectMulticastPortsStore.remove(key);
xconnectMulticastNextStore.remove(key);
}
private boolean hasAccessPortInMulticastGroup(DeviceId deviceId, VlanId vlanId, PortNumber pairPort) {
VlanNextObjectiveStoreKey key = new VlanNextObjectiveStoreKey(deviceId, vlanId);
if (!xconnectMulticastPortsStore.containsKey(key)) {
return false;
}
List<PortNumber> ports = xconnectMulticastPortsStore.get(key).value();
return ports.stream().anyMatch(p -> !p.equals(pairPort));
}
}

12
apps/workflow/BUCK Normal file
View File

@ -0,0 +1,12 @@
BUNDLES = [
'//apps/workflow/api:onos-apps-workflow-api',
'//apps/workflow/app:onos-apps-workflow-app',
]
onos_app (
category = 'Utility',
description = "Workflow application",
included_bundles = BUNDLES,
title = 'Workflow',
url = 'http://onosproject.org',
)

12
apps/workflow/BUILD Normal file
View File

@ -0,0 +1,12 @@
BUNDLES = [
"//apps/workflow/api:onos-apps-workflow-api",
"//apps/workflow/app:onos-apps-workflow-app",
]
onos_app(
category = "Utility",
description = "Workflow application",
included_bundles = BUNDLES,
title = "Workflow",
url = "http://onosproject.org",
)

11
apps/workflow/api/BUCK Normal file
View File

@ -0,0 +1,11 @@
COMPILE_DEPS = [
'//lib:CORE_DEPS',
'//lib:jackson-core',
'//lib:jackson-annotations',
'//lib:jackson-databind',
'//core/store/serializers:onos-core-serializers',
]
osgi_jar_with_tests (
deps = COMPILE_DEPS,
)

7
apps/workflow/api/BUILD Normal file
View File

@ -0,0 +1,7 @@
COMPILE_DEPS = CORE_DEPS + KRYO + JACKSON + [
"//core/store/serializers:onos-core-serializers",
]
osgi_jar_with_tests(
deps = COMPILE_DEPS,
)

View File

@ -0,0 +1,52 @@
/*
* Copyright 2018-present Open Networking Foundation
*
* 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.
*/
package org.onosproject.workflow.api;
import java.net.URI;
/**
* Abstract class for workflow.
*/
public abstract class AbstractWorkflow implements Workflow {
/**
* ID of workflow.
*/
private URI id;
/**
* Constructor for AbstractWorkflow.
* @param id ID of workflow
*/
protected AbstractWorkflow(URI id) {
this.id = id;
}
@Override
public URI id() {
return id;
}
@Override
public WorkflowContext buildContext(Workplace workplace, DataModelTree data) throws WorkflowException {
return new DefaultWorkflowContext(id, workplace.name(), data);
}
@Override
public WorkflowContext buildSystemContext(Workplace workplace, DataModelTree data) throws WorkflowException {
return new SystemWorkflowContext(id, workplace.name(), data);
}
}

View File

@ -0,0 +1,46 @@
/*
* Copyright 2018-present Open Networking Foundation
*
* 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.
*/
package org.onosproject.workflow.api;
import org.onosproject.event.Event;
/**
* Abstract class for worklet.
*/
public abstract class AbstractWorklet implements Worklet {
@Override
public String tag() {
return this.getClass().getName();
}
@Override
public boolean isCompleted(WorkflowContext context, Event event)throws WorkflowException {
throw new WorkflowException("(" + tag() + ").isCompleted should not be called");
}
@Override
public boolean isNext(WorkflowContext context) throws WorkflowException {
throw new WorkflowException("(" + tag() + ").isNext should not be called");
}
@Override
public void timeout(WorkflowContext context) throws WorkflowException {
throw new WorkflowException("Timeout happened");
}
}

View File

@ -0,0 +1,41 @@
/*
* Copyright 2018-present Open Networking Foundation
*
* 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.
*/
package org.onosproject.workflow.api;
/**
* An interface representing branch worklet. Branch worklet is used for branching workflow execution.
*/
public interface BranchWorklet extends Worklet {
@Override
default void process(WorkflowContext context) throws WorkflowException {
throw new WorkflowException("This workletType.process should not be called");
}
@Override
default boolean isNext(WorkflowContext context) throws WorkflowException {
throw new WorkflowException("This workletType.isNext should not be called");
}
/**
* Returns next worklet class for branching.
* @param context workflow context
* @return next worklet class
* @throws WorkflowException workflow exception
*/
Class<? extends Worklet> next(WorkflowContext context) throws WorkflowException;
}

View File

@ -0,0 +1,83 @@
/*
* Copyright 2018-present Open Networking Foundation
*
* 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.
*/
package org.onosproject.workflow.api;
import com.fasterxml.jackson.databind.node.ObjectNode;
import org.onosproject.store.service.DocumentPath;
import org.onosproject.store.service.Versioned;
import java.util.Map;
/**
* WorkflowContext Event Map Store.
*/
public interface ContextEventMapStore {
/**
* Registers workflow context event mapping.
* @param eventType the class name of event
* @param eventHint event hint string value of the event
* @param contextName workflow context name
* @param workletType the class name of worklet
* @throws WorkflowException workflow exception
*/
void registerEventMap(String eventType, String eventHint,
String contextName, String workletType) throws WorkflowException;
/**
* Unregisters workflow context event mapping.
* @param eventType the class name of event
* @param eventHint event hint string value of the event
* @param contextName workflow context name
* @throws WorkflowException workflow exception
*/
void unregisterEventMap(String eventType, String eventHint,
String contextName) throws WorkflowException;
/**
* Returns workflow context event mapping.
* @param eventType the class name of event
* @param eventHint event hint string value of the event
* @return workflow context event mapping
* @throws WorkflowException workflow exception
*/
Map<String, String> getEventMap(String eventType, String eventHint) throws WorkflowException;
/**
* Returns child nodes on document tree path.
* @param path document tree path
* @return children under document tree path
* @throws WorkflowException workflow exception
*/
Map<String, Versioned<String>> getChildren(String path) throws WorkflowException;
/**
* Returns document path.
* @param path document path string
* @return document tree
* @throws WorkflowException workflow exception
*/
DocumentPath getDocumentPath(String path) throws WorkflowException;
/**
* Transforms document tree to json tree.
* @return json tree
* @throws WorkflowException workflow exception
*/
ObjectNode asJsonTree() throws WorkflowException;
}

View File

@ -0,0 +1,64 @@
/*
* Copyright 2018-present Open Networking Foundation
*
* 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.
*/
package org.onosproject.workflow.api;
/**
* Common data model tree path.
*/
public interface DataModelPointer {
/**
* Workplace array pointer.
*/
String WORKPLACES_PTR = "/workplaces";
/**
* Workplace name pointer.
*/
String WORKPLACE_NAME_PTR = "/name";
/**
* Workplace data pointer.
*/
String WORKPLACE_DATA_PTR = "/data";
/**
* Workplace workflow pointer.
*/
String WORKPLACE_WORKFLOWS_PTR = "/workflows";
/**
* Workflow op pointer.
*/
String WORKFLOW_OP_PTR = "/op";
/**
* Workflow id pointer.
*/
String WORKFLOW_ID_PTR = "/id";
/**
* Workflow data pointer.
*/
String WORKFLOW_DATA_PTR = "/data";
/**
* Gets path string.
* @return path string
*/
String getPath();
}

View File

@ -0,0 +1,63 @@
/*
* Copyright 2018-present Open Networking Foundation
*
* 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.
*/
package org.onosproject.workflow.api;
/**
* Interface for data model tree.
*/
public interface DataModelTree {
/**
* Data model tree node type (map or array).
*/
enum Nodetype {
/**
* Map type data model tree node.
*/
MAP,
/**
* Array type data model tree node.
*/
ARRAY
}
/**
* Returns subtree on the path.
* @param path data model tree path
* @return subtree on the path
*/
DataModelTree subtree(String path);
/**
* Attaches subtree on the path.
* @param path data model tree path where subtree will be attached
* @param tree subtree to be attached
* @throws WorkflowException workflow exception
*/
void attach(String path, DataModelTree tree) throws WorkflowException;
/**
* Allocates leaf node on the path.
* @param path data model tree path where new leaf node will be allocated
* @param leaftype leaf node type
* @return data model tree
* @throws WorkflowException workflow exception
*/
DataModelTree alloc(String path, Nodetype leaftype) throws WorkflowException;
}

View File

@ -0,0 +1,177 @@
/*
* Copyright 2018-present Open Networking Foundation
*
* 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.
*/
package org.onosproject.workflow.api;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.MissingNode;
import com.fasterxml.jackson.databind.node.TextNode;
import com.google.common.base.MoreObjects;
/**
* Class of workflow RPC description.
*/
public final class DefaultRpcDescription implements RpcDescription {
/**
* Workflow RPC operation.
*/
private final String op;
/**
* Parameters.
*/
private final JsonNode params;
/**
* Invocation ID.
*/
private final String id;
/**
* Constructor of workplace description.
* @param builder workplace builder
*/
private DefaultRpcDescription(Builder builder) {
this.op = builder.op;
this.params = builder.params;
this.id = builder.id;
}
@Override
public String op() {
return this.op;
}
@Override
public JsonNode params() {
return this.params;
}
@Override
public String id() {
return this.id;
}
/**
* Creating workflow RPC description from json tree.
* @param root root node for workflow RPC description
* @return workflow RPC description
* @throws WorkflowException workflow exception
*/
public static DefaultRpcDescription valueOf(JsonNode root) throws WorkflowException {
JsonNode node = root.at(RPC_OP_PTR);
if (!(node instanceof TextNode)) {
throw new WorkflowException("invalid RPC operation for " + root);
}
String rpcOp = node.asText();
node = root.at(RPC_PARAMS_PTR);
if (node instanceof MissingNode) {
throw new WorkflowException("invalid RPC parameters for " + root);
}
JsonNode rpcParams = node;
node = root.at(RPC_ID_PTR);
if (!(node instanceof TextNode)) {
throw new WorkflowException("invalid RPC invocation ID for " + root);
}
String rpcId = node.asText();
return builder()
.setOp(rpcOp)
.setParams(rpcParams)
.setId(rpcId)
.build();
}
@Override
public String toString() {
return MoreObjects.toStringHelper(getClass())
.add("op", op())
.add("params", params())
.add("id", id())
.toString();
}
/**
* Gets builder instance.
* @return builder instance
*/
public static Builder builder() {
return new Builder();
}
/**
* Builder for workplace RPC description.
*/
public static class Builder {
/**
* Workflow RPC operation.
*/
private String op;
/**
* Parameters.
*/
private JsonNode params;
/**
* Invocation ID.
*/
private String id;
/**
* Sets workflow RPC operation.
* @param op workflow RPC operation
* @return builder
*/
public Builder setOp(String op) {
this.op = op;
return this;
}
/**
* Sets workflow RPC parameters.
* @param params workflow RPC parameters
* @return builder
*/
public Builder setParams(JsonNode params) {
this.params = params;
return this;
}
/**
* Sets workflow RPC invocation ID.
* @param id workflow invocation ID
* @return builder
*/
public Builder setId(String id) {
this.id = id;
return this;
}
/**
* Builds workplace RPC description from builder.
* @return instance of workflow RPC description
*/
public DefaultRpcDescription build() {
return new DefaultRpcDescription(this);
}
}
}

View File

@ -0,0 +1,242 @@
/*
* Copyright 2018-present Open Networking Foundation
*
* 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.
*/
package org.onosproject.workflow.api;
import com.google.common.base.MoreObjects;
import org.onosproject.event.Event;
import java.net.URI;
/**
* Default implementation of WorkflowContext.
*/
public class DefaultWorkflowContext extends WorkflowContext {
/**
* ID of workflow.
*/
private URI workflowId;
/**
* Workplace name of the workflow.
*/
private String workplaceName;
/**
* State of workflow.
*/
private WorkflowState state;
/**
* Current worklet of the workflow.
*/
private String current;
/**
* Cause of workflow exception.
*/
private String cause;
/**
* Completion event type.
*/
private transient Class<? extends Event> completionEventType;
/**
* Completion event hint.
*/
private transient String completionEventHint;
/**
* Completion event generator method reference.
*/
private transient WorkExecutor completionEventGenerator;
/**
* Completion event timeout milliseconds.
*/
private transient long completionEventTimeoutMs;
/**
* Service reference for workflow service.
*/
private transient WorkflowExecutionService workflowExecutionService;
/**
* Service reference for workflow store.
*/
private transient WorkflowStore workflowStore;
/**
* Service reference for workplace store.
*/
private transient WorkplaceStore workplaceStore;
/**
* Constructor of DefaultWorkflowContext.
* @param workflowId ID of workflow
* @param workplaceName name of workplace
* @param data data model tree
*/
public DefaultWorkflowContext(URI workflowId, String workplaceName, DataModelTree data) {
super(data);
this.workflowId = workflowId;
this.workplaceName = workplaceName;
this.state = WorkflowState.IDLE;
this.current = Worklet.Common.INIT.name();
}
/**
* DefaultWorkflowContext name builder.
* @param workflowid workflow id
* @param workplacename workplace name
* @return DefaultWorkflowContext name
*/
public static String nameBuilder(URI workflowid, String workplacename) {
return workflowid.toString() + "@" + workplacename;
}
@Override
public String name() {
return nameBuilder(workflowId, workplaceName);
}
@Override
public String distributor() {
return workplaceName();
}
@Override
public URI workflowId() {
return this.workflowId;
}
@Override
public String workplaceName() {
return workplaceName;
}
@Override
public WorkflowState state() {
return state;
}
@Override
public void setState(WorkflowState state) {
this.state = state;
}
@Override
public String current() {
return this.current;
}
@Override
public void setCurrent(Worklet worklet) {
this.current = worklet.tag();
}
@Override
public String cause() {
return this.cause;
}
@Override
public void setCause(String cause) {
this.cause = cause;
}
@Override
public void completed() {
setTriggerNext(true);
}
@Override
public void waitCompletion(Class<? extends Event> eventType, String eventHint,
WorkExecutor eventGenerator, long timeoutMs) {
this.completionEventType = eventType;
this.completionEventHint = eventHint;
this.completionEventGenerator = eventGenerator;
this.completionEventTimeoutMs = timeoutMs;
}
@Override
public void waitFor(long timeoutMs) {
this.completionEventTimeoutMs = timeoutMs;
}
@Override
public Class<? extends Event> completionEventType() {
return completionEventType;
}
@Override
public String completionEventHint() {
return completionEventHint;
}
@Override
public WorkExecutor completionEventGenerator() {
return completionEventGenerator;
}
@Override
public long completionEventTimeout() {
return completionEventTimeoutMs;
}
@Override
public void setWorkflowExecutionService(WorkflowExecutionService workflowExecutionService) {
this.workflowExecutionService = workflowExecutionService;
}
@Override
public WorkflowExecutionService workflowService() {
return workflowExecutionService;
}
@Override
public void setWorkflowStore(WorkflowStore workflowStore) {
this.workflowStore = workflowStore;
}
@Override
public WorkflowStore workflowStore() {
return workflowStore;
}
@Override
public void setWorkplaceStore(WorkplaceStore workplaceStore) {
this.workplaceStore = workplaceStore;
}
@Override
public WorkplaceStore workplaceStore() {
return workplaceStore;
}
@Override
public String toString() {
return MoreObjects.toStringHelper(getClass())
.add("name", name())
.add("triggernext", triggerNext())
.add("data", data())
.add("current", current)
.add("state", state())
.add("cause", cause())
.toString();
}
}

Some files were not shown because too many files have changed in this diff Show More