blob: 731c5b91ae06ab949663caa05df3d391a3b75668 [file] [log] [blame]
#!/usr/bin/python
# Copyright 2011 Google Inc. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# TR-069 has mandatory attribute names that don't comply with policy
# pylint:disable=invalid-name
"""Implementation of tr-181 Device.DeviceInfo object.
Handles the Device.DeviceInfo portion of TR-181, as described
by http://www.broadband-forum.org/cwmp/tr-181-2-2-0.html
"""
__author__ = 'dgentry@google.com (Denton Gentry)'
import abc
import glob
import os
import tornado.ioloop
import tr.core
import tr.session
import tr.x_catawampus_tr098_1_0
import tr.x_catawampus_tr181_2_0
import tr.cwmptypes
import temperature
BASE98 = tr.x_catawampus_tr098_1_0.X_CATAWAMPUS_ORG_InternetGatewayDevice_v1_0
BASE98IGD = BASE98.InternetGatewayDevice
BASE181 = tr.x_catawampus_tr181_2_0.X_CATAWAMPUS_ORG_Device_v2_0
BASE181DEVICE = BASE181.Device
# Unit tests can override these with fake data
PERIODICCALL = tornado.ioloop.PeriodicCallback
PROC_MEMINFO = '/proc/meminfo'
PROC_NET_DEV = '/proc/net/dev'
PROC_UPTIME = '/proc/uptime'
PROC_STAT = '/proc/stat'
SLASH_PROC = '/proc'
class DeviceIdMeta(object):
"""Class to provide platform-specific fields for DeviceInfo.
Each platform is expected to subclass DeviceIdMeta and supply concrete
implementations of all methods. We use a Python Abstract Base Class
to protect against future versions. If we add fields to this class,
any existing platform implementations will be prompted to add implementations
(because they will fail to startup when their DeviceId fails to
instantiate.
"""
__metaclass__ = abc.ABCMeta
@abc.abstractproperty
def Manufacturer(self):
return None
@abc.abstractproperty
def ManufacturerOUI(self):
return None
@abc.abstractproperty
def ModelName(self):
return None
@abc.abstractproperty
def Description(self):
return None
@abc.abstractproperty
def SerialNumber(self):
return None
@abc.abstractproperty
def HardwareVersion(self):
return None
@abc.abstractproperty
def AdditionalHardwareVersion(self):
return None
@abc.abstractproperty
def SoftwareVersion(self):
return None
@abc.abstractproperty
def AdditionalSoftwareVersion(self):
return None
@abc.abstractproperty
def ProductClass(self):
return None
@abc.abstractproperty
def ModemFirmwareVersion(self):
return None
def _GetUptime():
"""Return the number of seconds since boot."""
uptime = float(open(PROC_UPTIME).read().split()[0])
return int(uptime)
class DeviceInfo181Linux26(BASE181DEVICE.DeviceInfo):
"""Implements tr-181 DeviceInfo for Linux 2.6 and similar systems."""
def __init__(self, device_id, ioloop=None):
super(DeviceInfo181Linux26, self).__init__()
assert isinstance(device_id, DeviceIdMeta)
self.ioloop = ioloop or tornado.ioloop.IOLoop.instance()
self._device_id = device_id
self.Unexport(['FirstUseDate', 'ProvisioningCode'])
self.Unexport(objects=['NetworkProperties', 'ProxierInfo'])
self.LocationList = {}
self.ProcessStatus = ProcessStatusLinux26(ioloop=ioloop)
self.TemperatureStatus = temperature.TemperatureStatus()
self.VendorLogFileList = {}
self.VendorConfigFileList = {}
self.SupportedDataModelList = {}
self.ProcessorList = {}
self.X_CATAWAMPUS_ORG_LedStatusList = {}
self._next_led_number = 1
def __getattr__(self, name):
"""Allows passthrough of parameters to the platform-supplied device_id."""
return getattr(self._device_id, name)
@property
def MemoryStatus(self):
return MemoryStatusLinux26()
@property
def UpTime(self):
return _GetUptime()
@property
def VendorLogFileNumberOfEntries(self):
return len(self.VendorLogFileList)
@property
def VendorConfigFileNumberOfEntries(self):
return len(self.VendorConfigFileList)
@property
def LocationNumberOfEntries(self):
return len(self.LocationList)
@property
def ProcessorNumberOfEntries(self):
return len(self.ProcessorList)
@property
def SupportedDataModelNumberOfEntries(self):
return len(self.SupportedDataModelList)
@property
def X_CATAWAMPUS_ORG_LedStatusNumberOfEntries(self):
return len(self.X_CATAWAMPUS_ORG_LedStatusList)
def AddLedStatus(self, led):
self.X_CATAWAMPUS_ORG_LedStatusList[self._next_led_number] = led
self._next_led_number += 1
class MemoryStatusLinux26(BASE181DEVICE.DeviceInfo.MemoryStatus):
"""Abstraction to get memory information from the underlying platform.
Reads /proc/meminfo to find TotalMem and FreeMem.
"""
Free = tr.cwmptypes.ReadOnlyUnsigned(0)
Total = tr.cwmptypes.ReadOnlyUnsigned(0)
def __init__(self):
super(MemoryStatusLinux26, self).__init__()
(total, free) = self._GetMemInfo()
type(self).Free.Set(self, free)
type(self).Total.Set(self, total)
def _GetMemInfo(self):
"""Fetch TotalMem and FreeMem from the underlying platform.
Returns:
a list of two integers, (totalmem, freemem)
"""
totalmem = 0
freemem = 0
with open(PROC_MEMINFO) as pfile:
for line in pfile:
fields = line.split()
name = fields[0]
value = fields[1]
if name == 'MemTotal:':
totalmem = int(value)
elif name == 'MemFree:':
freemem = int(value)
return (totalmem, freemem)
class ProcessStatusLinux26(BASE181DEVICE.DeviceInfo.ProcessStatus):
"""Get information about running processes on Linux 2.6.
Reads /proc/<pid> to get information about processes.
"""
# Field ordering in /proc/<pid>/stat
_PID = 0
_COMM = 1
_STATE = 2
_UTIME = 13
_STIME = 14
_PRIO = 17
_RSS = 23
Process = tr.core.Extensible(BASE181DEVICE.DeviceInfo.ProcessStatus.Process)
def __init__(self, ioloop=None):
super(ProcessStatusLinux26, self).__init__()
tick = os.sysconf(os.sysconf_names['SC_CLK_TCK'])
self._msec_per_jiffy = 1000.0 / tick
self.ioloop = ioloop or tornado.ioloop.IOLoop.instance()
self.scheduler = PERIODICCALL(self.CpuUsageTimer, 300 * 1000,
io_loop=self.ioloop)
self.scheduler.start()
self.cpu_usage = 0.0
self.cpu_used = 0
self.cpu_total = 0
self.ProcessList = tr.core.AutoDict('ProcessList',
iteritems=self.IterProcesses,
getitem=self.GetProcess)
def _LinuxStateToTr181(self, linux_state):
"""Maps Linux process states to TR-181 process state names.
Args:
linux_state: One letter describing the state of the linux process,
as described in proc(5). One of "RSDZTW"
Returns:
the tr-181 string describing the process state.
"""
mapping = {
'R': 'Running',
'S': 'Sleeping',
'D': 'Uninterruptible',
'Z': 'Zombie',
'T': 'Stopped',
'W': 'Uninterruptible'}
return mapping.get(linux_state, 'Sleeping')
def _JiffiesToMsec(self, utime, stime):
ticks = int(utime) + int(stime)
msecs = ticks * self._msec_per_jiffy
return int(msecs)
def _RemoveParens(self, command):
return command[1:-1]
def _ProcFileName(self, pid):
return '%s/%s/stat' % (SLASH_PROC, pid)
def _ParseProcStat(self):
"""Compute CPU utilization using /proc/stat.
Returns:
(used, total)
used: number of jiffies where CPU was active
total: total number of jiffies including idle
"""
with open(PROC_STAT) as f:
for line in f:
fields = line.split()
if fields[0] == 'cpu':
user = float(fields[1])
nice = float(fields[2])
syst = float(fields[3])
idle = float(fields[4])
iowt = float(fields[5])
irq = float(fields[6])
sirq = float(fields[7])
total = user + nice + syst + idle + iowt + irq + sirq
used = total - idle
return (used, total)
return (0, 0)
def CpuUsageTimer(self):
"""Called periodically to compute CPU utilization since last call."""
(new_used, new_total) = self._ParseProcStat()
total = new_total - self.cpu_total
used = new_used - self.cpu_used
if total == 0:
self.cpu_usage = 0.0
else:
self.cpu_usage = (used / total) * 100.0
self.cpu_total = new_total
self.cpu_used = new_used
@property
def CPUUsage(self):
return int(self.cpu_usage)
@property
def ProcessNumberOfEntries(self):
return len(self.ProcessList)
def GetProcess(self, pid):
"""Get a self.Process() object for the given pid."""
try:
with open(self._ProcFileName(pid)) as f:
fields = f.read().split()
p = self.Process(PID=int(fields[self._PID]),
Command=self._RemoveParens(fields[self._COMM]),
Size=int(fields[self._RSS]),
Priority=int(fields[self._PRIO]),
CPUTime=self._JiffiesToMsec(fields[self._UTIME],
fields[self._STIME]),
State=self._LinuxStateToTr181(fields[self._STATE]))
except IOError:
# This isn't an error. We have a list of files which existed the
# moment the glob.glob was run. If a process exits before we get
# around to reading it, its /proc files will go away.
p = self.Process(PID=pid, Command='<exited>', Size=0, Priority=0,
CPUTime=0, State='X_CATAWAMPUS-ORG_Exited')
return p
@tr.session.cache_as_list
def IterProcesses(self):
"""Walks through /proc/<pid>/stat to return a list of all processes."""
for filename in glob.glob(self._ProcFileName('[0123456789]*')):
pid = int(filename.split('/')[-2])
proc = self.GetProcess(pid)
yield pid, proc
class LedStatusReadFromFile(
BASE181DEVICE.DeviceInfo.X_CATAWAMPUS_ORG_LedStatus):
"""X_CATAWAMPUS-ORG_LedStatus implementation to read a line from a file."""
Name = tr.cwmptypes.ReadOnlyString('')
def __init__(self, name, filename):
super(LedStatusReadFromFile, self).__init__()
type(self).Name.Set(self, name)
self._filename = filename
@property
def Status(self):
return open(self._filename).readline().strip()
class DeviceInfo98Linux26(BASE98IGD.DeviceInfo):
"""Implementation of tr-98 DeviceInfo for Linux."""
SpecVersion = tr.cwmptypes.ReadOnlyString('1.0')
def __init__(self, device_id):
super(DeviceInfo98Linux26, self).__init__()
assert isinstance(device_id, DeviceIdMeta)
self._device_id = device_id
self.Unexport(params=['DeviceLog', 'EnabledOptions', 'FirstUseDate',
'ProvisioningCode',
'SupportedDataModelNumberOfEntries',
'ProcessorNumberOfEntries',
'LocationNumberOfEntries',
'VendorLogFileNumberOfEntries'],
objects=['NetworkProperties',
'ProcessStatus',
'ProxierInfo',
'MemoryStatus',
'TemperatureStatus'],
lists=['VendorLogFile',
'SupportedDataModel',
'Processor',
'Location'])
self.VendorConfigFileList = {}
@property
def UpTime(self):
return _GetUptime()
@property
def VendorConfigFileNumberOfEntries(self):
return len(self.VendorConfigFileList)
def __getattr__(self, name):
if hasattr(self._device_id, name):
return getattr(self._device_id, name)
else:
raise AttributeError('No such attribute %s' % name)
def main():
dp = DeviceInfo181Linux26('deviceid')
# print tr.handle.DumpSchema(dp)
print tr.handle.Dump(dp)
if __name__ == '__main__':
main()