diff --git a/src/pgmon.py b/src/pgmon.py index 6654d1e..bf0e341 100755 --- a/src/pgmon.py +++ b/src/pgmon.py @@ -496,14 +496,93 @@ def get_cluster_version(): return cluster_version +def version_num_to_release(version_num): + """ + Extract the revease from a version_num. + + In other words, this converts things like: + 90603 => 9.6 + 130010 => 13 + """ + if version_num // 10000 < 10: + return version_num // 10000 + (version_num % 10000 // 100 / 10) + else: + return version_num // 10000 + + +def parse_version_rss(raw_rss, release): + """ + Parse the raw RSS from the versions.rss feed to extract the latest version of + PostgreSQL that's availabe for the cluster being monitored. + + This sets these global variables: + latest_version + latest_version_next_check + release_supported + + It is expected that the caller already holds the latest_version_lock lock. + + params: + raw_rss: The raw rss text from versions.rss + release: The PostgreSQL release we care about (ex: 9.2, 14) + """ + global latest_version + global latest_version_next_check + global release_supported + + # Regular expressions for parsing the RSS document + version_line = re.compile(r".*?([0-9][0-9.]+) is the latest release in the {} series.*".format(release)) + unsupported_line = re.compile(r"^This version is unsupported") + + # Loop through the RSS until we find the current release + release_found = False + for line in raw_rss.splitlines(): + m = version_line.match(line) + if m: + # Note that we found the version we were looking for + release_found = True + + # Convert the version to version_num format + version = m.group(1) + parts = list(map(int, version.split("."))) + if parts[0] < 10: + latest_version = int( + "{}{:02}{:02}".format(parts[0], parts[1], parts[2]) + ) + else: + latest_version = int( + "{}00{:02}".format(parts[0], parts[1]) + ) + elif release_found: + # The next line after the version tells if the version is supported + if unsupported_line.match(line): + release_supported = False + else: + release_supported = True + break + + # Make sure we actually found it + if not release_found: + raise LatestVersionCheckError( + "Current release ({}) not found".format(release) + ) + + log.info( + "Got latest PostgreSQL version: {} supported={}".format( + latest_version, release_supported + ) + ) + log.debug( + "Next latest PostgreSQL version check will be after: {}".format( + latest_version_next_check + ) + ) + def get_latest_version(): """ Get the latest supported version of the major PostgreSQL release running on the server being monitored. """ - global latest_version - global latest_version_next_check - global release_supported # If we don't know the latest version or it's past the recheck time, get the # version from the PostgreSQL RSS feed. Only one thread needs to do this, so @@ -513,6 +592,10 @@ def get_latest_version(): or latest_version_next_check is None or latest_version_next_check < datetime.now() ): + # Note: we get the cluster version here before grabbing the latest_version_lock + # lock so it's not held while trying to talk with the DB. + release = version_num_to_release(get_cluster_version()) + with latest_version_lock: # Only check if nobody already got the version before us if ( @@ -521,74 +604,17 @@ def get_latest_version(): or latest_version_next_check < datetime.now() ): log.info("Checking latest PostgreSQL version") - cluster_version = get_cluster_version() latest_version_next_check = datetime.now() + timedelta( seconds=int(config["latest_version_check_period"]) ) - # Extract the release - # 90603 => 9.6 - # 130010 => 13 - if cluster_version // 10000 < 10: - release = cluster_version // 10000 + ( - cluster_version % 10000 // 100 / 10 - ) - else: - release = cluster_version // 10000 - # Grab the RSS feed raw_rss = requests.get("https://www.postgresql.org/versions.rss") - if raw_rss.status != 200: - raise LatestVersionCheckError("code={}".format(r.status)) + if raw_rss.status_code != 200: + raise LatestVersionCheckError("code={}".format(r.status_code)) - # Regular expressions for parsing the RSS document - version_line = re.compile( - r"([0-9][0-9.]+) is the latest release in the ([0-9][0-9.]+) series" - ) - unsupported_line = re.compile(r"^This version is unsupported") - - # Loop through the RSS until we find the current release - release_found = False - for line in raw_rss.text.lines(): - m = version_line.match(line) - if m: - if release == int(m.group(2)): - release_found = True - version = float(m.group(1)) - if version < 10: - parts = list(map(int, version.split("."))) - latest_version = int( - "{}{:02}{:02}".format(parts[0], parts[1], parts[2]) - ) - else: - parts = list(map(int, version.split("."))) - latest_version = int( - "{}00{:02}".format(parts[0], parts[1]) - ) - release_found = True - elif release_found: - if unsupported.match(line): - release_supported = False - else: - release_supported = True - break - - # Make sure we actually found it - if not release_found: - raise LatestVersionCheckError( - "Current release ({}) not found".format(release) - ) - - log.info( - "Got latest PostgreSQL version: {} supported={}".format( - latest_version, release_supported - ) - ) - log.debug( - "Next latest PostgreSQL version check will be after: {}".format( - latest_version_next_check - ) - ) + # Parse the RSS body and set global variables + parse_version_rss(raw_rss.text, release) return latest_version diff --git a/src/test_pgmon.py b/src/test_pgmon.py index 6eaf5e9..d38762f 100644 --- a/src/test_pgmon.py +++ b/src/test_pgmon.py @@ -661,3 +661,115 @@ metrics: ) self.assertEqual(pgmon.config["dbuser"], "postgres") self.assertEqual(pgmon.config["metrics"]["test1"]["query"][0], "TEST1") + + def test_version_num_to_release(self): + self.assertEqual(pgmon.version_num_to_release(90602), 9.6) + self.assertEqual(pgmon.version_num_to_release(130002), 13) + + def test_parse_version_rss(self): + rss = """ + +PostgreSQL latest versionshttps://www.postgresql.org/PostgreSQL latest versionsen-usThu, 08 May 2025 00:00:00 +000017.5 +https://www.postgresql.org/docs/17/release-17-5.html17.5 is the latest release in the 17 series. + +Thu, 08 May 2025 00:00:00 +0000https://www.postgresql.org/docs/17/release-17-5.html16.9 +https://www.postgresql.org/docs/16/release-16-9.html16.9 is the latest release in the 16 series. + +Thu, 08 May 2025 00:00:00 +0000https://www.postgresql.org/docs/16/release-16-9.html15.13 +https://www.postgresql.org/docs/15/release-15-13.html15.13 is the latest release in the 15 series. + +Thu, 08 May 2025 00:00:00 +0000https://www.postgresql.org/docs/15/release-15-13.html14.18 +https://www.postgresql.org/docs/14/release-14-18.html14.18 is the latest release in the 14 series. + +Thu, 08 May 2025 00:00:00 +0000https://www.postgresql.org/docs/14/release-14-18.html13.21 +https://www.postgresql.org/docs/13/release-13-21.html13.21 is the latest release in the 13 series. + +Thu, 08 May 2025 00:00:00 +0000https://www.postgresql.org/docs/13/release-13-21.html12.22 +https://www.postgresql.org/docs/12/release-12-22.html12.22 is the latest release in the 12 series. +This version is unsupported! +Thu, 21 Nov 2024 00:00:00 +0000https://www.postgresql.org/docs/12/release-12-22.html11.22 +https://www.postgresql.org/docs/11/release-11-22.html11.22 is the latest release in the 11 series. +This version is unsupported! +Thu, 09 Nov 2023 00:00:00 +0000https://www.postgresql.org/docs/11/release-11-22.html10.23 +https://www.postgresql.org/docs/10/release-10-23.html10.23 is the latest release in the 10 series. +This version is unsupported! +Thu, 10 Nov 2022 00:00:00 +0000https://www.postgresql.org/docs/10/release-10-23.html9.6.24 +https://www.postgresql.org/docs/9.6/release-9-6-24.html9.6.24 is the latest release in the 9.6 series. +This version is unsupported! +Thu, 11 Nov 2021 00:00:00 +0000https://www.postgresql.org/docs/9.6/release-9-6-24.html9.5.25 +https://www.postgresql.org/docs/9.5/release-9-5-25.html9.5.25 is the latest release in the 9.5 series. +This version is unsupported! +Thu, 11 Feb 2021 00:00:00 +0000https://www.postgresql.org/docs/9.5/release-9-5-25.html9.4.26 +https://www.postgresql.org/docs/9.4/release-9-4-26.html9.4.26 is the latest release in the 9.4 series. +This version is unsupported! +Thu, 13 Feb 2020 00:00:00 +0000https://www.postgresql.org/docs/9.4/release-9-4-26.html9.3.25 +https://www.postgresql.org/docs/9.3/release-9-3-25.html9.3.25 is the latest release in the 9.3 series. +This version is unsupported! +Thu, 08 Nov 2018 00:00:00 +0000https://www.postgresql.org/docs/9.3/release-9-3-25.html9.2.24 +https://www.postgresql.org/docs/9.2/release-9-2-24.html9.2.24 is the latest release in the 9.2 series. +This version is unsupported! +Thu, 09 Nov 2017 00:00:00 +0000https://www.postgresql.org/docs/9.2/release-9-2-24.html9.1.24 +https://www.postgresql.org/docs/9.1/release-9-1-24.html9.1.24 is the latest release in the 9.1 series. +This version is unsupported! +Thu, 27 Oct 2016 00:00:00 +0000https://www.postgresql.org/docs/9.1/release-9-1-24.html9.0.23 +https://www.postgresql.org/docs/9.0/release-9-0-23.html9.0.23 is the latest release in the 9.0 series. +This version is unsupported! +Thu, 08 Oct 2015 00:00:00 +0000https://www.postgresql.org/docs/9.0/release-9-0-23.html8.4.22 +https://www.postgresql.org/docs/8.4/release-8-4-22.html8.4.22 is the latest release in the 8.4 series. +This version is unsupported! +Thu, 24 Jul 2014 00:00:00 +0000https://www.postgresql.org/docs/8.4/release-8-4-22.html8.3.23 +https://www.postgresql.org/docs/8.3/release-8-3-23.html8.3.23 is the latest release in the 8.3 series. +This version is unsupported! +Thu, 07 Feb 2013 00:00:00 +0000https://www.postgresql.org/docs/8.3/release-8-3-23.html8.2.23 +https://www.postgresql.org/docs/8.2/release-8-2-23.html8.2.23 is the latest release in the 8.2 series. +This version is unsupported! +Mon, 05 Dec 2011 00:00:00 +0000https://www.postgresql.org/docs/8.2/release-8-2-23.html8.1.23 +https://www.postgresql.org/docs/8.1/release.html8.1.23 is the latest release in the 8.1 series. +This version is unsupported! +Thu, 16 Dec 2010 00:00:00 +0000https://www.postgresql.org/docs/8.1/release.html8.0.26 +https://www.postgresql.org/docs/8.0/release.html8.0.26 is the latest release in the 8.0 series. +This version is unsupported! +Mon, 04 Oct 2010 00:00:00 +0000https://www.postgresql.org/docs/8.0/release.html7.4.30 +https://www.postgresql.org/docs/7.4/release.html7.4.30 is the latest release in the 7.4 series. +This version is unsupported! +Mon, 04 Oct 2010 00:00:00 +0000https://www.postgresql.org/docs/7.4/release.html7.3.21 +https://www.postgresql.org/docs/7.3/release.html7.3.21 is the latest release in the 7.3 series. +This version is unsupported! +Mon, 07 Jan 2008 00:00:00 +0000https://www.postgresql.org/docs/7.3/release.html7.2.8 +https://www.postgresql.org/docs/7.2/release.html7.2.8 is the latest release in the 7.2 series. +This version is unsupported! +Mon, 09 May 2005 00:00:00 +0000https://www.postgresql.org/docs/7.2/release.html7.1.3 +https://www.postgresql.org/docs/7.1/release.html7.1.3 is the latest release in the 7.1 series. +This version is unsupported! +Fri, 17 Aug 2001 00:00:00 +0000https://www.postgresql.org/docs/7.1/release.html7.0.3 +https://www.postgresql.org/docs/7.0/release.htm7.0.3 is the latest release in the 7.0 series. +This version is unsupported! +Sun, 12 Nov 2000 00:00:00 +0000https://www.postgresql.org/docs/7.0/release.htm6.5.3 +https://www.postgresql.org/docs/6.5/release.htm6.5.3 is the latest release in the 6.5 series. +This version is unsupported! +Thu, 04 Nov 1999 00:00:00 +0000https://www.postgresql.org/docs/6.5/release.htm6.4.2 +https://www.postgresql.org/docs/6.4/release.htm6.4.2 is the latest release in the 6.4 series. +This version is unsupported! +Sun, 03 Jan 1999 00:00:00 +0000https://www.postgresql.org/docs/6.4/release.htm6.3.2 +https://www.postgresql.org/docs/6.3/c2701.htm6.3.2 is the latest release in the 6.3 series. +This version is unsupported! +Mon, 23 Feb 1998 00:00:00 +0000https://www.postgresql.org/docs/6.3/c2701.htm +""" + pgmon.parse_version_rss(rss, 13) + self.assertEqual(pgmon.latest_version, 130021) + self.assertTrue(pgmon.release_supported) + + pgmon.parse_version_rss(rss, 9.6) + self.assertEqual(pgmon.latest_version, 90624) + self.assertFalse(pgmon.release_supported) + + def test_get_latest_version(self): + # Define a cluster version here so the test doesn't need a database + pgmon.cluster_version_next_check = datetime.now() + timedelta(hours=1) + pgmon.cluster_version = 90623 + + # Set up a default config + pgmon.update_deep(pgmon.config, pgmon.default_config) + + # Make sure we can pull the RSS file (we assume the 9.6 series won't be getting any more updates) + self.assertEqual(pgmon.get_latest_version(), 90624)