Skip to content

Commit

Permalink
Display device stats from opsi/puppet in readable form
Browse files Browse the repository at this point in the history
  • Loading branch information
phillipthelen committed Apr 2, 2020
1 parent 2c479fd commit 8545221
Show file tree
Hide file tree
Showing 18 changed files with 1,001 additions and 735 deletions.
1 change: 1 addition & 0 deletions Lagerregal/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,7 @@
path('devices_ajax/get_attributes/', login_required(devicetypes_ajax.GetTypeAttributes.as_view()), name="get-attributes"),
path('devices_ajax/user_lendings/', login_required(devices_ajax.UserLendings.as_view()), name="get-user-lendings"),
path('devices_ajax/devicedetails/<int:device>/', login_required(devicedata_ajax.DeviceDetails.as_view()), name="device-details"),
path('devices_ajax/devicedetails/<int:device>/json', login_required(devicedata_ajax.DeviceDetailsJson.as_view()), name="device-details-json"),
path('devices_ajax/devicesoftware/<int:device>/', login_required(devicedata_ajax.DeviceSoftware.as_view()), name="device-software"),
]

Expand Down
32 changes: 27 additions & 5 deletions devicedata/ajax.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from django.http import HttpResponse
from django.http import HttpResponse, JsonResponse
from django.shortcuts import get_object_or_404
from django.shortcuts import render
from django.views.generic.base import View
Expand All @@ -7,12 +7,15 @@
from devicedata.providers.opsi import OpsiProvider
from devicedata.providers.puppet import PuppetProvider
from devices.models import Device
import logging

data_providers = {
"opsi": OpsiProvider,
"puppet": PuppetProvider
}

logger = logging.getLogger(__name__)


def _get_provider(device):
if device.data_provider is not None and device.data_provider in data_providers.keys():
Expand All @@ -31,24 +34,43 @@ def get(request, device):
device = get_object_or_404(Device, pk=device)
try:
device_info = _get_provider(device).get_device_info(device)
except:
except Exception as e:
logger.error(e)
return HttpResponse(_("Could not load data."))
context = {
'device_info': device_info
'device_info': device_info.raw_entries
}
return render(request, 'devicedata/device_info.html', context)


class DeviceDetailsJson(View):

@staticmethod
def get(request, device):
device = get_object_or_404(Device, pk=device)
provider = _get_provider(device)
if provider is None:
return JsonResponse({})
device_info = provider.get_device_info(device)
raw_entries = [{"name": entry.name,
"type": entry.type,
"raw_value": entry.raw_value} for entry in device_info.raw_entries]
formatted_entries = [{"name": entry.name,
"value": entry.value} for entry in device_info.formatted_entries]
return JsonResponse({"raw_entries": raw_entries, "formatted_entries": formatted_entries})


class DeviceSoftware(View):

@staticmethod
def get(request, device):
device = get_object_or_404(Device, pk=device)
try:
software_info = _get_provider(device).get_software_info(device)
except:
except Exception as e:
logger.error(e)
return HttpResponse(_("Could not load data."))
context = {
'software_info': software_info
}
return render(request, 'devicedata/software_info.html', context)
return render(request, 'devicedata/software_info.html', context)
26 changes: 24 additions & 2 deletions devicedata/providers/base_provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,30 @@
from abc import ABC, abstractmethod


class DeviceInfo:
pass
class BaseDeviceInfo(ABC):
formatted_entries = []
raw_entries = []

def find_entries(self, entry_type):
return [entry for entry in self.raw_entries if entry.type == entry_type]

def __init__(self, raw_entries):
self.formatted_entries = []
self.raw_entries = raw_entries
self.format_entries()

@abstractmethod
def format_entries(self):
pass


class FormattedDeviceInfoEntry:
name = ""
value = ""

def __init__(self, name, value):
self.name = name
self.value = value


class DeviceInfoEntry:
Expand Down
8 changes: 8 additions & 0 deletions devicedata/providers/helpers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
def format_bytes(size, power=2**10):
# 2**10 = 1024
n = 0
power_labels = {0: '', 1: 'K', 2: 'M', 3: 'G', 4: 'T'}
while size > power:
size /= power
n += 1
return "{0:g}{1}".format(size, power_labels[n]+"B")
75 changes: 73 additions & 2 deletions devicedata/providers/opsi.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,77 @@
from django.core.exceptions import ObjectDoesNotExist

from Lagerregal import settings
from devicedata.providers.base_provider import BaseProvider, SoftwareEntry, DeviceInfoEntry
from devicedata.providers.base_provider import BaseProvider, SoftwareEntry, DeviceInfoEntry, BaseDeviceInfo, \
FormattedDeviceInfoEntry
from devicedata.providers.helpers import format_bytes
from devicedata.providers.opsirpc import OpsiConnection
from django.utils.translation import ugettext_lazy as _


class OpsiDeviceInfo(BaseDeviceInfo):

def format_chassis(self):
entries = self.find_entries("CHASSIS")
if len(entries) > 0:
self.formatted_entries.append(
FormattedDeviceInfoEntry(_("Serial Number"), entries[0].raw_value["serialNumber"]))
self.formatted_entries.append(FormattedDeviceInfoEntry(_("Type"), entries[0].raw_value["chassisType"]))

def format_system(self):
entries = self.find_entries("COMPUTER_SYSTEM")
if len(entries) > 0:
self.formatted_entries.append(FormattedDeviceInfoEntry(_("Manufacturer"), entries[0].raw_value["vendor"]))
self.formatted_entries.append(FormattedDeviceInfoEntry(_("Hostname"), entries[0].raw_value["hostId"]))
self.formatted_entries.append(FormattedDeviceInfoEntry(_("Last Seen"), entries[0].raw_value["lastseen"]))

def format_processor(self):
entries = self.find_entries("PROCESSOR")
if len(entries) > 0:
self.formatted_entries.append(FormattedDeviceInfoEntry(_("Processor"), entries[0].name))

def format_memory(self):
entries = self.find_entries("MEMORY_MODULE")
capacities = []
for entry in entries:
capacities.append(entry.raw_value["capacity"])
total_capacity = format_bytes(sum(capacities))
formatted_capacities = ", ".join([format_bytes(capacity) for capacity in capacities])
self.formatted_entries.append(
FormattedDeviceInfoEntry(_("Memory"), "{0} ({1})".format(total_capacity, formatted_capacities)))

def format_storage(self):
entries = self.find_entries("HARDDISK_DRIVE")
drives = []
for entry in entries:
if "USB" not in entry.raw_value["name"]:
drives.append(entry.raw_value)
formatted_capacities = "<br />".join(
["{0} {1}".format(drive["model"], format_bytes(drive["size"], power=1000)) for drive in drives])
self.formatted_entries.append(FormattedDeviceInfoEntry(_("Storage"), formatted_capacities))

def format_network(self):
entries = self.find_entries("NETWORK_CONTROLLER")
controllers = []
for entry in entries:
if entry.raw_value["ipAddress"] is not None:
controllers.append(entry.raw_value)
formatted_controllers = "<br />".join(
["{0} {1}".format(controller["description"], controller["ipAddress"]) for controller in controllers])
self.formatted_entries.append(FormattedDeviceInfoEntry(_("Network"), formatted_controllers))

def format_graphics(self):
entries = self.find_entries("VIDEO_CONTROLLER")
if len(entries) > 0:
self.formatted_entries.append(FormattedDeviceInfoEntry(_("Graphics"), entries[0].name))

def format_entries(self):
self.format_chassis()
self.format_system()
self.format_processor()
self.format_memory()
self.format_storage()
self.format_network()
self.format_graphics()


class OpsiProvider(BaseProvider):
Expand All @@ -15,6 +84,8 @@ def __get_host(self, device):
host = None
for ip in device.ipaddress_set.all():
response = self.__connection.host_getObjects(ipAddress=ip.address)
if len(response) == 0:
raise ObjectDoesNotExist()
host = response[0]
for h in response:
if str(device.id) in h['id']:
Expand All @@ -31,7 +102,7 @@ def get_device_info(self, device):
device_entries = []
for entry in hardware:
device_entries.append(DeviceInfoEntry(entry["hardwareClass"], entry["name"], entry))
return device_entries
return OpsiDeviceInfo(device_entries)

def get_software_info(self, device):
host = self.__get_host(device)
Expand Down
94 changes: 89 additions & 5 deletions devicedata/providers/puppet.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,95 @@
import requests
from django.core.exceptions import ValidationError, ObjectDoesNotExist

from django.http import HttpResponse
from urllib3.exceptions import SubjectAltNameWarning

from Lagerregal import settings
from devicedata.providers.base_provider import BaseProvider, DeviceInfoEntry, SoftwareEntry
from devicedata.providers.base_provider import BaseProvider, BaseDeviceInfo, DeviceInfoEntry, SoftwareEntry, \
FormattedDeviceInfoEntry
from django.utils.translation import ugettext_lazy as _

from devicedata.providers.helpers import format_bytes


class PuppetDeviceInfo(BaseDeviceInfo):

def format_serialnumber(self):
entries = self.find_entries("sp_serial_number")
entries.extend(self.find_entries("serialnumber"))
if len(entries) > 0:
self.formatted_entries.append(FormattedDeviceInfoEntry(_("Serial Number"), entries[0].raw_value))

def format_type(self):
entries = self.find_entries("sp_machine_name")
if len(entries) > 0:
self.formatted_entries.append(FormattedDeviceInfoEntry(_("Type"), entries[0].raw_value))

def format_hostname(self):
entries = self.find_entries("fqdn")
if len(entries) > 0:
self.formatted_entries.append(FormattedDeviceInfoEntry(_("Hostname"), entries[0].raw_value))

def format_lastseen(self):
entries = self.find_entries("timestamp")
if len(entries) > 0:
self.formatted_entries.append(FormattedDeviceInfoEntry(_("Last Seen"), entries[0].raw_value))

def format_processor(self):
entries = self.find_entries("processors")
if len(entries) > 0:
processors = {processor for processor in entries[0].raw_value["models"]}
self.formatted_entries.append(
FormattedDeviceInfoEntry(_("Processor"), "<br />".join(processors)))

def format_memory(self):
entries = self.find_entries("memory")
if len(entries) > 0:
system = entries[0].raw_value["system"]
self.formatted_entries.append(
FormattedDeviceInfoEntry(_("Memory"), format_bytes(system["total_bytes"])))

def format_storage(self):
entries = self.find_entries("disks")
drives = []
for entry in entries:
for disk_identifier in entry.raw_value:
disk = entry.raw_value[disk_identifier]
if "size_bytes" in disk and disk["size_bytes"] < 1000000000:
# Smaller than 10gb. This most likely is not a hard drive.
continue
if "model" in disk and "USB" not in disk["model"]:
disk["identifier"] = disk_identifier
drives.append(disk)
formatted_capacities = "<br />".join(
["{0} ({1}): {2}".format(drive["model"], drive["identifier"], format_bytes(drive["size_bytes"])) for drive in drives])
self.formatted_entries.append(FormattedDeviceInfoEntry(_("Storage"), formatted_capacities))

def format_network(self):
entries = self.find_entries("networking")
controllers = []
for entry in entries:
interfaces = entry.raw_value["interfaces"]
for interface in interfaces:
if "mac" in interfaces[interface] and interfaces[interface]["mac"] is not None:
interfaces[interface]["identifier"] = interface
controllers.append(interfaces[interface])
formatted_controllers = "<br />".join(
["{0}: {1}".format(controller["identifier"], controller["ip"]) for controller in controllers])
self.formatted_entries.append(FormattedDeviceInfoEntry(_("Network"), formatted_controllers))

def format_graphics(self):
entries = self.find_entries("VIDEO_CONTROLLER")
if len(entries) > 0:
self.formatted_entries.append(FormattedDeviceInfoEntry(_("Graphics"), entries[0].name))

def format_entries(self):
self.format_serialnumber()
self.format_type()
self.format_hostname()
self.format_lastseen()
self.format_processor()
self.format_memory()
self.format_storage()
self.format_network()
self.format_graphics()


class PuppetProvider(BaseProvider):
Expand Down Expand Up @@ -56,7 +140,7 @@ def get_device_info(self, device):
device_entries = []
for entry in res:
device_entries.append(DeviceInfoEntry(entry["name"], None, entry["value"]))
return device_entries
return PuppetDeviceInfo(device_entries)

def get_software_info(self, device):
software_fact = settings.PUPPETDB_SETTINGS['software_fact']
Expand Down
2 changes: 1 addition & 1 deletion devices/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -258,7 +258,7 @@ def clean(self):
if key.startswith("attribute_") and attribute != "":
attributenumber = key.split("_")[1]
typeattribute = get_object_or_404(TypeAttribute, pk=attributenumber)
if re.match(typeattribute.regex, attribute) is None:
if typeattribute.regex is not None and re.match(typeattribute.regex, attribute) is None:
self._errors[key] = self.error_class(
[_("Doesn't match the given regex \"{0}\".".format(typeattribute.regex))])
unclean_data.append(key)
Expand Down
3 changes: 2 additions & 1 deletion devices/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,7 @@ class DeviceDetail(PermissionRequiredMixin, DetailView):
context_object_name = 'device'
object = None
permission_required = 'devices.view_device'
template_name = "devices/detail/device_detail.html"

def get_object(self, queryset=None):
if self.object is not None:
Expand Down Expand Up @@ -1729,7 +1730,7 @@ def get_context_data(self, **kwargs):


class PublicDeviceDetailView(DetailView):
template_name = "devices/device_detail.html"
template_name = "devices/detail/device_detail.html"
context_object_name = "device"

def get_queryset(self):
Expand Down
Loading

0 comments on commit 8545221

Please sign in to comment.