]> git.ipfire.org Git - ipfire.org.git/blobdiff - webapp/backend/fireinfo.py
fireinfo: Don't constantly wipe the log table
[ipfire.org.git] / webapp / backend / fireinfo.py
index c6144088fea965915bfaff0c8483214f1b3ef1d2..4529fea3275a2a5e19acc8f74e5c0117d0dda2c7 100644 (file)
@@ -33,7 +33,9 @@ CPU_VENDORS = {
 CPU_STRINGS = (
        ### AMD ###
        # APU
+       (r"AMD (Sempron)\(tm\) (\d+) APU with Radeon\(tm\) R\d+", r"AMD \1 \2 APU"),
        (r"AMD ([\w\-]+) APU with Radeon\(tm\) HD Graphics", r"AMD \1 APU"),
+       (r"AMD ([\w\-]+) Radeon R\d+, \d+ Compute Cores \d+C\+\d+G", r"AMD \1 APU"),
        # Athlon
        (r"AMD Athlon.* II X2 ([a-z0-9]+).*", r"AMD Athlon X2 \1"),
        (r"AMD Athlon\(tm\) 64 Processor (\w+)", r"AMD Athlon64 \1"),
@@ -45,6 +47,8 @@ CPU_STRINGS = (
        # Geode
        (r"Geode\(TM\) Integrated Processor by AMD PCS", r"AMD Geode"),
        (r"(Geode).*", r"\1"),
+       # Mobile
+       (r"Mobile AMD (Athlon|Sempron)\(tm\) Processor (\d+\+?)", r"AMD \1-M \2"),
 
        # Intel
        (r"Intel\(R\) (Atom|Celeron).*CPU\s*([A-Z0-9]+) .*", r"Intel \1 \2"),
@@ -169,10 +173,18 @@ class Processor(Object):
                except KeyError:
                        return self.data.vendor
 
+       @property
+       def family(self):
+               return self.data.family
+
        @property
        def model(self):
                return self.data.model
 
+       @property
+       def stepping(self):
+               return self.data.stepping
+
        @property
        def model_string(self):
                s = self.data.model_string.split()
@@ -187,6 +199,9 @@ class Processor(Object):
                return flag in self.flags
 
        def uses_ht(self):
+               if self.vendor == "Intel" and self.family == 6 and self.model in (15, 55, 76, 77):
+                       return False
+
                return self.has_flag("ht")
 
        @property
@@ -483,6 +498,13 @@ class Profile(Object):
                if location:
                        self.set_location(location)
 
+               self.log_profile_update()
+
+       def log_profile_update(self):
+               # Log that an update was performed for this profile id
+               self.db.execute("INSERT INTO fireinfo_profiles_log(public_id) \
+                       VALUES(%s)", self.public_id)
+
        def expired(self, when=None):
                self.db.execute("UPDATE fireinfo_profiles \
                        SET time_valid = then_or_now(%s) WHERE id = %s", when, self.id)
@@ -663,7 +685,9 @@ class Profile(Object):
        def appliance_id(self):
                if not hasattr(self, "_appliance_id"):
                        appliances = (
+                               ("fountainnetworks-duo-box", self._appliance_test_fountainnetworks_duo_box),
                                ("fountainnetworks-prime", self._appliance_test_fountainnetworks_prime),
+                               ("lightningwirelabs-eco-plus", self._appliance_test_lightningwirelabs_eco_plus),
                                ("lightningwirelabs-eco", self._appliance_test_lightningwirelabs_eco),
                        )
 
@@ -679,12 +703,34 @@ class Profile(Object):
 
        @property
        def appliance(self):
-               if self.appliance_id == "fountainnetworks-prime":
+               if self.appliance_id == "fountainnetworks-duo-box":
+                       return "Fountain Networks - IPFire Duo Box"
+
+               elif self.appliance_id == "fountainnetworks-prime":
                        return "Fountain Networks - IPFire Prime Box"
 
+               elif self.appliance_id == "lightningwirelabs-eco-plus":
+                       return "Lightning Wire Labs - IPFire Eco Plus Appliance"
+
                elif self.appliance_id == "lightningwirelabs-eco":
                        return "Lightning Wire Labs - IPFire Eco Appliance"
 
+       def _appliance_test_fountainnetworks_duo_box(self):
+               if not self.processor.vendor == "Intel":
+                       return False
+
+               if not self.processor.model_string == "Intel(R) Celeron(R) 2957U @ 1.40GHz":
+                       return False
+
+               if not self.count_device("pci", "10ec", "8168") == 2:
+                       return False
+
+               # WiFi module
+               if self.count_device("usb", "148f", "5572") < 1:
+                       return False
+
+               return True
+
        def _appliance_test_fountainnetworks_prime(self):
                if not self.system in (("SECO", None), ("SECO", "0949")):
                        return False
@@ -704,8 +750,18 @@ class Profile(Object):
                if not network_adapters_count == 4:
                        return False
 
-               # 4GB of memory
-               if not self.memory >= 4230823936 * 0.95:
+               return True
+
+       def _appliance_test_lightningwirelabs_eco_plus(self):
+               if not self.system_vendor == "ASUS":
+                       return False
+
+               if not self.system_model.startswith("P9A-I/2550"):
+                       return False
+
+               # Must have four Intel network adapters
+               network_adapters_count = self.count_device("pci", "8086", "1f41")
+               if not network_adapters_count == 4:
                        return False
 
                return True
@@ -1374,7 +1430,7 @@ class ProfileParser(Object):
                # Remove the arch bit
                if release:
                        r = [e for e in release.split() if e]
-                       for s in ("(i586)", "(armv5tel)"):
+                       for s in ("(x86_64)", "(i586)", "(armv5tel)"):
                                try:
                                        r.remove(s)
                                        break
@@ -1446,26 +1502,42 @@ class Fireinfo(Object):
 
                return False
 
-       def can_update_profile(self, public_id, private_id, when=None):
-               res = self.db.get("SELECT 1 FROM fireinfo_profiles \
-                       WHERE public_id = %s AND private_id = %s \
-                       AND time_updated + INTERVAL '60 minutes' <= then_or_now(%s) \
-                       AND time_valid >= then_or_now(%s) ORDER BY time_updated DESC LIMIT 1",
-                       public_id, private_id, when, when)
+       def profile_rate_limit_active(self, public_id, when=None):
+               res = self.db.get("SELECT COUNT(*) AS count FROM fireinfo_profiles_log \
+                       WHERE public_id = %s AND ts >= then_or_now(%s) - INTERVAL '60 minutes'",
+                        public_id, when)
 
-               if res:
+               if res and res.count >= 10:
                        return True
 
                return False
 
+       def is_private_id_change_permitted(self, public_id, private_id, when=None):
+               # Check if a profile exists with a different private id that is still valid
+               res = self.db.get("SELECT 1 FROM fireinfo_profiles \
+                       WHERE public_id = %s AND NOT private_id = %s \
+                       AND time_valid >= then_or_now(%s) LIMIT 1", public_id, private_id, when)
+
+               if res:
+                       return False
+
+               return True
+
        def get_profile(self, public_id, private_id=None, when=None):
                res = self.db.get("SELECT * FROM fireinfo_profiles \
                        WHERE public_id = %s AND \
                                (CASE WHEN %s IS NULL THEN TRUE ELSE private_id = %s END) AND \
-                               (CASE WHEN %s IS NULL THEN TRUE ELSE \
-                                       then_or_now(%s) BETWEEN time_created AND time_valid END) \
-                       ORDER BY time_updated DESC LIMIT 1", public_id,
-                       private_id, private_id, when, when)
+                               then_or_now(%s) BETWEEN time_created AND time_valid \
+                       ORDER BY time_updated DESC LIMIT 1",
+                       public_id, private_id, private_id, when)
+
+               if res:
+                       return Profile(self.backend, res.id, res)
+
+       def get_profile_with_data(self, public_id, when=None):
+               res = self.db.get("WITH profiles AS (SELECT fireinfo_profiles_with_data_at(%s) AS id) \
+                       SELECT * FROM profiles JOIN fireinfo_profiles ON profiles.id = fireinfo_profiles.id \
+                               WHERE public_id = %s ORDER BY time_updated DESC LIMIT 1", when, public_id)
 
                if res:
                        return Profile(self.backend, res.id, res)
@@ -1490,7 +1562,10 @@ class Fireinfo(Object):
                        public_id, private_id, when, when, when, valid)
 
                if res:
-                       return Profile(self.backend, res.id)
+                       p = Profile(self.backend, res.id)
+                       p.log_profile_update()
+
+                       return p
 
        # Devices
 
@@ -1649,9 +1724,12 @@ class Fireinfo(Object):
                profile = self.fireinfo.get_profile(public_id, private_id=private_id, when=when)
 
                # Check if the update can actually be updated
-               if profile and not self.fireinfo.can_update_profile(public_id, private_id, when=when):
-                       logging.warning("Profile not updated because private ID does not"
-                               " match or last update is too recent: %s" % public_id)
+               if profile and self.fireinfo.profile_rate_limit_active(public_id, when=when):
+                       logging.warning("There were too many updates for this profile in the last hour: %s" % public_id)
+                       return
+
+               elif not self.is_private_id_change_permitted(public_id, private_id, when=when):
+                       logging.warning("Changing private id is not permitted for profile: %s" % public_id)
                        return
 
                # Parse the profile
@@ -1745,7 +1823,9 @@ class Fireinfo(Object):
                        MAX(fireinfo_profiles_processors.clock_speed) AS max FROM profiles \
                        LEFT JOIN fireinfo_profiles_processors ON profiles.id = fireinfo_profiles_processors.profile_id \
                        WHERE NOT fireinfo_profiles_processors.processor_id IS NULL \
-                       AND fireinfo_profiles_processors.clock_speed > 0", when)
+                       AND fireinfo_profiles_processors.clock_speed > 0 \
+                       AND fireinfo_profiles_processors.clock_speed < fireinfo_profiles_processors.bogomips \
+                       AND fireinfo_profiles_processors.bogomips <= %s", when, 10000)
 
                if res:
                        return (res.avg or 0, res.stddev or 0, res.min or 0, res.max or 0)
@@ -1773,7 +1853,7 @@ class Fireinfo(Object):
                        flags = (
                                "aes", "avx", "avx2", "lm", "mmx", "mmxext", "nx", "pae",
                                "pni", "popcnt", "sse", "sse2", "rdrand", "ssse3", "sse4a",
-                               "sse4_1", "sse4_2"
+                               "sse4_1", "sse4_2", "sha", "pclmulqdq", "rdseed",
                        )
                else:
                        return