diff --git a/.circleci/main/commands/executions/run-smoke-tests.yml b/.circleci/main/commands/executions/run-smoke-tests.yml index 025ddebfbf6f..5b41b9af0f82 100644 --- a/.circleci/main/commands/executions/run-smoke-tests.yml +++ b/.circleci/main/commands/executions/run-smoke-tests.yml @@ -52,7 +52,7 @@ commands: find . -type f -regex ".*/target/surefire-reports/.*xml" -exec cp {} ~/test-results/junit/ \; find . -type f -regex ".*/target/failsafe-reports/.*xml" -exec cp {} ~/test-results/junit/ \; mkdir -p ~/test-artifacts/recordings - cp -R ~/project/smoke-test/target/*.flv ~/test-artifacts/recordings || true + cp -R ~/project/smoke-test/target/*.{flv,mp4} ~/test-artifacts/recordings || true cp -R ~/project/smoke-test/target/screenshots ~/test-artifacts/ || true cp -R ~/project/smoke-test/target/logs ~/test-artifacts/ || true - store_test_results: diff --git a/core/web-assets/ci.js b/core/web-assets/ci.js index 0a0d315c34c5..b2e10bbe2832 100755 --- a/core/web-assets/ci.js +++ b/core/web-assets/ci.js @@ -32,10 +32,6 @@ if (!fs.existsSync(touchfile) if (doUpdate) { console.info('node_modules is potentially out of date compared to package-lock.json'); - const cache_cleanup_child = child_process.execFile(path.join(__dirname, 'target', 'node', 'npm'), ['cache', 'clean', '--force']); - cache_cleanup_child.stdout.pipe(process.stdout); - cache_cleanup_child.stderr.pipe(process.stderr); - const child = child_process.execFile(path.join(__dirname, 'target', 'node', 'npm'), [ '--prefer-offline', '--no-progress', 'ci' ]); child.stdout.pipe(process.stdout); child.stderr.pipe(process.stderr); diff --git a/core/web-assets/package-lock.json b/core/web-assets/package-lock.json index dc2cab9b3c30..99bcf0dfaa42 100644 --- a/core/web-assets/package-lock.json +++ b/core/web-assets/package-lock.json @@ -18051,6 +18051,8 @@ }, "@babel/plugin-transform-destructuring": { "version": "7.20.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.20.0.tgz", + "integrity": "sha512-1dIhvZfkDVx/zn2S1aFwlruspTt4189j7fEkH0Y0VyuDM6bQt7bD6kLcz3l4IlLG+e5OReaBz9ROAbttRtUHqA==", "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.19.0" @@ -18391,7 +18393,9 @@ } }, "@babel/runtime-corejs3": { - "version": "7.20.0", + "version": "7.20.1", + "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.20.1.tgz", + "integrity": "sha512-CGulbEDcg/ND1Im7fUNRZdGXmX2MTWVVZacQi/6DiKE5HNwZ3aVTm5PV4lO8HHz0B2h8WQyvKKjbX5XgTtydsg==", "requires": { "core-js-pure": "^3.25.1", "regenerator-runtime": "^0.13.10" @@ -18897,10 +18901,6 @@ } } }, - "@polka/url": { - "version": "1.0.0-next.21", - "dev": true - }, "@rangerrick/moment-javaformat": { "version": "2.0.2", "dev": true, @@ -20837,7 +20837,9 @@ } }, "core-js": { - "version": "3.26.0" + "version": "3.26.0", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.26.0.tgz", + "integrity": "sha512-+DkDrhoR4Y0PxDz6rurahuB+I45OsEUv8E1maPTB6OuHRohMMcznBq9TMpdpDMm/hUPob/mJJS3PqgbHpMTQgw==" }, "core-js-compat": { "version": "3.26.0", @@ -20847,7 +20849,9 @@ } }, "core-js-pure": { - "version": "3.26.0" + "version": "3.26.0", + "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.26.0.tgz", + "integrity": "sha512-LiN6fylpVBVwT8twhhluD9TzXmZQQsr2I2eIKtWNbZI1XMfBT7CV18itaN6RA7EtQd/SDdRx/wzvAShX2HvhQA==" }, "core-util-is": { "version": "1.0.3", @@ -21332,10 +21336,6 @@ "is-obj": "^2.0.0" } }, - "duplexer": { - "version": "0.1.2", - "dev": true - }, "duplexify": { "version": "3.7.1", "dev": true, @@ -25110,10 +25110,6 @@ } } }, - "mrmime": { - "version": "1.0.1", - "dev": true - }, "ms": { "version": "2.1.2", "dev": true @@ -25490,10 +25486,6 @@ "mimic-fn": "^2.1.0" } }, - "opener": { - "version": "1.5.2", - "dev": true - }, "optionator": { "version": "0.8.3", "dev": true, @@ -27190,15 +27182,6 @@ } } }, - "sirv": { - "version": "1.0.19", - "dev": true, - "requires": { - "@polka/url": "^1.0.0-next.20", - "mrmime": "^1.0.0", - "totalist": "^1.0.0" - } - }, "sisteransi": { "version": "1.0.5", "dev": true @@ -28125,10 +28108,6 @@ "through2": "^2.0.3" } }, - "totalist": { - "version": "1.1.0", - "dev": true - }, "touch": { "version": "3.1.0", "dev": true, @@ -29009,77 +28988,6 @@ } } }, - "webpack-bundle-analyzer": { - "version": "4.7.0", - "dev": true, - "requires": { - "acorn": "^8.0.4", - "acorn-walk": "^8.0.0", - "chalk": "^4.1.0", - "commander": "^7.2.0", - "gzip-size": "^6.0.0", - "lodash": "^4.17.20", - "opener": "^1.5.2", - "sirv": "^1.0.7", - "ws": "^7.3.1" - }, - "dependencies": { - "acorn": { - "version": "8.8.1", - "dev": true - }, - "acorn-walk": { - "version": "8.2.0", - "dev": true - }, - "ansi-styles": { - "version": "4.3.0", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "dev": true - }, - "commander": { - "version": "7.2.0", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - }, - "ws": { - "version": "7.5.9", - "dev": true, - "requires": {} - } - } - }, "webpack-cli": { "version": "3.3.12", "dev": true, diff --git a/core/web-assets/package.json b/core/web-assets/package.json index 3caf673034d8..2af9587be87a 100644 --- a/core/web-assets/package.json +++ b/core/web-assets/package.json @@ -69,7 +69,6 @@ "ts-jest": "^24.3.0", "typescript": "^4.5.5", "webpack": "^4.46.0", - "webpack-bundle-analyzer": "^4.7.0", "webpack-cli": "^3.3.12", "yargs": "^16.2.0" }, diff --git a/core/web-assets/webpack.config.js b/core/web-assets/webpack.config.js index bad864e04eef..5700c244b3ed 100644 --- a/core/web-assets/webpack.config.js +++ b/core/web-assets/webpack.config.js @@ -9,7 +9,6 @@ var fs = require('fs'); const SpeedMeasurePlugin = require('speed-measure-webpack-plugin'); var AssetsPlugin = require('assets-webpack-plugin'); -var BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin; var CopyWebpackPlugin = require('copy-webpack-plugin'); var ExtractTextPlugin = require('extract-text-webpack-plugin'); var StringReplacePlugin = require('string-replace-webpack-plugin'); @@ -229,7 +228,8 @@ var config = { entry: allEntries, output: { path: distdir, - libraryTarget: 'umd' + libraryTarget: 'umd', + hashFunction: 'sha256', }, target: 'web', module: { @@ -547,8 +547,6 @@ function createConfig(options) { compress: true } })); - } else { - //myconf.plugins.push(new BundleAnalyzerPlugin()); } myconf.plugins.push(new AssetsPlugin({ diff --git a/debian/control b/debian/control index b3733a3e24fd..56bb3f7ccc0f 100644 --- a/debian/control +++ b/debian/control @@ -7,7 +7,7 @@ Standards-Version: 3.7.3 Package: opennms Architecture: all -Depends: opennms-common (=${binary:Version}), opennms-db (=${binary:Version}), opennms-server (=${binary:Version}), opennms-webapp-jetty (=${binary:Version}) +Depends: opennms-common (=${binary:Version}), opennms-db (=${binary:Version}), opennms-server (=${binary:Version}), opennms-webapp-jetty (=${binary:Version}), opennms-plugin-cloud (>= 1.0.0) Recommends: opennms-source (=${binary:Version}), openjdk-11-jdk-headless | openjdk-11-jdk | adoptopenjdk-11-openj9xl | adoptopenjdk-11-openj9 | adoptopenjdk-11-hotspot | java11-jdk | temurin-11-jdk Suggests: opennms-doc Description: Enterprise-grade Open-source Network Management Platform (Full Install) @@ -123,6 +123,7 @@ Depends: ${perl:Depends}, libdbi-perl, libdbd-pg-perl Recommends: libnet-snmp-perl, libxml2-utils, libwww-perl, libxml-twig-perl Conflicts: opennms-plugin-protocol-dhcp (<<${binary:Version}), opennms-plugin-protocol-xml (<<${binary:Version}) Replaces: opennms-plugin-protocol-dhcp (<<${binary:Version}), opennms-plugin-protocol-xml (<<${binary:Version}) +Provides: opennms-plugin-api (= OPA_VERSION) Description: Enterprise-grade Open-source Network Management Platform (Common Files) OpenNMS is an enterprise-grade network management system written in Java. . @@ -443,4 +444,3 @@ Description: Enterprise-grade Open-source Network Management Platform (RT Ticket events (such as SNMP traps), and generates notices via email, pager, SMS, etc. . This package provides support for opening RT tickets from OpenNMS alarms. - diff --git a/docs/modules/deployment/nav.adoc b/docs/modules/deployment/nav.adoc index a61c75750031..5c86d5c8e7b3 100644 --- a/docs/modules/deployment/nav.adoc +++ b/docs/modules/deployment/nav.adoc @@ -27,6 +27,7 @@ ** xref:time-series-storage/timeseries/ts-integration-layer.adoc[] *** xref:time-series-storage/timeseries/configuration.adoc[] ** xref:time-series-storage/timeseries/cortex.adoc[] +** xref:time-series-storage/timeseries/hosted-tss.adoc[] * xref:opentracing/jaeger-tracing.adoc[] diff --git a/docs/modules/deployment/pages/core/getting-started.adoc b/docs/modules/deployment/pages/core/getting-started.adoc index 60b6eac20539..b416cd684a2a 100644 --- a/docs/modules/deployment/pages/core/getting-started.adoc +++ b/docs/modules/deployment/pages/core/getting-started.adoc @@ -13,10 +13,10 @@ This section explains how to install and configure a new {page-component-title} NOTE: The default configuration is not optimized to run in production or monitor large networks. ifeval::["{page-component-title}" == "Horizon"] -** By default, your time series storage is JRobin, which persists RRD files on the local file system. +** By default, your time series database is JRobin, which persists RRD files on the local file system. endif::[] ifeval::["{page-component-title}" == "Meridian"] -** By default, your time series storage is RRDtool, which persists RRD files on the local file system. +** By default, your time series database is RRDtool, which persists RRD files on the local file system. endif::[] * Log in to the web UI and change the default admin password. diff --git a/docs/modules/deployment/pages/time-series-storage/newts/introduction.adoc b/docs/modules/deployment/pages/time-series-storage/newts/introduction.adoc index 1615f55d01c7..248f81456253 100644 --- a/docs/modules/deployment/pages/time-series-storage/newts/introduction.adoc +++ b/docs/modules/deployment/pages/time-series-storage/newts/introduction.adoc @@ -3,7 +3,7 @@ = Newts link:http://newts.io/[Newts] is a time series data schema for link:http://cassandra.apache.org/[Apache Cassandra]. -It enables link:https://en.wikipedia.org/wiki/Scalability#Horizontal[horizontal scaling] for your time series storage and is an alternative to link:https://github.com/OpenNMS/jrobin[JRobin] and link:http://oss.oetiker.ch/rrdtool/[RRDtool]. +It enables link:https://en.wikipedia.org/wiki/Scalability#Horizontal[horizontal scaling] for your time series database and is an alternative to link:https://github.com/OpenNMS/jrobin[JRobin] and link:http://oss.oetiker.ch/rrdtool/[RRDtool]. This section describes how to xref:time-series-storage/newts/newts.adoc#newts-configure[configure] {page-component-title} to use an existing Cassandra cluster, including how to initialize the Newts keyspace using `newts init` with STCS without production-ready tuning, and verify that time series data is stored and can be accessed diff --git a/docs/modules/deployment/pages/time-series-storage/newts/newts.adoc b/docs/modules/deployment/pages/time-series-storage/newts/newts.adoc index b89995b8da3e..b14214d79255 100644 --- a/docs/modules/deployment/pages/time-series-storage/newts/newts.adoc +++ b/docs/modules/deployment/pages/time-series-storage/newts/newts.adoc @@ -2,7 +2,7 @@ [[newts-configure]] = Configure {page-component-title} Core to Use Newts -.Create a configuration file with the time series storage settings +.Create a configuration file with the time series database settings [source, console] ---- sudo vi etc/opennms.properties.d/timeseries.properties diff --git a/docs/modules/deployment/pages/time-series-storage/timeseries/cortex.adoc b/docs/modules/deployment/pages/time-series-storage/timeseries/cortex.adoc index ebf12c54fe5c..eb9221f6e6ee 100644 --- a/docs/modules/deployment/pages/time-series-storage/timeseries/cortex.adoc +++ b/docs/modules/deployment/pages/time-series-storage/timeseries/cortex.adoc @@ -28,7 +28,7 @@ If your aim is to get up to speed quickly in a test environment, you can use Doc . Download the plugin's `.kar` file from GitHub (https://github.com/OpenNMS/opennms-cortex-tss-plugin/releases). . Copy the file into `$\{OPENNMS_HOME}/deploy`. -. Enable and configure time series storage plugins in {page-component-title}. +. Enable and configure time series database plugins in {page-component-title}. + .Create or modify the `$\{OPENNMS_HOME}/etc/opennms.properties.d/cortex.properties` file [source, properties] diff --git a/docs/modules/deployment/pages/time-series-storage/timeseries/hosted-tss.adoc b/docs/modules/deployment/pages/time-series-storage/timeseries/hosted-tss.adoc new file mode 100644 index 000000000000..78078414cfdd --- /dev/null +++ b/docs/modules/deployment/pages/time-series-storage/timeseries/hosted-tss.adoc @@ -0,0 +1,104 @@ + += OpenNMS Time Series DB + +The OpenNMS Time Series DB provides a scalable cloud-hosted time series database to store and persist performance metrics that {page-component-title} collects from the devices it monitors. +In addition, users can view time series summary data including health status, capacity, and metrics per second in the OpenNMS Portal. + +== Requirements + +You must have the following to use the Time Series DB: + +* Horizon 31+ (Meridian availability with the 2023 release). +* Time Series DB subscription that provides access to the https://portal.opennms.com[OpenNMS Portal] to retrieve your activation key and view Time Series DB metrics. + +Contact sales@opennms.com to sign up for the Time Series DB. + +== Deployment + +There are three phases of deployment to set up the Time Series DB for your system: + +* Update OpenNMS Cloud Services Connector files. +* Obtain an activation key for the Cloud Services Connector. +* Enable cloud services connectivity. + +=== Update OpenNMS Cloud Services Connector files + +The OpenNMS Cloud Services Connector is an extension that lets a {page-component-title} instance send time series data to an OpenNMS-hosted cloud service. + +This extension is installed by default in Horizon 31. +However, you need to move the configuration files into the correct location and restart {page-component-title} as part of the Time Series DB setup process: + +[source, console] +---- +cp /opt/opennms/etc/examples/opennms.properties.d/timeseries.properties /opt/opennms/etc/opennms.properties.d/timeseries.properties + +systemctl restart openmns +---- + +=== Obtain an activation key + +The activation key lets {page-component-title} connect with OpenNMS cloud services (currently the Time Series DB). + +You must log in to the OpenNMS Portal to get this activation key, copy it, and then paste it into the appropriate screen in {page-component-title}. + +When you signed up for the Time Series DB, you received an activation email from OpenNMS with a login for the OpenNMS Portal. + +To obtain the activation key, follow these steps: + +. If you have not already done so, activate your OpenNMS Portal account by clicking the *Account Activation* button in your welcome email. +. On the Create Your OpenNMS Account screen, type a password in the *Password* box and click *Create My Account*. +. Sign in to the OpenNMS Portal with your username and password. +. Choose *OpenNMS Core Instances* in the left menu and click *Add New*. +. Type in the name of your instance and click *Generate Activation Key*. +. Copy the activation key and click *Save & Close*. + ++ +NOTE: The key is valid for one hour. +If you do not use it within that time, you must log back into the OpenNMS Portal, delete the old instance, create a new one, and generate a new key. + +. Log in to the OpenNMS {page-component-title} web UI for your instance. +. Click menu:Plugins[Cloud Services] in the top menu bar. +. Turn the *Cloud Services Deactivate* toggle to on. +. Paste the key in the *Enter Activation Key* box and click *Activate*. + +By default, collectd collects data every five minutes. +You should see time series summary information in the OpenNMS Portal UI within 15 minutes. +For more information on how to view this information, see <>. +To change the data collection frequency, see xref:operation:deep-dive/performance-data-collection/collectd/collection-packages.adoc#ga-collectd-packages-services[Service configuration attributes]. + +== Troubleshooting + +To verify that your Time Series DB is set up correctly, run the `health-check` command in Karaf. +You should see something like the following: + +[source,karaf] +---- +admin@opennms()> health-check +Verifying the health of the container + +Verifying installed bundles [ Success ] +Cloud status [ Success ] => Cloud status=SERVING +Connecting to ElasticSearch ReST API (Flows) [ Success ] => Not configured + +=> Everything is awesome +---- + +Check Karaf logs if the registration fails or the health check does not say `SERVING`. +If this is the case, you will need to open a support ticket. + +You can also check that `OPENNMS_HOME/share/rrd` files are still being updated. +If they are not, this indicates that the Time Series DB is not working. + +[[view-time-series-data]] +== View time series summary data + +You can view time series summary data including health status, capacity, and metrics per second in the OpenNMS Portal. +By default, the screen displays data for all OpenNMS instances you have configured to use the Time Series DB. +You can filter to see data for a specific instance. + +. Sign in to the https://portal.opennms.com[OpenNMS Portal] with your username and password. +. In the left menu, click *Time Series*. +. View the information. +. To see summary data for a specific instance, type an instance name in the *Search Instances* field. ++ +The screen updates to display only that instance and its associated summary data. \ No newline at end of file diff --git a/docs/modules/deployment/pages/time-series-storage/timeseries/rrdtool.adoc b/docs/modules/deployment/pages/time-series-storage/timeseries/rrdtool.adoc index a77fcc3f016b..5705fc15fdc4 100644 --- a/docs/modules/deployment/pages/time-series-storage/timeseries/rrdtool.adoc +++ b/docs/modules/deployment/pages/time-series-storage/timeseries/rrdtool.adoc @@ -20,7 +20,7 @@ sudo dnf config-manager --enable opennms-repo-stable-* sudo dnf -y install rrdtool jrrd2 ---- -.Create a configuration file with the time series storage settings +.Create a configuration file with the time series database settings [source, console] ---- sudo vi etc/opennms.properties.d/timeseries.properties @@ -76,7 +76,7 @@ sudo yum-config-manager --enable opennms-repo-stable-* sudo yum -y install rrdtool jrrd2 ---- -.Create a configuration file with the time series storage settings +.Create a configuration file with the time series database settings [source, console] ---- sudo vi etc/opennms.properties.d/timeseries.properties @@ -131,7 +131,7 @@ Debian/Ubuntu:: apt -y install rrdtool jrrd2 ---- -.Create a configuration file with the time series storage settings +.Create a configuration file with the time series database settings [source, console] ---- sudo vi etc/opennms.properties.d/timeseries.properties diff --git a/docs/modules/deployment/pages/time-series-storage/timeseries/time-series-storage.adoc b/docs/modules/deployment/pages/time-series-storage/timeseries/time-series-storage.adoc index f897305f9729..f78eb4f0c867 100644 --- a/docs/modules/deployment/pages/time-series-storage/timeseries/time-series-storage.adoc +++ b/docs/modules/deployment/pages/time-series-storage/timeseries/time-series-storage.adoc @@ -1,16 +1,16 @@ [[time-series-storage]] -= Time Series Storage += Time Series Database -{page-component-title} stores performance data in a time series storage (JRobin by default). -For different scenarios you may want to use a different time series storage. +{page-component-title} stores performance data in a time series database (JRobin by default). +For different scenarios you may want to use a different time series database. The following implementations are installed by default: .Supported time series databases [width="100%", cols="1,4"] |=== | JRobin -| JRobin is a clone of RRDTool written in Java and is the default time series storage when you install {page-component-title}. +| JRobin is a clone of RRDTool written in Java and is the default time series database when you install {page-component-title}. It does not fully cover the latest feature set of RRDTool. Data is stored on the local file system of the {page-component-title} node. Depending on I/O capabilities, it works well for small- to medium-size installations. @@ -21,14 +21,21 @@ Data is stored on the local file system of the {page-component-title} node. Depending on I/O capabilities it works well for small- to medium-size installations. | Newts -| link:http://opennms.github.io/newts/[Newts] is a database schema for link:http://cassandra.apache.org[Cassandra]. +| link:http://opennms.github.io/newts/[Newts] is an OpenNMS database schema for link:http://cassandra.apache.org[Cassandra]. The time series is stored on a dedicated Cassandra cluster, which gives growth flexibility and lets time series data persist in a large scale. + +| OpenNMS Time Series DB +| The OpenNMS Group now provides a limited availability cloud-hosted time series database to store performance metrics that {page-component-title} collects from the devices it monitors. +The Time Series DB lets you move the most storage-intensive component of your network management system off-premises, for a smaller {page-component-title} footprint and improved redundancy. +For additional information, see xref:time-series-storage/timeseries/hosted-tss.adoc[OpenNMS Time Series DB]. |=== -You can use time series storage integrations with plugins based on our OpenNMS Plugin API (formerly OIA). -Several plugins are available as a replacement for the ones shipped with OpenNMS {page-component-title} (see xref:time-series-storage/timeseries/ts-integration-layer.adoc#ts-integration-layer[time series integration layer]). +This chapter describes how to configure {page-component-title} to use RRDTool, Newts, and the OpenNMS Time Series DB. -This section describes how to configure {page-component-title} to use RRDTool and Newts. +== Time series database plugins + +You can use time series database integrations with plugins based on our OpenNMS Plugin API (formerly OIA). +Several plugins are available as a replacement for the ones shipped with OpenNMS {page-component-title} (see xref:time-series-storage/timeseries/ts-integration-layer.adoc#ts-integration-layer[time series integration layer]). CAUTION: The way data is stored in different time series databases makes it extremely hard to migrate from one technology to another. You cannot prevent data loss when you switch from one to another. @@ -37,7 +44,7 @@ You cannot prevent data loss when you switch from one to another. The default time series strategy (RRD, Newts, integration) is used for both read/write by default. You can add another time series write in parallel to the default strategy. -This additional time series storage may be used for validation purposes or as a backup time series before moving from one time series to another. +This additional time series database may be used for validation purposes or as a backup time series before moving from one time series to another. == Write to Newts in parallel diff --git a/docs/modules/operation/images/logging/log-file-viewer.png b/docs/modules/operation/images/logging/log-file-viewer.png index 71e0a4af8655..66d71044fb27 100644 Binary files a/docs/modules/operation/images/logging/log-file-viewer.png and b/docs/modules/operation/images/logging/log-file-viewer.png differ diff --git a/docs/modules/operation/pages/deep-dive/admin/introduction.adoc b/docs/modules/operation/pages/deep-dive/admin/introduction.adoc deleted file mode 100644 index dee094f9ea5b..000000000000 --- a/docs/modules/operation/pages/deep-dive/admin/introduction.adoc +++ /dev/null @@ -1,15 +0,0 @@ - -= {page-component-title} Administration - -This section describes additional administrative tasks you may want to perform or configure: - -* xref:deep-dive/admin/webui/introduction.adoc[admin UI customization] -* xref:deep-dive/admin/system-properties/introduction.adoc[system properties] -* xref:deep-dive/admin/config-tester.adoc[configuration tester] -* xref:deep-dive/admin/rmi.adoc[enable RMI] -* xref:deep-dive/admin/geocoder.adoc[geolocation resolution] -* xref:deep-dive/admin/http-ssl.adoc[HTTPS/SSL configuration] -* xref:deep-dive/admin/request-logging.adoc[request logging] -* xref:deep-dive/admin/restart.adoc[shutdown and restart] -* xref:deep-dive/admin/logging/log-file-viewer.adoc[view log files] -* xref:deep-dive/admin/housekeeping/introduction.adoc[housekeeping tasks] diff --git a/docs/modules/operation/pages/deep-dive/admin/logging/log-file-viewer.adoc b/docs/modules/operation/pages/deep-dive/admin/logging/log-file-viewer.adoc index 760f72e62e2c..e691fa11e84b 100644 --- a/docs/modules/operation/pages/deep-dive/admin/logging/log-file-viewer.adoc +++ b/docs/modules/operation/pages/deep-dive/admin/logging/log-file-viewer.adoc @@ -2,20 +2,19 @@ [[ga-operation-log-file-viewer]] = View Log Files -You can view log files using the {page-component-title} UI. +You can view log files in the {page-component-title} UI. +To do so, click menu:Info[Logs] in the top menu bar. -Navigate to the new UI (click *UI Preview*), and select "Logs" from the navigation pane. -The Log File Viewer is displayed. +.Log File Viewer page +image::logging/log-file-viewer.png[Log file viewer, 700] -.Log File Viewer -image::logging/log-file-viewer.png[] +On the log file viewer page, the left pane displays a list of log files. +Select a file name to display its contents in the right pane. +You can scroll through its contents both vertically and horizontally. -The left pane displays the log files. -Click on a file name to display the contents in the right pane. -You can scroll through contents vertically and horizontally. +Type in the *Search Logs* box to search for any part of a log file's name -You can search for any part of a log file name in the "Search logs" box. +Click the *arrow* symbol at the top-right of the content pane to order the data from oldest to newest, or newest to oldest. -Click the arrow in the upper right corner of the right pane to order the log data by newest or oldest entries first. - -The log file data does not automatically refresh; click on the log file name in the left pane to refresh that log's data. +NOTE: The log file's data is not automatically refreshed. +Click a file name in the left pane to refresh that log's data. diff --git a/docs/modules/operation/pages/deep-dive/admin/scv-configuration.adoc b/docs/modules/operation/pages/deep-dive/admin/scv-configuration.adoc index 84f37e00e638..e84ce8640191 100644 --- a/docs/modules/operation/pages/deep-dive/admin/scv-configuration.adoc +++ b/docs/modules/operation/pages/deep-dive/admin/scv-configuration.adoc @@ -1,52 +1,67 @@ + [[scv-overview]] = Secure Credentials Vault -Secure Credentials Vault (SCV) lets you encrypt credentials the {page-component-title} system uses. -SCV uses a Java KeyStore (JKS) to store credentials in JKS format. -SCV is enabled by default on {page-component-title}. +The secure credentials vault (SCV) lets you encrypt credentials {page-component-title} uses. +It stores credentials in the Java KeyStore (JKS) format. + +The SCV is enabled by default. + +== Configure SCV password -== Configuring SCV password +A custom SCV password is defined by the `org.opennms.features.scv.jceks.key` property in `$\{OPENNMS_HOME}/etc/opennms.properties.d/svc.properties`. +The SCV keystore file is located at `$\{OPENNMS_HOME}/etc/scv.jce`. -A custom SCV password can be defined with the `org.opennms.features.scv.jceks.key` system property in the `$\{OPENNMS_HOME}/etc/opennms.properties.d/svc.properties` file. -The SCV keystore file is stored at `$\{OPENNMS_HOME}/etc/scv.jce`. +IMPORTANT: You cannot change the SCV password once credentials have been stored. +You must delete `$\{OPENNMS_HOME}/etc/scv.jce` and manually re-add any credentials if you want to change the password after the file has been created. -IMPORTANT: The SCV password cannot be changed once it has credentials stored. -The keystore file must be deleted and credentials re-added if you want to change the password from the default after the file has been created. +== Store credentials -== Storing credentials in SCV +Entries in the SCV are identified by aliases. +For each alias, you can store a username, a password, and a set of key-value pair attributes. -SCV entries are identified by an alias. -For each alias, you can store `username`, `password`, and a set of key-value pair attributes. -To access or add credentials, navigate to menu:UI Preview[Secure Credentials Vault]. +To access or add credentials, click menu:Info[Secure Credentials Vault] in the top menu bar. -== Accessing SCV entries +== Access entries -Access SCV entries from metadata with context `scv`. +=== Metadata expressions -${scv:juniper-vm:username}:: -This metadata expression can access the username from the `juniper-vm` alias. +You can access SCV entries from metadata using the `scv` context. -${scv:juniper-vm:password}:: -This metadata expression can access the password from the `juniper-vm` alias. +.Example metadata expressions for accessing SCV entries +[options="header, autowidth" cols="1,2"] +|=== +| Expression +| Description -${scv:juniper-vm:user1}:: -This metadata expression can access the `user1` property from the `juniper-vm` alias. +| ${scv:juniper-vm:username} +| Accesses the username from the `juniper-vm` alias. -${scv:corp-directory:domain}:: -This metadata expression can access the `domain` property from the `corp-directory` alias. +| ${scv:juniper-vm:password} +| Accesses the password from the `juniper-vm` alias. +| ${scv:juniper-vm:user1} +| Accesses the `user1` property from the `juniper-vm` alias. -== Karaf commands +| ${scv:corp-directory:domain} +| Accesses the `domain` property from the `corp-directory` alias. +|=== -You can update and access SCV entries from the Karaf shell. +=== Karaf commands +You can access and update SCV entries from the Karaf shell. +To do so, follow the instructions below: + +. Open an SSH session: ++ [source, console] ---- ssh -p 8101 admin@localhost ---- -Once in the shell, you can set credentials from the `scv-set` command as follows: - +. Access or update SCV entries using the shell. +** Use the following command to set credentials: ++ [source, karaf] .Set SCV entries ---- @@ -71,8 +86,8 @@ ARGUMENTS (required) ---- -You can also access SCV entries from the `scv-get` command as follows: - +** Use the following command to access SCV entries: ++ [source, karaf] .Get SCV entries ---- diff --git a/docs/modules/operation/pages/deep-dive/events/perf-data.adoc b/docs/modules/operation/pages/deep-dive/events/perf-data.adoc index abb287ea4167..63bd89be19e8 100644 --- a/docs/modules/operation/pages/deep-dive/events/perf-data.adoc +++ b/docs/modules/operation/pages/deep-dive/events/perf-data.adoc @@ -125,7 +125,7 @@ report.TIME.columns=TIME report.TIME.type=nodeSnmp report.TIME.command=--title="TIME" \ --vertical-label="Time in seconds" \ - DEF:octIn={rrd1}:TIME:AVERAGE \ + DEF:octIn=\{rrd1}:TIME:AVERAGE \ AREA:octIn#73d216: \ LINE1:octIn#4e9a06:"In " \ GPRINT:octIn:AVERAGE:"Avg \\: %8.2lf %s" \ @@ -137,7 +137,7 @@ report.STATUS.columns=STATUS report.STATUS.type=nodeSnmp report.STATUS.command=--title="STATUS" \ --vertical-label="Numeric status value" \ - DEF:octIn={rrd1}:STATUS:AVERAGE \ + DEF:octIn=\{rrd1}:STATUS:AVERAGE \ AREA:octIn#73d216: \ LINE1:octIn#4e9a06:"In " \ GPRINT:octIn:AVERAGE:"Avg \\: %8.2lf %s" \ @@ -149,7 +149,7 @@ report.VALUE.columns=VALUE report.VALUE.type=eventType report.VALUE.command=--title="VALUE" \ --vertical-label="Some units" \ - DEF:octOut={rrd1}:VALUE:AVERAGE \ + DEF:octOut=\{rrd1}:VALUE:AVERAGE \ AREA:octOut#73d216: \ LINE1:octOut#4e9a06:"Out " \ GPRINT:octOut:AVERAGE:"Avg \\: %8.2lf %s" @@ -159,13 +159,13 @@ report.uei.opennms.org_traps_test_varbind.columns=uei.opennms.org_traps_test_var report.uei.opennms.org_traps_test_varbind.type=interfaceSnmp report.uei.opennms.org_traps_test_varbind.command=--title="uei.opennms.org_traps_test_varbind" \ --vertical-label="Bytes per second" \ - DEF:octIn={rrd1}:uei.opennms.org_traps_test_varbind:AVERAGE \ + DEF:octIn=\{rrd1}:uei.opennms.org_traps_test_varbind:AVERAGE \ AREA:octIn#73d216: \ LINE1:octIn#4e9a06:"In " \ GPRINT:octIn:AVERAGE:"Avg \\: %8.2lf %s" ---- -Note that the value {NAME} in the attribute `columns` (`report.{REPORT}.columns={NAME}`) should match the collection name from an event configuration (``) and the expression in DEF (`DEF:octIn={rrd1}:{NAME}:AVERAGE`). +Note that the value \{NAME} in the attribute `columns` (`report.\{REPORT}.columns=\{NAME}`) should match the collection name from an event configuration (``) and the expression in DEF (`DEF:octIn=\{rrd1}:\{NAME}:AVERAGE`). You can view the resulting performance graphs in menu:Reports[Resource Graphs]. @@ -179,10 +179,10 @@ There is also `$\{instance}` variable available that you can use to enrich the r [source, xml] ---- - - - - + + + + ---- diff --git a/docs/modules/operation/pages/deep-dive/user-management/security-roles.adoc b/docs/modules/operation/pages/deep-dive/user-management/security-roles.adoc index a94a6bfe416a..d2191bb2b468 100644 --- a/docs/modules/operation/pages/deep-dive/user-management/security-roles.adoc +++ b/docs/modules/operation/pages/deep-dive/user-management/security-roles.adoc @@ -2,46 +2,50 @@ [[ga-role-user-management-roles]] = Assign User Permissions -Create user permission levels by assigning security roles. -These roles regulate access to the web UI and the REST API to exchange monitoring and inventory information. +You can control user permission levels by creating and assigning security roles. +These roles regulate access to the {page-component-title} web UI and the REST API to exchange monitoring and inventory information. ifndef::opennms-prime[] -In a distributed installation, Minion instances require an account with the `ROLE_MINION` permission to interact with {page-component-title}. -This can be one account for all Minions to share, or one account per Minion. +In a distributed installation, Minion instances require an account that is assigned the `ROLE_MINION` security role to interact with {page-component-title}. +You can create one account for all Minions to share, or one account per Minion. endif::opennms-prime[] -.Built-in security roles (those with an asterisk are the most commonly used) -[options="header"] +== Built-in security roles + +The following security roles are built in to {page-component-title} by default. +Roles marked with an asterisk are the most commonly used. + +[options="header", "autowidth"] [cols="1,3"] |=== | Security Role Name | Description -| ROLE_ADMIN* -| Permissions to create, read, update, and delete in the web UI and the REST API (except see ROLE_FILESYSTEM_EDITOR below). +| ROLE_ADMIN ^*^ +| Permissions to create, read, update, and delete in the web UI and the REST API (see `ROLE_FILESYSTEM_EDITOR` for exceptions). | ROLE_ASSET_EDITOR -| Permissions only to update the asset records from nodes. +| Permissions only to update asset records from nodes. | ROLE_FILESYSTEM_EDITOR -| Permissions only to view and update file configuration data via the REST API (and consequently, from the UI Preview). -Note that even ROLE_ADMIN cannot view or edit configurations unless they also have the ROLE_FILESYSTEM_EDITOR role. -Also, for a user with ROLE_FILESYSTEM_EDITOR to use the UI, they will also need the ROLE_USER or similar role. +| Permissions only to view and update file configuration data via the REST API. + +Note that `ROLE_ADMIN` users cannot view or edit configurations unless they also have the `ROLE_FILESYSTEM_EDITOR` role. +Also, for a user with `ROLE_FILESYSTEM_EDITOR` to use the UI, they will also need the `ROLE_USER` or similar role. | ROLE_DASHBOARD -| Allow user access only to the dashboard. +| Permissions only to view the dashboard. | ROLE_DELEGATE -| Allow actions (such as acknowledging an alarm) to be performed on behalf of another user. +| Permissions to perform actions (such as acknowledging an alarm) on behalf of another user. | ROLE_DEVICE_CONFIG_BACKUP -| Allow user to view and trigger device configuration backups. +| Permissions to view and trigger device configuration backups. | ROLE_FLOW_MANAGER -| Allow user to edit flow classifications. +| Permissions to edit flow classifications. | ROLE_JMX -| Allow retrieving JMX metrics but do not allow executing MBeans of the {page-component-title} JVM, even if they just return simple values. +| Permissions to retrieve JMX metrics, but not to execute MBeans of the {page-component-title} JVM, even if they just return simple values. ifndef::opennms-prime[] | ROLE_MINION @@ -49,49 +53,51 @@ ifndef::opennms-prime[] endif::opennms-prime[] | ROLE_MOBILE -| Allow user to use OpenNMS COMPASS mobile application to acknowledge alarms and notifications via the REST API. +| Permissions to use OpenNMS COMPASS mobile application to acknowledge alarms and notifications via the REST API. | ROLE_PROVISION -| Allow user to use the provisioning system and configure SNMP in {page-component-title} to access management information from devices. +| Permissions to use the provisioning system and configure SNMP in {page-component-title} to access management information from devices. -| ROLE_READONLY* -| User limited to reading information in the web UI; unable to change alarm states or notifications. +| ROLE_READONLY ^*^ +| Permissions only to read information in the web UI; user cannot change alarm states or notifications. | ROLE_REPORT_DESIGNER | Permissions to manage reports in the web UI and REST API. | ROLE_REST -| Allow users to interact with the entire {page-component-title} REST API. +| Permissions to interact with the entire {page-component-title} REST API. -| ROLE_RTC* -| Exchange information with the {page-component-title} Real-Time Console for availability calculations. +| ROLE_RTC ^*^ +| Permissions to exchange information with the {page-component-title} Real-Time Console for availability calculations. -| ROLE_USER* +| ROLE_USER ^*^ | Default permissions for a new user to interact with the web UI: can escalate and acknowledge alarms and notifications. |=== -== Assign roles to users +== Assign security roles + +Follow the steps below to assign security roles to user accounts: -. Log in as a user with admin permissions. -. Click the gear icon at the top-right of the screen. -. Choose menu:Configure OpenNMS[Configure Users, Groups and On-Call Roles > Configure Users]. -. Click the modify icon next to the user you want to update. -. Select the role from menu:Security Roles[Available Roles]. -. Click *Add* to assign the security role to the user. +. Click the *gear* symbol at the top-right of the screen. +. Under Configure OpenNMS, click menu:Configure Users, Groups and On-Call Roles[Configure Users]. +. Find the user to whom you want to assign a security role and click *Modify* beside their name. +. Select the role to add to the user account from the *Available Roles* list, and click *Add*. +** Follow this step to add as many roles as necessary to the account. . Click *Finish* to apply the changes. -. Log out and log back in to apply the new security role settings. +. Log out of {page-component-title} and log back in to apply the new security role settings. == Create custom security roles -To create a custom security role you need to define the name and specify the security permissions. - -. Create a file called `$\{OPENNMS_HOME}/etc/security-roles.properties`. -. Add a property called `roles`, and for its value, a comma-separated list of the custom security roles as in the following example: +To create a custom security role, you need to define its name and specify the permissions it will provide. +Follow the steps below to create a custom role: +. Create `$\{OPENNMS_HOME}/etc/security-roles.properties` in your OpenNMS directory. +. Add a `roles` property, and enter a comma-separated list of the custom security roles for its value. ++ +.Example of a `roles` property [source, properties] ---- roles=operator,stage ---- -To define permissions associated with the custom security role, manually update the application context of the Spring security at `$\{OPENNMS_HOME}/jetty-webapps/opennms/WEB-INF/applicationContext-spring-security.xml`. - +To define permissions associated with a custom security role, you must manually update the application context of the Spiring security in `$\{OPENNMS_HOME}/jetty-webapps/opennms/WEB-INF/applicationContext-spring-security.xml`. diff --git a/docs/modules/reference/images/configuration/file-editor-main.png b/docs/modules/reference/images/configuration/file-editor-main.png index d6f4bec95faf..9aff7c60bf7a 100644 Binary files a/docs/modules/reference/images/configuration/file-editor-main.png and b/docs/modules/reference/images/configuration/file-editor-main.png differ diff --git a/docs/modules/reference/pages/configuration/file-editor.adoc b/docs/modules/reference/pages/configuration/file-editor.adoc index fafb099b71f0..870522500d3d 100644 --- a/docs/modules/reference/pages/configuration/file-editor.adoc +++ b/docs/modules/reference/pages/configuration/file-editor.adoc @@ -1,66 +1,70 @@ -[[file-editor]] -= Manage Configuration Files in the UI -The File Editor, available in the new UI, lets users view and edit configuration files (`.xml`, `.cfg`, `.drl`, etc.) with the OpenNMS UI instead of having to edit them directly in the file system (for example in the `/etc` directory). +[[file-editor]] += File Editor -NOTE: Making changes to configuration files will affect how OpenNMS operates and incorrectly editing a file could result in features no longer working or not working as intended. +The File Editor page lets you view and edit configuration files (`.xml`, `.cfg`, `.drl`, and so on) in the {page-component-title} UI instead of having to edit them in your local file system (for example, by locating the file in your `/etc` directory and editing it with a text editor). +To access the file editor, log in to the web UI and click menu:Info[File Editor] in the top menu bar. -To access the File Editor, go to the new UI, for example by clicking on "UI Preview", then navigating to "File Editor". +NOTE: Changing the configuration files will affect the operation of {page-component-title}. +Incorrectly editing a file could result in features no longer working, or not working as intended. -== View Files +== View files -After navigating to the File Editor, the UI displays a tree view of the OpenNMS `/etc` directory. +By default, the File Editor page displays a tree view of your OpenNMS `/etc` directory. +From this page, you can do the following: -From here you can: +* Navigate to and open any file in your OpenNMS directory. +* Expand and collapse subtrees (subdirectories). +* Search for a directory name. +* Search for any part of a file. +* View and edit files. -- navigate to any file -- expand and collapse subtrees -- search for any part of a file or directory name -- view and edit files +.File Editor page with directory tree and an open configuration file +image::configuration/file-editor-main.png[File editor, 700] -.File Editor main screen -image::configuration/file-editor-main.png[] +== Edit files -## Edit Files +To edit a file, open it on the File Editor page and type in the text box. +Click *Save* to save your changes and update the configuration file. -To edit a file, type in the file text display. +CAUTION: Changing configuration files on the File Editor page overwrites the file on your disk. +There is currently no versioning or support for backup files. +While the file editor does not have support for Git, you may want to xref:deployment:upgrade/git-diff.adoc[set up your config folder as a Git repository]. -To save the file, click "Save". +Click *Reset* to reload the file from your local disk into the editor. -CAUTION: This will overwrite the file on disk; there is no versioning or backup file support at this time. +NOTE: Reloading the file will display it as it was last saved to your disk, not necessarily the file from before you edited it. -Click the *Reset* button to reload the file from disk into the editor. -This will be the text as it was last saved to disk, not necessarily the original text before you began editing. +=== Activate configuration changes -## Add Files +Depending on which file you edit, {page-component-title} may detect and apply the configuration changes automatically. +In other cases, you may need to reload a daemon or restart your instance. -To add a new file, navigate to a folder and click the plus `+` sign. +The following documentation provides more information on configuration files: -Enter a file name, add some text, and click *Save*. +* xref:operation:deep-dive/admin/daemon-config-files.adoc[Daemon Config Files] +* xref:reference:daemons/introduction.adoc#ga-daemons[Daemon reference] (overview of all daemons, related configuration files, and which daemons can be reloaded without restarting {page-component-title}) -Note that validation will run when you attempt to save the file and may prevent it from being saved. -A message at the bottom of the text editor provides more information on why validation failed. +== Add and delete files -To delete a file, click on the minus sign `-` to the right of the file name. -A confirmation dialog appears. -Click *Confirm* to delete the file or *Cancel* to cancel the action. +To add a new file to your OpenNMS directory, navigate to a subdirectory and click *Add* (*+*). +Type a name for the file, enter text in the *Edit* box, and click *Save*. -CAUTION: Deleting a file is permanent; the file will be removed from disk and is unrecoverable. +Note that validation will run on the file when you attempt to save it, and may prevent it from being saved. +If an error occurs, a message at the bottom of the text editor provides more information on why validation failed. .Add a new file image::configuration/file-editor-add-new.png[300,300] -## Activate the Changes - -Depending on which file was edited, OpenNMS may detect and apply the configuration changes automatically, or else may require a restart. - -Some additional information can be found in these topics: - -** xref:operation:deep-dive/admin/daemon-config-files.adoc[] -** xref:reference:daemons/introduction#ga-daemons[Daemon Reference] provides an overview of all daemons, their related configuration files, and which ones you can reload without restarting OpenNMS +To delete a file from your directory, navigate to a subdirectory, find the file that you want to delete, and click the *minus* symbol (*-*) beside its name. +Click *Confirm* in the dialog that appears to confirm deletion. -## Console +CAUTION: Deleting a file is permanent. +The file is removed from your local disk and is unrecoverable. -A console window at the bottom of the edit pane appears with some detailed information after you perform actions such as save or delete. -Click *Clear* to clear the console text or *Minimize* to minimize the console window. +== The console +A console box is displayed at the bottom of the *Edit* box. +It displays detailed information after your perform certain actions (for example, saving or deleting a file). +To clear the console of text, click *Clear*. +To hide the console box, click *Minimize*. diff --git a/docs/modules/reference/pages/daemons/daemon-config-files/enlinkd.adoc b/docs/modules/reference/pages/daemons/daemon-config-files/enlinkd.adoc index 9a8827a7ddd9..d0142023673d 100644 --- a/docs/modules/reference/pages/daemons/daemon-config-files/enlinkd.adoc +++ b/docs/modules/reference/pages/daemons/daemon-config-files/enlinkd.adoc @@ -2,7 +2,7 @@ [[ref-daemon-config-files-enlinkd]] = Enlinkd -The enhanced link daemon collects xref:operation:topology/enlinkd/introduction.adoc[topology information] via SNMP to determine neighboring nodes. +The enhanced link daemon collects xref:operation:deep-dive/topology/enlinkd/introduction.adoc[topology information] via SNMP to determine neighboring nodes. The daemon essentially asks each device the following question: "What is the network topology from your point of view?" The enlinkd discovery process attempts to discover bridge domain links with the data coming from all collected bridge forwarding tables. The enlinkd updaters correlate all collected topology data to generate a global topology layout of your network. diff --git a/docs/modules/reference/pages/glossary.adoc b/docs/modules/reference/pages/glossary.adoc index bc7ef337e0cb..2204aa935d18 100644 --- a/docs/modules/reference/pages/glossary.adoc +++ b/docs/modules/reference/pages/glossary.adoc @@ -207,7 +207,7 @@ The xref:operation:deep-dive/telemetryd/introduction.adoc[Telemetry daemon] in { Time series, time-series database (TSDB):: Time series is a sequence of data points that occur in successive order over a period of time. A time series database (TSDB) is designed to store and serve time series data. -(See xref:deployment:time-series-storage/timeseries/time-series-storage.adoc[Time Series Storage] and https://en.wikipedia.org/wiki/Time_series_database[Time Series Database].) +(See xref:deployment:time-series-storage/timeseries/time-series-storage.adoc[Time Series Database] and https://en.wikipedia.org/wiki/Time_series_database[Time Series Database].) xref:operation:deep-dive/topology/introduction.adoc[Topology]:: A description of a network's elements (devices, services) and the connections among them. diff --git a/docs/modules/reference/pages/provisioning/handlers/introduction.adoc b/docs/modules/reference/pages/provisioning/handlers/introduction.adoc index aaa263c7e7db..71eb8ff20590 100644 --- a/docs/modules/reference/pages/provisioning/handlers/introduction.adoc +++ b/docs/modules/reference/pages/provisioning/handlers/introduction.adoc @@ -60,8 +60,10 @@ Larger systems may require larger values. To adjust them, follow these steps: . Log in to {page-component-title} as an admin user. -. Click the gear icon and click *Configure External Requisitions and Thread Pools*. -. In the left menu, click *External Requisitions*. +. Click the gear icon and click *Configure External Requisitions*. ++ +You can also select menu:Info[External Requisitions] to access this screen. + . In the *Thread Pools* area, select a value from the drop-down list for each item you want to update. . Click *Update Thread Pools*. @@ -79,8 +81,7 @@ The following example shows how to create an external requisition to schedule an NOTE: You can also create an external requisition via the xref:development:rest/config_management.adoc#config-rest-api[config management REST API]. . Log in to {page-component-title} as an admin user. -. Click the gear icon and click *Configure External Requisitions and Thread Pools*. -. In the left menu, click *External Requisitions*. +. Click the gear icon and click *Configure External Requisitions*. . Click *Add External Requisition*. . Specify a name for the external requisition. . From the *Type* drop-down, choose *DNS*. diff --git a/features/api-layer/core/pom.xml b/features/api-layer/core/pom.xml index bbbb4d772923..c0e84a387ec0 100644 --- a/features/api-layer/core/pom.xml +++ b/features/api-layer/core/pom.xml @@ -135,5 +135,11 @@ test-jar test + + org.opennms.integration.api + config + ${opennmsApiVersion} + test + diff --git a/features/api-layer/core/src/main/java/org/opennms/features/apilayer/config/PollerConfExtensionManager.java b/features/api-layer/core/src/main/java/org/opennms/features/apilayer/config/PollerConfExtensionManager.java new file mode 100644 index 000000000000..d6fd5e11f32a --- /dev/null +++ b/features/api-layer/core/src/main/java/org/opennms/features/apilayer/config/PollerConfExtensionManager.java @@ -0,0 +1,172 @@ +/******************************************************************************* + * This file is part of OpenNMS(R). + * + * Copyright (C) 2022 The OpenNMS Group, Inc. + * OpenNMS(R) is Copyright (C) 1999-2022 The OpenNMS Group, Inc. + * + * OpenNMS(R) is a registered trademark of The OpenNMS Group, Inc. + * + * OpenNMS(R) is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, + * or (at your option) any later version. + * + * OpenNMS(R) is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with OpenNMS(R). If not, see: + * http://www.gnu.org/licenses/ + * + * For more information contact: + * OpenNMS(R) Licensing + * http://www.opennms.org/ + * http://www.opennms.com/ + *******************************************************************************/ + +package org.opennms.features.apilayer.config; + +import java.time.Duration; +import java.util.ArrayList; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +import org.opennms.integration.api.v1.config.poller.PollerConfigurationExtension; +import org.opennms.netmgt.config.PollerConfig; +import org.opennms.netmgt.config.poller.Downtime; +import org.opennms.netmgt.config.poller.ExcludeRange; +import org.opennms.netmgt.config.poller.IncludeRange; +import org.opennms.netmgt.config.poller.Monitor; +import org.opennms.netmgt.config.poller.Package; +import org.opennms.netmgt.config.poller.Parameter; +import org.opennms.netmgt.config.poller.Rrd; +import org.opennms.netmgt.config.poller.Service; + +public class PollerConfExtensionManager extends ConfigExtensionManager { + + public static class PollerConfigurationPart { + private List packages = new ArrayList<>(); + private List monitors = new ArrayList<>();; + + public List getPackages() { + return packages; + } + + public List getMonitors() { + return monitors; + } + } + + private final PollerConfig pollerConfig; + + public PollerConfExtensionManager(PollerConfig pollerConfig) { + super(PollerConfigurationPart.class, new PollerConfigurationPart()); + this.pollerConfig = pollerConfig; + } + + @Override + protected PollerConfigurationPart getConfigForExtensions(Set extensions) { + PollerConfigurationPart pollerConfigurationPart = new PollerConfigurationPart(); + pollerConfigurationPart.packages = extensions.stream().flatMap(pce -> pce.getPackages().stream()).map(PollerConfExtensionManager::map).collect(Collectors.toUnmodifiableList()); + pollerConfigurationPart.monitors = extensions.stream().flatMap(pce -> pce.getMonitors().stream()).map(PollerConfExtensionManager::map).collect(Collectors.toUnmodifiableList()); + return pollerConfigurationPart; + } + + @Override + protected void triggerReload() { + if (pollerConfig != null) { + final PollerConfigurationPart object = getObject(); + pollerConfig.setExternalData(object.packages, object.monitors); + } + } + + public static Monitor map(org.opennms.integration.api.v1.config.poller.Monitor apiMonitor) { + Monitor result = new Monitor(); + result.setClassName(apiMonitor.getClassName()); + result.setParameters(apiMonitor.getParameters().stream().map(PollerConfExtensionManager::map).collect(Collectors.toList())); + result.setService(apiMonitor.getService()); + return result; + } + + public static Parameter map(org.opennms.integration.api.v1.config.poller.Parameter apiParameter) { + Parameter result = new Parameter(); + result.setKey(apiParameter.getKey()); + result.setValue(apiParameter.getValue()); + return result; + } + + public static Package map(org.opennms.integration.api.v1.config.poller.Package apiPackage) { + Package result = new Package(); + result.setDowntimes(apiPackage.getDowntimes().stream().map(PollerConfExtensionManager::map).collect(Collectors.toList())); + result.setName(apiPackage.getName()); + result.setFilter(apiPackage.getFilter()); + result.setSpecifics(apiPackage.getSpecifics()); + result.setIncludeRanges(apiPackage.getIncludeRanges().stream().map(PollerConfExtensionManager::mapIncludeRanges).collect(Collectors.toList())); + result.setExcludeRanges(apiPackage.getExcludeRanges().stream().map(PollerConfExtensionManager::mapExcludeRanges).collect(Collectors.toList())); + result.setRrd(map(apiPackage.getRrd())); + result.setServices(apiPackage.getServices().stream().map(PollerConfExtensionManager::map).collect(Collectors.toList())); + result.setOutageCalendars(apiPackage.getOutageCalendars()); + return result; + } + + private static Service map(org.opennms.integration.api.v1.config.poller.Service apiService) { + Service result = new Service(); + result.setName(apiService.getName()); + result.setUserDefined(apiService.isUserDefined() ? "true" : "false"); + result.setStatus(apiService.isEnabled() ? "on" : "off"); + result.setPattern(apiService.getPattern().orElse(null)); + result.setParameters(apiService.getParameters().stream().map(PollerConfExtensionManager::map).collect(Collectors.toList())); + result.setInterval(apiService.getInterval()); + return result; + } + + + private static IncludeRange mapIncludeRanges(org.opennms.integration.api.v1.config.poller.AddressRange addressRange) { + final IncludeRange result = new IncludeRange(); + result.setBegin(addressRange.getBegin()); + result.setEnd(addressRange.getEnd()); + return result; + } + + private static ExcludeRange mapExcludeRanges(org.opennms.integration.api.v1.config.poller.AddressRange addressRange) { + final ExcludeRange result = new ExcludeRange(); + result.setBegin(addressRange.getBegin()); + result.setEnd(addressRange.getEnd()); + return result; + } + + public static Downtime map(org.opennms.integration.api.v1.config.poller.Downtime apiDowntime) { + Downtime result = new Downtime(); + result.setBegin(apiDowntime.getBegin().getSeconds()); + result.setInterval(apiDowntime.getInterval().orElse(null)); + result.setEnd(apiDowntime.getEnd().map(Duration::getSeconds).orElse(null)); + if (apiDowntime.getDelete().isPresent()) { + final org.opennms.integration.api.v1.config.poller.Downtime.DeletingMode deletingMode = apiDowntime.getDelete().get(); + switch (deletingMode) { + case ALWAYS: + result.setDelete(Downtime.DELETE_ALWAYS); + break; + case MANAGED: + result.setDelete(Downtime.DELETE_MANAGED); + break; + case NEVER: + result.setDelete(Downtime.DELETE_NEVER); + break; + } + } else { + result.setDelete(null); + } + + return result; + } + + public static Rrd map(org.opennms.integration.api.v1.config.poller.Rrd apiRrd) { + Rrd result = new Rrd(); + result.setStep(apiRrd.getStep()); + result.setRras(apiRrd.getRras()); + return result; + } +} diff --git a/features/api-layer/core/src/main/resources/OSGI-INF/blueprint/blueprint.xml b/features/api-layer/core/src/main/resources/OSGI-INF/blueprint/blueprint.xml index 848c5131ab18..959146e9bdbf 100644 --- a/features/api-layer/core/src/main/resources/OSGI-INF/blueprint/blueprint.xml +++ b/features/api-layer/core/src/main/resources/OSGI-INF/blueprint/blueprint.xml @@ -42,6 +42,7 @@ + @@ -141,6 +142,10 @@ + + + + diff --git a/features/api-layer/core/src/test/java/org/opennms/features/apilayer/config/PollerConfExtensionManagerTest.java b/features/api-layer/core/src/test/java/org/opennms/features/apilayer/config/PollerConfExtensionManagerTest.java new file mode 100644 index 000000000000..9f2d2fafb3bf --- /dev/null +++ b/features/api-layer/core/src/test/java/org/opennms/features/apilayer/config/PollerConfExtensionManagerTest.java @@ -0,0 +1,170 @@ +/******************************************************************************* + * This file is part of OpenNMS(R). + * + * Copyright (C) 2022 The OpenNMS Group, Inc. + * OpenNMS(R) is Copyright (C) 1999-2022 The OpenNMS Group, Inc. + * + * OpenNMS(R) is a registered trademark of The OpenNMS Group, Inc. + * + * OpenNMS(R) is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, + * or (at your option) any later version. + * + * OpenNMS(R) is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with OpenNMS(R). If not, see: + * http://www.gnu.org/licenses/ + * + * For more information contact: + * OpenNMS(R) Licensing + * http://www.opennms.org/ + * http://www.opennms.com/ + *******************************************************************************/ + +package org.opennms.features.apilayer.config; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +import java.time.Duration; + +import org.junit.Before; +import org.junit.Test; +import org.opennms.integration.api.v1.config.poller.AddressRange; +import org.opennms.integration.api.v1.config.poller.Downtime; +import org.opennms.integration.api.v1.config.poller.Monitor; +import org.opennms.integration.api.v1.config.poller.Package; +import org.opennms.integration.api.v1.config.poller.Parameter; +import org.opennms.integration.api.v1.config.poller.PollerConfigurationExtension; +import org.opennms.integration.api.v1.config.poller.Service; +import org.opennms.integration.api.xml.ClasspathPollerConfigurationLoader; +import org.opennms.netmgt.config.PollerConfigManager; +import org.opennms.netmgt.config.poller.ExcludeRange; + +public class PollerConfExtensionManagerTest { + + static PollerConfigurationExtension pollerConfiguration1; + static PollerConfigurationExtension pollerConfiguration2; + + @Before + public void init() { + pollerConfiguration1 = new ClasspathPollerConfigurationLoader(PollerConfExtensionManagerTest.class, "poller", + "poller-configuration1.xml").getPollerConfiguration(); + pollerConfiguration2 = new ClasspathPollerConfigurationLoader(PollerConfExtensionManagerTest.class, "poller", + "poller-configuration2.xml").getPollerConfiguration(); + } + + @Test + public void callbackCalled() { + PollerConfigManager objectToNotify = mock(PollerConfigManager.class); + PollerConfExtensionManager instance = new PollerConfExtensionManager(objectToNotify); + instance.onBind(pollerConfiguration1, null); + verify(objectToNotify, times(1)).setExternalData(any(), any()); + instance.onBind(pollerConfiguration2, null); + verify(objectToNotify, times(2)).setExternalData(any(), any()); + } + + @Test + public void dataMatches() { + PollerConfigManager objectToNotify = mock(PollerConfigManager.class); + PollerConfExtensionManager instance = new PollerConfExtensionManager(objectToNotify); + instance.onBind(pollerConfiguration1, null); + instance.onBind(pollerConfiguration2, null); + + final PollerConfExtensionManager.PollerConfigurationPart dataToTest = instance.getObject(); + + assertEquals(pollerConfiguration1.getPackages().size() + pollerConfiguration2.getPackages().size(), + dataToTest.getPackages().size()); + assertEquals(pollerConfiguration1.getMonitors().size() + pollerConfiguration2.getMonitors().size(), + dataToTest.getMonitors().size()); + + //first Package of pollerConfiguration1 matches to the first of dataToTest + assertMatches( + pollerConfiguration1.getPackages().get(0), + dataToTest.getPackages().get(0) + ); + + //lasts Package of pollerConfiguration2 matches to the last of dataToTest + assertMatches( + pollerConfiguration2.getPackages().get(pollerConfiguration2.getPackages().size() - 1), + dataToTest.getPackages().get(dataToTest.getPackages().size() - 1) + ); + + //first Monitor of pollerConfiguration1 matches to the first of dataToTest + assertMatches( + pollerConfiguration1.getMonitors().get(0), + dataToTest.getMonitors().get(0) + ); + + //lasts Monitor of pollerConfiguration2 matches to the last of dataToTest + assertMatches( + pollerConfiguration2.getMonitors().get(pollerConfiguration2.getMonitors().size() - 1), + dataToTest.getMonitors().get(dataToTest.getMonitors().size() - 1) + ); + } + + private static void assertMatches(Monitor source, org.opennms.netmgt.config.poller.Monitor result) { + assertEquals(source.getClassName(), result.getClassName()); + assertEquals(source.getService(), result.getService()); + assertEquals(source.getParameters().size(), result.getParameters().size()); + + for (int i = 0; i < source.getParameters().size(); i++) { + assertEquals(source.getParameters().get(i).getValue(), result.getParameters().get(i).getValue()); + assertEquals(source.getParameters().get(i).getKey(), result.getParameters().get(i).getKey()); + assertNull(result.getParameters().get(i).getAnyObject()); + } + } + + private static void assertMatches(Package source, org.opennms.netmgt.config.poller.Package result) { + assertEquals(source.getName(), result.getName()); + assertEquals(source.getFilter(), result.getFilter().getContent()); + assertEquals(source.getDowntimes().size(), result.getDowntimes().size()); + assertEquals(source.getOutageCalendars().size(), result.getOutageCalendars().size()); + assertEquals(source.getRrd().getStep(), result.getRrd().getStep().intValue()); + assertEquals(source.getRrd().getRras(), result.getRrd().getRras()); + assertEquals(source.getServices().size(), result.getServices().size()); + assertEquals(source.getExcludeRanges().size(), result.getExcludeRanges().size()); + assertEquals(source.getIncludeRanges().size(), result.getIncludeRanges().size()); + assertEquals(source.getSpecifics(), result.getSpecifics()); + + final Downtime firstSourceDowntime = source.getDowntimes().get(0); + final org.opennms.netmgt.config.poller.Downtime firstResultDowntime = result.getDowntimes().get(0); + assertEquals(firstSourceDowntime.getDelete().map(it -> it.toString().toLowerCase()).orElse(null), + firstResultDowntime.getDelete()); + assertEquals(firstSourceDowntime.getBegin().getSeconds(), firstResultDowntime.getBegin().longValue()); + assertEquals(firstSourceDowntime.getEnd().map(Duration::getSeconds).orElse(null), firstResultDowntime.getEnd()); + assertEquals(firstSourceDowntime.getInterval().orElse(null), firstResultDowntime.getInterval()); + + final Service firstSourceService = source.getServices().get(0); + final org.opennms.netmgt.config.poller.Service firstResultService = result.getServices().get(0); + assertEquals(firstSourceService.getInterval(), firstResultService.getInterval().longValue()); + assertEquals(firstSourceService.getName(), firstResultService.getName()); + assertEquals(firstSourceService.getParameters().size(), firstResultService.getParameters().size()); + assertEquals(firstSourceService.getPattern().orElse(null), firstResultService.getPattern()); + + if (firstSourceService.getParameters().size() > 0) { + final Parameter firstSourceParameter = firstSourceService.getParameters().get(0); + final org.opennms.netmgt.config.poller.Parameter firstResultParameter = + firstResultService.getParameters().get(0); + assertEquals(firstSourceParameter.getKey(), firstResultParameter.getKey()); + assertEquals(firstSourceParameter.getValue(), firstResultParameter.getValue()); + } + + if (source.getExcludeRanges().size() > 0) { + final AddressRange firstSourceExcludeRange = source.getExcludeRanges().get(0); + final ExcludeRange firstResultExcludeRange = result.getExcludeRanges().get(0); + assertEquals(firstSourceExcludeRange.getBegin(), firstResultExcludeRange.getBegin()); + assertEquals(firstSourceExcludeRange.getEnd(), firstResultExcludeRange.getEnd()); + } + } + +} diff --git a/features/api-layer/core/src/test/resources/poller/eventconf.xml b/features/api-layer/core/src/test/resources/poller/eventconf.xml new file mode 100644 index 000000000000..d0dec2f77855 --- /dev/null +++ b/features/api-layer/core/src/test/resources/poller/eventconf.xml @@ -0,0 +1,269 @@ + + + + logmsg + operaction + autoaction + tticket + script + + + events/opennms.snmp.trap.translator.events.xml + events/opennms.ackd.events.xml + events/opennms.alarm.events.xml + events/opennms.bmp.events.xml + events/opennms.bsm.events.xml + events/opennms.capsd.events.xml + events/opennms.collectd.events.xml + events/opennms.config.events.xml + events/opennms.correlation.events.xml + events/opennms.default.threshold.events.xml + events/opennms.discovery.events.xml + events/opennms.hyperic.events.xml + events/opennms.internal.events.xml + events/opennms.linkd.events.xml + events/opennms.mib.events.xml + events/opennms.pollerd.events.xml + events/opennms.provisioning.events.xml + events/opennms.minion.events.xml + events/opennms.perspective.poller.events.xml + events/opennms.reportd.events.xml + events/opennms.syslogd.events.xml + events/opennms.ticketd.events.xml + events/opennms.tl1d.events.xml + events/Rancid.events.xml + events/GraphMLAssetPluginEvents.xml + events/3Com.events.xml + events/AdaptecRaid.events.xml + events/ADIC-v2.events.xml + events/Adtran.events.xml + events/Adtran.Atlas.events.xml + events/Aedilis.events.xml + events/AirDefense.events.xml + events/AIX.events.xml + events/AKCP.events.xml + events/AlcatelLucent.OmniSwitch.events.xml + events/AlcatelLucent.SMSBrick.events.xml + events/Allot.events.xml + events/Allot.NetXplorer.events.xml + events/Allot.SM.events.xml + events/Alteon.events.xml + events/Altiga.events.xml + events/APC.events.xml + events/APC.Best.events.xml + events/APC.Exide.events.xml + events/ApacheHTTPD.syslog.events.xml + events/Aruba.AP.events.xml + events/Aruba.Switch.events.xml + events/Aruba.events.xml + events/Ascend.events.xml + events/ASYNCOS-MAIL-MIB.events.xml + events/Avocent.ACS.events.xml + events/Avocent.ACS5000.events.xml + events/Avocent.AMX5000.events.xml + events/Avocent.AMX5010.events.xml + events/Avocent.AMX5020.events.xml + events/Avocent.AMX5030.events.xml + events/Avocent.CCM.events.xml + events/Avocent.DSR.events.xml + events/Avocent.DSR1021.events.xml + events/Avocent.DSR2010.events.xml + events/Avocent-DSView.events.xml + events/Avocent.Mergepoint.events.xml + events/Avocent.PMTrap.events.xml + events/Audiocodes.events.xml + events/A10.AX.events.xml + events/ATMForum.events.xml + events/BackupExec.events.xml + events/BEA.events.xml + events/BGP4.events.xml + events/BladeNetwork.events.xml + events/Bluecat.events.xml + events/BlueCoat.events.xml + events/Brocade.events.xml + events/Broadcom-BASPTrap.events.xml + events/CA.ArcServe.events.xml + events/Ceragon-FA1500.events.xml + events/Cisco.airespace.xml + events/Cisco.CIDS.events.xml + events/Cisco.5300dchan.events.xml + events/Cisco.mcast.events.xml + events/Cisco.SCE.events.xml + events/Cisco2.events.xml + events/Cisco.events.xml + events/CitrixNetScaler.events.xml + events/Colubris.events.xml + events/ComtechEFData.events.xml + events/Concord.events.xml + events/Covergence.events.xml + events/CPQHPIM.events.xml + events/Clarent.events.xml + events/Clarinet.events.xml + events/Clavister.events.xml + events/Compuware.events.xml + events/Cricket.events.xml + events/CRITAPP.events.xml + events/Crossbeam.events.xml + events/Dell-Asf.events.xml + events/DellArrayManager.events.xml + events/DellEquallogic.events.xml + events/Dell-DRAC2.events.xml + events/Dell-ITassist.events.xml + events/Dell-F10-bgb4-v2.events.xml + events/Dell-F10-chassis.events.xml + events/Dell-F10-copy-config.events.xml + events/Dell-F10-mstp.events.xml + events/Dell-F10-system-component.events.xml + events/DellOpenManage.events.xml + events/DellRacHost.events.xml + events/DellStorageManagement.events.xml + events/DISMAN.events.xml + events/DISMAN-PING.events.xml + events/Dlink.events.xml + events/DMTF.events.xml + events/DPS.events.xml + events/DS1.events.xml + events/EMC.events.xml + events/EMC-Celerra.events.xml + events/EMC-Clariion.events.xml + events/Evertz.7780ASI-IP2.events.xml + events/Evertz.7880IP-ASI-IP.events.xml + events/Evertz.7880IP-ASI-IP-FR.events.xml + events/Evertz.7881DEC-MP2-HD.events.xml + events/Extreme.events.xml + events/F5.events.xml + events/fcmgmt.events.xml + events/Fore.events.xml + events/Fortinet-FortiCore-v52.events.xml + events/Fortinet-FortiGate-v52.events.xml + events/Fortinet-FortiMail.events.xml + events/Fortinet-FortiManager-Analyzer.events.xml + events/Fortinet-FortiRecorder.events.xml + events/Fortinet-FortiVoice.events.xml + events/Fortinet-FortiCore-v4.events.xml + events/Fortinet-FortiGate-v4.events.xml + events/FoundryNetworks.events.xml + events/FoundryNetworks2.events.xml + events/FujitsuSiemens.events.xml + events/GGSN.events.xml + events/Groupwise.events.xml + events/HP.events.xml + events/HWg.Poseidon.events.xml + events/Hyperic.events.xml + events/IBM.events.xml + events/IBM-UMS.events.xml + events/IBMRSA2.events.xml + events/IBM.EIF.events.xml + events/IEEE802dot11.events.xml + events/ietf.dlsw.events.xml + events/ietf.docsis.events.xml + events/ietf.events.xml + events/ietf.ptopo.events.xml + events/ietf.sna-dlc.events.xml + events/ietf.tn3270e.events.xml + events/ietf.vrrp.events.xml + events/Infoblox.events.xml + events/Intel.events.xml + events/INTEL-LAN-ADAPTERS-MIB.events.xml + events/InteractiveIntelligence.events.xml + events/IronPort.events.xml + events/ISS.events.xml + events/IPUnity-SES-MIB.events.xml + events/IPV6.events.xml + events/Juniper.mcast.events.xml + events/Juniper.events.xml + events/Juniper.ive.events.xml + events/Juniper.screen.events.xml + events/Junos.events.xml + events/JunosV1.events.xml + events/K5Systems.events.xml + events/Konica.events.xml + events/LLDP.events.xml + events/Liebert.events.xml + events/Liebert.600SM.events.xml + events/Linksys.events.xml + events/LinuxKernel.syslog.events.xml + events/Lucent.events.xml + events/MadgeNetworks.events.xml + events/McAfee.events.xml + events/MGE-UPS.events.xml + events/Microsoft.events.xml + events/MikrotikRouterOS.events.xml + events/Multicast.standard.events.xml + events/MPLS.events.xml + events/MRV.events.xml + events/MSDP.events.xml + events/Mylex.events.xml + events/NetApp.events.xml + events/Netbotz.events.xml + events/Netgear.events.xml + events/NetgearProsafeSmartSwitch.events.xml + events/NetgearProsafeSmartSwitch.syslog.events.xml + events/Netscreen.events.xml + events/NetSNMP.events.xml + events/Nokia.events.xml + events/NORTEL.Contivity.events.xml + events/Novell.events.xml + events/OpenSSH.syslog.events.xml + events/OpenWrt.syslog.events.xml + events/Oracle.events.xml + events/OSPF.events.xml + events/Overland.events.xml + events/Overture.events.xml + events/Procmail.syslog.events.xml + events/POSIX.syslog.events.xml + events/Postfix.syslog.events.xml + events/Packeteer.events.xml + events/Patrol.events.xml + events/PCube.events.xml + events/Pingtel.events.xml + events/Pixelmetrix.events.xml + events/Polycom.events.xml + events/Powerware.events.xml + events/Primecluster.events.xml + events/Quintum.events.xml + events/Raytheon.events.xml + events/RADLAN-MIB.events.xml + events/RAPID-CITY.events.xml + events/Redline.events.xml + events/RFC1382.events.xml + events/RFC1628.events.xml + events/Rightfax.events.xml + events/RiverbedSteelhead.events.xml + events/RMON.events.xml + events/Sensaphone.events.xml + events/Sentry.events.xml + events/Siemens-HiPath3000.events.xml + events/Siemens-HiPath3000-HG1500.events.xml + events/Siemens-HiPath4000.events.xml + events/Siemens-HiPath8000-OpenScapeVoice.events.xml + events/SNA-NAU.events.xml + events/SNMP-REPEATER.events.xml + events/Snort.events.xml + events/SonicWall.events.xml + events/Sonus.events.xml + events/Sudo.syslog.events.xml + events/SunILOM.events.xml + events/Symbol.events.xml + events/Syslogd.events.xml + events/SystemEdge.events.xml + events/SwissQual.events.xml + events/TransPath.events.xml + events/Trendmicro.events.xml + events/TrippLite.events.xml + events/TUT.events.xml + events/UPS-MIB.events.xml + events/Uptime.events.xml + events/Veeam_Backup-Replication.events.xml + events/Veraz.events.xml + events/VMWare.env.events.xml + events/VMWare.vc.events.xml + events/VMWare.vminfo.events.xml + events/VMWare.obsolete.events.xml + events/VMWare.events.xml + events/Waverider.3000.events.xml + events/Websense.events.xml + events/Xerox-V2.events.xml + events/Xerox.events.xml + events/opennms.catch-all.events.xml + diff --git a/features/api-layer/core/src/test/resources/poller/poller-configuration1.xml b/features/api-layer/core/src/test/resources/poller/poller-configuration1.xml new file mode 100644 index 000000000000..56c877e443e3 --- /dev/null +++ b/features/api-layer/core/src/test/resources/poller/poller-configuration1.xml @@ -0,0 +1,29 @@ + + + + IPADDR IPLIKE 2.*.*.* + + + + + RRA:AVERAGE:0.5:1:2016 + RRA:AVERAGE:0.5:12:1488 + + + + + + + + + + zzz from poll-outages.xml zzz + + + + + + + + + diff --git a/features/api-layer/core/src/test/resources/poller/poller-configuration2.xml b/features/api-layer/core/src/test/resources/poller/poller-configuration2.xml new file mode 100644 index 000000000000..dae60aa29901 --- /dev/null +++ b/features/api-layer/core/src/test/resources/poller/poller-configuration2.xml @@ -0,0 +1,61 @@ + + + + IPADDR != '0.0.0.0' + + + + RRA:AVERAGE:0.5:288:366 + RRA:MAX:0.5:288:366 + + + + + + + + + + + + + + + + + + + + + + zzz from poll-outages.xml zzz + + + + + + + + + + IPADDR != '0.0.0.0' + + + RRA:MIN:0.5:288:366 + RRA:MAX:0.5:288:366 + + + + + + + + + + + + + + + + diff --git a/features/api-layer/dao-common/src/main/java/org/opennms/features/apilayer/model/mappers/IpInterfaceMapper.java b/features/api-layer/dao-common/src/main/java/org/opennms/features/apilayer/model/mappers/IpInterfaceMapper.java index 8b13ca93a2ac..39620c6c1d75 100644 --- a/features/api-layer/dao-common/src/main/java/org/opennms/features/apilayer/model/mappers/IpInterfaceMapper.java +++ b/features/api-layer/dao-common/src/main/java/org/opennms/features/apilayer/model/mappers/IpInterfaceMapper.java @@ -32,7 +32,7 @@ import org.opennms.integration.api.v1.model.immutables.ImmutableIpInterface; import org.opennms.netmgt.model.OnmsIpInterface; -@Mapper(uses = {MetaDataMapper.class, SnmpInterfaceMapper.class}) +@Mapper(uses = {MetaDataMapper.class, SnmpInterfaceMapper.class, MonitoredServiceMapper.class}) public interface IpInterfaceMapper { ImmutableIpInterface map(OnmsIpInterface onmsIpInterface); } diff --git a/features/api-layer/dao-common/src/main/java/org/opennms/features/apilayer/model/mappers/MonitoredServiceMapper.java b/features/api-layer/dao-common/src/main/java/org/opennms/features/apilayer/model/mappers/MonitoredServiceMapper.java new file mode 100644 index 000000000000..8afd411bf150 --- /dev/null +++ b/features/api-layer/dao-common/src/main/java/org/opennms/features/apilayer/model/mappers/MonitoredServiceMapper.java @@ -0,0 +1,41 @@ +/******************************************************************************* + * This file is part of OpenNMS(R). + * + * Copyright (C) 2022 The OpenNMS Group, Inc. + * OpenNMS(R) is Copyright (C) 1999-2022 The OpenNMS Group, Inc. + * + * OpenNMS(R) is a registered trademark of The OpenNMS Group, Inc. + * + * OpenNMS(R) is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, + * or (at your option) any later version. + * + * OpenNMS(R) is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with OpenNMS(R). If not, see: + * http://www.gnu.org/licenses/ + * + * For more information contact: + * OpenNMS(R) Licensing + * http://www.opennms.org/ + * http://www.opennms.com/ + *******************************************************************************/ + +package org.opennms.features.apilayer.model.mappers; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.opennms.integration.api.v1.model.immutables.ImmutableMonitoredService; +import org.opennms.netmgt.model.OnmsMonitoredService; + +@Mapper(uses = {MetaDataMapper.class}) +public interface MonitoredServiceMapper { + + @Mapping(target = "name", source = "serviceType.name") + ImmutableMonitoredService map(OnmsMonitoredService onmsMonitoredService); +} diff --git a/features/api-layer/dao-common/src/main/java/org/opennms/features/apilayer/model/mappers/NodeMapper.java b/features/api-layer/dao-common/src/main/java/org/opennms/features/apilayer/model/mappers/NodeMapper.java index d32d105f4c12..d74fa2a6486e 100644 --- a/features/api-layer/dao-common/src/main/java/org/opennms/features/apilayer/model/mappers/NodeMapper.java +++ b/features/api-layer/dao-common/src/main/java/org/opennms/features/apilayer/model/mappers/NodeMapper.java @@ -41,7 +41,7 @@ import org.opennms.netmgt.model.monitoringLocations.OnmsMonitoringLocation; @Mapper(uses = {GeolocationMapper.class, NodeAssetRecordMapper.class, IpInterfaceMapper.class, - SnmpInterfaceMapper.class, MetaDataMapper.class}) + SnmpInterfaceMapper.class, MetaDataMapper.class, MonitoredServiceMapper.class}) public interface NodeMapper { ImmutableNode map(OnmsNode onmsNode); diff --git a/integrations/opennms-jasper-extensions/src/main/java/org/opennms/netmgt/jasper/grafana/GrafanaPanelDatasource.java b/integrations/opennms-jasper-extensions/src/main/java/org/opennms/netmgt/jasper/grafana/GrafanaPanelDatasource.java index 5b118e8bcaaa..286968d7d627 100644 --- a/integrations/opennms-jasper-extensions/src/main/java/org/opennms/netmgt/jasper/grafana/GrafanaPanelDatasource.java +++ b/integrations/opennms-jasper-extensions/src/main/java/org/opennms/netmgt/jasper/grafana/GrafanaPanelDatasource.java @@ -42,6 +42,7 @@ import java.util.stream.Collectors; import java.util.stream.Stream; +import jdk.internal.joptsimple.internal.Strings; import org.opennms.netmgt.endpoints.grafana.api.Dashboard; import org.opennms.netmgt.endpoints.grafana.api.GrafanaClient; import org.opennms.netmgt.endpoints.grafana.api.Panel; @@ -60,6 +61,7 @@ public class GrafanaPanelDatasource implements JRRewindableDataSource { public static final String TITLE_FIELD_NAME = "title"; public static final String DATASOURCE_FIELD_NAME = "datasource"; public static final String DESCRIPTION_FIELD_NAME = "description"; + public static final String ROW_TITLE_FIELD_NAME = "rowTitle"; private final GrafanaClient client; private final Dashboard dashboard; @@ -114,6 +116,8 @@ public Object getFieldValue(final JRField jrField) { return currentPanel.getDatasource(); } else if (Objects.equals(DESCRIPTION_FIELD_NAME, fieldName)) { return currentPanel.getDescription(); + } else if (Objects.equals(ROW_TITLE_FIELD_NAME, fieldName)) { + return getRowTitle(currentPanel); } else if (Objects.equals(IMAGE_FIELD_NAME, fieldName)) { try { maybeRenderPanels(); @@ -149,4 +153,24 @@ private void maybeRenderPanels() { } } + /** + * Gets the current panel the previous row panel title if it has one. + * @param panel + * @return String with the row panel title if exists or an empty string if not. + */ + private String getRowTitle(final Panel panel){ + String currentRowPanel = Strings.EMPTY; + for(final Panel p : this.dashboard.getPanels()){ + if(p.getType().equals("row")){ + currentRowPanel = p.getTitle().trim(); + } else if (p.equals(panel)){ + return currentRowPanel; + } else { + // clear the title right after passing the first panel after the row panel. + currentRowPanel = Strings.EMPTY; + } + } + return currentRowPanel; + } + } diff --git a/makedeb.sh b/makedeb.sh index 254f30ed45b6..3b7e23997a05 100755 --- a/makedeb.sh +++ b/makedeb.sh @@ -126,6 +126,12 @@ function version() head -n 1 } +function opa_version() +{ + grep '' pom.xml | \ + sed -e 's,^[^>]*>,,' -e 's,<.*$,,' -e 's,-[^-]*-SNAPSHOT$,,' -e 's,-SNAPSHOT$,,' -e 's,-testing$,,' -e 's,-,.,g' | \ + head -n 1 +} function skipCompile() { if $ASSEMBLY_ONLY; then echo 1; else echo 0; fi @@ -199,6 +205,7 @@ fi EXTRA_INFO=$(extraInfo) EXTRA_INFO2=$(extraInfo2) VERSION=$(version) +OPA_VERSION=$(opa_version) export PATH="$TOPDIR/maven/bin:$JAVA_HOME/bin:$PATH" @@ -211,7 +218,9 @@ function build_opennms() echo echo "Version: " $VERSION echo "Release: " $RELEASE + echo "OPA VERSION: " $OPA_VERSION echo + sed -i "s/OPA_VERSION/$OPA_VERSION/g" debian/control if $DO_CHANGELOG; then echo "- adding auto-generated changelog entry" @@ -224,7 +233,7 @@ function build_opennms() ./compile.pl -N install - dpkg-buildpackage "-p${TRUE_BIN}" -us -uc + dpkg-buildpackage "-p${TRUE_BIN}" -us -uc } function build_minion() @@ -233,8 +242,9 @@ function build_minion() echo echo "Version: " $VERSION echo "Release: " $RELEASE + echo "OPA VERSION: " $OPA_VERSION echo - + sed -i "s/OPA_VERSION/$OPA_VERSION/g" $TOPDIR/opennms-assemblies/minion/src/main/filtered/debian/control local _extra_args=() if [ "$OPENNMS_ENABLE_SNAPSHOTS" = "1" ]; then _extra_args+=("-s") @@ -274,7 +284,9 @@ function build_sentinel() { echo echo "Version: " $VERSION echo "Release: " $RELEASE + echo "OPA VERSION: " $OPA_VERSION echo + sed -i "s/OPA_VERSION/$OPA_VERSION/g" $TOPDIR/opennms-assemblies/sentinel/src/main/filtered/debian/control local _extra_args=() if [ "$OPENNMS_ENABLE_SNAPSHOTS" = "1" ]; then diff --git a/makerpm.sh b/makerpm.sh index 0f35c29b626d..66dc6cd0a516 100755 --- a/makerpm.sh +++ b/makerpm.sh @@ -131,6 +131,13 @@ function version() head -n 1 } +function opa_version() +{ + grep '' pom.xml | \ + sed -e 's,^[^>]*>,,' -e 's,<.*$,,' -e 's,-[^-]*-SNAPSHOT$,,' -e 's,-SNAPSHOT$,,' -e 's,-testing$,,' -e 's,-,.,g' | \ + head -n 1 +} + function skipCompile() { if $ASSEMBLY_ONLY; then echo 1; else echo 0; fi @@ -154,6 +161,7 @@ function main() PACKAGE_NAME="opennms" PACKAGE_DESCRIPTION="OpenNMS" + RELEASE_MAJOR=0 local RELEASE_MINOR="$(calcMinor)" local RELEASE_MICRO=1 @@ -201,6 +209,8 @@ function main() EXTRA_INFO=$(extraInfo) EXTRA_INFO2=$(extraInfo2) VERSION=$(version) + OPA_VERSION=$(opa_version) + if $BUILD_RPM; then if [ "$SPECS" == "" ]; then @@ -219,6 +229,7 @@ function main() echo "Version: " $VERSION echo "Release: " $RELEASE echo "Specs : " $SPECS + echo "OPA VERSION: " $OPA_VERSION echo echo "=== Creating Working Directories ===" @@ -249,6 +260,7 @@ function main() --define "releasenumber $RELEASE" \ --define "_name $PACKAGE_NAME" \ --define "_descr $PACKAGE_DESCRIPTION" \ + --define "opa_version $OPA_VERSION" \ $spec || die "failed to build $spec" done fi diff --git a/opennms-assemblies/minion/src/main/filtered/debian/control b/opennms-assemblies/minion/src/main/filtered/debian/control index 31bd247dbdb1..ecf6b6386902 100644 --- a/opennms-assemblies/minion/src/main/filtered/debian/control +++ b/opennms-assemblies/minion/src/main/filtered/debian/control @@ -10,6 +10,7 @@ Architecture: all Depends: adduser, openssh-client, uuid-runtime, sudo Recommends: openjdk-11-jre-headless | openjdk-11-jre | adoptopenjdk-11-openj9xl-jre | adoptopenjdk-11-openj9-jre | adoptopenjdk-11-hotspot-jre | temurin-11-jdk, haveged, jicmp (>= 2.0.0), jicmp6 (>= 2.0.0) Conflicts: opennms-minion-container (<< 25.0.0), opennms-minion-features-core (<<25.0.0), opennms-minion-features-default (<<25.0.0) +Provides: opennms-plugin-api (= OPA_VERSION) Description: distributed OpenNMS monitoring client OpenNMS Minion is a container infrastructure for distributed, scalable network management and monitoring. diff --git a/opennms-assemblies/sentinel/src/main/filtered/debian/control b/opennms-assemblies/sentinel/src/main/filtered/debian/control index 3af6656797d0..0c1def2ecaa0 100644 --- a/opennms-assemblies/sentinel/src/main/filtered/debian/control +++ b/opennms-assemblies/sentinel/src/main/filtered/debian/control @@ -9,6 +9,7 @@ Package: opennms-sentinel Architecture: all Depends: adduser, openssh-client, uuid-runtime, sudo Recommends: openjdk-11-jre-headless | openjdk-11-jre | adoptopenjdk-11-openj9xl-jre | adoptopenjdk-11-openj9-jre | adoptopenjdk-11-hotspot-jre | temurin-11-jdk, haveged, jicmp (>= 2.0.0), jicmp6 (>= 2.0.0) +Provides: opennms-plugin-api (= OPA_VERSION) Description: horizontal scaling for OpenNMS OpenNMS Sentinel is a container for running a subset of OpenNMS services in a standalone container, suitable for horizontally diff --git a/opennms-base-assembly/src/main/filtered/etc/report-templates/Grafana-Dashboard-Report-1ppp.jrxml b/opennms-base-assembly/src/main/filtered/etc/report-templates/Grafana-Dashboard-Report-1ppp.jrxml index b5404f5952a3..ce464de876a4 100644 --- a/opennms-base-assembly/src/main/filtered/etc/report-templates/Grafana-Dashboard-Report-1ppp.jrxml +++ b/opennms-base-assembly/src/main/filtered/etc/report-templates/Grafana-Dashboard-Report-1ppp.jrxml @@ -1,52 +1,52 @@ - - + - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + - - - - + + + - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + - - - - - - - - <band splitType="Stretch"/> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + }]]> + + + + + + + + + + <band splitType="Stretch"/> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/opennms-base-assembly/src/main/filtered/etc/report-templates/Grafana-Dashboard-Report-2ppp.jrxml b/opennms-base-assembly/src/main/filtered/etc/report-templates/Grafana-Dashboard-Report-2ppp.jrxml index dc79fbb720b3..c812e221eba0 100644 --- a/opennms-base-assembly/src/main/filtered/etc/report-templates/Grafana-Dashboard-Report-2ppp.jrxml +++ b/opennms-base-assembly/src/main/filtered/etc/report-templates/Grafana-Dashboard-Report-2ppp.jrxml @@ -1,52 +1,52 @@ - - + - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + - - - - + + + - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + - - - - - - - - <band splitType="Stretch"/> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + }]]> + + + + + + + + + + <band splitType="Stretch"/> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -145,50 +147,62 @@ new GregorianCalendar(new GregorianCalendar().get(Calendar.YEAR), new GregorianC + + + + + + 0]]> + + + + + + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/opennms-base-assembly/src/main/filtered/etc/report-templates/Grafana-Dashboard-Report-4ppp.jrxml b/opennms-base-assembly/src/main/filtered/etc/report-templates/Grafana-Dashboard-Report-4ppp.jrxml index 240b859b83a6..019f019f0fe6 100644 --- a/opennms-base-assembly/src/main/filtered/etc/report-templates/Grafana-Dashboard-Report-4ppp.jrxml +++ b/opennms-base-assembly/src/main/filtered/etc/report-templates/Grafana-Dashboard-Report-4ppp.jrxml @@ -1,52 +1,52 @@ - - + - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + - - - - + + + - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + - - - - - - - - <band splitType="Stretch"/> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + }]]> + + + + + + + + + + <band splitType="Stretch"/> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -145,50 +147,62 @@ new GregorianCalendar(new GregorianCalendar().get(Calendar.YEAR), new GregorianC + + + + + + 0]]> + + + + + + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/opennms-config-jaxb/src/main/java/org/opennms/netmgt/config/poller/PollerConfiguration.java b/opennms-config-jaxb/src/main/java/org/opennms/netmgt/config/poller/PollerConfiguration.java index 02f4a9957826..46eeca2d7c1b 100644 --- a/opennms-config-jaxb/src/main/java/org/opennms/netmgt/config/poller/PollerConfiguration.java +++ b/opennms-config-jaxb/src/main/java/org/opennms/netmgt/config/poller/PollerConfiguration.java @@ -31,7 +31,6 @@ import java.io.Serializable; import java.net.InetAddress; import java.util.ArrayList; -import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Objects; @@ -89,9 +88,6 @@ public class PollerConfiguration implements Serializable { @XmlJavaTypeAdapter(InetAddressXmlAdapter.class) private InetAddress m_defaultCriticalPathIp; - @XmlAttribute(name="defaultCriticalPathService") - private String m_defaultCriticalPathService; - @XmlAttribute(name="defaultCriticalPathTimeout") private Integer m_defaultCriticalPathTimeout; @@ -174,10 +170,9 @@ public void setNodeOutage(final NodeOutage nodeOutage) { public List getPackages() { if (m_packages == null) { - return Collections.emptyList(); - } else { - return Collections.unmodifiableList(m_packages); + m_packages = new ArrayList<>(); } + return m_packages; } public void setPackages(final List packages) { @@ -185,15 +180,15 @@ public void setPackages(final List packages) { } public void addPackage(final Package pack) throws IndexOutOfBoundsException { - m_packages.add(pack); + getPackages().add(pack); } public boolean removePackage(final Package pack) { - return m_packages.remove(pack); + return getPackages().remove(pack); } public Package getPackage(final String packageName) { - for (final Package pkg : m_packages) { + for (final Package pkg : getPackages()) { if (pkg.getName().equals(packageName)) { return pkg; } @@ -203,10 +198,9 @@ public Package getPackage(final String packageName) { public List getMonitors() { if (m_monitors == null) { - return Collections.emptyList(); - } else { - return Collections.unmodifiableList(m_monitors); + m_monitors = new ArrayList<>(); } + return m_monitors; } public void setMonitors(final List monitors) { @@ -214,7 +208,7 @@ public void setMonitors(final List monitors) { } public void addMonitor(final Monitor monitor) throws IndexOutOfBoundsException { - m_monitors.add(monitor); + getMonitors().add(monitor); } public void addMonitor(final String service, final String className) { @@ -222,7 +216,7 @@ public void addMonitor(final String service, final String className) { } public boolean removeMonitor(final Monitor monitor) { - return m_monitors.remove(monitor); + return getMonitors().remove(monitor); } public PollerConfiguration getPollerConfigurationForPackages(final List pollingPackageNames) { @@ -285,7 +279,9 @@ public void setDefaultCriticalPathRetries(final Integer retries) { @Override public int hashCode() { - return Objects.hash(m_threads, m_nextOutageId, m_serviceUnresponsiveEnabled, m_pathOutageEnabled, m_defaultCriticalPathIp, m_defaultCriticalPathService, m_defaultCriticalPathTimeout, m_defaultCriticalPathRetries, m_nodeOutage, m_packages, m_monitors); + return Objects.hash(getThreads(), getNextOutageId(), getServiceUnresponsiveEnabled(), getPathOutageEnabled(), + getDefaultCriticalPathIp(), getDefaultCriticalPathTimeout(), getDefaultCriticalPathRetries(), + getNodeOutage(), getPackages(), getMonitors()); } @Override @@ -293,32 +289,30 @@ public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; final PollerConfiguration that = (PollerConfiguration) o; - return Objects.equals(m_threads, that.m_threads) - && Objects.equals(m_nextOutageId, that.m_nextOutageId) - && Objects.equals(m_serviceUnresponsiveEnabled, that.m_serviceUnresponsiveEnabled) - && Objects.equals(m_pathOutageEnabled, that.m_pathOutageEnabled) - && Objects.equals(m_defaultCriticalPathIp, that.m_defaultCriticalPathIp) - && Objects.equals(m_defaultCriticalPathService, that.m_defaultCriticalPathService) - && Objects.equals(m_defaultCriticalPathTimeout, that.m_defaultCriticalPathTimeout) - && Objects.equals(m_defaultCriticalPathRetries, that.m_defaultCriticalPathRetries) - && Objects.equals(m_nodeOutage, that.m_nodeOutage) - && Objects.equals(m_packages, that.m_packages) - && Objects.equals(m_monitors, that.m_monitors); + return Objects.equals(getThreads(), that.getThreads()) + && Objects.equals(getNextOutageId(), that.getNextOutageId()) + && Objects.equals(getServiceUnresponsiveEnabled(), that.getServiceUnresponsiveEnabled()) + && Objects.equals(getPathOutageEnabled(), that.getPathOutageEnabled()) + && Objects.equals(getDefaultCriticalPathIp(), that.getDefaultCriticalPathIp()) + && Objects.equals(getDefaultCriticalPathTimeout(), that.getDefaultCriticalPathTimeout()) + && Objects.equals(getDefaultCriticalPathRetries(), that.getDefaultCriticalPathRetries()) + && Objects.equals(getNodeOutage(), that.getNodeOutage()) + && Objects.equals(getPackages(), that.getPackages()) + && Objects.equals(getMonitors(), that.getMonitors()); } @Override public String toString() { return "PollerConfiguration[" + - "threads=" + m_threads + - ",nextOutageId=" + m_nextOutageId + - ",serviceUnresponsiveEnabled=" + m_serviceUnresponsiveEnabled + - ",pathOutageEnabled=" + m_pathOutageEnabled + - ",pathOutageDefaultCriticalPathIp=" + m_defaultCriticalPathIp + - ",pathOutageDefaultCriticalPathService=" + m_defaultCriticalPathService + - ",pathOutageDefaultCriticalPathTimeout=" + m_defaultCriticalPathTimeout + - ",pathOutageDefaultCriticalPathRetries=" + m_defaultCriticalPathRetries + - ",nodeOutage=" + m_nodeOutage + - ",packages=" + m_packages + - ",monitors=" + m_monitors + + "threads=" + getThreads() + + ",nextOutageId=" + getNextOutageId() + + ",serviceUnresponsiveEnabled=" + getServiceUnresponsiveEnabled() + + ",pathOutageEnabled=" + getPathOutageEnabled() + + ",pathOutageDefaultCriticalPathIp=" + getDefaultCriticalPathIp() + + ",pathOutageDefaultCriticalPathTimeout=" + getDefaultCriticalPathTimeout() + + ",pathOutageDefaultCriticalPathRetries=" + getDefaultCriticalPathRetries() + + ",nodeOutage=" + getNodeOutage() + + ",packages=" + getPackages() + + ",monitors=" + getMonitors() + "]"; } diff --git a/opennms-config/src/main/java/org/opennms/netmgt/config/PollerConfig.java b/opennms-config/src/main/java/org/opennms/netmgt/config/PollerConfig.java index 9c619a10af67..e779902098a2 100644 --- a/opennms-config/src/main/java/org/opennms/netmgt/config/PollerConfig.java +++ b/opennms-config/src/main/java/org/opennms/netmgt/config/PollerConfig.java @@ -402,11 +402,20 @@ default Optional findService(final String ipAddr, final St void addMonitor(String svcName, String className); /** - *

getConfiguration

+ *

getLocalConfiguration

* * @return a {@link org.opennms.netmgt.config.poller.PollerConfiguration} object. */ - PollerConfiguration getConfiguration(); + PollerConfiguration getLocalConfiguration(); + + /** + *

getExtendedConfiguration

+ * + * @return a {@link org.opennms.netmgt.config.poller.PollerConfiguration} object. + */ + default PollerConfiguration getExtendedConfiguration() { + return getLocalConfiguration(); + } /** *

getServiceMonitorLocators

@@ -417,6 +426,8 @@ default Optional findService(final String ipAddr, final St ServiceMonitorRegistry getServiceMonitorRegistry(); + default void setExternalData(List externalPackages, List externalMonitors) {}; + /** *

getReadLock

* diff --git a/opennms-config/src/main/java/org/opennms/netmgt/config/PollerConfigFactory.java b/opennms-config/src/main/java/org/opennms/netmgt/config/PollerConfigFactory.java index 7dc7102f2687..2623e66512a7 100644 --- a/opennms-config/src/main/java/org/opennms/netmgt/config/PollerConfigFactory.java +++ b/opennms-config/src/main/java/org/opennms/netmgt/config/PollerConfigFactory.java @@ -134,7 +134,7 @@ public static synchronized void init() throws IOException { IOUtils.closeQuietly(stream); } - validate(config.getConfiguration()); + validate(config.getLocalConfiguration()); setInstance(config); } diff --git a/opennms-config/src/main/java/org/opennms/netmgt/config/PollerConfigManager.java b/opennms-config/src/main/java/org/opennms/netmgt/config/PollerConfigManager.java index 0d59e992dc23..d16694fb8b85 100644 --- a/opennms-config/src/main/java/org/opennms/netmgt/config/PollerConfigManager.java +++ b/opennms-config/src/main/java/org/opennms/netmgt/config/PollerConfigManager.java @@ -28,6 +28,7 @@ package org.opennms.netmgt.config; +import static java.util.Objects.requireNonNull; import static org.opennms.core.utils.InetAddressUtils.addr; import static org.opennms.core.utils.InetAddressUtils.toIpAddrBytes; @@ -53,6 +54,7 @@ import java.util.concurrent.locks.ReentrantReadWriteLock; import java.util.regex.Pattern; +import org.apache.commons.collections.ListUtils; import org.apache.commons.io.IOUtils; import org.opennms.core.network.IpListFromUrl; import org.opennms.core.utils.ByteArrayComparator; @@ -61,6 +63,7 @@ import org.opennms.netmgt.config.poller.ExcludeRange; import org.opennms.netmgt.config.poller.IncludeRange; import org.opennms.netmgt.config.poller.Monitor; +import org.opennms.netmgt.config.poller.NodeOutage; import org.opennms.netmgt.config.poller.Package; import org.opennms.netmgt.config.poller.Parameter; import org.opennms.netmgt.config.poller.PollerConfiguration; @@ -83,12 +86,185 @@ * @author Mathew Brozowski * @author David Hustace */ -abstract public class PollerConfigManager implements PollerConfig { +abstract public class PollerConfigManager implements PollerConfig { + + /** + * This class is used to redirect getters for packages and monitors to their "merged" versions in + * PollerConfigManager. Other methods are redirected to "local" configuration + */ + public static class ReadOnlyProxyPollerConfiguration extends PollerConfiguration { + + private static final String MESSAGE = "Modifying is not allowed in ReadOnlyProxyPollerConfiguration"; + private final PollerConfigManager pollerConfigManager; + + ReadOnlyProxyPollerConfiguration(PollerConfigManager pollerConfigManager) { + this.pollerConfigManager = requireNonNull(pollerConfigManager); + } + + @Override + public List getPackages() { + return pollerConfigManager.mergedPackages; + } + + @Override + public void setPackages(List packages) { + throw new UnsupportedOperationException(MESSAGE); + } + + @Override + public void addPackage(Package pack) throws IndexOutOfBoundsException { + throw new UnsupportedOperationException(MESSAGE); + } + + @Override + public boolean removePackage(Package pack) { + throw new UnsupportedOperationException(MESSAGE); + } + + @Override + public Package getPackage(String packageName) { + return super.getPackage(packageName); + } + + @Override + public List getMonitors() { + return pollerConfigManager.mergedMonitors; + } + + @Override + public void setMonitors(List monitors) { + throw new UnsupportedOperationException(MESSAGE); + } + + @Override + public void addMonitor(Monitor monitor) throws IndexOutOfBoundsException { + throw new UnsupportedOperationException(MESSAGE); + } + + @Override + public void addMonitor(String service, String className) { + throw new UnsupportedOperationException(MESSAGE); + } + + @Override + public boolean removeMonitor(Monitor monitor) { + throw new UnsupportedOperationException(MESSAGE); + } + + @Override + public Integer getThreads() { + return pollerConfigManager.m_config.getThreads(); + } + + @Override + public void setThreads(Integer threads) { + throw new UnsupportedOperationException(MESSAGE); + } + + @Override + public String getNextOutageId() { + return pollerConfigManager.m_config.getNextOutageId(); + } + + @Override + public void setNextOutageId(String nextOutageId) { + throw new UnsupportedOperationException(MESSAGE); + } + + @Override + public String getServiceUnresponsiveEnabled() { + return pollerConfigManager.m_config.getServiceUnresponsiveEnabled(); + } + + @Override + public void setServiceUnresponsiveEnabled(String serviceUnresponsiveEnabled) { + throw new UnsupportedOperationException(MESSAGE); + } + + @Override + public String getPathOutageEnabled() { + return pollerConfigManager.m_config.getPathOutageEnabled(); + } + + @Override + public void setPathOutageEnabled(String pathOutageEnabled) { + throw new UnsupportedOperationException(MESSAGE); + } + + @Override + public NodeOutage getNodeOutage() { + return pollerConfigManager.m_config.getNodeOutage(); + } + + @Override + public void setNodeOutage(NodeOutage nodeOutage) { + throw new UnsupportedOperationException(MESSAGE); + } + + @Override + public PollerConfiguration getPollerConfigurationForPackages(List pollingPackageNames) { + return pollerConfigManager.m_config.getPollerConfigurationForPackages(pollingPackageNames); + } + + @Override + public InetAddress getDefaultCriticalPathIp() { + return pollerConfigManager.m_config.getDefaultCriticalPathIp(); + } + + @Override + public void setDefaultCriticalPathIp(InetAddress ip) { + throw new UnsupportedOperationException(MESSAGE); + } + + @Override + public Integer getDefaultCriticalPathTimeout() { + return pollerConfigManager.m_config.getDefaultCriticalPathTimeout(); + } + + @Override + public void setDefaultCriticalPathTimeout(Integer timeout) { + throw new UnsupportedOperationException(MESSAGE); + } + + @Override + public int getDefaultCriticalPathRetries() { + return pollerConfigManager.m_config.getDefaultCriticalPathRetries(); + } + + @Override + public void setDefaultCriticalPathRetries(Integer retries) { + throw new UnsupportedOperationException(MESSAGE); + } + } + + private static final Logger LOG = LoggerFactory.getLogger(PollerConfigManager.class); private final ReadWriteLock m_globalLock = new ReentrantReadWriteLock(); private final Lock m_readLock = m_globalLock.readLock(); private final Lock m_writeLock = m_globalLock.writeLock(); + private List localPackages = new ArrayList<>(); + private List externalPackages = new ArrayList<>(); + private List mergedPackages = new ArrayList<>(); + //private boolean packagesUpdated = false; + + private List localMonitors = new ArrayList<>(); + private List externalMonitors = new ArrayList<>(); + private List mergedMonitors = new ArrayList<>(); + //private boolean monitorsUpdated = false; + + @Override + public void setExternalData(List externalPackages, List externalMonitors) { + try { + getWriteLock().lock(); + this.externalPackages = externalPackages; + this.externalMonitors = externalMonitors; + this.setUpInternalData(); + } finally { + getWriteLock().unlock(); + } + } + private static final ServiceMonitorRegistry s_serviceMonitorRegistry = new DefaultServiceMonitorRegistry(); /** @@ -123,6 +299,8 @@ public Lock getWriteLock() { protected void setUpInternalData() { getReadLock().lock(); try { + mergedMonitors = ListUtils.union(m_config.getMonitors(), externalMonitors); + mergedPackages = ListUtils.union(m_config.getPackages(), externalPackages); createUrlIpMap(); createPackageIpListMap(); initializeServiceMonitors(); @@ -153,6 +331,7 @@ public void update() throws IOException { * The config class loaded from the config file */ protected PollerConfiguration m_config; + /** * A mapping of the configured URLs to a list of the specific IPs configured * in each - so as to avoid file reads @@ -208,12 +387,12 @@ public void save() throws IOException { } /** - * Return the poller configuration object. + * Return the local poller configuration object. * * @return a {@link org.opennms.netmgt.config.poller.PollerConfiguration} object. */ @Override - public PollerConfiguration getConfiguration() { + public PollerConfiguration getLocalConfiguration() { try { getReadLock().lock(); return m_config; @@ -222,6 +401,16 @@ public PollerConfiguration getConfiguration() { } } + /** + * Return the local poller configuration object. + * + * @return a {@link org.opennms.netmgt.config.poller.PollerConfiguration} object. + */ + @Override + public PollerConfiguration getExtendedConfiguration() { + return new ReadOnlyProxyPollerConfiguration(this); + } + /** {@inheritDoc} */ @Override public Package getPackage(final String name) { @@ -866,7 +1055,7 @@ public List getRRAList(final Package pkg) { public Enumeration enumeratePackage() { try { getReadLock().lock(); - return Collections.enumeration(getConfiguration().getPackages()); + return Collections.enumeration(mergedPackages); } finally { getReadLock().unlock(); } @@ -876,7 +1065,7 @@ public Enumeration enumeratePackage() { public List getPackages() { try { getReadLock().lock(); - return getConfiguration().getPackages(); + return mergedPackages; } finally { getReadLock().unlock(); } @@ -936,7 +1125,7 @@ public Iterable parameters(final Service svc) { private Iterable packages() { try { getReadLock().lock(); - return getConfiguration().getPackages(); + return mergedPackages; } finally { getReadLock().unlock(); } @@ -950,7 +1139,7 @@ private Iterable packages() { private Iterable monitors() { try { getReadLock().lock(); - return getConfiguration().getMonitors(); + return mergedMonitors; } finally { getReadLock().unlock(); } @@ -965,7 +1154,7 @@ private Iterable monitors() { public int getThreads() { try { getReadLock().lock(); - return getConfiguration().getThreads(); + return m_config.getThreads(); } finally { getReadLock().unlock(); } @@ -1073,6 +1262,6 @@ public String getNextOutageIdSql() { @Override public List getConfiguredMonitors() { - return m_config.getMonitors(); + return mergedMonitors; } } diff --git a/opennms-services/src/test/java/org/opennms/netmgt/mock/MockPollerConfig.java b/opennms-services/src/test/java/org/opennms/netmgt/mock/MockPollerConfig.java index 955577095257..6f4c3da80adb 100644 --- a/opennms-services/src/test/java/org/opennms/netmgt/mock/MockPollerConfig.java +++ b/opennms-services/src/test/java/org/opennms/netmgt/mock/MockPollerConfig.java @@ -39,7 +39,6 @@ import java.util.Enumeration; import java.util.List; import java.util.Map; -import java.util.Optional; import java.util.Set; import java.util.TreeMap; import java.util.Vector; @@ -512,7 +511,7 @@ public void addPackage(final Package pkg) { } @Override - public PollerConfiguration getConfiguration() { + public PollerConfiguration getLocalConfiguration() { // FIXME: need to actually implement this return null; } diff --git a/opennms-webapp-rest/src/main/java/org/opennms/web/rest/v1/ScheduledOutagesRestService.java b/opennms-webapp-rest/src/main/java/org/opennms/web/rest/v1/ScheduledOutagesRestService.java index fe9d2647b57e..4aec680c1df1 100644 --- a/opennms-webapp-rest/src/main/java/org/opennms/web/rest/v1/ScheduledOutagesRestService.java +++ b/opennms-webapp-rest/src/main/java/org/opennms/web/rest/v1/ScheduledOutagesRestService.java @@ -378,7 +378,7 @@ private void updatePollerd(ConfigAction action, String outageName, String packag pkg.removeOutageCalendar(outageName); } if (action.equals(ConfigAction.REMOVE_FROM_ALL)) { - for (org.opennms.netmgt.config.poller.Package pkg : PollerConfigFactory.getInstance().getConfiguration().getPackages()) { + for (org.opennms.netmgt.config.poller.Package pkg : PollerConfigFactory.getInstance().getExtendedConfiguration().getPackages()) { pkg.removeOutageCalendar(outageName); } } diff --git a/opennms-webapp/src/main/java/org/opennms/web/admin/config/PollerConfigServlet.java b/opennms-webapp/src/main/java/org/opennms/web/admin/config/PollerConfigServlet.java index 02bb08468545..76ddd0d231a1 100644 --- a/opennms-webapp/src/main/java/org/opennms/web/admin/config/PollerConfigServlet.java +++ b/opennms-webapp/src/main/java/org/opennms/web/admin/config/PollerConfigServlet.java @@ -66,8 +66,6 @@ public class PollerConfigServlet extends HttpServlet { * */ private static final long serialVersionUID = 2622622848304715121L; - - PollerConfiguration pollerConfig = null; protected String redirectSuccess; @@ -84,24 +82,7 @@ public class PollerConfigServlet extends HttpServlet { */ @Override public void init() throws ServletException { - ServletConfig config = this.getServletConfig(); - try { - PollerConfigFactory.init(); - pollerFactory = PollerConfigFactory.getInstance(); - pollerConfig = pollerFactory.getConfiguration(); - - if (pollerConfig == null) { - throw new ServletException("Poller Configuration file is empty"); - } - - } catch (Throwable e) { - throw new ServletException(e.getMessage()); - } - initPollerServices(); - this.redirectSuccess = config.getInitParameter("redirect.success"); - if (this.redirectSuccess == null) { - throw new ServletException("Missing required init parameter: redirect.success"); - } + reloadFiles(); } /** @@ -114,9 +95,8 @@ public void reloadFiles() throws ServletException { try { PollerConfigFactory.init(); pollerFactory = PollerConfigFactory.getInstance(); - pollerConfig = pollerFactory.getConfiguration(); - if (pollerConfig == null) { + if (pollerFactory.getLocalConfiguration() == null) { throw new ServletException("Poller Configuration file is empty"); } @@ -134,7 +114,7 @@ public void reloadFiles() throws ServletException { *

initPollerServices

*/ public void initPollerServices() { - Collection packageColl = pollerConfig.getPackages(); + Collection packageColl = pollerFactory.getExtendedConfiguration().getPackages(); if (packageColl != null) { Iterator pkgiter = packageColl.iterator(); if (pkgiter.hasNext()) { @@ -187,7 +167,7 @@ public void doGet(HttpServletRequest request, HttpServletResponse response) thro deleteThese(deleteList); try(Writer poller_fileWriter = new OutputStreamWriter(new FileOutputStream(ConfigFileConstants.getFile(ConfigFileConstants.POLLER_CONFIG_FILE_NAME)), StandardCharsets.UTF_8)) { - JaxbUtils.marshal(pollerConfig, poller_fileWriter); + JaxbUtils.marshal(pollerFactory.getLocalConfiguration(), poller_fileWriter); } } @@ -244,26 +224,20 @@ public void deleteThese(java.util.List deleteServices) throws IOExceptio /** *

removeMonitor

* - * FIXME: I think that this should be using Iterator.remove() - * * @param service a {@link java.lang.String} object. */ public void removeMonitor(String service) { // Add the new monitor with the protocol. - Collection monitorColl = pollerConfig.getMonitors(); - Monitor newMonitor = new Monitor(); + Collection monitorColl = pollerFactory.getLocalConfiguration().getMonitors(); if (monitorColl != null) { for (Monitor mon : monitorColl) { if (mon != null) { if (mon.getService().equals(service)) { - newMonitor.setService(service); - newMonitor.setClassName(mon.getClassName()); - newMonitor.setParameters(mon.getParameters()); + pollerFactory.getLocalConfiguration().removeMonitor(mon); break; } } } - monitorColl.remove(newMonitor); } } diff --git a/opennms-webapp/src/main/webapp/admin/sched-outages/editoutage.jsp b/opennms-webapp/src/main/webapp/admin/sched-outages/editoutage.jsp index 992739089cef..6e7303653362 100644 --- a/opennms-webapp/src/main/webapp/admin/sched-outages/editoutage.jsp +++ b/opennms-webapp/src/main/webapp/admin/sched-outages/editoutage.jsp @@ -302,7 +302,7 @@ Could not find an outage to edit because no outage name parameter was specified // ******* Polling outages config ********* PollerConfigFactory.init(); Map> pollingOutages = new HashMap>(); - for (org.opennms.netmgt.config.poller.Package thisPackage : PollerConfigFactory.getInstance().getConfiguration().getPackages()) { + for (org.opennms.netmgt.config.poller.Package thisPackage : PollerConfigFactory.getInstance().getExtendedConfiguration().getPackages()) { pollingOutages.put(thisPackage, thisPackage.getOutageCalendars()); if (thisPackage.getOutageCalendars().contains(theOutage.getName())) { enabledOutages.add("polling-" + thisPackage.getName()); diff --git a/opennms-webapp/src/main/webapp/admin/sched-outages/index.jsp b/opennms-webapp/src/main/webapp/admin/sched-outages/index.jsp index f6c8811a280b..076a25c35a60 100644 --- a/opennms-webapp/src/main/webapp/admin/sched-outages/index.jsp +++ b/opennms-webapp/src/main/webapp/admin/sched-outages/index.jsp @@ -80,7 +80,7 @@ thisPackage.removeOutageCalendar(deleteName); //Will quietly do nothing if outage doesn't exist } - for (final org.opennms.netmgt.config.poller.Package thisPackage : PollerConfigFactory.getInstance().getConfiguration().getPackages()) { + for (final org.opennms.netmgt.config.poller.Package thisPackage : PollerConfigFactory.getInstance().getExtendedConfiguration().getPackages()) { thisPackage.removeOutageCalendar(deleteName); //Will quietly do nothing if outage doesn't exist } @@ -164,7 +164,7 @@ PollerConfigFactory.init(); //Force init List pollingOutages = new ArrayList<>(); - for (final org.opennms.netmgt.config.poller.Package pkg : PollerConfigFactory.getInstance().getConfiguration().getPackages()) { + for (final org.opennms.netmgt.config.poller.Package pkg : PollerConfigFactory.getInstance().getExtendedConfiguration().getPackages()) { pollingOutages.addAll(pkg.getOutageCalendars()); } diff --git a/pom.xml b/pom.xml index ae9be6dd6b40..8c0f6015f6b0 100644 --- a/pom.xml +++ b/pom.xml @@ -1747,8 +1747,8 @@ 2.0.9 3.10.0 1.14.0 - 1.2.0-SNAPSHOT - 1.2.0.SNAPSHOT + 1.2.0 + 1.2.0 7.0.0 7.0.0 7.0.0 diff --git a/smoke-test/pom.xml b/smoke-test/pom.xml index 8fa18add99db..854add74c096 100644 --- a/smoke-test/pom.xml +++ b/smoke-test/pom.xml @@ -13,13 +13,20 @@ 2.19.1 2.18 2.22.2 + 2.5.16 0.0.29 2.12.3 2.36 4.3.6 - 3.141.59 - 1.15.3 + 4.6.0 + + 4.6.0-20221104 + 1.17.5 + 4.1.78.Final 5.8.0 + 3.10.0 0 1800 @@ -68,6 +75,7 @@ ${skip.surefire.tests} + false @@ -76,7 +84,9 @@ ${failsafeVersion} - + + ${seleniarm.version} + false + false ${argLine} @@ -333,6 +344,28 @@ jackson-module-jaxb-annotations ${jackson2Version} + + io.netty + netty-bom + ${netty4Version} + pom + import + + + org.codehaus.groovy + groovy + ${groovyVersion} + + + org.codehaus.groovy + groovy-xml + ${groovyVersion} + + + org.codehaus.groovy + groovy-json + ${groovyVersion} + @@ -737,6 +770,13 @@ test + + + com.squareup.okhttp3 + okhttp + ${okhttpVersion} + + diff --git a/smoke-test/src/main/java/org/opennms/smoketest/containers/ElasticsearchContainer.java b/smoke-test/src/main/java/org/opennms/smoketest/containers/ElasticsearchContainer.java index 3e9eee6b3ecf..f8331cd6bc4b 100644 --- a/smoke-test/src/main/java/org/opennms/smoketest/containers/ElasticsearchContainer.java +++ b/smoke-test/src/main/java/org/opennms/smoketest/containers/ElasticsearchContainer.java @@ -36,7 +36,7 @@ public class ElasticsearchContainer extends org.testcontainers.elasticsearch.ElasticsearchContainer { public ElasticsearchContainer() { - super("docker.elastic.co/elasticsearch/elasticsearch-oss:7.6.1"); + super("docker.elastic.co/elasticsearch/elasticsearch-oss:7.10.2"); withEnv("ES_JAVA_OPTS", "-Xms512m -Xmx512m") .withNetwork(Network.SHARED) .withNetworkAliases(OpenNMSContainer.ELASTIC_ALIAS) diff --git a/smoke-test/src/main/java/org/opennms/smoketest/containers/LocalOpenNMS.java b/smoke-test/src/main/java/org/opennms/smoketest/containers/LocalOpenNMS.java new file mode 100644 index 000000000000..280836763dba --- /dev/null +++ b/smoke-test/src/main/java/org/opennms/smoketest/containers/LocalOpenNMS.java @@ -0,0 +1,64 @@ +/******************************************************************************* + * This file is part of OpenNMS(R). + * + * Copyright (C) 2022 The OpenNMS Group, Inc. + * OpenNMS(R) is Copyright (C) 1999-2022 The OpenNMS Group, Inc. + * + * OpenNMS(R) is a registered trademark of The OpenNMS Group, Inc. + * + * OpenNMS(R) is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, + * or (at your option) any later version. + * + * OpenNMS(R) is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with OpenNMS(R). If not, see: + * http://www.gnu.org/licenses/ + * + * For more information contact: + * OpenNMS(R) Licensing + * http://www.opennms.org/ + * http://www.opennms.com/ + *******************************************************************************/ + +package org.opennms.smoketest.containers; + +import java.net.MalformedURLException; +import java.net.URL; + +import org.opennms.smoketest.stacks.OpenNMSProfile; +import org.opennms.smoketest.stacks.StackModel; + +public class LocalOpenNMS extends OpenNMSContainer { + public LocalOpenNMS() { + super(StackModel.newBuilder().build(), OpenNMSProfile.DEFAULT); + } + + @Override + public URL getBaseUrlInternal() { + try { + return new URL(String.format("http://%s:%d/", "host.docker.internal", 8980)); + } catch (MalformedURLException e) { + throw new RuntimeException(e); + } + } + + @Override + public URL getBaseUrlExternal() { + try { + return new URL(String.format("http://%s:%d/", "localhost", 8980)); + } catch (MalformedURLException e) { + throw new RuntimeException(e); + } + } + + @Override + public String toString() { + return "I'm an empty opennms container, y'all!"; + } +} diff --git a/smoke-test/src/main/java/org/opennms/smoketest/containers/MinionContainer.java b/smoke-test/src/main/java/org/opennms/smoketest/containers/MinionContainer.java index 4ac273a7274a..6d0ff7ad7dd0 100644 --- a/smoke-test/src/main/java/org/opennms/smoketest/containers/MinionContainer.java +++ b/smoke-test/src/main/java/org/opennms/smoketest/containers/MinionContainer.java @@ -56,7 +56,6 @@ import org.opennms.smoketest.stacks.NetworkProtocol; import org.opennms.smoketest.stacks.StackModel; import org.opennms.smoketest.utils.DevDebugUtils; -import org.opennms.smoketest.utils.KarafShellUtils; import org.opennms.smoketest.utils.OverlayUtils; import org.opennms.smoketest.utils.RestHealthClient; import org.opennms.smoketest.utils.SshClient; @@ -101,11 +100,26 @@ private MinionContainer(final StackModel model, final String id, final String lo this.id = Objects.requireNonNull(id); this.location = Objects.requireNonNull(location); - this.container = withExposedPorts(MINION_DEBUG_PORT, MINION_SSH_PORT, MINION_SYSLOG_PORT, MINION_SNMP_TRAP_PORT, MINION_TELEMETRY_FLOW_PORT, MINION_TELEMETRY_IPFIX_TCP_PORT, MINION_TELEMETRY_JTI_PORT, MINION_TELEMETRY_NXOS_PORT, MINION_JETTY_PORT) + Integer[] tcpPorts = { + MINION_DEBUG_PORT, + MINION_SSH_PORT, + MINION_TELEMETRY_FLOW_PORT, + MINION_TELEMETRY_IPFIX_TCP_PORT, + MINION_JETTY_PORT, + }; + int[] udpPorts = { + MINION_SYSLOG_PORT, + MINION_SNMP_TRAP_PORT, + MINION_TELEMETRY_FLOW_PORT, + MINION_TELEMETRY_JTI_PORT, + MINION_TELEMETRY_NXOS_PORT, + }; + + this.container = withExposedPorts(tcpPorts) .withCreateContainerCmdModifier(cmd -> { final CreateContainerCmd createCmd = (CreateContainerCmd)cmd; TestContainerUtils.setGlobalMemAndCpuLimits(createCmd); - TestContainerUtils.exposePortsAsUdp(createCmd, MINION_SNMP_TRAP_PORT, MINION_TELEMETRY_FLOW_PORT, MINION_TELEMETRY_JTI_PORT, MINION_TELEMETRY_NXOS_PORT); + TestContainerUtils.exposePortsAsUdp(createCmd, udpPorts); }) .withEnv("OPENNMS_HTTP_USER", "admin") .withEnv("OPENNMS_HTTP_PASS", "admin") diff --git a/smoke-test/src/main/java/org/opennms/smoketest/containers/OpenNMSContainer.java b/smoke-test/src/main/java/org/opennms/smoketest/containers/OpenNMSContainer.java index 4fe9013f441f..44d309b0140c 100644 --- a/smoke-test/src/main/java/org/opennms/smoketest/containers/OpenNMSContainer.java +++ b/smoke-test/src/main/java/org/opennms/smoketest/containers/OpenNMSContainer.java @@ -135,6 +135,7 @@ public class OpenNMSContainer extends GenericContainer implements KarafContainer private final OpenNMSProfile profile; private final Path overlay; private int generatedUserId = -1; + private boolean afterTestCalled = false; public OpenNMSContainer(StackModel model, OpenNMSProfile profile) { super("horizon"); @@ -154,8 +155,10 @@ public OpenNMSContainer(StackModel model, OpenNMSProfile profile) { this.withEnv("OPENNMS_TIMESERIES_STRATEGY", model.getTimeSeriesStrategy().name().toLowerCase()); } - final Integer[] exposedPorts = new ArrayList<>(networkProtocolMap.values()) - .toArray(new Integer[0]); + final Integer[] exposedPorts = networkProtocolMap.entrySet().stream() + .filter(e -> InternetProtocol.TCP.equals(e.getKey().getIpProtocol())) + .map(Map.Entry::getValue) + .toArray(Integer[]::new); final int[] exposedUdpPorts = networkProtocolMap.entrySet().stream() .filter(e -> InternetProtocol.UDP.equals(e.getKey().getIpProtocol())) .mapToInt(Map.Entry::getValue) @@ -300,7 +303,7 @@ private void writeOverlay(Path home) throws IOException { /** * @return the URL in a form consumable by containers networked with this one using the alias and internal port */ - public static URL getBaseUrlInternal() { + public URL getBaseUrlInternal() { try { return new URL(String.format("http://%s:%d/", ALIAS, OPENNMS_WEB_PORT)); } catch (MalformedURLException e) { @@ -467,6 +470,11 @@ public int getGeneratedUserId() { @Override public void afterTest(final TestDescription description, final Optional throwable) { + if (afterTestCalled) { + LOG.warn("afterTest has already been called, not running on subsequent calls"); + return; + } + afterTestCalled = true; KarafShellUtils.saveCoverage(this, description.getFilesystemFriendlyName(), ALIAS); retainLogsfNeeded(description.getFilesystemFriendlyName(), !throwable.isPresent()); } @@ -475,10 +483,12 @@ private void retainLogsfNeeded(String prefix, boolean succeeded) { LOG.info("Triggering thread dump..."); DevDebugUtils.triggerThreadDump(this); LOG.info("Gathering logs..."); - copyLogs(this, prefix); + var logs = copyLogs(this, prefix); + LOG.info("Logs: {}", logs.toUri()); + LOG.info("Console log: {}", logs.resolve(DevDebugUtils.CONTAINER_STDOUT_STDERR).toUri()); } - private static void copyLogs(OpenNMSContainer container, String prefix) { + private static Path copyLogs(OpenNMSContainer container, String prefix) { // List of known log files we expect to find in the container final List logFiles = Arrays.asList("alarmd.log", "collectd.log", @@ -490,13 +500,15 @@ private static void copyLogs(OpenNMSContainer container, String prefix) { "provisiond.log", "trapd.log", "web.log"); + Path targetLogFolder = Paths.get("target", "logs", prefix, ALIAS); DevDebugUtils.copyLogs(container, // dest - Paths.get("target", "logs", prefix, ALIAS), + targetLogFolder, // source folder Paths.get("/opt", ALIAS, "logs"), // log files logFiles); + return targetLogFolder; } } diff --git a/smoke-test/src/main/java/org/opennms/smoketest/selenium/AbstractOpenNMSSeleniumHelper.java b/smoke-test/src/main/java/org/opennms/smoketest/selenium/AbstractOpenNMSSeleniumHelper.java index 25f081f8944a..16b531cfbc7c 100644 --- a/smoke-test/src/main/java/org/opennms/smoketest/selenium/AbstractOpenNMSSeleniumHelper.java +++ b/smoke-test/src/main/java/org/opennms/smoketest/selenium/AbstractOpenNMSSeleniumHelper.java @@ -45,6 +45,7 @@ import java.nio.file.Path; import java.nio.file.Paths; import java.nio.file.StandardCopyOption; +import java.time.Duration; import java.util.ArrayList; import java.util.List; import java.util.Objects; @@ -148,8 +149,8 @@ protected void starting(final Description description) { } getDriver().manage().window().setPosition(new Point(0,0)); getDriver().manage().window().maximize(); - wait = new WebDriverWait(getDriver(), TimeUnit.SECONDS.convert(LOAD_TIMEOUT, TimeUnit.MILLISECONDS)); - requisitionWait = new WebDriverWait(getDriver(), TimeUnit.SECONDS.convert(REQ_TIMEOUT, TimeUnit.MILLISECONDS)); + wait = new WebDriverWait(getDriver(), Duration.ofMillis(LOAD_TIMEOUT)); + requisitionWait = new WebDriverWait(getDriver(), Duration.ofMillis(REQ_TIMEOUT)); login(); @@ -232,7 +233,7 @@ public WebDriver.Timeouts setImplicitWait(final long time, final TimeUnit unit) } protected WebDriverWait waitFor(final long seconds) { - return new WebDriverWait(getDriver(), seconds); + return new WebDriverWait(getDriver(), Duration.ofSeconds(seconds)); } protected void waitForClose(final By selector) { @@ -422,8 +423,8 @@ protected String handleAlert(final String expectedText) { } protected void setChecked(final By by) { - LOG.debug("setChecked: locator=", by); - final WebElement element = getDriver().findElement(by); + LOG.debug("setChecked: locator={}", by); + final var element = scrollToElement(by); if (element.isSelected()) { return; } else { @@ -432,8 +433,8 @@ protected void setChecked(final By by) { } protected void setUnchecked(final By by) { - LOG.debug("setUnchecked: locator=", by); - final WebElement element = getDriver().findElement(by); + LOG.debug("setUnchecked: locator={}", by); + final var element = scrollToElement(by); if (element.isSelected()) { element.click(); } else { @@ -454,7 +455,7 @@ protected void clickMenuItem(final String menuItemText, final String submenuItem // Repeat the process altering the offset slightly everytime final AtomicInteger offset = new AtomicInteger(10); - final WebDriverWait shortWait = new WebDriverWait(getDriver(), 1); + final WebDriverWait shortWait = new WebDriverWait(getDriver(), Duration.ofSeconds(1)); Unreliables.retryUntilSuccess(timeout, TimeUnit.SECONDS, () -> { final Actions action = new Actions(getDriver()); @@ -810,7 +811,7 @@ public WebElement enterText(final By selector, final CharSequence... text) { boolean found = false; int count = 0; - final WebDriverWait shortWait = new WebDriverWait(getDriver(), 10); + final WebDriverWait shortWait = new WebDriverWait(getDriver(), Duration.ofSeconds(10)); do { LOG.debug("enterText({},{}): {}", selector, text, ++count); @@ -894,7 +895,7 @@ protected WebElement scrollToElement(final WebElement element) { protected static WebElement scrollToElement(final WebDriver driver, final WebElement element) { LOG.debug("scrollToElement: element={}", element); - final List bounds = getBoundedRectangleOfElement(driver, element); + final List bounds = getAbsoluteBoundedRectangleOfElement(driver, element); final int windowHeight = driver.manage().window().getSize().getHeight(); final JavascriptExecutor je = (JavascriptExecutor)driver; je.executeScript("window.scrollTo(0, " + (bounds.get(1) - (windowHeight/2)) + ");"); @@ -934,10 +935,29 @@ protected WebElement scrollToElement(final By by, final boolean waitForElement) private WebElement doScroll(final WebDriver driver, final WebElement element) { try { - final List bounds = getBoundedRectangleOfElement(driver, element); - final int windowHeight = driver.manage().window().getSize().getHeight(); + final List bounds = getAbsoluteBoundedRectangleOfElement(driver, element); + final var windowSize = driver.manage().window().getSize(); final JavascriptExecutor je = (JavascriptExecutor)driver; - je.executeScript("window.scrollTo(0, " + (bounds.get(1) - (windowHeight/2)) + ");"); + + // If the left and right bounds of the element are within the width of the page, don't scroll (keep x = 0) + final int x; + if (bounds.get(0) < windowSize.width && (bounds.get(0) + bounds.get(2)) < windowSize.width) { + x = 0; + } else { + x = bounds.get(0) - (windowSize.width / 2); + } + + // Same as above + final int y; + if (bounds.get(1) < windowSize.height && (bounds.get(1) + bounds.get(3)) < windowSize.height) { + y = 0; + } else { + y = bounds.get(1) - (windowSize.height / 2); + } + + je.executeScript("window.scrollTo(" + x + ", " + y + ");"); + LOG.debug("doScroll: element={}, x={}, y={}, windowSize={}x{}, bounds={}", + element, x, y, windowSize.width, windowSize.height, bounds); return element; } catch (final Exception e) { return null; @@ -977,16 +997,17 @@ public void clickElementUntilElementDisappears(final By click, final By disappea } @SuppressWarnings("unchecked") - protected static List getBoundedRectangleOfElement(final WebDriver driver, final WebElement we) { - LOG.debug("getBoundedRectangleOfElement: element={}", we); + protected static List getAbsoluteBoundedRectangleOfElement(final WebDriver driver, final WebElement we) { final JavascriptExecutor je = (JavascriptExecutor)driver; final List bounds = (ArrayList) je.executeScript( "var rect = arguments[0].getBoundingClientRect();" + - "return [ '' + parseInt(rect.left), '' + parseInt(rect.top), '' + parseInt(rect.width), '' + parseInt(rect.height) ]", we); - final List ret = new ArrayList<>(); + "return [ '' + parseInt(rect.left + window.scrollX), '' + parseInt(rect.top + window.scrollY), '' + parseInt(rect.width), '' + parseInt(rect.height) ]", we); + assertEquals("bounding box array size returned from Javascript", 4, bounds.size()); + final List ret = new ArrayList<>(bounds.size()); for (final String entry : bounds) { ret.add(Integer.valueOf(entry)); } + LOG.debug("getAbsoluteBoundedRectangleOfElement: element={}, ret={}", we, ret); return ret; } @@ -1007,7 +1028,7 @@ protected void clickId(final String id, final boolean refresh) throws Interrupte try { element = findElementById(id); } catch (final Throwable t) { - LOG.warn("Failed to locate id=" + id, t); + LOG.warn("Failed to locate id={}", id, t); } final long waitUntil = System.currentTimeMillis() + 60000; @@ -1023,7 +1044,7 @@ protected void clickId(final String id, final boolean refresh) throws Interrupte wait.until(ExpectedConditions.elementToBeClickable(By.id(id))); element = findElementById(id); } catch (final Throwable t) { - LOG.warn("Failed to locate id=" + id, t); + LOG.warn("Failed to locate id={}", id, t); } } sleepQuietly(50); @@ -1219,13 +1240,13 @@ protected void createTestRequisition() { } protected void createRequisition(final String foreignSource) { - LOG.debug("Creating empty requisition: " + foreignSource); + LOG.debug("Creating empty requisition: {}", foreignSource); final String emptyRequisition = ""; createRequisition(foreignSource, emptyRequisition, 0); } protected void createRequisition(final String foreignSource, final String xml, final int expectedNodes) { - LOG.debug("Creating requisition from XML: " + foreignSource); + LOG.debug("Creating requisition from XML: {}", foreignSource); try { final String foreignSourceUrlFragment = URLEncoder.encode(foreignSource, "UTF-8"); @@ -1467,7 +1488,7 @@ public Boolean apply(final WebDriver input) { return true; } } catch (final Exception e) { - LOG.warn("WaitForNodesInRequisition: foreignSource={}, expectedNodes={}: Failed to get nodes in requisition {}.", m_foreignSource, m_numberToMatch, e); + LOG.warn("WaitForNodesInRequisition: foreignSource={}, expectedNodes={}: Failed to get nodes in requisition.", m_foreignSource, m_numberToMatch, e); } return null; } diff --git a/smoke-test/src/main/java/org/opennms/smoketest/selenium/AbstractPage.java b/smoke-test/src/main/java/org/opennms/smoketest/selenium/AbstractPage.java index 5d5395432837..40c06a2dd537 100644 --- a/smoke-test/src/main/java/org/opennms/smoketest/selenium/AbstractPage.java +++ b/smoke-test/src/main/java/org/opennms/smoketest/selenium/AbstractPage.java @@ -34,6 +34,7 @@ import org.openqa.selenium.support.ui.ExpectedCondition; import org.openqa.selenium.support.ui.WebDriverWait; +import java.time.Duration; import java.util.List; import java.util.Objects; import java.util.concurrent.TimeUnit; @@ -109,7 +110,7 @@ protected WebElement enterText(final By by, final String text) { protected void waitUntil(ExpectedCondition condition) { try { testCase.setImplicitWait(LONG_WAIT_SECONDS, TimeUnit.SECONDS); - new WebDriverWait(getDriver(), LONG_WAIT_SECONDS).until(condition); + new WebDriverWait(getDriver(), Duration.ofSeconds(LONG_WAIT_SECONDS)).until(condition); } finally { testCase.setImplicitWait(); } diff --git a/smoke-test/src/main/java/org/opennms/smoketest/stacks/OpenNMSStack.java b/smoke-test/src/main/java/org/opennms/smoketest/stacks/OpenNMSStack.java index 9c68da61d36b..2a262eee7639 100644 --- a/smoke-test/src/main/java/org/opennms/smoketest/stacks/OpenNMSStack.java +++ b/smoke-test/src/main/java/org/opennms/smoketest/stacks/OpenNMSStack.java @@ -39,6 +39,7 @@ import org.junit.runners.model.Statement; import org.opennms.smoketest.containers.CassandraContainer; import org.opennms.smoketest.containers.ElasticsearchContainer; +import org.opennms.smoketest.containers.LocalOpenNMS; import org.opennms.smoketest.containers.MinionContainer; import org.opennms.smoketest.containers.OpenNMSContainer; import org.opennms.smoketest.containers.PostgreSQLContainer; @@ -62,6 +63,11 @@ */ public final class OpenNMSStack implements TestRule { + /** + * This creates an empty OpenNMS stack for testing with locally-installed components outside of Docker. + */ + public static final OpenNMSStack NONE = new OpenNMSStack(); + public static final OpenNMSStack MINIMAL = OpenNMSStack.withModel(StackModel.newBuilder() .withOpenNMS(OpenNMSProfile.newBuilder() .withFile("empty-discovery-configuration.xml", "etc/discovery-configuration.xml") @@ -93,7 +99,7 @@ public static OpenNMSStack withModel(StackModel model) { private final TestRule delegateTestRule; - private final PostgreSQLContainer postgreSQLContainer = new PostgreSQLContainer(); + private final PostgreSQLContainer postgreSQLContainer; private final OpenNMSContainer opennmsContainer; @@ -107,9 +113,25 @@ public static OpenNMSStack withModel(StackModel model) { private final List sentinelContainers; + /** + * Create an empty OpenNMS stack for testing with locally-installed components outside of Docker. + */ + private OpenNMSStack() { + postgreSQLContainer = null; + elasticsearchContainer = null; + kafkaContainer = null; + cassandraContainer = null; + opennmsContainer = new LocalOpenNMS(); + minionContainers = Collections.EMPTY_LIST; + sentinelContainers = Collections.EMPTY_LIST; + + delegateTestRule = RuleChain.emptyRuleChain(); + return; + } + private OpenNMSStack(StackModel model) { - RuleChain chain = RuleChain - .outerRule(postgreSQLContainer); + postgreSQLContainer = new PostgreSQLContainer(); + RuleChain chain = RuleChain.outerRule(postgreSQLContainer); if (model.isElasticsearchEnabled()) { elasticsearchContainer = new ElasticsearchContainer(); diff --git a/smoke-test/src/main/java/org/opennms/smoketest/stacks/StackModel.java b/smoke-test/src/main/java/org/opennms/smoketest/stacks/StackModel.java index 60c5e8d47c27..b35d433c0071 100644 --- a/smoke-test/src/main/java/org/opennms/smoketest/stacks/StackModel.java +++ b/smoke-test/src/main/java/org/opennms/smoketest/stacks/StackModel.java @@ -137,7 +137,7 @@ public Builder withMinions(MinionProfile... minions) { /** * Enable a Minion using the given configuration. * - * @param minion configuration to use + * @param configuration minion configuration to use * @return this builder */ @@ -149,7 +149,7 @@ public Builder withMinion(final Map configuration) { /** * Enable many Minions using the given configurations. * - * @param minions configurations to use + * @param configurations minions configurations to use * @return this builder */ diff --git a/smoke-test/src/main/java/org/opennms/smoketest/ui/framework/Toggle.java b/smoke-test/src/main/java/org/opennms/smoketest/ui/framework/Toggle.java index 8dee54310359..0d81716eace8 100644 --- a/smoke-test/src/main/java/org/opennms/smoketest/ui/framework/Toggle.java +++ b/smoke-test/src/main/java/org/opennms/smoketest/ui/framework/Toggle.java @@ -28,6 +28,7 @@ package org.opennms.smoketest.ui.framework; +import java.time.Duration; import java.util.List; import org.openqa.selenium.By; @@ -53,7 +54,7 @@ public boolean isOn() { public void toggle() { boolean previousState = isOn(); execute(() -> driver.findElement(By.id(elementId))).click(); - new WebDriverWait(driver, 5, 500).until((Function) webDriver -> previousState != isOn()); + new WebDriverWait(driver, Duration.ofSeconds(5), Duration.ofMillis(500)).until((Function) webDriver -> previousState != isOn()); } public void setValue(boolean value) { diff --git a/smoke-test/src/main/java/org/opennms/smoketest/utils/DevDebugUtils.java b/smoke-test/src/main/java/org/opennms/smoketest/utils/DevDebugUtils.java index 81af42a439f7..756c3dc9e722 100644 --- a/smoke-test/src/main/java/org/opennms/smoketest/utils/DevDebugUtils.java +++ b/smoke-test/src/main/java/org/opennms/smoketest/utils/DevDebugUtils.java @@ -65,6 +65,7 @@ public class DevDebugUtils { private static final TimeLimiter LIMITER = SimpleTimeLimiter.create(Executors.newCachedThreadPool(new ThreadFactoryBuilder() .setNameFormat("dev-debug-utils-pool-%d") .build())); + public static final String CONTAINER_STDOUT_STDERR = "container_stdout_stderr"; public static String convertToContainerAccessibleUrl(String url, String defaultAlias, int defaultPort) { final URI uri; @@ -104,6 +105,11 @@ public static void setupMavenRepoBind(Container container, String containerPath) } public static void triggerThreadDump(Container container) { + if (!container.isRunning()) { + LOG.warn("triggerThreadDump can only be used on a running container. Container is not running: {}", container); + return; + } + try { LIMITER.callWithTimeout(() -> { LOG.info("kill -3 -1"); @@ -125,7 +131,7 @@ public static void copyLogs(Container container, Path targetLogFolder, Path sour throw new RuntimeException("Failed to create " + targetLogFolder, e); } - final Path containerLogOutputFile = targetLogFolder.resolve("container_stdout_stderr"); + final Path containerLogOutputFile = targetLogFolder.resolve(CONTAINER_STDOUT_STDERR); try { LIMITER.runWithTimeout(() -> { try { diff --git a/smoke-test/src/main/java/org/opennms/smoketest/utils/OpenNMSImageNameSubstitutor.java b/smoke-test/src/main/java/org/opennms/smoketest/utils/OpenNMSImageNameSubstitutor.java new file mode 100644 index 000000000000..7f22f3bba02b --- /dev/null +++ b/smoke-test/src/main/java/org/opennms/smoketest/utils/OpenNMSImageNameSubstitutor.java @@ -0,0 +1,64 @@ +/******************************************************************************* + * This file is part of OpenNMS(R). + * + * Copyright (C) 2022 The OpenNMS Group, Inc. + * OpenNMS(R) is Copyright (C) 1999-2022 The OpenNMS Group, Inc. + * + * OpenNMS(R) is a registered trademark of The OpenNMS Group, Inc. + * + * OpenNMS(R) is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, + * or (at your option) any later version. + * + * OpenNMS(R) is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with OpenNMS(R). If not, see: + * http://www.gnu.org/licenses/ + * + * For more information contact: + * OpenNMS(R) Licensing + * http://www.opennms.org/ + * http://www.opennms.com/ + *******************************************************************************/ + +package org.opennms.smoketest.utils; + +import org.junit.Assert; +import org.testcontainers.utility.DockerImageName; +import org.testcontainers.utility.ImageNameSubstitutor; + +public class OpenNMSImageNameSubstitutor extends ImageNameSubstitutor { + @Override + public DockerImageName apply(DockerImageName original) { + // Use the multi-arch (ARM64, ARM/v7, and AMD64) Seleniarm images for + // Selenium 4.x and higher. + // Ref: https://github.com/seleniumhq-community/docker-seleniarm/releases + // Note: "-debug" images are used to get VNC recording for 3.x selenium so + // use a startsWith match on the name. + if (original.getUnversionedPart().startsWith("selenium/standalone-firefox") + && original.getVersionPart().startsWith("4.")) { + return DockerImageName.parse("seleniarm/standalone-firefox:" + getSeleniarmVersion()); + } else if (original.getUnversionedPart().startsWith("selenium/standalone-chrome") + && original.getVersionPart().startsWith("4.")) { + return DockerImageName.parse("seleniarm/standalone-chromium:" + getSeleniarmVersion()); + } else { + return original; + } + } + + private String getSeleniarmVersion() { + var version = System.getProperty("seleniarm.version"); + Assert.assertNotNull("The system property 'seleniarm.version' needs to be set to do browser testing. Make sure it is set in pom.xml.", version); + return version; + } + + @Override + protected String getDescription() { + return "OpenNMS image name substitutor"; + } +} diff --git a/smoke-test/src/main/java/org/opennms/smoketest/utils/TestContainerUtils.java b/smoke-test/src/main/java/org/opennms/smoketest/utils/TestContainerUtils.java index 2cced94decc5..632af78a8eae 100644 --- a/smoke-test/src/main/java/org/opennms/smoketest/utils/TestContainerUtils.java +++ b/smoke-test/src/main/java/org/opennms/smoketest/utils/TestContainerUtils.java @@ -32,8 +32,8 @@ import java.io.InputStreamReader; import java.nio.charset.StandardCharsets; import java.nio.file.Path; -import java.util.HashMap; -import java.util.Map; +import java.util.ArrayList; +import java.util.List; import java.util.concurrent.TimeUnit; import org.slf4j.Logger; @@ -46,6 +46,7 @@ import com.github.dockerjava.api.model.ExposedPort; import com.github.dockerjava.api.model.HostConfig; import com.github.dockerjava.api.model.InternetProtocol; +import com.github.dockerjava.api.model.Ports; import com.google.common.io.CharStreams; public class TestContainerUtils { @@ -126,24 +127,24 @@ public static void restartContainer(Container container) { * @param ports */ public static void exposePortsAsUdp(CreateContainerCmd cmd, int... ports) { - final ExposedPort[] exposedPorts = cmd.getExposedPorts(); - if (exposedPorts == null && ports.length > 0) { - throw new RuntimeException("There are 1+ ports to convert to UDP, but no exposed ports were found."); + // Add already exposed TCP ports + List exposedPorts = new ArrayList<>(); + for (ExposedPort p : cmd.getExposedPorts()) { + exposedPorts.add(p); } - - // Index the ports for easy lookup - final Map portToIdx = new HashMap<>(); - for (int i = 0; i < exposedPorts.length; i++) { - portToIdx.put(exposedPorts[i].getPort(), i); + // Add our UDP portts + for (int port : ports) { + exposedPorts.add(ExposedPort.udp(port)); } + cmd.withExposedPorts(exposedPorts); + // Add previous port bindings + Ports portBindings = cmd.getHostConfig().getPortBindings(); + // Add port bindings for our UDP ports for (int port : ports) { - final Integer idx = portToIdx.get(port); - if (idx == null) { - throw new RuntimeException("No exposed port entry found for: " + port); - } - exposedPorts[idx] = new ExposedPort(port, InternetProtocol.UDP); + portBindings.bind(ExposedPort.udp(port), Ports.Binding.empty()); } + cmd.getHostConfig().withPortBindings(portBindings); } /** @@ -155,10 +156,16 @@ public static void exposePortsAsUdp(CreateContainerCmd cmd, int... ports) { */ // public static int getMappedUdpPort(Container container, int port) { - final String hostPortSpec = container.getContainerInfo().getNetworkSettings().getPorts() - .getBindings().get(new ExposedPort(port, InternetProtocol.UDP))[0].getHostPortSpec(); - final int hostPort = Integer.parseInt(hostPortSpec); - return hostPort; + final ExposedPort searchForPort = new ExposedPort(port, InternetProtocol.UDP); + final Ports.Binding[] bindings = container.getContainerInfo() + .getNetworkSettings() + .getPorts() + .getBindings() + .get(searchForPort); + if (bindings == null || bindings.length == 0) { + throw new RuntimeException("No exposed port bindings found for " + searchForPort); + } + return Integer.parseInt(bindings[0].getHostPortSpec()); } } diff --git a/smoke-test/src/main/resources/testcontainers.properties b/smoke-test/src/main/resources/testcontainers.properties new file mode 100644 index 000000000000..d1d73f7bb2e2 --- /dev/null +++ b/smoke-test/src/main/resources/testcontainers.properties @@ -0,0 +1 @@ +image.substitutor=org.opennms.smoketest.utils.OpenNMSImageNameSubstitutor diff --git a/smoke-test/src/test/java/org/opennms/smoketest/AngularLoginRedirectIT.java b/smoke-test/src/test/java/org/opennms/smoketest/AngularLoginRedirectIT.java index a37e3d1542e3..69450ba1a5e1 100644 --- a/smoke-test/src/test/java/org/opennms/smoketest/AngularLoginRedirectIT.java +++ b/smoke-test/src/test/java/org/opennms/smoketest/AngularLoginRedirectIT.java @@ -31,6 +31,7 @@ import static org.junit.Assert.assertEquals; import java.io.IOException; +import java.time.Duration; import java.util.List; import java.util.Objects; import java.util.Set; @@ -157,7 +158,7 @@ public void verifyRedirectToLogin() throws IOException { sleep(SLEEP_TIME); // Verify we have been forwarded to the login page - new WebDriverWait(driver, 5).until(input -> { + new WebDriverWait(driver, Duration.ofSeconds(5)).until(input -> { LOG.info("{}: Verify redirect to login.jsp occurred", eachCheck.url); return driver.getCurrentUrl().matches("http://opennms:8980/opennms/login\\.jsp[?;].*"); } diff --git a/smoke-test/src/test/java/org/opennms/smoketest/BSMAdminIT.java b/smoke-test/src/test/java/org/opennms/smoketest/BSMAdminIT.java index dff29efc4a0c..bf2d410decd1 100644 --- a/smoke-test/src/test/java/org/opennms/smoketest/BSMAdminIT.java +++ b/smoke-test/src/test/java/org/opennms/smoketest/BSMAdminIT.java @@ -29,6 +29,7 @@ package org.opennms.smoketest; import java.text.SimpleDateFormat; +import java.time.Duration; import java.util.Date; import java.util.List; import java.util.Objects; @@ -784,7 +785,7 @@ private void verifyElementPresent(String text) { * @param by */ private static void verifyElementNotPresent(AbstractOpenNMSSeleniumHelper testCase, final By by) { - new WebDriverWait(testCase.getDriver(), 5 /* seconds */).until( + new WebDriverWait(testCase.getDriver(), Duration.ofSeconds(5)).until( ExpectedConditions.not(new ExpectedCondition() { @Nullable @Override diff --git a/smoke-test/src/test/java/org/opennms/smoketest/ClassificationRulePageIT.java b/smoke-test/src/test/java/org/opennms/smoketest/ClassificationRulePageIT.java index df568b08d04f..a18459c5022c 100644 --- a/smoke-test/src/test/java/org/opennms/smoketest/ClassificationRulePageIT.java +++ b/smoke-test/src/test/java/org/opennms/smoketest/ClassificationRulePageIT.java @@ -33,6 +33,7 @@ import static org.hamcrest.Matchers.is; import static org.junit.Assert.assertThat; +import java.time.Duration; import java.util.ArrayList; import java.util.List; import java.util.NoSuchElementException; @@ -445,7 +446,7 @@ public Page(String baseUrl) { public Page open() { driver.get(url); - new WebDriverWait(driver, 15).until((driver) -> getTabs().size() == expectedTabs.size()); + new WebDriverWait(driver, Duration.ofSeconds(15)).until((driver) -> getTabs().size() == expectedTabs.size()); return this; } @@ -539,7 +540,7 @@ public Tab(Page page, String name, String label, int count, boolean isSelected) public void click() { getElement().click(); - new WebDriverWait(driver, 5).until((ExpectedCondition) input -> page.getTab(name).isActive()); + new WebDriverWait(driver, Duration.ofSeconds(5)).until((ExpectedCondition) input -> page.getTab(name).isActive()); } public WebElement getElement() { @@ -635,7 +636,7 @@ public void setEnabled(boolean enabled) { sleep(DEFAULT_WAIT_TIME); // Verify the tab was actually enabled/disabled - new WebDriverWait(driver, 5) + new WebDriverWait(driver, Duration.ofSeconds(5)) .until(input -> { final Group group = tab.getGroup(name); return enabled == group.isEnabled(); @@ -763,7 +764,7 @@ public void cancel() { // Ensure dialog closes private void ensureClosed() { - execute(() -> new WebDriverWait(driver, 5).until(ExpectedConditions.numberOfElementsToBe(By.id("ruleModal"), 0))); + execute(() -> new WebDriverWait(driver, Duration.ofSeconds(5)).until(ExpectedConditions.numberOfElementsToBe(By.id("ruleModal"), 0))); } private void setInput(String id, String input) { @@ -810,7 +811,7 @@ public RuleModal newModal() { .open(() -> { // Click add rule button clickElement(By.id("action.addRule")); - new WebDriverWait(driver, 5).until(pageContainsText("Create Classification Rule")); + new WebDriverWait(driver, Duration.ofSeconds(5)).until(pageContainsText("Create Classification Rule")); }); } @@ -819,7 +820,7 @@ public RuleModal editModal(int position) { .open(() -> { // click edit button findElementById("action." + position + ".edit").click(); - new WebDriverWait(driver, 5).until(pageContainsText("Edit Classification Rule")); + new WebDriverWait(driver, Duration.ofSeconds(5)).until(pageContainsText("Edit Classification Rule")); }); } @@ -860,7 +861,7 @@ public void deleteGroup(int position) { execute(() -> { final String deleteActionId = "action." + position + ".delete"; findElementById(deleteActionId).click(); - new WebDriverWait(driver, 5).until(ExpectedConditions.numberOfElementsToBe(By.id(deleteActionId), 0)); + new WebDriverWait(driver, Duration.ofSeconds(5)).until(ExpectedConditions.numberOfElementsToBe(By.id(deleteActionId), 0)); return null; }); } @@ -874,7 +875,7 @@ public void navigateToPage(int page) { sleep(DEFAULT_WAIT_TIME); // Verify the page is actually active - execute(() -> new WebDriverWait(driver, 5).until( + execute(() -> new WebDriverWait(driver, Duration.ofSeconds(5)).until( ExpectedConditions.visibilityOf( findElementByXpath("//li[contains(@class, 'active')]/a[contains(text(), '" + page + "')]")))); } diff --git a/smoke-test/src/test/java/org/opennms/smoketest/DatabaseReportPageIT.java b/smoke-test/src/test/java/org/opennms/smoketest/DatabaseReportPageIT.java index f9fff686bc66..8101c8c7ab57 100644 --- a/smoke-test/src/test/java/org/opennms/smoketest/DatabaseReportPageIT.java +++ b/smoke-test/src/test/java/org/opennms/smoketest/DatabaseReportPageIT.java @@ -615,7 +615,7 @@ public void setTriggerName(String triggerName) { public void edit(DeliveryOptions deliveryOptions, String cronExpression) { LOG.debug("Try updating report schedule for trigger '{}' with delivery options {} and cron expression '{}'", triggerName, deliveryOptions, cronExpression); - final WebDriverWait webDriverWait = new WebDriverWait(getDriver(), 30, 1000); + final WebDriverWait webDriverWait = new WebDriverWait(getDriver(), Duration.ofSeconds(30), Duration.ofMillis(1000)); execute(() -> findElementById("action.edit." + triggerName)).click(); webDriverWait.until((driver) -> execute(() -> driver.findElements(By.id("loading-bar-spinner")).isEmpty()) && findElementById("action.update." + triggerName) != null diff --git a/smoke-test/src/test/java/org/opennms/smoketest/GeocoderServiceConfigurationPageIT.java b/smoke-test/src/test/java/org/opennms/smoketest/GeocoderServiceConfigurationPageIT.java index 8006d75888a0..980b96183786 100644 --- a/smoke-test/src/test/java/org/opennms/smoketest/GeocoderServiceConfigurationPageIT.java +++ b/smoke-test/src/test/java/org/opennms/smoketest/GeocoderServiceConfigurationPageIT.java @@ -32,6 +32,7 @@ import static org.junit.Assert.assertNull; import static org.junit.Assert.assertThat; +import java.time.Duration; import java.util.ArrayList; import java.util.List; import java.util.NoSuchElementException; @@ -230,7 +231,7 @@ public Page(String baseUrl) { public Page open() { driver.get(url); - new WebDriverWait(driver, 5).until(driver -> getTabs().size() == expectedTabs.size()); + new WebDriverWait(driver, Duration.ofSeconds(5)).until(driver -> getTabs().size() == expectedTabs.size()); return this; } @@ -290,7 +291,7 @@ public Tab(Page page, String name, String label) { public void click() { getElement().click(); sleep(2000); - new WebDriverWait(driver, 5).until((ExpectedCondition) input -> page.getTab(name).isActive()); + new WebDriverWait(driver, Duration.ofSeconds(5)).until((ExpectedCondition) input -> page.getTab(name).isActive()); } public WebElement getElement() { @@ -360,7 +361,7 @@ public boolean isDirty() { public void update() { getSaveButtonElement().click(); - new WebDriverWait(driver, 5, 500).until(webDriver -> !isDirty()); + new WebDriverWait(driver, Duration.ofSeconds(5), Duration.ofMillis(500)).until(webDriver -> !isDirty()); } WebElement getSaveButtonElement() { diff --git a/smoke-test/src/test/java/org/opennms/smoketest/GrafanaEndpointPageIT.java b/smoke-test/src/test/java/org/opennms/smoketest/GrafanaEndpointPageIT.java index 69579032e1fc..934d7d41ec35 100644 --- a/smoke-test/src/test/java/org/opennms/smoketest/GrafanaEndpointPageIT.java +++ b/smoke-test/src/test/java/org/opennms/smoketest/GrafanaEndpointPageIT.java @@ -34,6 +34,7 @@ import static org.junit.Assert.assertThat; import java.io.IOException; +import java.time.Duration; import java.util.List; import java.util.Objects; import java.util.stream.Collectors; @@ -165,7 +166,7 @@ public Page(String baseUrl) { public Page open() { driver.get(url); - new WebDriverWait(driver, 5).until(pageContainsText("Grafana Endpoints")); + new WebDriverWait(driver, Duration.ofSeconds(5)).until(pageContainsText("Grafana Endpoints")); return this; } @@ -183,7 +184,7 @@ public List getEndpoints() { // Click reveal to get the API KEY and afterwards click again to hide new Button(getDriver(), "action.revealApiKey." + id).click(); - new WebDriverWait(driver, 5).until(webDriver -> !row.findElements(By.xpath("./td")).get(2).getText().contains("****")); + new WebDriverWait(driver, Duration.ofSeconds(5)).until(webDriver -> !row.findElements(By.xpath("./td")).get(2).getText().contains("****")); final String apiKey = columns.get(2).getText(); new Button(getDriver(), "action.revealApiKey." + id).click(); @@ -207,14 +208,14 @@ public EndpointModal newModal() { return new EndpointModal() .open(() -> { findElementById("action.addGrafanaEndpoint").click(); // Click add button - new WebDriverWait(driver, 5).until(pageContainsText("Add Grafana Endpoint")); + new WebDriverWait(driver, Duration.ofSeconds(5)).until(pageContainsText("Add Grafana Endpoint")); }); } public EndpointModal editModal(Long endpointId) { return new EndpointModal().open(() -> { findElementById("action.edit." + endpointId).click(); - new WebDriverWait(driver, 5).until(pageContainsText("Edit Grafana Endpoint")); + new WebDriverWait(driver, Duration.ofSeconds(5)).until(pageContainsText("Edit Grafana Endpoint")); }); } @@ -223,7 +224,7 @@ public void deleteEndpoint(GrafanaEndpoint endpoint) { // Click Delete findElementById("action.delete." + endpoint.getId()).click(); // Wait for confirm popover - new WebDriverWait(driver, 5).until(pageContainsText("Delete Endpoint")); + new WebDriverWait(driver, Duration.ofSeconds(5)).until(pageContainsText("Delete Endpoint")); // Click Yes in popover final String confirmButtonXpath = String.format("//div[@class='popover-content']//p[contains(text(), \"UID '%s'\")]/..//button[text() = 'Yes']", endpoint.getUid()); final WebElement confirmElement = findElementByXpath(confirmButtonXpath); @@ -281,7 +282,7 @@ public void cancel() { // Ensure dialog closes private void ensureClosed() { - execute(() -> new WebDriverWait(driver, 5).until(ExpectedConditions.numberOfElementsToBe(By.id("endpointModal"), 0))); + execute(() -> new WebDriverWait(driver, Duration.ofSeconds(5)).until(ExpectedConditions.numberOfElementsToBe(By.id("endpointModal"), 0))); } } diff --git a/smoke-test/src/test/java/org/opennms/smoketest/IndexPageIT.java b/smoke-test/src/test/java/org/opennms/smoketest/IndexPageIT.java index bcd6a9f3a90d..504471c7c5ae 100644 --- a/smoke-test/src/test/java/org/opennms/smoketest/IndexPageIT.java +++ b/smoke-test/src/test/java/org/opennms/smoketest/IndexPageIT.java @@ -28,6 +28,7 @@ package org.opennms.smoketest; +import java.time.Duration; import java.util.List; import java.util.concurrent.TimeUnit; @@ -98,7 +99,7 @@ public void verifyStatusMap() { // try every 5 seconds, for 120 seconds, until the service on 127.0.0.2 has been detected as "down", or fail afterwards try { setImplicitWait(5, TimeUnit.SECONDS); - new WebDriverWait(driver, 120).until(input -> { + new WebDriverWait(driver, Duration.ofSeconds(120)).until(input -> { // refresh page input.get(getBaseUrlInternal() + "opennms/index.jsp"); diff --git a/smoke-test/src/test/java/org/opennms/smoketest/JmxConfigurationGeneratorIT.java b/smoke-test/src/test/java/org/opennms/smoketest/JmxConfigurationGeneratorIT.java index d65901b361af..1b0459c70dc3 100644 --- a/smoke-test/src/test/java/org/opennms/smoketest/JmxConfigurationGeneratorIT.java +++ b/smoke-test/src/test/java/org/opennms/smoketest/JmxConfigurationGeneratorIT.java @@ -127,7 +127,7 @@ public void verifyCompMemberSelection() throws Exception { protected void configureJMXConnection(final boolean skipDefaultVM) throws Exception { final long end = System.currentTimeMillis() + LOAD_TIMEOUT; - final WebDriverWait shortWait = new WebDriverWait(driver, 10); + final WebDriverWait shortWait = new WebDriverWait(driver, Duration.ofSeconds(10)); final String skipDefaultVMxpath = "//span[@id='skipDefaultVM']/input"; final boolean selected = waitForElement(By.xpath(skipDefaultVMxpath)).isSelected(); diff --git a/smoke-test/src/test/java/org/opennms/smoketest/MenuHeaderIT.java b/smoke-test/src/test/java/org/opennms/smoketest/MenuHeaderIT.java index 82fc67b15969..283bf7d406c4 100644 --- a/smoke-test/src/test/java/org/opennms/smoketest/MenuHeaderIT.java +++ b/smoke-test/src/test/java/org/opennms/smoketest/MenuHeaderIT.java @@ -32,6 +32,7 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertThat; +import java.time.Duration; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; @@ -211,7 +212,7 @@ private void clickMenuItemWithIcon(String menuEntryName, String submenuText, Str // Repeat the process altering the offset slightly each time final AtomicInteger offset = new AtomicInteger(10); - final WebDriverWait shortWait = new WebDriverWait(getDriver(), 1); + final WebDriverWait shortWait = new WebDriverWait(getDriver(), Duration.ofSeconds(1)); try { setImplicitWait(5, TimeUnit.SECONDS); Unreliables.retryUntilSuccess(30, TimeUnit.SECONDS, () -> { diff --git a/smoke-test/src/test/java/org/opennms/smoketest/OpenNMSSeleniumIT.java b/smoke-test/src/test/java/org/opennms/smoketest/OpenNMSSeleniumIT.java index 725dc2a2f6d4..8ffa141bfcdb 100644 --- a/smoke-test/src/test/java/org/opennms/smoketest/OpenNMSSeleniumIT.java +++ b/smoke-test/src/test/java/org/opennms/smoketest/OpenNMSSeleniumIT.java @@ -30,14 +30,18 @@ import java.io.File; import java.io.IOException; +import java.lang.reflect.Field; import java.nio.file.Files; import java.nio.file.attribute.PosixFilePermissions; +import java.util.Optional; import org.junit.BeforeClass; import org.junit.ClassRule; +import org.junit.Rule; import org.junit.rules.RuleChain; import org.junit.rules.TestRule; -import org.opennms.smoketest.containers.OpenNMSContainer; +import org.junit.rules.TestWatcher; +import org.junit.runner.Description; import org.opennms.smoketest.selenium.AbstractOpenNMSSeleniumHelper; import org.opennms.smoketest.stacks.OpenNMSStack; import org.opennms.smoketest.utils.TestContainerUtils; @@ -45,8 +49,13 @@ import org.openqa.selenium.firefox.FirefoxOptions; import org.openqa.selenium.firefox.FirefoxProfile; import org.openqa.selenium.remote.RemoteWebDriver; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.testcontainers.containers.BrowserWebDriverContainer; +import org.testcontainers.containers.DefaultRecordingFileFactory; import org.testcontainers.containers.Network; +import org.testcontainers.containers.VncRecordingContainer; +import org.testcontainers.lifecycle.TestDescription; import com.github.dockerjava.api.command.CreateContainerCmd; @@ -54,13 +63,35 @@ * Base class for Selenium based testing of the OpenNMS web application. */ public class OpenNMSSeleniumIT extends AbstractOpenNMSSeleniumHelper { + private static final Logger LOG = LoggerFactory.getLogger(OpenNMSSeleniumIT.class); - public static final BrowserWebDriverContainer firefox = (BrowserWebDriverContainer) new BrowserWebDriverContainer() + /** + * When to save web browser video recordings. + * The default, RECORD_FAILING will only save recordings when there are failed tests. + * Other options: SKIP or RECORD_ALL. + * + * Historical warning: This container can fail and cause test instability - for example if retrieving the + * recording from the container fails, an exception will be thrown causing the test to fail. + * + * Although RECORD_FAILING is the default in BrowserWebDriverContainer, the constructor we use resets + * it to SKIP. :( So we always explicitly set it. + */ + private static final BrowserWebDriverContainer.VncRecordingMode RECORDING_MODE = + BrowserWebDriverContainer.VncRecordingMode.RECORD_FAILING; // RECORD_ALL, RECORD_FAILING, or SKIP + + // Use mp4 since everything supports it + private static final VncRecordingContainer.VncRecordingFormat RECORDING_FORMAT = + VncRecordingContainer.VncRecordingFormat.MP4; + + private static final File RECORDING_DIRECTORY = new File("target"); + + private static final CachingRecordingFileFactory RECORDING_FILE_FACTORY = new CachingRecordingFileFactory(); + + public static final WorkaroundBrowserWebDriverContainer firefox = + (WorkaroundBrowserWebDriverContainer) new WorkaroundBrowserWebDriverContainer() .withCapabilities(getFirefoxOptions()) - // Don't record. This container can fail and cause test instability - for example if retrieving the - // .flv from the container fails, an exception will be thrown causing the test to fail - .withRecordingMode(BrowserWebDriverContainer.VncRecordingMode.SKIP, new File("target")) - //.withRecordingMode(BrowserWebDriverContainer.VncRecordingMode.RECORD_ALL, new File("target")) + .withRecordingMode(RECORDING_MODE, RECORDING_DIRECTORY, RECORDING_FORMAT) + .withRecordingFileFactory(RECORDING_FILE_FACTORY) .withNetwork(Network.SHARED) // Increase the containers shared memory to 2GB to help prevent Firefox from crashing .withSharedMemorySize(2147483648L) @@ -88,7 +119,7 @@ public static FirefoxOptions getFirefoxOptions() { final FirefoxOptions options = new FirefoxOptions(); options.setProfile(new FirefoxProfile()); // Disable browser notifications - options.addPreference("dom.webnotifications.enabled", false); + options.addPreference("dom.webnotifications.enabled", Boolean.FALSE); // Increase the browser resolution on startup options.addArguments("--width=2048"); options.addArguments("--height=1400"); @@ -118,6 +149,26 @@ public static void setUpClass() { driver = firefox.getWebDriver(); } + public static Optional failed = Optional.empty(); + + /** + * @ClassRule does not seem to know if tests failed, but afterTest needs + * to know if a screen recording should be saved. Store the latest failure + * exception here. The specific failure and exception doesn't matter for + * this, we just need to know if there are >0 exceptions for afterTest. + */ + @Rule(order = Integer.MIN_VALUE) + public TestWatcher watchman= new TestWatcher() { + @Override + protected void failed(Throwable e, Description description) { + failed = Optional.of(e); + } + + @Override + protected void succeeded(Description description) { + } + }; + @Override public WebDriver getDriver() { return driver; @@ -125,7 +176,7 @@ public WebDriver getDriver() { @Override public String getBaseUrlInternal() { - return OpenNMSContainer.getBaseUrlInternal().toString(); + return stack.opennms().getBaseUrlInternal().toString(); } @Override @@ -133,4 +184,81 @@ public String getBaseUrlExternal() { return stack.opennms().getBaseUrlExternal().toString(); } + /** + * We work around some bugs and shortcomings in org.testcontainers' BrowserWebDriverContainer here until we can + * submit issues/PRs upstream and the fixes are released. + * First, when a test is complete, nothing is done to tell the VNC recorder Docker image to stop recording. + * Transcoding the recording is CPU-intensive and single-threaded and happens while new data is coming in if the + * VNC recorder process is still running. Unluckily for us, because of our large screen size, the transcoder can't + * keep up with the incoming data from the VNC recorder, so it never gets to EOF on its input file and will run + * forever. We work around that by sending a SIGSTOP to the VNC recorder process inside its Docker container (we + * can't just stop the container because we need it around and running because that's where the transcoder runs). + * Lastly, we provide an alternate RecordingFileFactory that caches the recording file name and we output the + * full path as a URI to make it easier to view the recordings. + * @param + */ + public static class WorkaroundBrowserWebDriverContainer> + extends BrowserWebDriverContainer { + @Override + public void beforeTest(TestDescription description) { + // These are static and reused across all test classes, so reset before every test class run + RECORDING_FILE_FACTORY.recordingFile = null; + OpenNMSSeleniumIT.failed = Optional.empty(); + + super.beforeTest(description); + } + + @Override + public void afterTest(TestDescription description, Optional throwable) { + try { + Field f = firefox.getClass().getSuperclass().getDeclaredField("vncRecordingContainer"); + f.setAccessible(true); + VncRecordingContainer vncRecordingContainer = (VncRecordingContainer) f.get(firefox); + if (vncRecordingContainer != null && vncRecordingContainer.isRunning()) { + var command = "kill -STOP $(grep -l -F flvrec.py /proc/*/cmdline | sed 's,^/proc/,,;s,/.*,,' " + + "| grep '^[0-9]*$' | grep -v '^1$' | sort -n | head -1)"; + var results = vncRecordingContainer.execInContainer("sh", "-c", command); + if (results.getExitCode() != 0) { + throw new RuntimeException("Got non-zero exit code " + results.getExitCode() + " when attempting to " + + "send a SIGSTOP to the VNC recorder process in the container.\n" + + "Command: " + command + "\n" + + "Stdout: " + results.getStdout() + "\n" + + "Stderr: " + results.getStderr()); + } + } + } catch (NoSuchFieldException e) { + throw new RuntimeException(e); + } catch (IllegalAccessException e) { + throw new RuntimeException(e); + } catch (IOException e) { + throw new RuntimeException(e); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + + // This is where the recording is transcoded and extracted to the local system. + super.afterTest(description, OpenNMSSeleniumIT.failed); + + // I really wanted a better message with a clickable URI at the end of the test. + if (RECORDING_FILE_FACTORY.recordingFile != null && + RECORDING_FILE_FACTORY.recordingFile.exists()) { + LOG.info("Recording: {}", RECORDING_FILE_FACTORY.recordingFile.toURI()); + } + } + } + + public static class CachingRecordingFileFactory extends DefaultRecordingFileFactory { + public File recordingFile = null; + + @Override + public File recordingFileForTest( + File vncRecordingDirectory, + String prefix, + boolean succeeded, + VncRecordingContainer.VncRecordingFormat recordingFormat + ) { + recordingFile = super.recordingFileForTest(vncRecordingDirectory, prefix, succeeded, recordingFormat); + return recordingFile; + } + } } diff --git a/smoke-test/src/test/java/org/opennms/smoketest/ScheduledOutageIT.java b/smoke-test/src/test/java/org/opennms/smoketest/ScheduledOutageIT.java index fd39c42ee494..446497485be8 100644 --- a/smoke-test/src/test/java/org/opennms/smoketest/ScheduledOutageIT.java +++ b/smoke-test/src/test/java/org/opennms/smoketest/ScheduledOutageIT.java @@ -32,6 +32,7 @@ import static java.util.concurrent.TimeUnit.SECONDS; import java.text.SimpleDateFormat; +import java.time.Duration; import java.util.Date; import org.junit.After; @@ -114,7 +115,7 @@ private void testOption(final String option, final String text) throws Exception // ...and confirm the alert box. getDriver().switchTo().alert().accept(); - final WebDriverWait webDriverWait = new WebDriverWait(getDriver(), 10); + final WebDriverWait webDriverWait = new WebDriverWait(getDriver(), Duration.ofSeconds(10)); final String outageTypeSelectorXPath = "//select[@id='outageTypeSelector']"; try { @@ -143,7 +144,7 @@ private void testCharactersInName(String name) throws Exception { findElementByXpath("//form[@id='matchAnyForm']//input[@name='matchAny']").click(); getDriver().switchTo().alert().accept(); - final WebDriverWait webDriverWait = new WebDriverWait(getDriver(), 10); + final WebDriverWait webDriverWait = new WebDriverWait(getDriver(), Duration.ofSeconds(10)); final String outageTypeSelectorXPath = "//select[@id='outageTypeSelector']"; try { @@ -162,7 +163,7 @@ private void testCharactersInName(String name) throws Exception { } finally { getDriver().get(getBaseUrlInternal() + "opennms/admin/sched-outages/index.jsp"); - final WebDriverWait webDriverWait = new WebDriverWait(getDriver(), 10); + final WebDriverWait webDriverWait = new WebDriverWait(getDriver(), Duration.ofSeconds(10)); final String deleteLink = "//a[@id='" + name + ".delete']"; try { @@ -205,7 +206,7 @@ public void testOutageTypeChange() throws Exception { // ...and confirm the alert box. getDriver().switchTo().alert().accept(); - final WebDriverWait webDriverWait = new WebDriverWait(getDriver(), 10); + final WebDriverWait webDriverWait = new WebDriverWait(getDriver(), Duration.ofSeconds(10)); final String outageTypeSelectorXPath = "//select[@id='outageTypeSelector']"; try { diff --git a/smoke-test/src/test/java/org/opennms/smoketest/TopologyIT.java b/smoke-test/src/test/java/org/opennms/smoketest/TopologyIT.java index f191ceb7dcae..f8e6bcc0c5de 100644 --- a/smoke-test/src/test/java/org/opennms/smoketest/TopologyIT.java +++ b/smoke-test/src/test/java/org/opennms/smoketest/TopologyIT.java @@ -36,6 +36,7 @@ import java.io.IOException; import java.lang.reflect.Field; +import java.time.Duration; import java.util.Arrays; import java.util.List; import java.util.Objects; @@ -210,7 +211,7 @@ public void select() { getElement().findElement(By.xpath(iconOverlayXpath)).click(); // Wait until vertex is actually selected before continuing - new WebDriverWait(testCase.getDriver(), 30).until(input -> { + new WebDriverWait(testCase.getDriver(), Duration.ofSeconds(30)).until(input -> { final WebElement element = getElement().findElement(By.xpath(iconOverlayXpath + "/..")); return element.getAttribute("class").contains("selected"); }); @@ -251,7 +252,7 @@ public void changeIcon(String iconName) { testCase.setImplicitWait(IMPLICIT_WAIT_SECONDS, TimeUnit.SECONDS); testCase.findElementByXpath("//*[contains(text(), 'Change Icon')]"); testCase.findElementByXpath(iconXpath).click(); - new WebDriverWait(testCase.getDriver(), 10).until(input -> { + new WebDriverWait(testCase.getDriver(), Duration.ofSeconds(10)).until(input -> { final WebElement elementByXpath = testCase.findElementByXpath(iconXpath); return elementByXpath.getAttribute("class").contains("selected"); }); diff --git a/smoke-test/src/test/java/org/opennms/smoketest/UiPageTest.java b/smoke-test/src/test/java/org/opennms/smoketest/UiPageTest.java index 9b6db4c3e1a0..5a02cfaf19cf 100644 --- a/smoke-test/src/test/java/org/opennms/smoketest/UiPageTest.java +++ b/smoke-test/src/test/java/org/opennms/smoketest/UiPageTest.java @@ -28,6 +28,7 @@ package org.opennms.smoketest; +import java.time.Duration; import java.util.concurrent.TimeUnit; import java.util.function.Supplier; @@ -75,7 +76,7 @@ protected X execute(Supplier supplier, int implicitWaitInSeconds) { } protected void verifyElementNotPresent(final By by) { - new WebDriverWait(driver, 7 /* seconds */).until( + new WebDriverWait(driver, Duration.ofSeconds(7)).until( ExpectedConditions.not((ExpectedCondition) input -> execute(() -> { try { WebElement elementFound = input.findElement(by); diff --git a/smoke-test/src/test/java/org/opennms/smoketest/opsboard/OpsBoardAdminPageIT.java b/smoke-test/src/test/java/org/opennms/smoketest/opsboard/OpsBoardAdminPageIT.java index e873f824a4d1..ed3254d3af35 100644 --- a/smoke-test/src/test/java/org/opennms/smoketest/opsboard/OpsBoardAdminPageIT.java +++ b/smoke-test/src/test/java/org/opennms/smoketest/opsboard/OpsBoardAdminPageIT.java @@ -33,6 +33,7 @@ import static org.openqa.selenium.support.ui.ExpectedConditions.not; import java.lang.NullPointerException; +import java.time.Duration; import java.util.List; import java.util.concurrent.TimeUnit; @@ -84,8 +85,8 @@ public void testHeaderHiddenForTopologyUI() { try { setImplicitWait(5, TimeUnit.SECONDS); - new WebDriverWait(driver, 5).until(not(pageContainsText("Access denied"))); - new WebDriverWait(driver, 5).until(pageContainsText("Topology")); + new WebDriverWait(driver, Duration.ofSeconds(5)).until(not(pageContainsText("Access denied"))); + new WebDriverWait(driver, Duration.ofSeconds(5)).until(pageContainsText("Topology")); // Verify that the header is hidden // This method can throw StateElementReference exceptions, so we try multiple times @@ -115,8 +116,8 @@ public void canCreateAndPreview() { // Now ensure that access was NOT denied try { setImplicitWait(1, TimeUnit.SECONDS); - new WebDriverWait(driver, 5).until(not(pageContainsText("Access denied"))); - new WebDriverWait(driver, 5).until(pageContainsText("Surveillance view")); + new WebDriverWait(driver, Duration.ofSeconds(5)).until(not(pageContainsText("Access denied"))); + new WebDriverWait(driver, Duration.ofSeconds(5)).until(pageContainsText("Surveillance view")); } finally { setImplicitWait(); } diff --git a/smoke-test/src/test/resources/log4j2-test.xml b/smoke-test/src/test/resources/log4j2-test.xml index 25dfbc15f10f..a843dd854d57 100644 --- a/smoke-test/src/test/resources/log4j2-test.xml +++ b/smoke-test/src/test/resources/log4j2-test.xml @@ -6,6 +6,10 @@ + + + + @@ -14,8 +18,12 @@ + + + + - \ No newline at end of file + diff --git a/tools/packages/minion/minion.spec b/tools/packages/minion/minion.spec index 99d1bb029463..2b032454f097 100644 --- a/tools/packages/minion/minion.spec +++ b/tools/packages/minion/minion.spec @@ -72,6 +72,7 @@ Requires: jicmp >= 2.0.0 Requires(pre): jicmp >= 2.0.0 Requires: jicmp6 >= 2.0.0 Requires(pre): jicmp6 >= 2.0.0 +Provides: opennms-plugin-api = %{opa_version} Recommends: haveged Conflicts: %{name}-container < %{version}-%{release} diff --git a/tools/packages/opennms/opennms.spec b/tools/packages/opennms/opennms.spec index e7cda03a2700..774240702d8d 100644 --- a/tools/packages/opennms/opennms.spec +++ b/tools/packages/opennms/opennms.spec @@ -65,11 +65,13 @@ Requires(pre): %{name}-webui = %{version}-%{release} Requires: %{name}-webui = %{version}-%{release} Requires(pre): %{name}-core = %{version}-%{release} Requires: %{name}-core = %{version}-%{release} +Requires: %{name}-plugin-cloud >= 1.0.0 Requires(pre): postgresql-server >= 10 Requires: postgresql-server >= 10 Requires(pre): %{jdk} Requires: %{jdk} + # don't worry about buildrequires, the shell script will bomb quick =) #BuildRequires: %{jdk} @@ -100,6 +102,7 @@ Requires: jicmp6 >= 2.0.0 Requires(pre): /usr/sbin/useradd Requires: /usr/sbin/useradd Obsoletes: opennms < 1.3.11 +Provides: %{name}-plugin-api = %{opa_version} Provides: %{name}-plugin-protocol-xml = %{version}-%{release} Obsoletes: %{name}-plugin-protocol-xml < %{version} Provides: %{name}-plugin-protocol-dhcp = %{version}-%{release} @@ -210,6 +213,7 @@ Requires(pre): %{name}-plugin-protocol-radius Requires: %{name}-plugin-protocol-radius Requires(pre): %{name}-plugin-collector-vtdxml-handler Requires: %{name}-plugin-collector-vtdxml-handler +Requires: %{name}-plugin-cloud %description plugins This installs all optional plugins. diff --git a/tools/packages/sentinel/sentinel.spec b/tools/packages/sentinel/sentinel.spec index 8a9031990e5e..aa26dd719e48 100644 --- a/tools/packages/sentinel/sentinel.spec +++ b/tools/packages/sentinel/sentinel.spec @@ -68,6 +68,7 @@ Requires(pre): /sbin/nologin Requires: /sbin/nologin Requires: /usr/bin/id Requires: /usr/bin/sudo +Provides: opennms-plugin-api = %{opa_version} Recommends: haveged Prefix: %{sentinelinstprefix}