Skip to content

Commit

Permalink
Initial multiradio status skeleton
Browse files Browse the repository at this point in the history
Jira-Id: SCDI-43

Signed-off-by: Mika Joenpera <[email protected]>
  • Loading branch information
joenpera committed Oct 11, 2023
1 parent 56bb175 commit 73c37a0
Show file tree
Hide file tree
Showing 6 changed files with 95 additions and 74 deletions.
42 changes: 32 additions & 10 deletions modules/sc-mesh-secure-deployment/src/nats/comms_nats_controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@ def __init__(self, loop_interval: int = 1000, logger: logging.Logger = None):
# milliseconds to seconds
self.interval = float(loop_interval / 1000.0)
self.logger = logger
self.batman_visual = batadvvis.BatAdvVis(self.interval*0.2)
self.batman = batstat.Batman(self.interval*0.2)
self.batman_visual = batadvvis.BatAdvVis(self.interval * 0.2)
self.batman = batstat.Batman(self.interval * 0.2)
self.visualisation_enabled = False

def mesh_visual(self):
Expand Down Expand Up @@ -76,6 +76,7 @@ class CommsController: # pylint: disable=too-few-public-methods
"""
Mesh network
"""

def __init__(self, server: str, port: str, interval: int = 1000):
self.nats_server = server
self.port = port
Expand All @@ -90,7 +91,11 @@ def __init__(self, server: str, port: str, interval: int = 1000):
console_handler.setFormatter(log_formatter)
self.main_logger.addHandler(console_handler)

self.comms_status = comms_status.CommsStatus(self.main_logger.getChild("status"))
self.comms_status = []
#TODO how many radios?
self.comms_status.append(comms_status.CommsStatus(self.main_logger.getChild("status 0")))
self.comms_status.append(comms_status.CommsStatus(self.main_logger.getChild("status 1")))
self.comms_status.append(comms_status.CommsStatus(self.main_logger.getChild("status 2")))
self.settings = comms_settings.CommsSettings(self.comms_status,
self.main_logger.getChild("settings"))
self.command = comms_command.Command(server, port, self.comms_status,
Expand All @@ -105,6 +110,7 @@ class CommsCsa: # pylint: disable=too-few-public-methods
"""
Comms CSA class to storage settings for CSA for a state change
"""

def __init__(self):
self.delay = "0"
self.ack_sent = False
Expand Down Expand Up @@ -209,14 +215,30 @@ async def message_handler(message):
await handle_settings_csa_post(ret)
else:
# Update status info
cc.comms_status.refresh_status()
# TODO how many radios?
cc.comms_status[0].refresh_status()
cc.comms_status[1].refresh_status()
cc.comms_status[2].refresh_status()
response = {'status': ret, 'info': info,
'mesh_status': cc.comms_status.mesh_status,
'mesh_cfg_status': cc.comms_status.mesh_cfg_status,
'visualisation_active': cc.comms_status.is_visualisation_active,
'mesh_radio_on': cc.comms_status.is_mesh_radio_on,
'ap_radio_on': cc.comms_status.is_ap_radio_on,
'security_status': cc.comms_status.security_status}
'mesh_status': [cc.comms_status[0].mesh_status,
cc.comms_status[1].mesh_status,
cc.comms_status[2].mesh_status],
'mesh_cfg_status': [cc.comms_status[0].mesh_cfg_status,
cc.comms_status[1].mesh_cfg_status,
cc.comms_status[2].mesh_cfg_status],
'visualisation_active': [cc.comms_status[0].is_visualisation_active,
cc.comms_status[1].is_visualisation_active,
cc.comms_status[2].is_visualisation_active],
'mesh_radio_on': [cc.comms_status[0].is_mesh_radio_on,
cc.comms_status[1].is_mesh_radio_on,
cc.comms_status[2].is_mesh_radio_on],
'ap_radio_on': [cc.comms_status[0].is_ap_radio_on,
cc.comms_status[1].is_ap_radio_on,
cc.comms_status[2].is_ap_radio_on],
'security_status': [cc.comms_status[0].security_status,
cc.comms_status[1].security_status,
cc.comms_status[2].security_status]
}

if resp != "":
response['data'] = resp
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ async def main():
# Connect to NATS!
nc = await client.connect_nats()

cmd_dict = {"api_version": 1, "cmd": "APPLY", "radio_index": "0"}
cmd_dict = {"api_version": 1, "cmd": "APPLY", "radio_index": "1"}
cmd = json.dumps(cmd_dict)
rep = await nc.request(
f"comms.command.{config.MODULE_IDENTITY}", cmd.encode(), timeout=10
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,12 +43,12 @@ async def main():
"subnet": "255.255.255.0",
"tx_power": "5",
"mode": "mesh",
"mesh_vif": "wlp1s0",
"phy": "phy0",
"mesh_vif": "wlp1s2",
"phy": "phy1",
"batman_iface": "bat0",
},
],
"bridge": [ { "br-mesh": "bat0 eth0 eth1 lan1" } ]
"bridge": "br-lan bat0 lan1"
}

cmd = json.dumps(cmd_dict)
Expand Down
31 changes: 17 additions & 14 deletions modules/sc-mesh-secure-deployment/src/nats/src/comms_command.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ class Command: # pylint: disable=too-few-public-methods, too-many-instance-attr
Command class
"""

def __init__(self, server, port, comms_status: CommsStatus, logger):
def __init__(self, server, port, comms_status: [CommsStatus, ...], logger):
self.nats_server = server
self.port = port
self.logger = logger
Expand Down Expand Up @@ -164,7 +164,7 @@ def __revoke(self, cc) -> (str, str):
+ str(ret.stderr)

self.logger.debug("Default mesh command applied")
if self.comms_status.is_visualisation_active:
if self.comms_status[int(self.radio_index)].is_visualisation_active:
ret, _, _ = self.__disable_visualisation(cc)
if ret == "FAIL":
return "FAIL", "Revoke failed partially." \
Expand All @@ -180,20 +180,20 @@ def __apply_mission_config(self, csa=False, delay="0") -> (str, str):
Returns:
tuple: (str, str)
"""
if self.comms_status.mesh_cfg_status == STATUS.mesh_cfg_stored:
if self.comms_status[int(self.radio_index)].mesh_cfg_status == STATUS.mesh_cfg_stored:
try:
os.replace("/opt/mesh_stored.conf", "/opt/mesh.conf")
os.replace(f"/opt/{self.radio_index}_mesh_stored.conf", f"/opt/{self.radio_index}_mesh.conf")
except:
self.logger.error("Error replacing active config file!")
return "FAIL", "Error replacing active config file"

# Create hash file for active config bookkeeping
try:
with open("/opt/mesh.conf", "rb") as f:
with open(f"/opt/{self.radio_index}_mesh.conf", "rb") as f:
data = f.read()
hash_obj = hashlib.sha256(data)
hash_hex = hash_obj.hexdigest()
with open("/opt/mesh.conf_hash", "w") as f_hash:
with open(f"/opt/{self.radio_index}_mesh.conf_hash", "w") as f_hash:
f_hash.write(hash_hex)
except:
self.logger.error("Error writing hash file!")
Expand Down Expand Up @@ -235,7 +235,7 @@ def __apply_mission_config(self, csa=False, delay="0") -> (str, str):
def __radio_down(self) -> (str, str):

for process in ["/opt/S9011sNatsMesh", "/opt/S90APoint"]:
ret = subprocess.run([process, "stop"],
ret = subprocess.run([process, "stop", "id" + self.radio_index],
shell=False, check=True, capture_output=True)
if ret.returncode != 0:
self.logger.error("Failed to deactivate radio")
Expand All @@ -250,7 +250,7 @@ def __radio_down(self) -> (str, str):
def __radio_up(self) -> (str, str):

for process in ["/opt/S9011sNatsMesh", "/opt/S90APoint"]:
ret = subprocess.run([process, "start"],
ret = subprocess.run([process, "start", "id" + self.radio_index],
shell=False, check=True,
capture_output=True)
if ret.returncode != 0:
Expand All @@ -271,7 +271,7 @@ def __enable_visualisation(self, cc) -> (str, str):
return "FAIL", "Failed to enable visualisation"

self.logger.debug("Visualisation enabled")
self.comms_status.is_visualisation_active = True
self.comms_status[int(self.radio_index)].is_visualisation_active = True
return "OK", "Visualisation enabled"

def __disable_visualisation(self, cc) -> (str, str):
Expand All @@ -283,7 +283,7 @@ def __disable_visualisation(self, cc) -> (str, str):
return "FAIL", "Failed to disable visualisation"

self.logger.debug("Visualisation disabled")
self.comms_status.is_visualisation_active = False
self.comms_status[int(self.radio_index)].is_visualisation_active = False
return "OK", "Visualisation disabled"

def __read_log_file(self, filename) -> bytes:
Expand Down Expand Up @@ -328,14 +328,14 @@ def __get_configs(self, param) -> (str, str, str):
file_b64 = b'None'
try:
files = ConfigFiles()
self.comms_status.refresh_status()
self.comms_status[int(self.radio_index)].refresh_status()
if param == files.WPA:
if_name = self.comms_status.mesh_interface_name
if_name = self.comms_status[int(self.radio_index)].mesh_interface_name
if if_name:
file_b64 = self.__read_log_file(
f"/var/run/wpa_supplicant-11s_{if_name}.conf")
elif param == files.HOSTAPD:
if_name = self.comms_status.ap_interface_name
if_name = self.comms_status[int(self.radio_index)].ap_interface_name
if if_name:
file_b64 = self.__read_log_file(
f"/var/run/hostapd-{if_name}.conf")
Expand All @@ -355,11 +355,14 @@ def get_identity(self) -> (str, str, dict):
identity_dict = {}
try:
files = ConfigFiles()
self.comms_status.refresh_status()
self.comms_status[0].refresh_status()
self.comms_status[1].refresh_status()
self.comms_status[2].refresh_status()

with open(files.IDENTITY, "rb") as f:
identity = f.read()
identity_dict["identity"] = identity.decode().strip()
# todo hardcoded interface name
identity_dict["nats_url"] = f"nats://{ni.ifaddresses('br-lan')[ni.AF_INET][0]['addr']}:4222"

except:
Expand Down
80 changes: 35 additions & 45 deletions modules/sc-mesh-secure-deployment/src/nats/src/comms_settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
"""
import json
from shlex import quote
import subprocess
import logging
import re

Expand All @@ -22,7 +21,7 @@ class CommsSettings: # pylint: disable=too-few-public-methods, too-many-instanc
Comms settings class
"""

def __init__(self, comms_status: cs.CommsStatus, logger):
def __init__(self, comms_status: [cs.CommsStatus, ...], logger):
self.logger: logging = logger
self.api_version: int = 1
self.radio_index = []
Expand All @@ -42,7 +41,7 @@ def __init__(self, comms_status: cs.CommsStatus, logger):
self.mesh_vif = []
self.phy = []
self.batman_iface = []
self.bridge = []
self.bridge: str = ""
self.msversion: str = ""
self.delay:str = "" # delay for channel change
self.comms_status = comms_status
Expand Down Expand Up @@ -203,76 +202,67 @@ def handle_mesh_settings(self, msg: str, path="/opt",
self.phy.append(quote(str(parameters["phy"])))
self.batman_iface.append(quote(str(parameters["batman_iface"])))

bridges = parameters_set["bridge"]
for bridge in bridges:
for key, values in bridge.items():
self.bridge.append(f"{quote(str(key))} {quote(str(values))}")
self.bridge = quote(str(parameters_set["bridge"]))

ret, info = "FAIL", "Before validation"

for index in self.radio_index:
self.logger.debug("Mesh settings validation index: %s", str(index))
ret, info = self.validate_mesh_settings(index)
if ret == "FAIL":
break

self.logger.debug("Mesh settings validation: %s, %s", ret, info)
if ret == "FAIL":
self.comms_status.mesh_cfg_status = \
comms.STATUS.mesh_cfg_not_stored
self.logger.error("save settings failed: %s, %s", ret, info)
else:
ret, info = self.__save_settings(path, file)
self.logger.debug("save settings: %s, %s", ret, info)
self.logger.debug("Mesh settings validation id %s: %s, %s", str(index), ret, info)
if ret == "FAIL":
self.comms_status[index].mesh_cfg_status = \
comms.STATUS.mesh_cfg_not_stored
self.logger.error("save settings failed: %s, %s", ret, info)
else:
ret, info = self.__save_settings(path, file, index)
self.logger.debug("save settings index %s: %s, %s", str(index), ret, info)

except (json.decoder.JSONDecodeError, KeyError,
TypeError, AttributeError) as error:
self.comms_status.mesh_cfg_status = \
comms.STATUS.mesh_cfg_not_stored
ret, status = "FAIL", self.comms_status.mesh_cfg_status
ret, _ = "FAIL", self.comms_status.mesh_cfg_status
info = "JSON format not correct" + str(error)
self.logger.error("Mesh settings validation: %s, %s", ret, info)

return ret, info

def __save_settings(self, path: str, file: str) -> (str, str):
def __save_settings(self, path: str, file: str, index: int) -> (str, str):
"""
Save mesh settings
"""
try:
with open(f"{path}/{file}", "w", encoding="utf-8") as mesh_conf:
with open(f"{path}/{str(index)}_{file}", "w", encoding="utf-8") as mesh_conf:
# not currently in json mesh_settings
mesh_conf.write(f"ROLE={quote(self.role)}\n")
mesh_conf.write(f"MSVERSION={quote(self.msversion)}\n")
for index in self.radio_index:
mesh_conf.write(f"id{index}_MODE={quote(self.mode[index])}\n")
mesh_conf.write(f"id{index}_IP={quote(self.ip_address[index])}\n")
mesh_conf.write(f"id{index}_MASK={quote(self.subnet[index])}\n")
mesh_conf.write(f"id{index}_MAC={quote(self.ap_mac[index])}\n")
mesh_conf.write(f"id{index}_KEY={quote(self.key[index])}\n")
mesh_conf.write(f"id{index}_ESSID={quote(self.ssid[index])}\n")
mesh_conf.write(f"id{index}_FREQ={quote(self.frequency[index])}\n")
mesh_conf.write(f"id{index}_FREQ_MCC={quote(self.frequency_mcc[index])}\n")
mesh_conf.write(f"id{index}_TXPOWER={quote(self.tx_power[index])}\n")
mesh_conf.write(f"id{index}_COUNTRY={quote(self.country[index]).upper()}\n")
mesh_conf.write(f"id{index}_ROUTING={quote(self.routing[index])}\n")
mesh_conf.write(f"id{index}_PRIORITY={quote(self.priority[index])}\n")
mesh_conf.write(f"id{index}_MESH_VIF={quote(self.mesh_vif[index])}\n")
mesh_conf.write(f"id{index}_PHY={quote(self.phy[index])}\n")
mesh_conf.write(f"id{index}_BATMAN_IFACE={quote(self.batman_iface[index])}\n")

mesh_conf.write("BRIDGE=")
for name in self.bridge:
mesh_conf.write(f"{name},")
mesh_conf.write("\n")
mesh_conf.write(f"id{str(index)}_MODE={quote(self.mode[index])}\n")
mesh_conf.write(f"id{str(index)}_IP={quote(self.ip_address[index])}\n")
mesh_conf.write(f"id{str(index)}_MASK={quote(self.subnet[index])}\n")
mesh_conf.write(f"id{str(index)}_MAC={quote(self.ap_mac[index])}\n")
mesh_conf.write(f"id{str(index)}_KEY={quote(self.key[index])}\n")
mesh_conf.write(f"id{str(index)}_ESSID={quote(self.ssid[index])}\n")
mesh_conf.write(f"id{str(index)}_FREQ={quote(self.frequency[index])}\n")
mesh_conf.write(f"id{str(index)}_FREQ_MCC={quote(self.frequency_mcc[index])}\n")
mesh_conf.write(f"id{str(index)}_TXPOWER={quote(self.tx_power[index])}\n")
mesh_conf.write(f"id{str(index)}_COUNTRY={quote(self.country[index]).upper()}\n")
mesh_conf.write(f"id{str(index)}_ROUTING={quote(self.routing[index])}\n")
mesh_conf.write(f"id{str(index)}_PRIORITY={quote(self.priority[index])}\n")
mesh_conf.write(f"id{str(index)}_MESH_VIF={quote(self.mesh_vif[index])}\n")
mesh_conf.write(f"id{str(index)}_PHY={quote(self.phy[index])}\n")
mesh_conf.write(f"id{str(index)}_BATMAN_IFACE={quote(self.batman_iface[index])}\n")
mesh_conf.write(f"BRIDGE=\"{self.bridge}\"\n")

except:
self.comms_status.mesh_cfg_status = \
self.comms_status[index].mesh_cfg_status = \
comms.STATUS.mesh_cfg_not_stored
self.logger.error("not able to write new %s", file)
self.logger.error("not able to write new %s", f"{str(index)}_{file}")
return "FAIL", "not able to write new mesh.conf"

self.comms_status.mesh_cfg_status = comms.STATUS.mesh_cfg_stored
self.logger.debug("%s written", file)
self.comms_status[index].mesh_cfg_status = comms.STATUS.mesh_cfg_stored
self.logger.debug("%s written", f"{str(index)}_{file}")
return "OK", "Mesh configuration stored"

def __read_configs(self, mesh_conf_lines):
Expand Down
8 changes: 7 additions & 1 deletion modules/utils/docker/entrypoint_nats.sh
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,13 @@ else

echo "starting 11s mesh service"
/opt/S9011sNatsMesh start id0
if [ -f "opt/1_mesh.conf" ]; then
/opt/S9011sNatsMesh start id1
fi
if [ -f "opt/2_mesh.conf" ]; then
/opt/S9011sNatsMesh start id2
fi


echo "starting AP service"
/opt/S90APoint start id0
Expand Down Expand Up @@ -50,7 +57,6 @@ else

echo "starting radvd & socat"
radvd -C /etc/radvd.conf # TODO: for some reason init.d is not working
/opt/S90socat start # socat is used to provide IPv6 NATS IF

echo "starting comms services"
/opt/S90comms_controller start
Expand Down

0 comments on commit 73c37a0

Please sign in to comment.