Skip to content

Commit

Permalink
Send inventory and heartbeat
Browse files Browse the repository at this point in the history
  • Loading branch information
DavidePrincipi committed Jan 12, 2024
1 parent 1119016 commit f867244
Show file tree
Hide file tree
Showing 7 changed files with 295 additions and 0 deletions.
11 changes: 11 additions & 0 deletions core/imageroot/etc/systemd/system/send-heartbeat.service
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
[Unit]
Description=Subscription heartbeat

[Service]
Type=simple
ExecStart=runagent send-heartbeat 600
Restart=on-failure
SyslogIdentifier=send-heartbeat

[Install]
WantedBy=default.target
7 changes: 7 additions & 0 deletions core/imageroot/etc/systemd/system/send-inventory.service
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
[Unit]
Description=Subscription inventory

[Service]
Type=simple
ExecStart=runagent send-inventory
SyslogIdentifier=send-inventory
11 changes: 11 additions & 0 deletions core/imageroot/etc/systemd/system/send-inventory.timer
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
[Unit]
Description=Trigger sending inventory for subscription

[Timer]
OnBootSec=600
OnCalendar=1:00:00
Persistent=true
RandomizedDelaySec=21600

[Install]
WantedBy=timers.target
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,8 @@ def terminate_nsent(rdb, attributes):
})
ofreekey = freekeyresp.json()

agent.run_helper("systemctl", "disable", "--now", "send-heartbeat.service", "send-inventory.timer")

return ofreekey

def subscribe_nsent(rdb, attributes):
Expand All @@ -86,6 +88,9 @@ def subscribe_nsent(rdb, attributes):
trx.publish('cluster/event/subscription-changed', json.dumps({"action": "subscribed"}))
trx.execute()

agent.run_helper("send-inventory")
agent.run_helper("systemctl", "enable", "--now", "send-inventory.timer", "send-heartbeat.service")

return dinfo


Expand Down
153 changes: 153 additions & 0 deletions core/imageroot/var/lib/nethserver/cluster/bin/print-inventory-nsent
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
#!/usr/bin/python3

#
# Copyright (C) 2022 Nethesis S.r.l.
# SPDX-License-Identifier: GPL-2.0-only
#

import json
import subprocess
import os
import sys
import ipaddress as ipm

def _run(cmd):
try:
proc = subprocess.run(cmd, shell=True, check=True, capture_output=True, text=True)
return proc.stdout.rstrip().lstrip()
except:
return ''

def _run_json(cmd):
try:
return json.loads(_run(cmd))
except:
return None

def _get_cpu_field(field, cpu_info):
for f in cpu_info:
if f['field'].startswith(field):
return f['data']

def _get_networks():
networks = []
ifgateway = {}

# Find gateway of interfaces
with subprocess.Popen(["ip", "-j", "route", "show"], stdout=subprocess.PIPE) as proc:
for iroute in json.load(proc.stdout):
if 'gateway' in iroute:
ifgateway[iroute["dev"]] = iroute["gateway"]

# Find the first IP address of every interface
with subprocess.Popen(["ip", "-j", "address", "show"], stdout=subprocess.PIPE) as proc:
for iface in json.load(proc.stdout):
try:
ainfo = iface['addr_info'][0] # get the first IP address
except:
continue # skip

addr = ipm.ip_address(ainfo['local'])

if(addr.is_unspecified or addr.is_reserved or addr.is_loopback or addr.is_link_local):
continue # skip

anet = ipm.ip_network(ainfo['local'] + '/' + str(ainfo['prefixlen']), strict=False)

if iface['ifname'] == 'wg0':
ifrole = 'gray'
iftype = 'ethernet'
elif addr.is_private:
ifrole = 'green'
iftype = 'ethernet'
else:
ifrole = 'orange'
iftype = 'ethernet'

# green interface
networks.append({
"type": iftype,
"name": iface['ifname'],
"props": {
"role": ifrole,
"ipaddr": ainfo['local'],
"netmask": str(anet.netmask),
"gateway": ifgateway.get(iface['ifname'], "")
},
})

return networks

def _get_nameservers():
output = _run("awk '/^nameserver/ {print $2}' < /etc/resolv.conf")
return output.replace("\n", ",")

def _get_mountpoints():
mountpoints = {}
try:
dfrows = _run("df -l -x tmpfs -x shm -x overlay -x devtmpfs --output=source,size,used,avail").split("\n")
dfrows.pop(0)
for dfline in dfrows:
src, size, used, avail = dfline.split()
mountpoints[src] = {
"size_bytes": int(size) * 1024,
"used_bytes": int(used) * 1024,
"available_bytes": int(avail) * 1024,
}
except Exception as ex:
print(ex, file=sys.stderr)
return mountpoints

def _get_memory():
memory = {}
memout = _run("free -b").split("\n")
memout.pop(0)
try:
for memline in memout:
cat, total, used, free, _ = (memline + " 0 0 0 0 0").split(sep=None, maxsplit=4)
if cat == 'Swap:':
key = 'swap'
elif cat == 'Mem:':
key = 'system'
else:
continue
memory[key] = {
"used_bytes": used,
"total_bytes": total,
"available_bytes": free,
}
except Exception as ex:
print(ex, file=sys.stderr)

return memory
release = "8"
cpu_info = _run_json('lscpu -J')['lscpu']
board = _run("cat /sys/devices/virtual/dmi/id/board_name") or "board.unknown"
product = _run("cat /sys/devices/virtual/dmi/id/product_name") or "product.unknown"

data = {
"arp_macs": _run('grep -v IP /proc/net/arp | wc -l'),
"dmi": { "product": { "name": product, "uuid": _run("cat /sys/class/dmi/id/product_uuid") }, "bios": { "version": _run("cat /sys/devices/virtual/dmi/id/bios_version"), "vendor": _run("cat /sys/devices/virtual/dmi/id/bios_vendor")}, "board": { "product": board, "manufacturer": _run("cat /sys/devices/virtual/dmi/id/sys_vendor") }},
"virtual": _get_cpu_field("Hypervisor vendor", cpu_info) if _get_cpu_field("Hypervisor vendor", cpu_info) else 'physical',
"kernel": _run('uname'),
"kernelrelease": _run('uname -r'),
"networking": { "fqdn": _run("hostname -f")},
"os": { "type": "nethserver", "name": "NS8", "release": { "full": release, "major": 7 }, "family": "os.family" },
"processors": { "count": "", "models": [ _get_cpu_field("Model name", cpu_info) ], "isa": _get_cpu_field("Architecture", cpu_info)},
"timezone": _run("timedatectl show -p Timezone --value"),
"system_uptime": { "seconds": _run("cat /proc/uptime | awk -F. '{print $1}'") },
"esmithdb": {
"networks": _get_networks(),
"configuration" : [
{ "name": "sysconfig", "type": "configuration", "props": {"Version": release} },
{ "name": "dns", "type": "configuration", "props": {"NameServers": _get_nameservers()} },
{ "name" : "SystemName", "type" : _run("hostname -s") },
{ "name" : "DomainName", "type" : _run("hostname -d") }
]
},
"memory": _get_memory(),
"mountpoints": _get_mountpoints(),
"rpms": {}
}

print(json.dumps(data))
56 changes: 56 additions & 0 deletions core/imageroot/var/lib/nethserver/cluster/bin/send-heartbeat
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
#!/bin/bash

#
# Copyright (C) 2024 Nethesis S.r.l.
# SPDX-License-Identifier: GPL-3.0-or-later
#

set -e

period="${1:-0}"

# Parse subscription configuration from Redis
while IFS='=' read -r key value; do
declare "${key}"="${value}"
done < <(redis-hgetall cluster/subscription)

function exit_error ()
{
echo "[ERROR]" "${@}" 1>&2
exit 1
}

function send_heartbeat_nsent ()
{
jq -n -c --arg system_id "${system_id:?}" '{"lk":$system_id}' | \
curl -m 180 --retry 3 -L -s \
--header "Accept: application/json" \
--header "Authorization: token ${auth_token:?}" \
--header "Content-Type: application/json" \
--data @- \
"https://my.nethesis.it/isa/heartbeats/store" >/dev/null || :
}


sendfunc="send_heartbeat_${provider}"
if [[ $(type -t "${sendfunc}") != function ]]; then
echo "[WARNING] Invalid provider, ignored: ${provider:-None}" 1>&2
exit 0
fi

${sendfunc}

if [[ "${period}" == 0 ]]; then
exit 0 # exit immediately if period parameter is not given
fi

trap 'kill -TERM -- -${BASHPID}' INT
while true; do
if (( period > 60 )); then
# approximate period to 5% tolerance
sleep $(( period + RANDOM % (period / 10) - (period / 20) ))
else
sleep "${period}"
fi
${sendfunc}
done
52 changes: 52 additions & 0 deletions core/imageroot/var/lib/nethserver/cluster/bin/send-inventory
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
#!/bin/bash

#
# Copyright (C) 2024 Nethesis S.r.l.
# SPDX-License-Identifier: GPL-3.0-or-later
#

set -e

# Parse subscription configuration from Redis
while IFS='=' read -r key value; do
declare "${key}"="${value}"
done < <(redis-hgetall cluster/subscription)

function exit_error ()
{
echo "[ERROR]" "${@}" 1>&2
exit 1
}

function send_inventory_nsent ()
{
print-inventory-nsent | jq -c \
--arg system_id "${system_id:?}" \
'{"data":{"lk":$system_id,"data":.}}' | \
curl -m 180 --retry 3 -L -s \
--header "Accept: application/json" \
--header "Authorization: token ${auth_token:?}" \
--header "Content-Type: application/json" \
--data @- \
"https://my.nethesis.it/isa/inventory/store/" >>/dev/null || :

# Update the registration date after inventory was submitted for the
# first time:
jq -n -c \
--arg auth_token "${auth_token:?}" \
'{"secret":$auth_token}' | \
/usr/bin/curl -m 180 --retry 3 -L -s \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--data @- \
https://my.nethesis.it/api/systems/info >>/dev/null || :
}


sendfunc="send_inventory_${provider}"
if [[ $(type -t "${sendfunc}") != function ]]; then
echo "[WARNING] Invalid provider, ignored: ${provider}" 1>&2
exit 0
fi

${sendfunc}

0 comments on commit f867244

Please sign in to comment.