Source code for rpymostat_common.unique_ids

"""
The latest version of this package is available at:
<http://github.com/jantman/rpymostat-common>

##################################################################################
Copyright 2016 Jason Antman <jason@jasonantman.com> <http://www.jasonantman.com>

    This file is part of rpymostat-common, also known as rpymostat-common.

    rpymostat-common 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.

    rpymostat-common 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 rpymostat-common.  If not, see <http://www.gnu.org/licenses/>.

The Copyright and Authors attributions contained herein may not be removed or
otherwise altered, except to add the Author attribution of a contributor to
this work. (Additional Terms pursuant to Section 7b of the AGPL v3)
##################################################################################
While not legally required, I sincerely request that anyone who finds
bugs please submit them at <https://github.com/jantman/rpymostat-common> or
to me via email, and that you send any contributions or improvements
either as a pull request on GitHub, or to me via email.
##################################################################################

AUTHORS:
Jason Antman <jason@jasonantman.com> <http://www.jasonantman.com>
##################################################################################
"""

import logging
import re
import uuid

logger = logging.getLogger(__name__)


[docs]class SystemID(object): """ Determine and retrieve a unique system ID for the hardware this is running on. """ # List of method names in this class to call when determining an ID # in :py:method:`.id_string`. id_methods = [ 'raspberrypi_cpu', 'uuid_getnode' ] # regex to match Hardware line from /proc/cpuinfo proc_cpuinfo_hw_re = re.compile('^Hardware\s+:\s+(\w+)$', flags=re.MULTILINE | re.IGNORECASE) # regex to match Revision line from /proc/cpuinfo proc_cpuinfo_rev_re = re.compile('^Revision\s+:\s+(\w+)$', flags=re.MULTILINE | re.IGNORECASE) # regex to match Serial line from /proc/cpuinfo proc_cpuinfo_serial_re = re.compile('^Serial\s+:\s+(\w+)$', flags=re.MULTILINE | re.IGNORECASE) # /proc/cpuinfo Hardware values for RPi rpi_hardware = ['BCM2708', 'BCM2709'] # map /proc/cpuinfo Revision value to string model name # thanks to http://elinux.org/RPi_HardwareHistory#Board_Revision_History # for this information rpi_revisions = { 'Beta': 'B (Beta) ? 256MB (Q1 2012 Beta Board)', '0002': 'B 1.0 256MB (Q1 2012)', '0003': 'B ECN0001 1.0 256MB (Q3 2012)', '0004': 'B 2.0 256MB (Q3 2012 Sony)', '0005': 'B 2.0 256MB (Q4 2012 Qisda)', '0006': 'B 2.0 256MB (Q4 2012 Egoman)', '0007': 'A 2.0 256MB (Q1 2013 Egoman)', '0008': 'A 2.0 256MB (Q1 2013 Sony)', '0009': 'A 2.0 256MB (Q1 2013 Qisda)', '000d': 'B 2.0 512MB (Q4 2012 Egoman)', '000e': 'B 2.0 512MB (Q4 2012 Sony)', '000f': 'B 2.0 512MB (Q4 2012 Qisda)', '0010': 'B+ 1.0 512MB (Q3 2014 Sony)', '0011': 'Compute Module 1.0 512MB (Q2 2014 Sony)', '0012': 'A+ 1.1 256MB (Q4 2014 Sony)', '0013': 'B+ 1.2 512MB (Q1 2015)', '0014': 'Compute Module 1.0 512MB (Q2 2014 Embest)', '0015': 'A+ 1.1 256MB (Embest)', 'a01041': '2 Model B 1.1 1GB (Q1 2015 Sony)', 'a21041': '2 Model B 1.1 1GB (Q1 2015 Embest)', '900092': 'Zero 1.2 512MB (Q4 2015 Sony)', '900093': 'Zero 1.3 512MB (Q2 2016)', 'a02082': '3 Model B 1.2 1024MB (Q1 2016 Sony)', 'a22082': '3 Model B 1.2 1024MB (Q1 2016)', } @property def id_string(self): """ Find/calculate and return the unique system ID string for the hardware this is running on. Internally, this calls all method whose names are listed in :py:attr:`.id_methods`, in order, and returns the value of the first one that returned something other than None. :return: unique, never-changing system ID :rtype: str """ id_str = None for meth_name in self.id_methods: try: s = getattr(self, meth_name)() if s is not None: id_str = s logger.debug('Determined SystemID via method %s', meth_name) break except Exception: logger.debug('Exception encountered when trying to determine ' 'system ID via method %s', meth_name, exc_info=1) # use the fallback if id_str is None: id_str = self.random_fallback() logger.debug('Determined SystemID via method random_fallback') logger.debug('Host ID: %s', id_str) return id_str
[docs] def raspberrypi_cpu(self): """ If this system is a Raspberry Pi, get its model and (CPU) serial number. Thanks to: http://elinux.org/RPi_HardwareHistory#Board_Revision_History :return: RaspberryPi serial number :rtype: str """ with open('/proc/cpuinfo', 'r') as fh: lines = fh.read() hw_match = self.proc_cpuinfo_hw_re.search(lines) rev_match = self.proc_cpuinfo_rev_re.search(lines) serial_match = self.proc_cpuinfo_serial_re.search(lines) serial = 'unknown' if serial_match is not None: serial = serial_match.group(1).strip('0 ') # check Hardware from /proc/cpuinfo if hw_match is None: logger.debug('Not RPi - hw_match is None') return None if hw_match.group(1) not in self.rpi_hardware: logger.debug('Not RPi (Hardware: %s)', hw_match.group(1)) return None logger.debug('Appears to be a Raspberry Pi (Hardware: %s)', hw_match.group(1)) # check Revision to get model if rev_match is None: return 'RaspberryPi/unknown_model/%s' % serial # find model if rev_match.group(1) not in self.rpi_revisions: return 'RaspberryPi/model_%s/%s' % (rev_match.group(1), serial) return 'RaspberryPi/%s/%s' % (self.rpi_revisions[rev_match.group(1)], serial)
[docs] def uuid_getnode(self): """ Determine this system's UUID via Python's :py:func:`uuid.getnode` (slow) method. :return: hardware system ID from Python's :py:func:`uuid.getnode` :rtype: str """ return 'uuid.getnode_%x' % uuid.getnode()
[docs] def random_fallback(self): """ Generate a host ID using a random UUID via Python's :py:func:`uuid.uuid4`. Used as a fallback when the ID can't be determined using any other method. :return: random UUID :rtype: str """ logger.warning('Could not determine system ID with any concrete method;' ' using a random UUID.') return uuid.uuid4().hex