Verified Commit c25db44a authored by Štěpán Henek's avatar Štěpán Henek 🌩

fixup! fixup! WIP: remote: adding subordinates

parent 2101a7e3
Pipeline #45330 failed with stage
in 1 minute and 11 seconds
......@@ -56,6 +56,14 @@ def path_exists(path):
return os.path.exists(inject_file_root(path))
def makedirs(path: str, mask: int):
""" Creates directories on the given path
:param path: path to be created
:param mask: last dir mask
"""
os.makedirs(inject_file_root(path), mask)
class BaseFile(object):
def _file_content(self, path):
""" Returns a content of a file
......
......@@ -23,7 +23,9 @@ import logging
import tarfile
import base64
import json
import uuid
import typing
import pathlib
import shutil
from io import BytesIO
from collections import OrderedDict
......@@ -31,16 +33,20 @@ from collections import OrderedDict
from foris_controller.app import app_info
from foris_controller_backends.cmdline import AsyncCommand, BaseCmdLine
from foris_controller_backends.files import BaseFile
from foris_controller_backends.files import BaseFile, makedirs, inject_file_root
from foris_controller_backends.uci import (
UciBackend, get_option_named, parse_bool, UciException, store_bool,
get_option_anonymous,
get_option_anonymous, get_sections_by_type
)
from foris_controller.utils import RWLock
from foris_controller_backends.services import OpenwrtServices
logger = logging.getLogger(__name__)
subordinate_dir_lock = RWLock(app_info["lock_backend"])
class CaGenAsync(AsyncCommand):
def generate_ca(self, notify_function, exit_notify_function, reset_notify_function):
......@@ -223,6 +229,77 @@ class RemoteUci(object):
return result
def list_subordinates(self):
with UciBackend() as backend:
fosquitto_data = backend.read("fosquitto")
res = []
# custom names map
name_map = {
e["name"]: e["data"].get("custom_name", "")
for e in get_sections_by_type(fosquitto_data, "fosquitto", "alias")
}
for item in get_sections_by_type(fosquitto_data, "fosquitto", "subordinate"):
if "id" not in item["data"]:
continue
controller_id = item["data"]["id"]
enabled = parse_bool(item["data"].get("enabled", "0"))
custom_name = name_map.get(controller_id, "")
res.append(
{"controller_id": controller_id, "enabled": enabled, "custom_name": custom_name}
)
return res
@staticmethod
def add_subordinate(controller_id: str, address: str, port: int):
with UciBackend() as backend:
new_section = backend.add_section("fosquitto", "subordinate")
backend.set_option("fosquitto", new_section, "id", controller_id)
backend.set_option("fosquitto", new_section, "enabled", store_bool(True))
backend.set_option("fosquitto", new_section, "address", address)
backend.set_option("fosquitto", new_section, "port", port)
def set_subordinate(self, controller_id: str, enabled: bool, custom_name: str) -> bool:
with UciBackend() as backend:
fosquitto_data = backend.read("fosquitto")
section = None
for item in get_sections_by_type(fosquitto_data, "fosquitto", "subordinate"):
if item["data"].get("id", None) == controller_id:
section = item["name"]
if not section:
return False
backend.set_option("fosquitto", section, "enabled", store_bool(enabled))
backend.add_section("fosquitto", "alias", controller_id)
backend.set_option("fosquitto", controller_id, "custom_name", custom_name)
with OpenwrtServices() as services:
services.reload("fosquitto")
return True
@staticmethod
def del_subordinate(controller_id: str) -> bool:
with UciBackend() as backend:
fosquitto_data = backend.read("fosquitto")
section = None
for item in get_sections_by_type(fosquitto_data, "fosquitto", "subordinate"):
if item["data"].get("id", None) == controller_id:
section = item["name"]
if not section:
return False
backend.del_section("fosquitto", section)
try:
backend.del_section("fosquitto", controller_id)
except UciException:
pass
with OpenwrtServices() as services:
services.reload("fosquitto")
return True
class RemoteFiles(BaseFile):
BASE_CERT_PATH = "/etc/ssl/ca/remote"
......@@ -324,3 +401,70 @@ class RemoteFiles(BaseFile):
fake_file.close()
return base64.b64encode(final_content).decode()
@staticmethod
def extract_token_subordinate(token: str) -> typing.Tuple[dict, dict]:
token_data = BytesIO(base64.b64decode(token))
with tarfile.open(fileobj=token_data, mode="r:gz") as tar:
config_file = [e.name for e in tar.getmembers() if e.name.endswith(".json")][0]
with tar.extractfile(config_file) as f:
conf = json.load(f)
file_data = {}
for member in tar.getmembers():
with tar.extractfile(member.name) as f:
file_data[os.path.basename(member.name)] = f.read()
return conf, file_data
@staticmethod
def store_subordinate_files(controller_id: str, file_data: dict):
path_root = pathlib.Path("/etc/fosquitto/bridges") / controller_id
makedirs(str(path_root), 0o0777)
for name, content in file_data.items():
new_file = pathlib.Path(inject_file_root(str(path_root / name)))
new_file.touch(0o0600)
with new_file.open("wb") as f:
f.write(content)
f.flush()
@staticmethod
def remove_subordinate(controller_id: str):
path = pathlib.Path("/etc/fosquitto/bridges") / controller_id
shutil.rmtree(inject_file_root(str(path)), True)
class RemoteComplex:
def add_subordinate(self, token):
if not app_info["bus"] == "mqtt":
return {"result": False}
conf, file_data = RemoteFiles.extract_token_subordinate(token)
with subordinate_dir_lock.writelock:
forbidden_controller_ids = [app_info["controller_id"]] + [
e["controller_id"] for e in RemoteUci().list_subordinates()
] # my controller_id + already stored controller ids
if conf["device_id"] in forbidden_controller_ids:
return {"result": False}
RemoteFiles.store_subordinate_files(conf["device_id"], file_data)
RemoteUci.add_subordinate(conf["device_id"], conf["ipv4_ips"][0], conf["port"])
with OpenwrtServices() as services:
services.reload("fosquitto")
return {"result": True, "controller_id": conf["device_id"]}
def del_subordinate(self, controller_id):
print("OVERXXX")
with subordinate_dir_lock.writelock:
if not RemoteUci.del_subordinate(controller_id):
return False
print("OVER")
RemoteFiles.remove_subordinate(controller_id)
with OpenwrtServices() as services:
services.reload("fosquitto")
return True
......@@ -89,8 +89,6 @@ def get_sections_by_type(data, config, section_type):
e for e in data[config]
if e["type"] == section_type
]
if not res:
raise UciRecordNotFound(config, section_type=section_type)
return res
......
......@@ -85,7 +85,7 @@ class RemoteModule(BaseModule):
if res["result"]:
self.notify(
"add_subordinate",
{"controller_id": res["controller_id"], "custom_name": data["custom_name"]}
{"controller_id": res["controller_id"]}
)
return res
......
......@@ -119,7 +119,7 @@ class MockRemoteHandler(Handler, BaseMockHandler):
return MockRemoteHandler.subordinates
@logger_wrapper(logger)
def add_subordinate(self, custom_name, token) -> dict:
def add_subordinate(self, token) -> dict:
if app_info["bus"] != "mqtt":
return {"result": False}
......@@ -135,8 +135,8 @@ class MockRemoteHandler(Handler, BaseMockHandler):
MockRemoteHandler.subordinates.append({
"controller_id": controller_id,
"custom_name": custom_name,
"enabled": True,
"custom_name": "",
})
return {"result": True, "controller_id": controller_id}
......
......@@ -22,7 +22,9 @@ import logging
from foris_controller.handler_base import BaseOpenwrtHandler
from foris_controller.utils import logger_wrapper
from foris_controller_backends.remote import CaGenAsync, CaGenCmds, RemoteUci, RemoteFiles
from foris_controller_backends.remote import (
CaGenAsync, CaGenCmds, RemoteUci, RemoteFiles, RemoteComplex
)
from .. import Handler
......@@ -35,6 +37,7 @@ class OpenwrtRemoteHandler(Handler, BaseOpenwrtHandler):
cmds = CaGenCmds()
uci = RemoteUci()
files = RemoteFiles()
complex = RemoteComplex()
@logger_wrapper(logger)
def generate_ca(self, notify, exit_notify, reset_notify):
......@@ -79,16 +82,16 @@ class OpenwrtRemoteHandler(Handler, BaseOpenwrtHandler):
@logger_wrapper(logger)
def list_subordinates(self):
raise NotImplementedError()
return OpenwrtRemoteHandler.uci.list_subordinates()
@logger_wrapper(logger)
def add_subordinate(self, custom_name, token):
raise NotImplementedError()
def add_subordinate(self, token):
return OpenwrtRemoteHandler.complex.add_subordinate(token)
@logger_wrapper(logger)
def del_subordinate(self, controller_id):
raise NotImplementedError()
return OpenwrtRemoteHandler.complex.del_subordinate(controller_id)
@logger_wrapper(logger)
def set_subordinate(self, controller_id, enabled, custom_name):
raise NotImplementedError()
return OpenwrtRemoteHandler.uci.set_subordinate(controller_id, enabled, custom_name)
......@@ -471,11 +471,10 @@
"data": {
"type": "object",
"properties": {
"custom_name": {"type": "string"},
"token": {"type": "string"}
},
"additionalProperties": false,
"required": ["custom_name", "token"]
"required": ["token"]
}
},
"additionalProperties": false,
......@@ -522,11 +521,9 @@
"data": {
"type": "object",
"properties": {
"custom_name": {"type": "string"},
"controller_id": {"$ref": "#/definitions/controller_id"}
},
"additionalProperties": false,
"required": ["custom_name"]
"additionalProperties": false
}
},
"additionalProperties": false,
......
......@@ -58,7 +58,7 @@ def prepare_subordinate_token(controller_id):
add_to_tar(tar, f"some_name/conf.json", json.dumps({
"name": "some_name",
"hostname": "localhost",
"ipv4_ips": [],
"ipv4_ips": ["123.123.123.123"],
"dhcp_names": [],
"port": 11884,
"device_id": controller_id,
......@@ -960,7 +960,6 @@ def test_complex_subordinates_unsupported(uci_configs_init, infrastructure, star
"action": "add_subordinate",
"kind": "request",
"data": {
"custom_name": "test_add1",
"token": prepare_subordinate_token("1122334455667788"),
}
})
......@@ -1001,7 +1000,9 @@ def test_complex_subordinates_unsupported(uci_configs_init, infrastructure, star
@pytest.mark.only_message_buses(['mqtt'])
def test_complex_subordinates(uci_configs_init, infrastructure, start_buses, file_root_init):
def test_complex_subordinates(
uci_configs_init, infrastructure, start_buses, file_root_init, init_script_result
):
def in_list(controller_id):
res = infrastructure.process_message({
"module": "remote",
......@@ -1028,7 +1029,6 @@ def test_complex_subordinates(uci_configs_init, infrastructure, start_buses, fil
"action": "add_subordinate",
"kind": "request",
"data": {
"custom_name": "test_add1",
"token": token,
}
})
......@@ -1039,14 +1039,16 @@ def test_complex_subordinates(uci_configs_init, infrastructure, start_buses, fil
"data": {"result": True, "controller_id": "1122334455667788"}
}
notifications = infrastructure.get_notifications(notifications, filters=filters)
if infrastructure.backend_name == "openwrt":
check_service_result("fosquitto", "reload", passed=True)
assert notifications[-1] == {
"module": "remote",
"action": "add_subordinate",
"kind": "notification",
"data": {"custom_name": "test_add1", "controller_id": "1122334455667788"}
"data": {"controller_id": "1122334455667788"}
}
assert in_list("1122334455667788") == {
"controller_id": "1122334455667788", "enabled": True, "custom_name": "test_add1"
"controller_id": "1122334455667788", "enabled": True, "custom_name": ""
}
res = infrastructure.process_message({
......@@ -1054,7 +1056,6 @@ def test_complex_subordinates(uci_configs_init, infrastructure, start_buses, fil
"action": "add_subordinate",
"kind": "request",
"data": {
"custom_name": "test_add2",
"token": token,
}
})
......@@ -1066,7 +1067,35 @@ def test_complex_subordinates(uci_configs_init, infrastructure, start_buses, fil
}
assert in_list("1122334455667788") == {
"controller_id": "1122334455667788", "enabled": True, "custom_name": "test_add1"
"controller_id": "1122334455667788", "enabled": True, "custom_name": ""
}
# add2
res = infrastructure.process_message({
"module": "remote",
"action": "add_subordinate",
"kind": "request",
"data": {
"token": prepare_subordinate_token("8877665544332211"),
}
})
assert res == {
"module": "remote",
"action": "add_subordinate",
"kind": "reply",
"data": {"result": True, "controller_id": "8877665544332211"}
}
notifications = infrastructure.get_notifications(notifications, filters=filters)
if infrastructure.backend_name == "openwrt":
check_service_result("fosquitto", "reload", passed=True)
assert notifications[-1] == {
"module": "remote",
"action": "add_subordinate",
"kind": "notification",
"data": {"controller_id": "8877665544332211"}
}
assert in_list("8877665544332211") == {
"controller_id": "8877665544332211", "enabled": True, "custom_name": ""
}
# set
......@@ -1086,6 +1115,8 @@ def test_complex_subordinates(uci_configs_init, infrastructure, start_buses, fil
"kind": "reply",
"data": {"result": True}
}
if infrastructure.backend_name == "openwrt":
check_service_result("fosquitto", "reload", passed=True)
notifications = infrastructure.get_notifications(notifications, filters=filters)
assert notifications[-1] == {
"module": "remote",
......@@ -1128,6 +1159,8 @@ def test_complex_subordinates(uci_configs_init, infrastructure, start_buses, fil
"kind": "reply",
"data": {"result": True}
}
if infrastructure.backend_name == "openwrt":
check_service_result("fosquitto", "reload", passed=True)
notifications = infrastructure.get_notifications(notifications, filters=filters)
assert notifications[-1] == {
"module": "remote",
......@@ -1154,8 +1187,37 @@ def test_complex_subordinates(uci_configs_init, infrastructure, start_buses, fil
assert None is in_list("1122334455667788")
@pytest.mark.skip("TODO")
@pytest.mark.only_backends(['openwrt'])
@pytest.mark.only_message_buses(['mqtt'])
def test_complex_subordinates_openwrt(uci_configs_init, infrastructure, start_buses, file_root_init):
raise NotImplementedError() # check fosquitto restart
def test_complex_subordinates_openwrt(
uci_configs_init, infrastructure, start_buses, file_root_init, init_script_result, lock_backend
):
uci = get_uci_module(lock_backend)
token = prepare_subordinate_token("1122334455667788")
res = infrastructure.process_message({
"module": "remote",
"action": "add_subordinate",
"kind": "request",
"data": {
"token": token,
}
})
assert res == {
"module": "remote",
"action": "add_subordinate",
"kind": "reply",
"data": {"result": True, "controller_id": "1122334455667788"}
}
with uci.UciBackend(UCI_CONFIG_DIR_PATH) as backend:
data = backend.read()
sections = [
e for e in uci.get_sections_by_type(data, "fosquitto", "subordinate")
if e["data"].get("id") == "1122334455667788"
]
assert len(sections) == 1
assert sections[0]["data"]["address"] == "123.123.123.123"
assert sections[0]["data"]["port"] == "11884"
assert uci.parse_bool(sections[0]["data"]["enabled"])
raise Exception("TODO")
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment