Compare commits

..

No commits in common. "main" and "main" have entirely different histories.
main ... main

2 changed files with 26 additions and 226 deletions

View File

@ -3,5 +3,5 @@
Agent for my-idlers Agent for my-idlers
``` ```
export AGENT_API=<API_KEY> HOST=https://idlers.test2.sysdeploy.org;python3 agent.py export API_KEY=<API_KEY> HOST=https://idlers.test2.sysdeploy.org/api/servers;python3 main.py
``` ```

248
agent.py
View File

@ -1,11 +1,10 @@
import os import os
import urllib.error
import urllib.request import urllib.request
import logging import logging
import json import json
import http.client
import subprocess import subprocess
import re import re
import shutil
NON_UPDATABLE_KEYS = [ NON_UPDATABLE_KEYS = [
'server_type', 'server_type',
@ -29,8 +28,6 @@ class ServerData:
self.hostname = os.uname().nodename self.hostname = os.uname().nodename
self.public_ip = self.get_public_ip() self.public_ip = self.get_public_ip()
self.dmidecode_data = self.parse_dmidecode_output() self.dmidecode_data = self.parse_dmidecode_output()
self.hdparm_data = self.parse_hdparm_output()
self.nvme_data = self.parse_nvme_devices()
logging.basicConfig(level=logging.INFO) logging.basicConfig(level=logging.INFO)
def parse_dmidecode_output(self): def parse_dmidecode_output(self):
@ -94,121 +91,14 @@ class ServerData:
pass pass
return [] return []
def parse_hdparm_output(self):
devices = {}
# Check if hdparm exists
if shutil.which('hdparm') is None:
logging.error("hdparm not found")
return devices
# Get the list of devices
try:
device_list = [device for device in os.listdir('/sys/block') if device.startswith('sd')]
except OSError as e:
logging.error("Failed to get device list: {}".format(e))
return devices
for device in device_list:
# Only get details of devices that start with 'sd': sda, sdb, etc.
if not device.startswith('sd'):
continue
device = '/dev/' + device
try:
# Get the output
output = subprocess.check_output(['hdparm', '-I', device], stderr=subprocess.STDOUT, universal_newlines=True)
except subprocess.CalledProcessError as e:
logging.error("Failed to get hdparm output for {}: {}".format(device, e))
continue
# Parse the output
details = {}
model_number_match = re.search(r'Model Number:\s*(.*)', output)
if model_number_match is None:
logging.warning("Skipping device {} as it does not have a model number".format(device))
continue
details['model_number'] = model_number_match.group(1)
details['serial_number'] = re.search(r'Serial Number:\s*(.*)', output).group(1)
details['firmware_revision'] = re.search(r'Firmware Revision:\s*(.*)', output).group(1)
details['transport'] = re.search(r'Transport:\s*(.*)', output).group(1)
details['checksum'] = re.search(r'Checksum:\s*(.*)', output).group(1)
details['buffer_size'] = re.search(r'cache/buffer size\s*=\s*(.*)', output).group(1)
details['form_factor'] = re.search(r'Form Factor:\s*(.*)', output).group(1)
devices[device] = details
return devices
def parse_nvme_devices(self):
'''
Example nvme id-ctrl output:
NVME Identify Controller:
vid : 0x144d
ssvid : 0x144d
sn : S3RVNA0K502408F
mn : Samsung SSD 970 EVO Plus 1TB
fr : 2B2QEXM7
rab : 2
....
'''
devices = {}
# Check if nvme exists
if shutil.which('nvme') is None:
print("nvme not found")
return devices
# Get the list of nvme devices starting with 'nvme'
nvme_devices = [device for device in os.listdir('/sys/block') if device.startswith('nvme')]
for device in nvme_devices:
device_path = '/dev/' + device
try:
# Get the output of nvme id-ctrl command
output = subprocess.check_output(['nvme', 'id-ctrl', device_path], stderr=subprocess.STDOUT, universal_newlines=True)
except subprocess.CalledProcessError as e:
print("Failed to get nvme id-ctrl output for {}: {}".format(device_path, e))
continue
# Split the output into lines
lines = output.split('\n')
# Parse each line
device_info = {}
for line in lines:
if ':' in line:
key, value = line.split(':', 1)
device_info[key.strip()] = value.strip()
devices[device_path] = device_info
return devices
def get_ram_and_disk(self): def get_ram_and_disk(self):
# RAM information
with open('/proc/meminfo', 'r') as f: with open('/proc/meminfo', 'r') as f:
meminfo = f.read() meminfo = f.read()
ram = int([x for x in meminfo.split('\n') if 'MemTotal' in x][0].split()[1]) // 1024 ram = int([x for x in meminfo.split('\n') if 'MemTotal' in x][0].split()[1]) // 1024
# Disk space information with open('/proc/diskstats', 'r') as f:
disk = 0 diskstats = f.read()
for device in os.listdir('/sys/block'): disk = sum(int(x.split()[9]) for x in diskstats.split('\n') if x) * 512 // 10**9
device_path = '/sys/block/{}/device'.format(device) logging.info(f"RAM: {ram}MB, Disk: {disk}GB")
size_path = '/sys/block/{}/size'.format(device)
if not os.path.islink(device_path):
continue
try:
with open(size_path, 'r') as f:
size = int(f.read().strip())
disk += size
except Exception:
pass
disk = disk * 512 // (1024**3) # convert to GB
logging.info("RAM: {}MB, Disk: {}GB".format(ram, disk))
return ram, disk return ram, disk
def get_cpu_count(self): def get_cpu_count(self):
@ -217,20 +107,17 @@ class ServerData:
if section['DMIType'] == 4: # 4 corresponds to processor if section['DMIType'] == 4: # 4 corresponds to processor
core_count = int(section.get('Core Count', '0')) core_count = int(section.get('Core Count', '0'))
thread_count = int(section.get('Thread Count', '0')) thread_count = int(section.get('Thread Count', '0'))
if thread_count: cpu_count = core_count * thread_count
cpu_count += thread_count
else:
cpu_count += core_count
if cpu_count == 0: if cpu_count == 0:
with open('/proc/cpuinfo', 'r') as f: with open('/proc/cpuinfo', 'r') as f:
cpuinfo = f.read() cpuinfo = f.read()
cpu_count = cpuinfo.count('processor') cpu_count = cpuinfo.count('processor')
logging.info("CPU Count: {}".format(cpu_count)) logging.info(f"CPU Count: {cpu_count}")
return cpu_count return cpu_count
def get_bandwidth(self): def get_bandwidth(self):
bandwidth = 2000 bandwidth = 2000
logging.info("Bandwidth: {}".format(bandwidth)) logging.info(f"Bandwidth: {bandwidth}")
return bandwidth return bandwidth
def get_public_ip(self): def get_public_ip(self):
@ -238,80 +125,19 @@ class ServerData:
response = urllib.request.urlopen('https://api.ipify.org') response = urllib.request.urlopen('https://api.ipify.org')
return response.read().decode() return response.read().decode()
except Exception as e: except Exception as e:
logging.error("Failed to get public IP: {}".format(e)) logging.error(f"Failed to get public IP: {e}")
return '127.0.0.1' return '127.0.0.1'
def get_os_release_info(self): def get_os(self):
os_release_info = {} os_id = 27
logging.info(f"OS ID: {os_id}")
# reading from /etc/os-release return os_id
try:
with open('/etc/os-release') as f:
lines = f.read().splitlines()
os_release_info = {line.split('=')[0]: line.split('=')[1].strip('"') for line in lines if '=' in line}
except FileNotFoundError:
logging.warning("/etc/os-release not found, trying /etc/VERSION")
except Exception as e:
logging.error("Failed to read /etc/os-release: {}".format(e))
return {}
# If /etc/os-release is not found or empty, fallback to /etc/VERSION def create_post_data(self):
if not os_release_info:
try:
with open('/etc/VERSION') as f:
lines = f.read().splitlines()
for line in lines:
if '=' in line:
key, value = line.split('=', 1)
os_release_info[key.strip()] = value.strip()
except Exception as e:
logging.error("Failed to read /etc/VERSION: {}".format(e))
return {}
return os_release_info
def get_os_id(self, os_list):
os_info = self.get_os_release_info()
if not os_info:
logging.error("No OS release info found.")
for os_entry in os_list:
if os_entry['name'].lower() in ["other", "custom"]:
return os_entry['id']
return 1
if 'ID' in os_info and 'VERSION_ID' in os_info:
current_os = (os_info.get('ID', 'Unknown') + " " + os_info.get('VERSION_ID', '').strip()).strip()
else:
current_os = (os_info.get('os_name', 'Unknown') +" "+ os_info.get('productversion', '').strip()).strip().lower()
for os_entry in os_list:
if current_os in os_entry['name'].lower():
return os_entry['id']
# Fallback checks for common OS names if full name doesn't match
for os_entry in os_list:
if 'ubuntu' in current_os and 'ubuntu' in os_entry['name'].lower():
return os_entry['id']
elif 'centos' in current_os and 'centos' in os_entry['name'].lower():
return os_entry['id']
elif 'fedora' in current_os and 'fedora' in os_entry['name'].lower():
return os_entry['id']
# Default to 'other' or 'custom' if no match found
for os_entry in os_list:
if os_entry['name'].lower() in ["other", "custom"]:
return os_entry['id']
return 1
def create_post_data(self, os_list):
ram, disk = self.get_ram_and_disk() ram, disk = self.get_ram_and_disk()
post_data = { post_data = {
"server_type": 1, "server_type": 1,
"os_id": self.get_os_id(os_list), "os_id": self.get_os(),
"provider_id": 10, "provider_id": 10,
"location_id": 15, "location_id": 15,
"ssh_port": 22, "ssh_port": 22,
@ -359,33 +185,15 @@ class ServerData:
size = ram.get('Size', 'Unknown') size = ram.get('Size', 'Unknown')
speed = ram.get('Speed', 'Unknown') speed = ram.get('Speed', 'Unknown')
configured_speed = ram.get('Configured Memory Speed', 'Unknown') configured_speed = ram.get('Configured Memory Speed', 'Unknown')
try: total_width = int(ram.get('Total Width', "0").split()[0])
total_width = int(ram.get('Total Width', "0").split()[0]) data_width = int(ram.get('Data Width', "0").split()[0])
except ValueError:
total_width = 0
try:
data_width = int(ram.get('Data Width', "0").split()[0])
except ValueError:
data_width = 0
ecc = 'Yes' if total_width > data_width else 'No' ecc = 'Yes' if total_width > data_width else 'No'
serial_number = ram.get('Serial Number', 'Unknown') serial_number = ram.get('Serial Number', 'Unknown')
ram_type = ram.get('Type', 'Unknown') ram_type = ram.get('Type', 'Unknown')
ram_details.append("Size: {}, Speed: {} @ {}, ECC: {}, Serial Number: {}, Type: {}".format(size, speed, configured_speed, ecc, serial_number, ram_type)) ram_details.append("Size: {}, Speed: {} @ {}, ECC: {}, Serial Number: {}, Type: {}".format(size, speed, configured_speed, ecc, serial_number, ram_type))
# SATA Storage media
sata_details = []
for device, details in self.hdparm_data.items():
sata_details.append("Device: {}, Model: {}, Serial: {}, Checksum: {}, Buffer Size: {}, Form Factor: {}".format(
device, details['model_number'], details['serial_number'], details['checksum'], details['buffer_size'], details['form_factor']))
# NVMe Storage media
nvme_details = []
for device, details in self.nvme_data.items():
nvme_details.append("Device: {}, Model: {}, Serial: {}, Firmware: {}".format(
device, details.get('mn', 'Unknown'), details.get('sn', 'Unknown'), details.get('fr', 'Unknown')))
note = "Chassis Model: {} | Serial Number: {} ||| Processor Model: {} | Count: {} ||| RAM Details: {} ||| SATA Details {} ||| NVME Details {}".format( note = "Chassis Model: {} | Serial Number: {} ||| Processor Model: {} | Count: {} ||| RAM Details: {}".format(
chassis_model, chassis_serial, processor_model, processor_count, ' | '.join(ram_details), ' | '.join(sata_details), ' | '.join(nvme_details)) chassis_model, chassis_serial, processor_model, processor_count, ' | '.join(ram_details))
note_data = { note_data = {
'note': note, 'note': note,
@ -415,7 +223,7 @@ class ServerManager:
else: else:
return response.read().decode() return response.read().decode()
except urllib.error.HTTPError as e: except urllib.error.HTTPError as e:
logging.error("Request failed with {}".format(e)) logging.error(f"Request failed with {e}")
raise raise
def get_existing_servers(self): def get_existing_servers(self):
@ -429,7 +237,7 @@ class ServerManager:
return self.send_request('POST', '/api/notes', post_data) return self.send_request('POST', '/api/notes', post_data)
def update_note(self, post_data, service_id): def update_note(self, post_data, service_id):
logging.info("Updating note with id {}...".format(service_id)) logging.info(f"Updating note with id {service_id}...")
return self.send_request('PUT', '/api/notes/' + str(service_id), post_data) return self.send_request('PUT', '/api/notes/' + str(service_id), post_data)
def create_server(self, post_data): def create_server(self, post_data):
@ -440,7 +248,7 @@ class ServerManager:
# remove following keys from post_data # remove following keys from post_data
for key in NON_UPDATABLE_KEYS: for key in NON_UPDATABLE_KEYS:
post_data.pop(key, None) post_data.pop(key, None)
logging.info("Updating server with id {}...".format(server_id)) logging.info(f"Updating server with id {server_id}...")
return self.send_request('PUT', '/api/servers/' + str(server_id), post_data) return self.send_request('PUT', '/api/servers/' + str(server_id), post_data)
def existing_server_id(self, post_data): def existing_server_id(self, post_data):
@ -479,11 +287,6 @@ class ServerManager:
return self.update_note(note_data, server_id) return self.update_note(note_data, server_id)
else: else:
return self.create_note(note_data) return self.create_note(note_data)
def get_os_list(self):
os_list = self.send_request('GET', '/api/os')
logging.info("OS list fetched successfully") if os_list else logging.error("Failed to fetch OS list")
return os_list or []
def validate_env_vars(): def validate_env_vars():
api_key = os.getenv('AGENT_API') api_key = os.getenv('AGENT_API')
@ -499,17 +302,14 @@ def main():
host, api_key = validate_env_vars() host, api_key = validate_env_vars()
server_manager = ServerManager(host, api_key)
server_data = ServerData() server_data = ServerData()
os_list = server_manager.get_os_list() post_data = server_data.create_post_data()
post_data = server_data.create_post_data(os_list)
server_manager = ServerManager(host, api_key)
server_id = server_manager.upsert_server(post_data) server_id = server_manager.upsert_server(post_data)
logging.info('Server id: {}'.format(server_id)) logging.info('Server id: {}'.format(server_id))
note_data = server_data.create_note_data() note_data = server_data.create_note_data()
server_manager.upsert_note(note_data, server_id) server_manager.upsert_note(note_data, server_id)