]> git.ipfire.org Git - people/shoehn/ipfire.org.git/blobdiff - webapp/backend/fireinfo.py
fireinfo: Remove x86_64 from release string
[people/shoehn/ipfire.org.git] / webapp / backend / fireinfo.py
index 09396dea2c67565279b2741633137f769c33e72d..1f8768085cdaecc9f8c110ffeb6a2d56fed7010f 100644 (file)
@@ -31,21 +31,35 @@ CPU_VENDORS = {
 }
 
 CPU_STRINGS = (
-       # AMD
+       ### 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"),
+       (r"AMD Athlon\(tm\) 64 X2 Dual Core Processor (\w+)", r"AMD Athlon64 X2 \1"),
        (r"(AMD Athlon).*(XP).*", r"\1 \2"),
        (r"(AMD Phenom).* ([0-9]+) .*", r"\1 \2"),
        (r"(AMD Phenom).*", r"\1"),
        (r"(AMD Sempron).*", r"\1"),
-       (r"AMD Athlon.* II X2 ([a-z0-9]+).*", r"AMD Athlon X2 \1"),
+       # 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"),
        (r"(Intel).*(Celeron).*", r"\1 \2"),
-       (r"Intel.* Core.*2 Duo *CPU .* ([A-Z0-9]+) .*", r"Intel C2D \1"),
-       (r"Intel.* Core.*2 CPU .* ([A-Z0-9]+) .*", r"Intel C2 \1"),
-       (r"Intel.* Core.*2 Quad *CPU .* ([A-Z0-9]+) .*", r"Intel C2Q \1"),
-       (r"Intel.* Xeon.* CPU .* ([A-Z0-9]+) .*", r"Intel Xeon \1"),
+       (r"Intel\(R\)? Core\(TM\)?2 Duo *CPU .* ([A-Z0-9]+) .*", r"Intel C2D \1"),
+       (r"Intel\(R\)? Core\(TM\)?2 Duo CPU (\w+)", r"Intel C2D \1"),
+       (r"Intel\(R\)? Core\(TM\)?2 CPU .* ([A-Z0-9]+) .*", r"Intel C2 \1"),
+       (r"Intel\(R\)? Core\(TM\)?2 Quad *CPU .* ([A-Z0-9]+) .*", r"Intel C2Q \1"),
+       (r"Intel\(R\)? Core\(TM\)? (i[753]\-\w+) CPU", r"Intel Core \1"),
+       (r"Intel\(R\)? Xeon\(R\)? CPU (\w+) (0|v\d+)", r"Intel Xeon \1 \2"),
+       (r"Intel\(R\)? Xeon\(R\)? CPU\s+(\w+)", r"Intel Xeon \1"),
        (r"(Intel).*(Xeon).*", r"\1 \2"),
        (r"Intel.* Pentium.* (D|4) .*", r"Intel Pentium \1"),
        (r"Intel.* Pentium.* Dual .* ([A-Z0-9]+) .*", r"Intel Pentium Dual \1"),
@@ -53,6 +67,9 @@ CPU_STRINGS = (
        (r"(Pentium I{2,3}).*", r"Intel \1"),
        (r"(Celeron \(Coppermine\))", r"Intel Celeron"),
 
+       # NSC
+       (r"Geode\(TM\) Integrated Processor by National Semi", r"NSC Geode"),
+
        # VIA
        (r"(VIA \w*).*", r"\1"),
 
@@ -72,6 +89,9 @@ class ProfileDict(object):
 
 class ProfileNetwork(ProfileDict):
        def __eq__(self, other):
+               if other is None:
+                       return False
+
                if not self.has_red == other.has_red:
                        return False
 
@@ -153,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()
@@ -171,6 +199,9 @@ class Processor(Object):
                return flag in self.flags
 
        def uses_ht(self):
+               if self.family == 6 and self.model in (55, 77):
+                       return False
+
                return self.has_flag("ht")
 
        @property
@@ -222,15 +253,34 @@ class Processor(Object):
 
        def format_model(self):
                s = self.model_string
+
+               # Remove everything after the @: Intel(R) Core(TM) i7-3770 CPU @ 3.40GHz
+               s, sep, rest = s.partition("@")
+
                for pattern, repl in CPU_STRINGS:
                        if re.match(pattern, s) is None:
                                continue
-                       return re.sub(pattern, repl, s)
+
+                       s = re.sub(pattern, repl, s)
+                       break
 
                # Otherwise remove the symbols
-               for i in ("C", "R", "TM"):
+               for i in ("C", "R", "TM", "tm"):
                        s = s.replace("(%s)" % i, "")
 
+               # Replace too long strings with shorter ones
+               pairs = (
+                       ("Quad-Core Processor", ""),
+                       ("Dual-Core Processor", ""),
+                       ("Processor", "CPU"),
+                       ("processor", "CPU"),
+               )
+               for k, v in pairs:
+                       s = s.replace(k, v)
+
+               # Remove too many spaces
+               s = " ".join((e for e in s.split() if e))
+
                return s
 
        @property
@@ -448,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)
@@ -557,11 +614,11 @@ class Profile(Object):
 
        devices = property(get_devices, set_devices)
 
-       def count_device(self, vendor, model):
+       def count_device(self, subsystem, vendor, model):
                counter = 0
 
                for dev in self.devices:
-                       if dev.vendor == vendor and dev.model == model:
+                       if dev.subsystem == subsystem and dev.vendor == vendor and dev.model == model:
                                counter += 1
 
                return counter
@@ -628,6 +685,8 @@ class Profile(Object):
        def appliance_id(self):
                if not hasattr(self, "_appliance_id"):
                        appliances = (
+                               ("fountainnetworks-prime", self._appliance_test_fountainnetworks_prime),
+                               ("lightningwirelabs-eco-plus", self._appliance_test_lightningwirelabs_eco_plus),
                                ("lightningwirelabs-eco", self._appliance_test_lightningwirelabs_eco),
                        )
 
@@ -643,20 +702,46 @@ class Profile(Object):
 
        @property
        def appliance(self):
-               if self.appliance_id == "lightningwirelabs-eco":
-                       return "Lightning Wire Labs IPFire Eco Appliance"
+               if 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_prime(self):
+               if not self.system in (("SECO", None), ("SECO", "0949")):
+                       return False
+
+               # Must have a wireless device
+               if self.count_device("usb", "148f", "5572") < 1:
+                       return False
+
+               return True
 
        def _appliance_test_lightningwirelabs_eco(self):
                if not self.system == ("MSI", "MS-9877"):
                        return False
 
                # Must have four Intel network adapters
-               network_adapters_count = self.count_device("8086", "10d3")
+               network_adapters_count = self.count_device("pci", "8086", "10d3")
                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
@@ -748,7 +833,7 @@ class Profile(Object):
 
        @property
        def friendly_memory(self):
-               return util.format_size(self.memory)
+               return util.format_size(self.memory or 0)
 
        # Storage
 
@@ -939,6 +1024,21 @@ class Profile(Object):
 
                return r
 
+       @property
+       def release_short(self):
+               pairs = (
+                       (r"Release Candidate (\d+)", r"RC\1"),
+               )
+
+               s = self.release
+               for pattern, repl in pairs:
+                       if re.search(pattern, s) is None:
+                               continue
+
+                       s = re.sub(pattern, repl, s)
+
+               return s
+
        # Virtual
 
        @property
@@ -1235,6 +1335,10 @@ class ProfileParser(Object):
                        for arg in self.__device_args:
                                args[arg] = _device.get(arg, None)
 
+                       # Skip if the subsystem is not set
+                       if not args.get("subsystem", None):
+                               continue
+
                        # Find the device or create a new one.
                        device = self.fireinfo.get_device(**args)
                        if not device:
@@ -1306,7 +1410,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
@@ -1323,8 +1427,9 @@ class ProfileParser(Object):
                self.release = release
 
        def __parse_language(self, language):
-               self.language, delim, rest = language.partition(".")
-               self.language, delim, rest = language.partition("_")
+               self.language = language
+               self.language, delim, rest = self.language.partition(".")
+               self.language, delim, rest = self.language.partition("_")
 
        def __parse_hypervisor(self, hypervisor):
                vendor = hypervisor.get("vendor", "other")
@@ -1354,8 +1459,9 @@ class Fireinfo(Object):
                if res:
                        return res.count
 
-       def get_archives_count(self):
-               res = self.db.get("SELECT COUNT(*) AS count FROM fireinfo_profiles")
+       def get_total_updates_count(self, when=None):
+               res = self.db.get("SELECT COUNT(*) + SUM(updates) AS count \
+                       FROM fireinfo_profiles WHERE time_created <= then_or_now(%s)", when)
 
                if res:
                        return res.count
@@ -1376,26 +1482,45 @@ 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):
+               # Remove all outdated entries
+               self.db.execute("DELETE FROM fireinfo_profiles_log \
+                       WHERE ts <= then_or_now(%s) - INTERVAL '60 minutes'", when)
 
-               if res:
+               res = self.db.get("SELECT COUNT(*) AS count FROM fireinfo_profiles_log \
+                       WHERE public_id = %s", public_id)
+
+               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)
@@ -1420,7 +1545,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
 
@@ -1579,9 +1707,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
@@ -1645,7 +1776,7 @@ class Fireinfo(Object):
                return ((r.location, r.count) for r in res)
 
        def get_language_map(self, when=None):
-               res = self.db.query("WITH profiles AS (SELECT fireinfo_profiles_at(%s) AS id) \
+               res = self.db.query("WITH profiles AS (SELECT fireinfo_profiles_with_data_at(%s) AS id) \
                        SELECT language, COUNT(language)::float / (SELECT COUNT(*) FROM profiles) AS count FROM profiles \
                        LEFT JOIN fireinfo_profiles_languages ON profiles.id = fireinfo_profiles_languages.profile_id \
                        WHERE fireinfo_profiles_languages.language IS NOT NULL GROUP BY language ORDER BY count DESC", when)
@@ -1675,7 +1806,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)