diff --git a/.github/workflows/plugin-ci-workflow.yml b/.github/workflows/plugin-ci-workflow.yml index a1d1f6f..4d9e5f1 100644 --- a/.github/workflows/plugin-ci-workflow.yml +++ b/.github/workflows/plugin-ci-workflow.yml @@ -39,8 +39,8 @@ jobs: - name: Lint every PHP file run: | - if find . -path ./vendor -prune -o -name '*.php' -print \ - | xargs -n1 php -l 2>&1 | grep -iv 'no syntax errors detected'; then + if find . -path ./vendor -prune -o -name '*.php' -print0 \ + | xargs -0 -n1 php -l 2>&1 | grep -iv 'no syntax errors detected'; then echo "Syntax errors found at the declared PHP floor" exit 1 fi @@ -153,19 +153,18 @@ jobs: - name: Create MySQL Config run: | - echo -e "[client]\nuser = root\npassword = cactiroot\nhost = 127.0.0.1\n" > ~/.my.cnf + printf '[client]\nuser = root\npassword = cactiroot\nhost = 127.0.0.1\n' \ + > "$RUNNER_TEMP/gpsmap-my.cnf" - name: Initialize Cacti Database - env: - MYSQL_AUTH_USR: '--defaults-file=~/.my.cnf' run: | - mysql $MYSQL_AUTH_USR -e 'CREATE DATABASE IF NOT EXISTS cacti;' - mysql $MYSQL_AUTH_USR -e "CREATE USER IF NOT EXISTS 'cactiuser'@'localhost' IDENTIFIED BY 'cactiuser';" - mysql $MYSQL_AUTH_USR -e "GRANT ALL PRIVILEGES ON cacti.* TO 'cactiuser'@'localhost';" - mysql $MYSQL_AUTH_USR -e "GRANT SELECT ON mysql.time_zone_name TO 'cactiuser'@'localhost';" - mysql $MYSQL_AUTH_USR -e "FLUSH PRIVILEGES;" - mysql $MYSQL_AUTH_USR cacti < ${{ github.workspace }}/cacti/cacti.sql - mysql $MYSQL_AUTH_USR -e "INSERT INTO settings (name, value) VALUES ('path_php_binary', '/usr/bin/php')" cacti + mysql --defaults-file="$RUNNER_TEMP/gpsmap-my.cnf" -e 'CREATE DATABASE IF NOT EXISTS cacti;' + mysql --defaults-file="$RUNNER_TEMP/gpsmap-my.cnf" -e "CREATE USER IF NOT EXISTS 'cactiuser'@'localhost' IDENTIFIED BY 'cactiuser';" + mysql --defaults-file="$RUNNER_TEMP/gpsmap-my.cnf" -e "GRANT ALL PRIVILEGES ON cacti.* TO 'cactiuser'@'localhost';" + mysql --defaults-file="$RUNNER_TEMP/gpsmap-my.cnf" -e "GRANT SELECT ON mysql.time_zone_name TO 'cactiuser'@'localhost';" + mysql --defaults-file="$RUNNER_TEMP/gpsmap-my.cnf" -e "FLUSH PRIVILEGES;" + mysql --defaults-file="$RUNNER_TEMP/gpsmap-my.cnf" cacti < ${{ github.workspace }}/cacti/cacti.sql + mysql --defaults-file="$RUNNER_TEMP/gpsmap-my.cnf" -e "INSERT INTO settings (name, value) VALUES ('path_php_binary', '/usr/bin/php')" cacti - name: Install Composer Dependencies run: | @@ -212,11 +211,15 @@ jobs: run: | cd ${{ github.workspace }}/cacti sudo php cli/plugin_manage.php --plugin=gpsmap --install --enable + mysql -u cactiuser -p'cactiuser' -h 127.0.0.1 cacti -sN \ + -e "SELECT COUNT(*) FROM information_schema.tables + WHERE table_schema = 'cacti' AND table_name = 'plugin_gpsmap_dns_cache';" \ + | grep -qx '1' - name: Check PHP Syntax for Plugin run: | cd ${{ github.workspace }}/cacti/plugins/gpsmap - if find . -name '*.php' -exec php -l {} 2>&1 \; | grep -iv 'no syntax errors detected'; then + if find . -name '*.php' -print0 | xargs -0 -n1 php -l 2>&1 | grep -iv 'no syntax errors detected'; then echo "Syntax errors found!" exit 1 fi @@ -293,12 +296,15 @@ jobs: fi echo "Using host_template id $TPL" sudo php cli/add_device.php --description=mapped --ip=127.0.0.1 --template="$TPL" + sudo php cli/add_device.php --description=mapped-hostname --ip=localhost --template="$TPL" mysql -u cactiuser -p'cactiuser' -h 127.0.0.1 cacti \ - -e "UPDATE host SET latitude='51.5074', longitude='-0.1278' WHERE description='mapped';" + -e "UPDATE host SET latitude='51.5074', longitude='-0.1278' + WHERE description IN ('mapped', 'mapped-hostname');" mysql -u cactiuser -p'cactiuser' -h 127.0.0.1 cacti \ -e "INSERT INTO gpsmap_templates (templateID, templateName, upimage, recoverimage, downimage, AP) SELECT host_template_id, 'ci', 'Green.png', 'Orange.png', 'Red.png', 0 FROM host WHERE description='mapped' LIMIT 1;" + sudo php plugins/gpsmap/gpsmap_dns.php - name: Run Cacti Poller run: | @@ -325,6 +331,11 @@ jobs: cat all.xml exit 1 fi + if ! grep -q 'address="localhost"' all.xml; then + echo "all.xml contains no hostname-resolved marker" + cat all.xml + exit 1 + fi - name: View Cacti Logs if: always() diff --git a/.gitignore b/.gitignore index 9e104b5..d3d6561 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ # review pipeline scratch .diffcheck.php .rv.tar +tests/_dbg.php diff --git a/CHANGELOG.md b/CHANGELOG.md index fa14bcb..de4b99d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ ## Changelog +--- 2.2 --- + +* issue#6, issue#104, issue#107, issue#109, issue#110: Refactor the poller for single-load dual-stack maps, asynchronous last-known-good DNS, thold-aware markers, and safe artifact pruning + --- 2.1 --- * issue: Add gpsmap_templates table index to be unique by device template diff --git a/INFO b/INFO index 2b0f08a..229defe 100644 --- a/INFO +++ b/INFO @@ -1,6 +1,6 @@ [info] name = gpsmap -version = 2.1 +version = 2.2 longname = Maps Plugin author = Andrew Aloia, Wixiweb, The Cacti Group email = diff --git a/README.md b/README.md index 915acf5..9dfac0b 100644 --- a/README.md +++ b/README.md @@ -11,8 +11,9 @@ Maps tab reads a static file instead of querying the database. ## Features -* Devices plotted by latitude and longitude, coloured by Up, Recovering, Down, - Disabled or Undefined status +* IPv4 and IPv6 Devices plotted by latitude and longitude, coloured by Up, + Recovering, Down, Disabled or Undefined status. When thold is enabled, an + otherwise-up Device with an active threshold uses its Down icon * Per Device Template marker icons for the Up, Recovering and Down states @@ -20,7 +21,7 @@ Maps tab reads a static file instead of querying the database. furthest Device sharing the Access Point's group number, and optionally limited to an arc -* Drill down by subnet, from the first octet through to individual Devices +* Drill down by subnet: /8, /16 and /24 for IPv4, and /16, /32 and /48 for IPv6 * KML export alongside the XML, so the same data opens in Google Earth @@ -55,8 +56,8 @@ JavaScript, and there is no workaround inside Cacti. Installing adds seven columns to Cacti's `host` table (`latitude`, `longitude`, `GPScoverage`, `start`, `stop`, `groupnum`, `rdistance`) and creates the -`gpsmap_templates` table. Uninstalling deliberately leaves both in place so -coordinates survive an accidental removal. +`gpsmap_templates` and `plugin_gpsmap_dns_cache` tables. Uninstalling +deliberately leaves them in place so coordinates survive an accidental removal. ## Configuration @@ -139,13 +140,21 @@ Understanding the order of events explains most of what can go wrong. 1. The poller runs and Cacti calls the plugin's `poller_bottom` hook. 2. The plugin selects every Device whose Device Template appears in - `gpsmap_templates`, resolves each hostname to an address, and discards any - Device without coordinates or whose name does not resolve to IPv4. + `gpsmap_templates`. Literal IPv4 and IPv6 addresses are used directly; + configured names use the last address in `plugin_gpsmap_dns_cache`. 3. It writes `plugins/gpsmap/XML/all.xml`, `all.kml` and `all-top.html`, then repeats for each subnet prefix so the drill-down views are prebuilt. -4. Opening the Maps tab serves a page that loads Google's map library and +4. It starts a separate DNS refresh worker after publishing. A newly configured + hostname can therefore appear on the following poll, while slow DNS never + holds up the poller. A failed refresh retains the last-known address. + +5. Generated subnet files not refreshed for three poller intervals are removed. + Cleanup runs only after a successful Device query; unrelated files are never + touched. + +6. Opening the Maps tab serves a page that loads Google's map library and fetches the matching `.xml` file over HTTP. Nothing appears until a poll cycle has completed after configuration. If @@ -165,9 +174,14 @@ cycle has completed since both were set; and `plugins/gpsmap/XML` contains files newer than the last poll. **Some Devices are missing.** A Device is skipped when it has no coordinates, -when its hostname does not resolve to an IPv4 address, when its Device Template -is not in Map Templates, or when it is disabled and Display Disabled Devices is -off. +when its hostname has no cached address yet, when its Device Template is not in +Map Templates, or when it is disabled and Display Disabled Devices is off. For +a newly added hostname, allow one additional poll cycle for the asynchronous DNS +worker to populate the cache. + +**An Up Device has a red marker.** When the thold plugin is enabled, gpsmap +uses the configured Down icon for an otherwise-up Device with any active, +enabled threshold. Disable or clear the threshold to restore the Up icon. **The XML directory is empty.** The Data Collector cannot write to it. Check ownership, permissions and SELinux context. The Cacti log records a failure diff --git a/gpsmap.php b/gpsmap.php index d0fba7b..05e7156 100644 --- a/gpsmap.php +++ b/gpsmap.php @@ -21,22 +21,24 @@ chdir('../../'); include('./include/auth.php'); +require_once('./plugins/gpsmap/gpsmap_security.php'); include_once('./plugins/gpsmap/includes/setup/show.php'); include_once('./plugins/gpsmap/includes/setup/gpsmapinitial.php'); $body = ''; general_header(); -//set headers to NOT cache a page -header("Cache-Control: no-cache, no-store, must-revalidate"); -header("Pragma: no-cache"); -header("Expires: 0"); +// set headers to NOT cache a page +header('Cache-Control: no-cache, no-store, must-revalidate'); +header('Pragma: no-cache'); +header('Expires: 0'); -//decide what needs to be shown +// decide what needs to be shown switch ($show) { - //selected nodes + // selected nodes case 'setup': $body = __('Please make sure to properly configure Maps first under Settings > Maps', 'gpsmap'); + break; default: if (!$parameter) { @@ -46,44 +48,44 @@ /* Reject any parameter that contains directory traversal sequences or * characters outside the safe set. basename() alone does not strip * embedded ../ so we validate the whole value first. */ - if (!preg_match('/^[a-zA-Z0-9_-]+(\.[a-zA-Z0-9_-]+)*$/', $parameter)) { + if (!gpsmap_artifact_parameter_is_valid($parameter)) { $parameter = 'all'; } $fileLocation = './plugins/gpsmap/XML/' . $parameter . '-top.html'; if (file_exists($fileLocation)) { - echo file_get_contents($fileLocation); + print file_get_contents($fileLocation); } break; } -//--------------------------------------------------------------- +// --------------------------------------------------------------- if ($show != 'setup') { ?>