(20250212) Started a new class (util) that handles MikroTik config. in async mode, and started implementing that in the PPPoE-1000 controller. I can now configure and roll back the first three steps. Long way to go :')
This commit is contained in:
@@ -215,7 +215,7 @@ async def authorize_software_client(
|
|||||||
|
|
||||||
# Note down the results:
|
# Note down the results:
|
||||||
success = response.success
|
success = response.success
|
||||||
message = response.message
|
message = f"Action Chain '{response.actionChain}': {response.message}"
|
||||||
|
|
||||||
# ┏┓ ┳┳┓•┓ ┏┳┓•┓ ┓┏ ┓┏┓┏┓┏┓
|
# ┏┓ ┳┳┓•┓ ┏┳┓•┓ ┓┏ ┓┏┓┏┓┏┓
|
||||||
# ┣ ┏┓┏┓ ┃┃┃┓┃┏┏┓┏┓ ┃ ┓┃┏ ┣┫┏┓╋┏┏┓┏┓╋ ┃┃┫┃┫┃┫
|
# ┣ ┏┓┏┓ ┃┃┃┓┃┏┏┓┏┓ ┃ ┓┃┏ ┣┫┏┓╋┏┏┓┏┓╋ ┃┃┫┃┫┃┫
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ sys.path.append(".")
|
|||||||
sys.path.append("..")
|
sys.path.append("..")
|
||||||
|
|
||||||
# My async utils:
|
# My async utils:
|
||||||
|
from utils_v2.mikrotik.controllers.async_mikrotik import AsyncMikroTik
|
||||||
from utils_v2.database.async_mysql_v2 import AsyncMySQL
|
from utils_v2.database.async_mysql_v2 import AsyncMySQL
|
||||||
from utils_v2.database.async_mongo_v2 import AsyncMongo
|
from utils_v2.database.async_mongo_v2 import AsyncMongo
|
||||||
from utils_v2.cache.async_redis_cache_v2 import AsyncRedisCache
|
from utils_v2.cache.async_redis_cache_v2 import AsyncRedisCache
|
||||||
@@ -181,11 +182,13 @@ class AllMikroTikController(MikroTikController):
|
|||||||
|
|
||||||
async def roll_back(
|
async def roll_back(
|
||||||
self,
|
self,
|
||||||
|
mikrotik_client: AsyncMikroTik,
|
||||||
mikrotik_auth: MikroTikPPPoE1000Auth | MikroTikHotspot1000Auth
|
mikrotik_auth: MikroTikPPPoE1000Auth | MikroTikHotspot1000Auth
|
||||||
) -> MikroTikRollBackAttemptResponse:
|
) -> MikroTikRollBackAttemptResponse:
|
||||||
"""
|
"""
|
||||||
The rolling-back to the original state (as best as possible) in case the configurations fails midway after
|
The rolling-back to the original state (as best as possible) in case the configurations fails midway after
|
||||||
completing some no. of steps.
|
completing some no. of steps.
|
||||||
|
:param mikrotik_client: The client to use.
|
||||||
:param mikrotik_auth: The set of credentials as received from the UI/API.
|
:param mikrotik_auth: The set of credentials as received from the UI/API.
|
||||||
:return: A structured response to indicate what happened during the configuration attempt.
|
:return: A structured response to indicate what happened during the configuration attempt.
|
||||||
"""
|
"""
|
||||||
@@ -194,10 +197,12 @@ class AllMikroTikController(MikroTikController):
|
|||||||
|
|
||||||
async def configure(
|
async def configure(
|
||||||
self,
|
self,
|
||||||
|
mikrotik_client: AsyncMikroTik,
|
||||||
mikrotik_auth: MikroTikPPPoE1000Auth | MikroTikHotspot1000Auth
|
mikrotik_auth: MikroTikPPPoE1000Auth | MikroTikHotspot1000Auth
|
||||||
) -> MikroTikConfigAttemptResponse:
|
) -> MikroTikConfigAttemptResponse:
|
||||||
"""
|
"""
|
||||||
Run the configuration steps for the system.
|
Run the configuration steps for the system.
|
||||||
|
:param mikrotik_client: The client to use.
|
||||||
:param mikrotik_auth: The set of credentials as received from the UI/API.
|
:param mikrotik_auth: The set of credentials as received from the UI/API.
|
||||||
:return: A structured response to indicate what happened during the configuration attempt.
|
:return: A structured response to indicate what happened during the configuration attempt.
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ sys.path.append("..")
|
|||||||
# My async utils:
|
# My async utils:
|
||||||
from utils_v2.string import json
|
from utils_v2.string import json
|
||||||
from utils_v2.date_time import date_time
|
from utils_v2.date_time import date_time
|
||||||
|
from utils_v2.mikrotik.controllers.async_mikrotik import AsyncMikroTik
|
||||||
from utils_v2.database.async_mysql_v2 import AsyncMySQL
|
from utils_v2.database.async_mysql_v2 import AsyncMySQL
|
||||||
from utils_v2.database.async_mongo_v2 import AsyncMongo
|
from utils_v2.database.async_mongo_v2 import AsyncMongo
|
||||||
from utils_v2.cache.async_redis_cache_v2 import AsyncRedisCache
|
from utils_v2.cache.async_redis_cache_v2 import AsyncRedisCache
|
||||||
@@ -179,416 +180,6 @@ class MikroTikController(CoreSoftwareController, ABC):
|
|||||||
debug_only_errors = debug_only_errors
|
debug_only_errors = debug_only_errors
|
||||||
)
|
)
|
||||||
|
|
||||||
# ┓┏ ┓
|
|
||||||
# ┣┫┏┓┃┏┓┏┓┏┓┏
|
|
||||||
# ┛┗┗ ┗┣┛┗ ┛ ┛
|
|
||||||
# ┛
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def get_mikrotik_url(
|
|
||||||
mikrotik_ip: str,
|
|
||||||
path: str,
|
|
||||||
port_no: int | str = None,
|
|
||||||
use_https: bool = True
|
|
||||||
) -> str:
|
|
||||||
|
|
||||||
"""
|
|
||||||
Simply creates the base URL for hitting the MikroTik server.
|
|
||||||
:param mikrotik_ip: The IP address of the MikroTik device.
|
|
||||||
:param path: The path of the REST API to hit.
|
|
||||||
:param port_no: The port no. to hit the MikroTik device on.
|
|
||||||
:param use_https: Whether to use HTTPS, or HTTP.
|
|
||||||
:return: The Base URL string.
|
|
||||||
"""
|
|
||||||
|
|
||||||
base_url = r"https://" if use_https else r"http://"
|
|
||||||
base_url += mikrotik_ip
|
|
||||||
if port_no is not None: base_url += f":{port_no}"
|
|
||||||
base_url += "/rest"
|
|
||||||
if not path.startswith("/"): path = "/" + path
|
|
||||||
return base_url + path
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def split_ipv4_range_among_targets(
|
|
||||||
start_ip: ipaddress.IPv4Address | str,
|
|
||||||
end_ip: ipaddress.IPv4Address | str,
|
|
||||||
targets: List
|
|
||||||
) -> List[dict]:
|
|
||||||
|
|
||||||
"""
|
|
||||||
Divides the IPv4 range over the list of VLAN ids.
|
|
||||||
:param start_ip: The first IP in the full pool (range).
|
|
||||||
:param end_ip: The last IP in the full pool (range).
|
|
||||||
:param targets: The list of targets (need not be sequential or ordered) like VLAN ids.
|
|
||||||
:return: A list of dicts that describes the subnet for each target.
|
|
||||||
"""
|
|
||||||
|
|
||||||
# Parse the inputs:
|
|
||||||
start_ip_obj = start_ip if isinstance(start_ip, ipaddress.IPv4Address) else ipaddress.IPv4Address(start_ip)
|
|
||||||
end_ip_obj = end_ip if isinstance(end_ip, ipaddress.IPv4Address) else ipaddress.IPv4Address(end_ip)
|
|
||||||
target_count = len(targets)
|
|
||||||
|
|
||||||
# Calculate the total number of IPs in the range:
|
|
||||||
total_ips = int(end_ip_obj) - int(start_ip_obj) + 1
|
|
||||||
|
|
||||||
# Calculate the no. of IPs each VLAN gets,
|
|
||||||
# and add three because we need IPs for network, gateway and broadcast:
|
|
||||||
ips_per_target = math.ceil(total_ips / target_count) + 3
|
|
||||||
|
|
||||||
# Generate subnets for each VLAN:
|
|
||||||
subnets = []
|
|
||||||
current_ip = start_ip_obj
|
|
||||||
for target in targets:
|
|
||||||
|
|
||||||
# Calculate the network address for the current VLAN:
|
|
||||||
subnet_network = ipaddress.IPv4Network(f"{current_ip}/{32 - ips_per_target.bit_length()}", strict = False)
|
|
||||||
|
|
||||||
# Check for overlap with the parent network:
|
|
||||||
subnet_start_ip_obj = subnet_network.network_address
|
|
||||||
subnet_end_ip_obj = subnet_network.broadcast_address
|
|
||||||
if subnet_start_ip_obj <= end_ip_obj:
|
|
||||||
|
|
||||||
# Add the calculated subnet to the list:
|
|
||||||
subnets.append({
|
|
||||||
"target": target,
|
|
||||||
"network": str(subnet_network),
|
|
||||||
"size": int(2 ** (32 - subnet_network.prefixlen)),
|
|
||||||
"startIp": str(subnet_start_ip_obj),
|
|
||||||
"endIp": str(subnet_end_ip_obj)
|
|
||||||
})
|
|
||||||
|
|
||||||
# Update the current IP for the next subnet
|
|
||||||
current_ip = ipaddress.IPv4Address(int(subnet_network.broadcast_address) + 1)
|
|
||||||
|
|
||||||
# Done here:
|
|
||||||
return subnets
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def create_comment_json(
|
|
||||||
created_by: str,
|
|
||||||
created_ts: datetime.datetime = None,
|
|
||||||
roll_back_config: dict = None
|
|
||||||
) -> str:
|
|
||||||
|
|
||||||
"""
|
|
||||||
To create a JSON string that can be put as a comment in any step to later identify the work that was done and
|
|
||||||
have some hint about how to roll it back if needed.
|
|
||||||
:param created_by: A hint to put to identify which process/tool created this comment/config/process.
|
|
||||||
:param created_ts: The datetime (preferably UTC) when this comment was created.
|
|
||||||
:param roll_back_config: The original config that can be used when rolling back.
|
|
||||||
:return: The comment string to be used.
|
|
||||||
"""
|
|
||||||
|
|
||||||
# Create the JSON:
|
|
||||||
comment_json = {
|
|
||||||
"createdBy": created_by,
|
|
||||||
"createdTs": (created_ts or date_time.get_current_utc_date_time(as_string = False)).timestamp(),
|
|
||||||
"rollbackConfig": roll_back_config
|
|
||||||
}
|
|
||||||
|
|
||||||
# Return a string:
|
|
||||||
return json.to_string(comment_json, no_space = True)
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def parse_comment_json(comment: str) -> dict | list | None:
|
|
||||||
|
|
||||||
"""
|
|
||||||
Tries to parse a comment as if it were a JSON string.
|
|
||||||
:param comment: The comment string.
|
|
||||||
:return: The dict/list parsed form the JSON, or a null value of the input was not a valid JSON string.
|
|
||||||
"""
|
|
||||||
|
|
||||||
# Start with a null value:
|
|
||||||
parsed = None
|
|
||||||
|
|
||||||
# Try to parse the comment as if it were a JSON string:
|
|
||||||
try: parsed = json.from_string(comment)
|
|
||||||
except: parsed = None
|
|
||||||
|
|
||||||
# Done here:
|
|
||||||
return parsed
|
|
||||||
|
|
||||||
# ┏┓
|
|
||||||
# ┗┓┓┏┏╋┏┓┏┳┓
|
|
||||||
# ┗┛┗┫┛┗┗ ┛┗┗
|
|
||||||
# ┛
|
|
||||||
|
|
||||||
async def get_system_resource(
|
|
||||||
self,
|
|
||||||
mikrotik_ip: str,
|
|
||||||
username: str,
|
|
||||||
password: str,
|
|
||||||
port_no: int | str = None,
|
|
||||||
use_https: bool = True
|
|
||||||
) -> ApiResponse:
|
|
||||||
|
|
||||||
"""
|
|
||||||
To get a summary of the hardware resources available in the MikroTik device. This also becomes a great way to
|
|
||||||
quickly check if any given device is valid, and up and running.
|
|
||||||
:param mikrotik_ip: The IP address of the MikroTik device.
|
|
||||||
:param username: The username to get access to the MikroTik device.
|
|
||||||
:param password: The password to get access to the MikroTik device.
|
|
||||||
:param port_no: The port no. to hit the MikroTik device on.
|
|
||||||
:param use_https: Whether to use HTTPS, or HTTP.
|
|
||||||
:return: A structured API response.
|
|
||||||
"""
|
|
||||||
|
|
||||||
# Make the API call:
|
|
||||||
api_response = await self._rest.get(
|
|
||||||
url = self.get_mikrotik_url(
|
|
||||||
mikrotik_ip = mikrotik_ip,
|
|
||||||
path = r"/system/resource",
|
|
||||||
port_no = port_no,
|
|
||||||
use_https = use_https
|
|
||||||
),
|
|
||||||
auth = httpx.BasicAuth(
|
|
||||||
username = username,
|
|
||||||
password = password
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
# Done here:
|
|
||||||
return api_response
|
|
||||||
|
|
||||||
# ┳ ┏
|
|
||||||
# ┃┏┓╋┏┓┏┓╋┏┓┏┏┓┏
|
|
||||||
# ┻┛┗┗┗ ┛ ┛┗┻┗┗ ┛
|
|
||||||
|
|
||||||
async def list_interfaces(
|
|
||||||
self,
|
|
||||||
mikrotik_ip: str,
|
|
||||||
username: str,
|
|
||||||
password: str,
|
|
||||||
port_no: int | str = None,
|
|
||||||
use_https: bool = True
|
|
||||||
) -> ApiResponse:
|
|
||||||
|
|
||||||
"""
|
|
||||||
To enlist all the physical connectivity interfaces available on the MikroTik device.
|
|
||||||
:param mikrotik_ip: The IP address of the MikroTik device.
|
|
||||||
:param username: The username to get access to the MikroTik device.
|
|
||||||
:param password: The password to get access to the MikroTik device.
|
|
||||||
:param port_no: The port no. to hit the MikroTik device on.
|
|
||||||
:param use_https: Whether to use HTTPS, or HTTP.
|
|
||||||
:return: A structured API response.
|
|
||||||
"""
|
|
||||||
|
|
||||||
# Make the API call:
|
|
||||||
api_response = await self._rest.get(
|
|
||||||
url = self.get_mikrotik_url(
|
|
||||||
mikrotik_ip = mikrotik_ip,
|
|
||||||
path = r"/interface",
|
|
||||||
port_no = port_no,
|
|
||||||
use_https = use_https
|
|
||||||
),
|
|
||||||
auth = httpx.BasicAuth(
|
|
||||||
username = username,
|
|
||||||
password = password
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
# Done here:
|
|
||||||
return api_response
|
|
||||||
|
|
||||||
async def update_interface(
|
|
||||||
self,
|
|
||||||
mikrotik_ip: str,
|
|
||||||
username: str,
|
|
||||||
password: str,
|
|
||||||
dot_id: str,
|
|
||||||
json_payload: dict,
|
|
||||||
port_no: int | str = None,
|
|
||||||
use_https: bool = True
|
|
||||||
) -> ApiResponse:
|
|
||||||
|
|
||||||
"""
|
|
||||||
To update an interface's configuration.
|
|
||||||
:param mikrotik_ip: The IP address of the MikroTik device.
|
|
||||||
:param username: The username to get access to the MikroTik device.
|
|
||||||
:param password: The password to get access to the MikroTik device.
|
|
||||||
:param dot_id: The value of the '.id' field in the list.
|
|
||||||
:param port_no: The port no. to hit the MikroTik device on.
|
|
||||||
:param use_https: Whether to use HTTPS, or HTTP.
|
|
||||||
:param json_payload: The JSON to send in the body pf the request.
|
|
||||||
:return: A structured API response.
|
|
||||||
"""
|
|
||||||
|
|
||||||
# Make the API call:
|
|
||||||
api_response = await self._rest.patch(
|
|
||||||
url = self.get_mikrotik_url(
|
|
||||||
mikrotik_ip = mikrotik_ip,
|
|
||||||
path = f"/interface/{dot_id}",
|
|
||||||
port_no = port_no,
|
|
||||||
use_https = use_https
|
|
||||||
),
|
|
||||||
auth = httpx.BasicAuth(
|
|
||||||
username = username,
|
|
||||||
password = password
|
|
||||||
),
|
|
||||||
json = json_payload
|
|
||||||
)
|
|
||||||
|
|
||||||
# Done here:
|
|
||||||
return api_response
|
|
||||||
|
|
||||||
# ┓┏┓ ┏┓┳┓
|
|
||||||
# ┃┃┃ ┣┫┃┃┏
|
|
||||||
# ┗┛┗┛┛┗┛┗┛
|
|
||||||
|
|
||||||
async def list_vlans(
|
|
||||||
self,
|
|
||||||
mikrotik_ip: str,
|
|
||||||
username: str,
|
|
||||||
password: str,
|
|
||||||
port_no: int | str = None,
|
|
||||||
use_https: bool = True
|
|
||||||
) -> ApiResponse:
|
|
||||||
|
|
||||||
"""
|
|
||||||
To enlist all the physical connectivity interfaces available on the MikroTik device.
|
|
||||||
:param mikrotik_ip: The IP address of the MikroTik device.
|
|
||||||
:param username: The username to get access to the MikroTik device.
|
|
||||||
:param password: The password to get access to the MikroTik device.
|
|
||||||
:param port_no: The port no. to hit the MikroTik device on.
|
|
||||||
:param use_https: Whether to use HTTPS, or HTTP.
|
|
||||||
:return: A structured API response.
|
|
||||||
"""
|
|
||||||
|
|
||||||
# Make the API call:
|
|
||||||
api_response = await self._rest.get(
|
|
||||||
url = self.get_mikrotik_url(
|
|
||||||
mikrotik_ip = mikrotik_ip,
|
|
||||||
path = r"/interface/vlan",
|
|
||||||
port_no = port_no,
|
|
||||||
use_https = use_https
|
|
||||||
),
|
|
||||||
auth = httpx.BasicAuth(
|
|
||||||
username = username,
|
|
||||||
password = password
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
# Done here:
|
|
||||||
return api_response
|
|
||||||
|
|
||||||
async def add_vlan(
|
|
||||||
self,
|
|
||||||
mikrotik_ip: str,
|
|
||||||
username: str,
|
|
||||||
password: str,
|
|
||||||
json_payload: dict,
|
|
||||||
port_no: int | str = None,
|
|
||||||
use_https: bool = True
|
|
||||||
) -> ApiResponse:
|
|
||||||
|
|
||||||
"""
|
|
||||||
To add a new VLAN to the MikroTik's configuration.
|
|
||||||
:param mikrotik_ip: The IP address of the MikroTik device.
|
|
||||||
:param username: The username to get access to the MikroTik device.
|
|
||||||
:param password: The password to get access to the MikroTik device.
|
|
||||||
:param json_payload: The JSON to send in the body pf the request.
|
|
||||||
:param port_no: The port no. to hit the MikroTik device on.
|
|
||||||
:param use_https: Whether to use HTTPS, or HTTP.
|
|
||||||
:return: A structured API response.
|
|
||||||
"""
|
|
||||||
|
|
||||||
# Make the API call:
|
|
||||||
api_response = await self._rest.put(
|
|
||||||
url = self.get_mikrotik_url(
|
|
||||||
mikrotik_ip = mikrotik_ip,
|
|
||||||
path = r"/interface/vlan",
|
|
||||||
port_no = port_no,
|
|
||||||
use_https = use_https
|
|
||||||
),
|
|
||||||
auth = httpx.BasicAuth(
|
|
||||||
username = username,
|
|
||||||
password = password
|
|
||||||
),
|
|
||||||
json = json_payload
|
|
||||||
)
|
|
||||||
|
|
||||||
# Done here:
|
|
||||||
return api_response
|
|
||||||
|
|
||||||
async def update_vlan(
|
|
||||||
self,
|
|
||||||
mikrotik_ip: str,
|
|
||||||
username: str,
|
|
||||||
password: str,
|
|
||||||
dot_id: str,
|
|
||||||
json_payload: dict,
|
|
||||||
port_no: int | str = None,
|
|
||||||
use_https: bool = True,
|
|
||||||
) -> ApiResponse:
|
|
||||||
|
|
||||||
"""
|
|
||||||
To update an existing VLAN in the MikroTik's configuration.
|
|
||||||
:param mikrotik_ip: The IP address of the MikroTik device.
|
|
||||||
:param username: The username to get access to the MikroTik device.
|
|
||||||
:param password: The password to get access to the MikroTik device.
|
|
||||||
:param dot_id: The value of the '.id' field in the list.
|
|
||||||
:param json_payload: The JSON to send in the body pf the request.
|
|
||||||
:param port_no: The port no. to hit the MikroTik device on.
|
|
||||||
:param use_https: Whether to use HTTPS, or HTTP.
|
|
||||||
:return: A structured API response.
|
|
||||||
"""
|
|
||||||
|
|
||||||
# Make the API call:
|
|
||||||
api_response = await self._rest.patch(
|
|
||||||
url = self.get_mikrotik_url(
|
|
||||||
mikrotik_ip = mikrotik_ip,
|
|
||||||
path = f"/interface/vlan/{dot_id}",
|
|
||||||
port_no = port_no,
|
|
||||||
use_https = use_https
|
|
||||||
),
|
|
||||||
auth = httpx.BasicAuth(
|
|
||||||
username = username,
|
|
||||||
password = password
|
|
||||||
),
|
|
||||||
json = json_payload
|
|
||||||
)
|
|
||||||
|
|
||||||
# Done here:
|
|
||||||
return api_response
|
|
||||||
|
|
||||||
async def remove_vlan(
|
|
||||||
self,
|
|
||||||
mikrotik_ip: str,
|
|
||||||
username: str,
|
|
||||||
password: str,
|
|
||||||
dot_id: str,
|
|
||||||
port_no: int | str = None,
|
|
||||||
use_https: bool = True,
|
|
||||||
):
|
|
||||||
|
|
||||||
"""
|
|
||||||
To delete an existing VLAN from the MikroTik's configuration.
|
|
||||||
:param mikrotik_ip: The IP address of the MikroTik device.
|
|
||||||
:param username: The username to get access to the MikroTik device.
|
|
||||||
:param password: The password to get access to the MikroTik device.
|
|
||||||
:param dot_id: The value of the '.id' field in the list.
|
|
||||||
:param port_no: The port no. to hit the MikroTik device on.
|
|
||||||
:param use_https: Whether to use HTTPS, or HTTP.
|
|
||||||
:return: A structured API response.
|
|
||||||
"""
|
|
||||||
|
|
||||||
# Make the API call:
|
|
||||||
api_response = await self._rest.delete(
|
|
||||||
url = self.get_mikrotik_url(
|
|
||||||
mikrotik_ip = mikrotik_ip,
|
|
||||||
path = f"/interface/vlan/{dot_id}",
|
|
||||||
port_no = port_no,
|
|
||||||
use_https = use_https
|
|
||||||
),
|
|
||||||
auth = httpx.BasicAuth(
|
|
||||||
username = username,
|
|
||||||
password = password
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
# Done here:
|
|
||||||
return api_response
|
|
||||||
|
|
||||||
# ┏┓ ┓
|
# ┏┓ ┓
|
||||||
# ┣┫┓┏╋┣┓
|
# ┣┫┓┏╋┣┓
|
||||||
# ┛┗┗┻┗┛┗
|
# ┛┗┗┻┗┛┗
|
||||||
@@ -620,12 +211,14 @@ class MikroTikController(CoreSoftwareController, ABC):
|
|||||||
@abstractmethod
|
@abstractmethod
|
||||||
async def roll_back(
|
async def roll_back(
|
||||||
self,
|
self,
|
||||||
|
mikrotik_client: AsyncMikroTik,
|
||||||
mikrotik_auth: MikroTikPPPoE1000Auth | MikroTikHotspot1000Auth
|
mikrotik_auth: MikroTikPPPoE1000Auth | MikroTikHotspot1000Auth
|
||||||
) -> MikroTikRollBackAttemptResponse:
|
) -> MikroTikRollBackAttemptResponse:
|
||||||
|
|
||||||
"""
|
"""
|
||||||
The rolling-back to the original state (as best as possible) in case the configurations fails midway after
|
The rolling-back to the original state (as best as possible) in case the configurations fails midway after
|
||||||
completing some no. of steps.
|
completing some no. of steps.
|
||||||
|
:param mikrotik_client: The client to use.
|
||||||
:param mikrotik_auth: The set of credentials as received from the UI/API.
|
:param mikrotik_auth: The set of credentials as received from the UI/API.
|
||||||
:return: A structured response to indicate what happened during the configuration attempt.
|
:return: A structured response to indicate what happened during the configuration attempt.
|
||||||
"""
|
"""
|
||||||
@@ -635,11 +228,13 @@ class MikroTikController(CoreSoftwareController, ABC):
|
|||||||
@abstractmethod
|
@abstractmethod
|
||||||
async def configure(
|
async def configure(
|
||||||
self,
|
self,
|
||||||
|
mikrotik_client: AsyncMikroTik,
|
||||||
mikrotik_auth: MikroTikPPPoE1000Auth | MikroTikHotspot1000Auth
|
mikrotik_auth: MikroTikPPPoE1000Auth | MikroTikHotspot1000Auth
|
||||||
) -> MikroTikConfigAttemptResponse:
|
) -> MikroTikConfigAttemptResponse:
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Run the configuration steps for the system.
|
Run the configuration steps for the system.
|
||||||
|
:param mikrotik_client: The client to use.
|
||||||
:param mikrotik_auth: The set of credentials as received from the UI/API.
|
:param mikrotik_auth: The set of credentials as received from the UI/API.
|
||||||
:return: A structured response to indicate what happened during the configuration attempt.
|
:return: A structured response to indicate what happened during the configuration attempt.
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -21,7 +21,8 @@
|
|||||||
N/A
|
N/A
|
||||||
|
|
||||||
"""
|
"""
|
||||||
import asyncio
|
|
||||||
|
|
||||||
# *****************************************************************************************************************
|
# *****************************************************************************************************************
|
||||||
# ***** ****
|
# ***** ****
|
||||||
# *** IMPORT ***
|
# *** IMPORT ***
|
||||||
@@ -37,6 +38,7 @@ sys.path.append("..")
|
|||||||
# My async utils:
|
# My async utils:
|
||||||
from utils_v2.string import json
|
from utils_v2.string import json
|
||||||
from utils_v2.date_time import date_time
|
from utils_v2.date_time import date_time
|
||||||
|
from utils_v2.mikrotik.controllers.async_mikrotik import AsyncMikroTik
|
||||||
from utils_v2.database.async_mysql_v2 import AsyncMySQL
|
from utils_v2.database.async_mysql_v2 import AsyncMySQL
|
||||||
from utils_v2.database.async_mongo_v2 import AsyncMongo
|
from utils_v2.database.async_mongo_v2 import AsyncMongo
|
||||||
from utils_v2.cache.async_redis_cache_v2 import AsyncRedisCache
|
from utils_v2.cache.async_redis_cache_v2 import AsyncRedisCache
|
||||||
@@ -54,10 +56,7 @@ from models.software.mikrotik.auth import (
|
|||||||
MikroTikHotspot1000Auth,
|
MikroTikHotspot1000Auth,
|
||||||
MikroTikAuthResponse
|
MikroTikAuthResponse
|
||||||
)
|
)
|
||||||
from models.software.mikrotik.configure import (
|
from models.software.mikrotik.configure import MikroTikConfigAttemptResponse
|
||||||
MikroTikConfigAttemptResponse,
|
|
||||||
MikroTikRollBackAttemptResponse
|
|
||||||
)
|
|
||||||
|
|
||||||
# To work with datatypes:
|
# To work with datatypes:
|
||||||
from typing import List, Any
|
from typing import List, Any
|
||||||
@@ -71,6 +70,9 @@ from bson.objectid import ObjectId
|
|||||||
# To make abstract classes:
|
# To make abstract classes:
|
||||||
from abc import ABC, abstractmethod
|
from abc import ABC, abstractmethod
|
||||||
|
|
||||||
|
# For asynchronous activities:
|
||||||
|
import asyncio
|
||||||
|
|
||||||
|
|
||||||
# *****************************************************************************************************************
|
# *****************************************************************************************************************
|
||||||
# ***** ****
|
# ***** ****
|
||||||
@@ -118,9 +120,16 @@ class MikroTikPPPoE1000Controller(MikroTikController):
|
|||||||
CLIENT_NAME = "mikrotikPPPoE1000"
|
CLIENT_NAME = "mikrotikPPPoE1000"
|
||||||
|
|
||||||
# For automated configuration, and identification of automated configuration:
|
# For automated configuration, and identification of automated configuration:
|
||||||
CREATED_BY_NAME = "easyfi"
|
NAME_PREFIX = "easyfi"
|
||||||
HW_INTERFACE_NAME = "easyfi-pppoe"
|
CREATED_BY_NAME = NAME_PREFIX + "-pppoe-1000"
|
||||||
VLAN_NAME = "easyfi-vlan-{}" # ... Substitute the VLAN's id here.
|
HW_INTERFACE_NAME = NAME_PREFIX + "-pppoe"
|
||||||
|
VLAN_NAME = NAME_PREFIX + "-vlan-{}" # .................... Substitute the VLAN's id here.
|
||||||
|
PRIVATE_IP_POOL_NAME = NAME_PREFIX + "-pppoe-pool-{}" # ... Substitute the VLAN's id here.
|
||||||
|
PPP_PROFILE_NAME = NAME_PREFIX + "-pppoe-prf-{}" # ........ Substitute the VLAN's id here.
|
||||||
|
PPP_SERVER_NAME = NAME_PREFIX + "-pppoe-srv-{}" # ......... Substitute the VLAN's id here.
|
||||||
|
RADIUS_SERVER_NAME = NAME_PREFIX + "-radius" # ............ Substitute the VLAN's id here.
|
||||||
|
NAT_RULE_NAME = NAME_PREFIX + "-pppoe-nat-{}" # ........... Substitute rule no. here.
|
||||||
|
SNMP_COMMUNITY_NAME = NAME_PREFIX + "-snmp"
|
||||||
|
|
||||||
# ┏┓
|
# ┏┓
|
||||||
# ┃ ┏┓┏┓┏╋┏┓┓┏┏╋┏┓┏┓
|
# ┃ ┏┓┏┓┏╋┏┓┓┏┏╋┏┓┏┓
|
||||||
@@ -162,46 +171,21 @@ class MikroTikPPPoE1000Controller(MikroTikController):
|
|||||||
# Init a variable in a parent:
|
# Init a variable in a parent:
|
||||||
self._client = self.CLIENT_NAME
|
self._client = self.CLIENT_NAME
|
||||||
|
|
||||||
# ┓┏ ┓
|
|
||||||
# ┣┫┏┓┃┏┓┏┓┏┓┏
|
|
||||||
# ┛┗┗ ┗┣┛┗ ┛ ┛
|
|
||||||
# ┛
|
|
||||||
|
|
||||||
def created_by_easyfi(
|
|
||||||
self,
|
|
||||||
resource_json: dict = None
|
|
||||||
) -> bool:
|
|
||||||
|
|
||||||
"""
|
|
||||||
Checks if the resource was created/modified by this class.
|
|
||||||
:param resource_json; The JSON of the resource that needs to be checked.
|
|
||||||
:return: True if created by this class, else False.
|
|
||||||
"""
|
|
||||||
|
|
||||||
# Extract values:
|
|
||||||
resource_name = resource_json.get("name") or ""
|
|
||||||
comment_json = self.parse_comment_json(resource_json.get("comment")) or {}
|
|
||||||
|
|
||||||
# Perform checks:
|
|
||||||
if (
|
|
||||||
comment_json.get("createdBy", "???") == self.CREATED_BY_NAME or
|
|
||||||
resource_name.lower().find("easyfi") >= 0
|
|
||||||
): return True
|
|
||||||
else: return False
|
|
||||||
|
|
||||||
# ┳ ┏
|
# ┳ ┏
|
||||||
# ┃┏┓╋┏┓┏┓╋┏┓┏┏┓┏
|
# ┃┏┓╋┏┓┏┓╋┏┓┏┏┓┏
|
||||||
# ┻┛┗┗┗ ┛ ┛┗┻┗┗ ┛
|
# ┻┛┗┗┗ ┛ ┛┗┻┗┗ ┛
|
||||||
|
|
||||||
async def set_up_interface(
|
async def set_up_interface(
|
||||||
self,
|
self,
|
||||||
mikrotik_auth: MikroTikPPPoE1000Auth,
|
mikrotik_client: AsyncMikroTik,
|
||||||
|
dot_id: str = None,
|
||||||
use_https: bool = True
|
use_https: bool = True
|
||||||
) -> MikroTikConfigAttemptResponse:
|
) -> MikroTikConfigAttemptResponse:
|
||||||
|
|
||||||
"""
|
"""
|
||||||
To find the first available interface and set it up for use.
|
To find the first available interface and set it up for use.
|
||||||
:param mikrotik_auth: The set of credentials as received from the UI/API.
|
:param mikrotik_client: The client to use.
|
||||||
|
:param dot_id: If you already have an interface in mind, send its id here.
|
||||||
:param use_https: Whether to use HTTPS, or HTTP.
|
:param use_https: Whether to use HTTPS, or HTTP.
|
||||||
:return: A structured response to indicate what happened during the process.
|
:return: A structured response to indicate what happened during the process.
|
||||||
"""
|
"""
|
||||||
@@ -210,13 +194,7 @@ class MikroTikPPPoE1000Controller(MikroTikController):
|
|||||||
step_response = MikroTikConfigAttemptResponse()
|
step_response = MikroTikConfigAttemptResponse()
|
||||||
|
|
||||||
# Enlist all the interfaces:
|
# Enlist all the interfaces:
|
||||||
api_response = await self.list_interfaces(
|
api_response = await mikrotik_client.list_interfaces(use_https = use_https)
|
||||||
mikrotik_ip = mikrotik_auth.nasIp,
|
|
||||||
port_no = mikrotik_auth.nasPort,
|
|
||||||
username = mikrotik_auth.username,
|
|
||||||
password = mikrotik_auth.password,
|
|
||||||
use_https = use_https
|
|
||||||
)
|
|
||||||
|
|
||||||
# If the listing failed:
|
# If the listing failed:
|
||||||
if not api_response.success:
|
if not api_response.success:
|
||||||
@@ -225,47 +203,51 @@ class MikroTikPPPoE1000Controller(MikroTikController):
|
|||||||
step_response.exception = step_response.exception
|
step_response.exception = step_response.exception
|
||||||
return step_response
|
return step_response
|
||||||
|
|
||||||
# We find the first available interface:
|
# We find/select an interface:
|
||||||
unused_if_json = None
|
selected_if_json = None
|
||||||
for if_json in await api_response.get_json():
|
for if_json in api_response.data:
|
||||||
if str(if_json.get("running")).lower() == "false":
|
if (
|
||||||
unused_if_json = if_json
|
str(if_json.get("type")).lower() == "ether" and
|
||||||
|
(
|
||||||
|
(
|
||||||
|
dot_id is not None and
|
||||||
|
str(if_json.get(".id")).lower() == str(dot_id)
|
||||||
|
) or
|
||||||
|
(
|
||||||
|
dot_id is None and
|
||||||
|
str(if_json.get("running")).lower() == "false"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
):
|
||||||
|
selected_if_json = if_json
|
||||||
break
|
break
|
||||||
|
|
||||||
# If we found no available interface:
|
# If we found no available interface:
|
||||||
if unused_if_json is None:
|
if selected_if_json is None:
|
||||||
step_response.success = False
|
step_response.success = False
|
||||||
step_response.message = "Failed to find an idle h/w interface."
|
step_response.message = "Failed to select a h/w interface."
|
||||||
step_response.exception = None
|
step_response.exception = None
|
||||||
return step_response
|
return step_response
|
||||||
|
|
||||||
# Reconfigure the idle interface:
|
# Reconfigure the idle interface:
|
||||||
api_response = await self.update_interface(
|
api_response = await mikrotik_client.update_interface(
|
||||||
mikrotik_ip = mikrotik_auth.nasIp,
|
dot_id = selected_if_json.get(".id"),
|
||||||
username = mikrotik_auth.username,
|
|
||||||
password = mikrotik_auth.password,
|
|
||||||
dot_id = unused_if_json.get(".id"),
|
|
||||||
port_no = mikrotik_auth.nasPort,
|
|
||||||
use_https = use_https,
|
|
||||||
json_payload = {
|
json_payload = {
|
||||||
"name": self.HW_INTERFACE_NAME,
|
"name": self.HW_INTERFACE_NAME,
|
||||||
"disabled": "false",
|
"disabled": "false",
|
||||||
"comment": self.create_comment_json(
|
"comment": mikrotik_client.create_comment_json(roll_back_config = selected_if_json)
|
||||||
created_by = self.CREATED_BY_NAME,
|
},
|
||||||
created_ts = date_time.get_current_utc_date_time(as_string = False),
|
use_https = use_https,
|
||||||
roll_back_config = unused_if_json
|
|
||||||
)
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# Check if the attempt was successful:
|
# Check if the attempt was successful:
|
||||||
if api_response.success:
|
if api_response.success:
|
||||||
step_response.success = True
|
step_response.success = True
|
||||||
step_response.message = f"H/w interface ('{unused_if_json.get('.id')}') configured."
|
step_response.message = f"H/w interface ('{selected_if_json.get('.id')}') configured."
|
||||||
step_response.exception = None
|
step_response.exception = None
|
||||||
else:
|
else:
|
||||||
step_response.success = False
|
step_response.success = False
|
||||||
step_response.message = f"H/w interface ('{unused_if_json.get('.id')}') found, but could NOT be configured."
|
step_response.message = f"H/w interface ('{selected_if_json.get('.id')}') found, but could NOT be configured."
|
||||||
step_response.exception = api_response.exception
|
step_response.exception = api_response.exception
|
||||||
|
|
||||||
# Done here:
|
# Done here:
|
||||||
@@ -273,13 +255,13 @@ class MikroTikPPPoE1000Controller(MikroTikController):
|
|||||||
|
|
||||||
async def roll_back_interface(
|
async def roll_back_interface(
|
||||||
self,
|
self,
|
||||||
mikrotik_auth: MikroTikPPPoE1000Auth,
|
mikrotik_client: AsyncMikroTik,
|
||||||
use_https: bool = True
|
use_https: bool = True
|
||||||
) -> MikroTikRollBackAttemptResponse:
|
) -> MikroTikConfigAttemptResponse:
|
||||||
|
|
||||||
"""
|
"""
|
||||||
To find the first available interface.
|
To find the first available interface.
|
||||||
:param mikrotik_auth: The set of credentials as received from the UI/API.
|
:param mikrotik_client: The client to use.
|
||||||
:param use_https: Whether to use HTTPS, or HTTP.
|
:param use_https: Whether to use HTTPS, or HTTP.
|
||||||
:return: A structured response to indicate what happened during the process.
|
:return: A structured response to indicate what happened during the process.
|
||||||
"""
|
"""
|
||||||
@@ -290,13 +272,7 @@ class MikroTikPPPoE1000Controller(MikroTikController):
|
|||||||
step_response = MikroTikConfigAttemptResponse()
|
step_response = MikroTikConfigAttemptResponse()
|
||||||
|
|
||||||
# Enlist all the interfaces:
|
# Enlist all the interfaces:
|
||||||
api_response = await self.list_interfaces(
|
api_response = await mikrotik_client.list_interfaces(use_https = use_https)
|
||||||
mikrotik_ip = mikrotik_auth.nasIp,
|
|
||||||
port_no = mikrotik_auth.nasPort,
|
|
||||||
username = mikrotik_auth.username,
|
|
||||||
password = mikrotik_auth.password,
|
|
||||||
use_https = use_https
|
|
||||||
)
|
|
||||||
|
|
||||||
# If the listing failed:
|
# If the listing failed:
|
||||||
if not api_response.success:
|
if not api_response.success:
|
||||||
@@ -308,35 +284,29 @@ class MikroTikPPPoE1000Controller(MikroTikController):
|
|||||||
# Loop through all the interfaces,
|
# Loop through all the interfaces,
|
||||||
# identify which ones were configured by this class,
|
# identify which ones were configured by this class,
|
||||||
# and perform rollback on those:
|
# and perform rollback on those:
|
||||||
for if_json in await api_response.get_json():
|
for if_json in api_response.data:
|
||||||
|
|
||||||
# Check if the configuration was made by this class:
|
# Extract the needed values:
|
||||||
created_by = None
|
comment_json = mikrotik_client.parse_comment_json(if_json.get("comment"))
|
||||||
original_config = None
|
original_config = mikrotik_client.get_original_config(comment_json)
|
||||||
comment_json = self.parse_comment_json(if_json.get("comment"))
|
|
||||||
if comment_json is not None:
|
|
||||||
created_by = comment_json.get("createdBy")
|
|
||||||
original_config = comment_json.get("rollbackConfig")
|
|
||||||
|
|
||||||
# If this was indeed set up by this class,
|
# If this was indeed set up by this class,
|
||||||
# and if rollback data is available:
|
# and if rollback data is available:
|
||||||
if created_by == self.CREATED_BY_NAME and original_config is not None:
|
if mikrotik_client.is_my_config(
|
||||||
api_response = await self.update_interface(
|
config_json = if_json,
|
||||||
mikrotik_ip = mikrotik_auth.nasIp,
|
name_substring = self.NAME_PREFIX
|
||||||
username = mikrotik_auth.username,
|
):
|
||||||
password = mikrotik_auth.password,
|
api_response = await mikrotik_client.update_interface(
|
||||||
dot_id = if_json.get(".id"),
|
dot_id = if_json.get(".id"),
|
||||||
port_no = mikrotik_auth.nasPort,
|
json_payload = {
|
||||||
use_https = use_https,
|
"name": original_config.get("name"),
|
||||||
json_payload={
|
|
||||||
"name": original_config.get("name", self.HW_INTERFACE_NAME),
|
|
||||||
"disabled": "false",
|
"disabled": "false",
|
||||||
"comment": original_config.get("comment", "")
|
"comment": original_config.get("comment")
|
||||||
}
|
},
|
||||||
|
use_https = use_https,
|
||||||
)
|
)
|
||||||
total_count += 1
|
total_count += 1
|
||||||
if api_response.success: rolled_back_count += 1
|
if api_response.success: rolled_back_count += 1
|
||||||
print("RB API RESP:", api_response)
|
|
||||||
|
|
||||||
# Construct the final response:
|
# Construct the final response:
|
||||||
step_response.message = f"Rolled back {rolled_back_count}/{total_count} h/w interface configuration(s)."
|
step_response.message = f"Rolled back {rolled_back_count}/{total_count} h/w interface configuration(s)."
|
||||||
@@ -351,16 +321,16 @@ class MikroTikPPPoE1000Controller(MikroTikController):
|
|||||||
|
|
||||||
async def set_up_one_vlan(
|
async def set_up_one_vlan(
|
||||||
self,
|
self,
|
||||||
mikrotik_auth: MikroTikPPPoE1000Auth,
|
mikrotik_client: AsyncMikroTik,
|
||||||
vlan_id: int,
|
vlan_id: int,
|
||||||
vlan_list: List[dict],
|
vlan_list: List[dict],
|
||||||
use_https: bool = True
|
use_https: bool = True
|
||||||
):
|
) -> MikroTikConfigAttemptResponse:
|
||||||
|
|
||||||
"""
|
"""
|
||||||
To set up just one of the needed VLANs. If the VLAN id is not taken, a new VLAN will be created, otherwise the
|
To set up just one of the needed VLANs. If the VLAN id is not taken, a new VLAN will be created, otherwise the
|
||||||
existing one will be updated.
|
existing one will be updated.
|
||||||
:param mikrotik_auth: The set of credentials as received from the UI/API.
|
:param mikrotik_client: The client to use.
|
||||||
:param vlan_id: The id of the VLAN you want to create.
|
:param vlan_id: The id of the VLAN you want to create.
|
||||||
:param vlan_list: The list of existing VLANs already configured in the MikroTik device. Helps us decide between
|
:param vlan_list: The list of existing VLANs already configured in the MikroTik device. Helps us decide between
|
||||||
the use of PUT and PATCH methods.
|
the use of PUT and PATCH methods.
|
||||||
@@ -386,22 +356,14 @@ class MikroTikPPPoE1000Controller(MikroTikController):
|
|||||||
if existing_dot_id is None:
|
if existing_dot_id is None:
|
||||||
|
|
||||||
# We add the VLAN:
|
# We add the VLAN:
|
||||||
api_response = await self.add_vlan(
|
api_response = await mikrotik_client.add_vlan(
|
||||||
mikrotik_ip = mikrotik_auth.nasIp,
|
|
||||||
username = mikrotik_auth.username,
|
|
||||||
password = mikrotik_auth.password,
|
|
||||||
json_payload = {
|
json_payload = {
|
||||||
"name": self.VLAN_NAME.format(vlan_id),
|
"name": self.VLAN_NAME.format(vlan_id),
|
||||||
"vlan-id": str(vlan_id),
|
"vlan-id": str(vlan_id),
|
||||||
"interface": self.HW_INTERFACE_NAME,
|
"interface": self.HW_INTERFACE_NAME,
|
||||||
"disabled": "false",
|
"disabled": "false",
|
||||||
"comment": self.create_comment_json(
|
"comment": mikrotik_client.create_comment_json(roll_back_config = existing_vlan_json)
|
||||||
created_by = self.CREATED_BY_NAME,
|
|
||||||
created_ts = date_time.get_current_utc_date_time(as_string = False),
|
|
||||||
roll_back_config = existing_vlan_json
|
|
||||||
)
|
|
||||||
},
|
},
|
||||||
port_no = mikrotik_auth.nasPort,
|
|
||||||
use_https = use_https
|
use_https = use_https
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -419,22 +381,14 @@ class MikroTikPPPoE1000Controller(MikroTikController):
|
|||||||
else:
|
else:
|
||||||
|
|
||||||
# We update it:
|
# We update it:
|
||||||
api_response = await self.update_vlan(
|
api_response = await mikrotik_client.update_vlan(
|
||||||
mikrotik_ip = mikrotik_auth.nasIp,
|
|
||||||
username = mikrotik_auth.username,
|
|
||||||
password = mikrotik_auth.password,
|
|
||||||
dot_id = existing_dot_id,
|
dot_id = existing_dot_id,
|
||||||
json_payload = {
|
json_payload = {
|
||||||
"name": self.VLAN_NAME.format(vlan_id),
|
"name": self.VLAN_NAME.format(vlan_id),
|
||||||
"interface": self.HW_INTERFACE_NAME,
|
"interface": self.HW_INTERFACE_NAME,
|
||||||
"disabled": "false",
|
"disabled": "false",
|
||||||
"comment": self.create_comment_json(
|
"comment": mikrotik_client.create_comment_json(roll_back_config = existing_vlan_json)
|
||||||
created_by = self.CREATED_BY_NAME,
|
|
||||||
created_ts = date_time.get_current_utc_date_time(as_string = False),
|
|
||||||
roll_back_config = existing_vlan_json
|
|
||||||
)
|
|
||||||
},
|
},
|
||||||
port_no = mikrotik_auth.nasPort,
|
|
||||||
use_https = use_https
|
use_https = use_https
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -453,12 +407,14 @@ class MikroTikPPPoE1000Controller(MikroTikController):
|
|||||||
|
|
||||||
async def set_up_vlans(
|
async def set_up_vlans(
|
||||||
self,
|
self,
|
||||||
|
mikrotik_client: AsyncMikroTik,
|
||||||
mikrotik_auth: MikroTikPPPoE1000Auth,
|
mikrotik_auth: MikroTikPPPoE1000Auth,
|
||||||
use_https: bool = True
|
use_https: bool = True
|
||||||
) -> ApiResponse:
|
) -> MikroTikConfigAttemptResponse:
|
||||||
|
|
||||||
"""
|
"""
|
||||||
To set up all the needed VLANs:
|
To set up all the needed VLANs:
|
||||||
|
:param mikrotik_client: The client to use.
|
||||||
:param mikrotik_auth: The set of credentials as received from the UI/API.
|
:param mikrotik_auth: The set of credentials as received from the UI/API.
|
||||||
:param use_https: Whether to use HTTPS, or HTTP.
|
:param use_https: Whether to use HTTPS, or HTTP.
|
||||||
:return: A structured response to indicate what happened during the process.
|
:return: A structured response to indicate what happened during the process.
|
||||||
@@ -468,13 +424,7 @@ class MikroTikPPPoE1000Controller(MikroTikController):
|
|||||||
step_response = MikroTikConfigAttemptResponse()
|
step_response = MikroTikConfigAttemptResponse()
|
||||||
|
|
||||||
# Enlist all the existing VLANs:
|
# Enlist all the existing VLANs:
|
||||||
api_response = await self.list_vlans(
|
api_response = await mikrotik_client.list_vlans(use_https = use_https)
|
||||||
mikrotik_ip = mikrotik_auth.nasIp,
|
|
||||||
port_no = mikrotik_auth.nasPort,
|
|
||||||
username = mikrotik_auth.username,
|
|
||||||
password = mikrotik_auth.password,
|
|
||||||
use_https = use_https
|
|
||||||
)
|
|
||||||
|
|
||||||
# If the listing failed:
|
# If the listing failed:
|
||||||
if not api_response.success:
|
if not api_response.success:
|
||||||
@@ -484,28 +434,26 @@ class MikroTikPPPoE1000Controller(MikroTikController):
|
|||||||
return step_response
|
return step_response
|
||||||
|
|
||||||
# Extract the list here:
|
# Extract the list here:
|
||||||
vlan_list = await api_response.get_json()
|
vlan_list = api_response.data
|
||||||
|
|
||||||
# Create a task for each VLAN that you need to set up,
|
# Create a task for each VLAN that you need to set up,
|
||||||
# then fire them all asynchronously:
|
# then fire them all asynchronously:
|
||||||
tasks = [
|
tasks = [
|
||||||
self.set_up_one_vlan(
|
self.set_up_one_vlan(
|
||||||
mikrotik_auth = mikrotik_auth,
|
mikrotik_client = mikrotik_client,
|
||||||
vlan_id = vlan_id,
|
vlan_id = vlan_id,
|
||||||
vlan_list = vlan_list,
|
vlan_list = vlan_list,
|
||||||
use_https = use_https
|
use_https = use_https
|
||||||
) for vlan_id in mikrotik_auth.vlanRange
|
) for vlan_id in mikrotik_auth.vlanIds
|
||||||
]
|
]
|
||||||
results = await asyncio.gather(*tasks)
|
results = await asyncio.gather(*tasks)
|
||||||
|
|
||||||
# Assess the results:
|
# Assess the results:
|
||||||
total_count = len(results)
|
total_count = len(results)
|
||||||
success_count = 0
|
success_count = 0
|
||||||
all_messages = []
|
|
||||||
for result in results:
|
for result in results:
|
||||||
if result.success: success_count += 1
|
if result.success: success_count += 1
|
||||||
all_messages.append(result.message)
|
step_response.message = f"{success_count}/{total_count} VLAN(s) configured."
|
||||||
step_response.message = " -> ".join(all_messages)
|
|
||||||
step_response.success = True if success_count == total_count else False
|
step_response.success = True if success_count == total_count else False
|
||||||
|
|
||||||
# Done here:
|
# Done here:
|
||||||
@@ -513,13 +461,13 @@ class MikroTikPPPoE1000Controller(MikroTikController):
|
|||||||
|
|
||||||
async def roll_back_vlans(
|
async def roll_back_vlans(
|
||||||
self,
|
self,
|
||||||
mikrotik_auth: MikroTikPPPoE1000Auth,
|
mikrotik_client: AsyncMikroTik,
|
||||||
use_https: bool = True
|
use_https: bool = True
|
||||||
) -> MikroTikRollBackAttemptResponse:
|
) -> MikroTikConfigAttemptResponse:
|
||||||
|
|
||||||
"""
|
"""
|
||||||
To reset VLANs to their original state (if they already existed) or remove them if this class added them.
|
To reset VLANs to their original state (if they already existed) or remove them if this class added them.
|
||||||
:param mikrotik_auth: The set of credentials as received from the UI/API.
|
:param mikrotik_client: The client to use.
|
||||||
:param use_https: Whether to use HTTPS, or HTTP.
|
:param use_https: Whether to use HTTPS, or HTTP.
|
||||||
:return: A structured response to indicate what happened during the process.
|
:return: A structured response to indicate what happened during the process.
|
||||||
"""
|
"""
|
||||||
@@ -531,13 +479,7 @@ class MikroTikPPPoE1000Controller(MikroTikController):
|
|||||||
step_response = MikroTikConfigAttemptResponse()
|
step_response = MikroTikConfigAttemptResponse()
|
||||||
|
|
||||||
# Enlist all the existing VLANs:
|
# Enlist all the existing VLANs:
|
||||||
api_response = await self.list_vlans(
|
api_response = await mikrotik_client.list_vlans(use_https = use_https)
|
||||||
mikrotik_ip = mikrotik_auth.nasIp,
|
|
||||||
port_no = mikrotik_auth.nasPort,
|
|
||||||
username = mikrotik_auth.username,
|
|
||||||
password = mikrotik_auth.password,
|
|
||||||
use_https = use_https
|
|
||||||
)
|
|
||||||
|
|
||||||
# If the listing failed:
|
# If the listing failed:
|
||||||
if not api_response.success:
|
if not api_response.success:
|
||||||
@@ -549,14 +491,17 @@ class MikroTikPPPoE1000Controller(MikroTikController):
|
|||||||
# Loop through all the VLANs,
|
# Loop through all the VLANs,
|
||||||
# and either reset them to their original state (if they already existed),
|
# and either reset them to their original state (if they already existed),
|
||||||
# or remove them entirely if they didn't exist before we came:
|
# or remove them entirely if they didn't exist before we came:
|
||||||
for vlan_json in await api_response.get_json():
|
for vlan_json in api_response.data:
|
||||||
|
|
||||||
# Check if the VLAN was created by this class:
|
# Check if the VLAN was created by this class:
|
||||||
if not self.created_by_easyfi(vlan_json): continue
|
if not mikrotik_client.is_my_config(
|
||||||
|
config_json = vlan_json,
|
||||||
|
name_substring = self.NAME_PREFIX
|
||||||
|
): continue
|
||||||
|
|
||||||
# Extract the rollback information:
|
# Extract the rollback information:
|
||||||
comment = self.parse_comment_json(vlan_json.get("comment")) or {}
|
comment_json = mikrotik_client.parse_comment_json(vlan_json.get("comment"))
|
||||||
roll_back_json = comment.get("rollbackConfig")
|
roll_back_json = mikrotik_client.get_original_config(comment_json)
|
||||||
vlan_id = vlan_json.get("vlan-id")
|
vlan_id = vlan_json.get("vlan-id")
|
||||||
dot_id = vlan_json.get(".id")
|
dot_id = vlan_json.get(".id")
|
||||||
|
|
||||||
@@ -564,14 +509,7 @@ class MikroTikPPPoE1000Controller(MikroTikController):
|
|||||||
if roll_back_json is None:
|
if roll_back_json is None:
|
||||||
|
|
||||||
# We delete the resource:
|
# We delete the resource:
|
||||||
await self.remove_vlan(
|
await mikrotik_client.remove_vlan(dot_id = dot_id, use_https = use_https)
|
||||||
mikrotik_ip = mikrotik_auth.nasIp,
|
|
||||||
username = mikrotik_auth.username,
|
|
||||||
password = mikrotik_auth.password,
|
|
||||||
dot_id = dot_id,
|
|
||||||
port_no = mikrotik_auth.nasPort,
|
|
||||||
use_https = use_https
|
|
||||||
)
|
|
||||||
|
|
||||||
# Assess the result:
|
# Assess the result:
|
||||||
total_count += 1
|
total_count += 1
|
||||||
@@ -584,10 +522,7 @@ class MikroTikPPPoE1000Controller(MikroTikController):
|
|||||||
else:
|
else:
|
||||||
|
|
||||||
# We roll back to the previous configuration:
|
# We roll back to the previous configuration:
|
||||||
await self.update_vlan(
|
await mikrotik_client.update_vlan(
|
||||||
mikrotik_ip = mikrotik_auth.nasIp,
|
|
||||||
username = mikrotik_auth.username,
|
|
||||||
password = mikrotik_auth.password,
|
|
||||||
dot_id = dot_id,
|
dot_id = dot_id,
|
||||||
json_payload = {
|
json_payload = {
|
||||||
"name": roll_back_json.get("name"),
|
"name": roll_back_json.get("name"),
|
||||||
@@ -595,7 +530,6 @@ class MikroTikPPPoE1000Controller(MikroTikController):
|
|||||||
"disabled": roll_back_json.get("disabled"),
|
"disabled": roll_back_json.get("disabled"),
|
||||||
"comment": ""
|
"comment": ""
|
||||||
},
|
},
|
||||||
port_no = mikrotik_auth.nasPort,
|
|
||||||
use_https = use_https
|
use_https = use_https
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -607,9 +541,235 @@ class MikroTikPPPoE1000Controller(MikroTikController):
|
|||||||
else: all_messages.append(f"Failed to roll back VLAN with id {vlan_id}")
|
else: all_messages.append(f"Failed to roll back VLAN with id {vlan_id}")
|
||||||
|
|
||||||
# Assess the overall results:
|
# Assess the overall results:
|
||||||
all_messages.append(f"Removed/rolled-back {success_count}/{total_count} VLANs.")
|
|
||||||
if success_count == total_count: step_response.success = True
|
if success_count == total_count: step_response.success = True
|
||||||
step_response.message = " -> ".join(all_messages)
|
step_response.message = f"Removed/rolled-back {success_count}/{total_count} VLAN(s)."
|
||||||
|
|
||||||
|
# Done here:
|
||||||
|
return step_response
|
||||||
|
|
||||||
|
# ┳┏┓ ┏┓ ┓
|
||||||
|
# ┃┃┃ ┃┃┏┓┏┓┃┏
|
||||||
|
# ┻┣┛ ┣┛┗┛┗┛┗┛
|
||||||
|
|
||||||
|
async def set_up_one_ip_pool(
|
||||||
|
self,
|
||||||
|
mikrotik_client: AsyncMikroTik,
|
||||||
|
ip_pool: dict,
|
||||||
|
existing_ip_pools: List[dict],
|
||||||
|
use_https: bool = True
|
||||||
|
) -> MikroTikConfigAttemptResponse:
|
||||||
|
|
||||||
|
"""
|
||||||
|
To set up one of the needed IP pools.
|
||||||
|
:param mikrotik_client: The client to use.
|
||||||
|
:param ip_pool: The IP pool you want configured by the end of the process.
|
||||||
|
:param existing_ip_pools: A list of the existing IP pools. Helps to decide between 'PUT' and 'PATCH' methods.
|
||||||
|
:param use_https: Whether to use HTTPS, or HTTP.
|
||||||
|
:return: A structured response to indicate what happened during the process.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Start by assuming failure:
|
||||||
|
step_response = MikroTikConfigAttemptResponse()
|
||||||
|
|
||||||
|
# Check if the configuration already exists:
|
||||||
|
existing_dot_id = None
|
||||||
|
existing_ip_pool_json = None
|
||||||
|
for existing_ip_pool in existing_ip_pools:
|
||||||
|
if existing_ip_pool.get("name", "???") == ip_pool["name"]:
|
||||||
|
existing_dot_id = existing_ip_pool[".id"]
|
||||||
|
existing_ip_pool_json = existing_ip_pool
|
||||||
|
break
|
||||||
|
|
||||||
|
# If the IP pool doesn't already exist:
|
||||||
|
if existing_dot_id is None:
|
||||||
|
|
||||||
|
# Add the configuration:
|
||||||
|
api_response = await mikrotik_client.add_ip_pool(
|
||||||
|
json_payload = {
|
||||||
|
"name": ip_pool["name"],
|
||||||
|
"ranges": ip_pool["network"],
|
||||||
|
"comment": mikrotik_client.create_comment_json()
|
||||||
|
},
|
||||||
|
use_https = use_https
|
||||||
|
)
|
||||||
|
|
||||||
|
# And assess the result:
|
||||||
|
if api_response.success:
|
||||||
|
step_response.success = True
|
||||||
|
step_response.message = f"Added new IP pool '{ip_pool['name']}'."
|
||||||
|
step_response.exception = None
|
||||||
|
else:
|
||||||
|
step_response.success = False
|
||||||
|
step_response.message = f"Failed to add new IP pool '{ip_pool['name']}'."
|
||||||
|
step_response.exception = api_response.exception
|
||||||
|
|
||||||
|
# If the IP pool already exists:
|
||||||
|
else:
|
||||||
|
|
||||||
|
# Update the configuration:
|
||||||
|
api_response = await mikrotik_client.update_ip_pool(
|
||||||
|
dot_id = existing_dot_id,
|
||||||
|
json_payload = {
|
||||||
|
"name": ip_pool["name"],
|
||||||
|
"ranges": ip_pool["network"],
|
||||||
|
"comment": mikrotik_client.create_comment_json(roll_back_config = existing_ip_pool_json)
|
||||||
|
},
|
||||||
|
use_https = use_https
|
||||||
|
)
|
||||||
|
|
||||||
|
# And assess the result:
|
||||||
|
if api_response.success:
|
||||||
|
step_response.success = True
|
||||||
|
step_response.message = f"Updated existing IP pool '{ip_pool['name']}'."
|
||||||
|
step_response.exception = None
|
||||||
|
else:
|
||||||
|
step_response.success = False
|
||||||
|
step_response.message = f"Failed to update existing IP pool '{ip_pool['name']}'."
|
||||||
|
step_response.exception = api_response.exception
|
||||||
|
|
||||||
|
# Done here:
|
||||||
|
return step_response
|
||||||
|
|
||||||
|
async def set_up_ip_pools(
|
||||||
|
self,
|
||||||
|
mikrotik_client: AsyncMikroTik,
|
||||||
|
mikrotik_auth: MikroTikPPPoE1000Auth,
|
||||||
|
use_https: bool = True
|
||||||
|
) -> MikroTikConfigAttemptResponse:
|
||||||
|
|
||||||
|
"""
|
||||||
|
To set up ALL the needed IP pools.
|
||||||
|
:param mikrotik_client: The client to use.
|
||||||
|
:param mikrotik_auth: The set of credentials as received from the UI/API.
|
||||||
|
:param use_https: Whether to use HTTPS, or HTTP.
|
||||||
|
:return: A structured response to indicate what happened during the process.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Start by assuming failure:
|
||||||
|
step_response = MikroTikConfigAttemptResponse()
|
||||||
|
|
||||||
|
# Enlist all the existing VLANs:
|
||||||
|
api_response = await mikrotik_client.list_ip_pools(use_https = use_https)
|
||||||
|
|
||||||
|
# If the listing failed:
|
||||||
|
if not api_response.success:
|
||||||
|
step_response.success = False
|
||||||
|
step_response.message = "Failed to enlist existing IP pools during configuration."
|
||||||
|
step_response.exception = step_response.exception
|
||||||
|
return step_response
|
||||||
|
|
||||||
|
# Extract the list here:
|
||||||
|
existing_ip_pools = api_response.data
|
||||||
|
|
||||||
|
# First we split the whole private IP range
|
||||||
|
# into the no. of VLANs we had been asked to make:
|
||||||
|
ip_pools = mikrotik_client.split_ipv4_range_equally(
|
||||||
|
start_ip = mikrotik_auth.firstPrivateIp,
|
||||||
|
end_ip = mikrotik_auth.lastPrivateIp,
|
||||||
|
targets = mikrotik_auth.vlanIds
|
||||||
|
)
|
||||||
|
|
||||||
|
# Create tasks to create IP Pools,
|
||||||
|
# and fire them asynchronously:
|
||||||
|
tasks = [
|
||||||
|
self.set_up_one_ip_pool(
|
||||||
|
mikrotik_client = mikrotik_client,
|
||||||
|
ip_pool = {
|
||||||
|
"name": self.PRIVATE_IP_POOL_NAME.format(ip_pool["target"]),
|
||||||
|
"network": ip_pool["network"],
|
||||||
|
},
|
||||||
|
existing_ip_pools = existing_ip_pools,
|
||||||
|
use_https = use_https
|
||||||
|
) for ip_pool in ip_pools
|
||||||
|
]
|
||||||
|
results = await asyncio.gather(*tasks)
|
||||||
|
|
||||||
|
# Assess the results:
|
||||||
|
total_count = len(results)
|
||||||
|
success_count = 0
|
||||||
|
for result in results:
|
||||||
|
if result.success: success_count += 1
|
||||||
|
step_response.message = f"{success_count}/{total_count} IP pool(s) configured."
|
||||||
|
step_response.success = True if success_count == total_count else False
|
||||||
|
|
||||||
|
# Done here:
|
||||||
|
return step_response
|
||||||
|
|
||||||
|
async def roll_back_ip_pools(
|
||||||
|
self,
|
||||||
|
mikrotik_client: AsyncMikroTik,
|
||||||
|
use_https: bool = True
|
||||||
|
) -> MikroTikConfigAttemptResponse:
|
||||||
|
|
||||||
|
"""
|
||||||
|
To reset IP pools to their original state (if they already existed) or remove them if this class added them.
|
||||||
|
:param mikrotik_client: The client to use.
|
||||||
|
:param use_https: Whether to use HTTPS, or HTTP.
|
||||||
|
:return: A structured response to indicate what happened during the process.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Start by assuming failure:
|
||||||
|
total_count = 0
|
||||||
|
success_count = 0
|
||||||
|
step_response = MikroTikConfigAttemptResponse()
|
||||||
|
|
||||||
|
# Enlist all the existing VLANs:
|
||||||
|
api_response = await mikrotik_client.list_ip_pools(use_https = use_https)
|
||||||
|
|
||||||
|
# If the listing failed:
|
||||||
|
if not api_response.success:
|
||||||
|
step_response.success = False
|
||||||
|
step_response.message = "Failed to enlist existing IP pools during rollback."
|
||||||
|
step_response.exception = step_response.exception
|
||||||
|
return step_response
|
||||||
|
|
||||||
|
# Loop through all the VLANs,
|
||||||
|
# and either reset them to their original state (if they already existed),
|
||||||
|
# or remove them entirely if they didn't exist before we came:
|
||||||
|
for resource_json in api_response.data:
|
||||||
|
|
||||||
|
# Check if the VLAN was created by this class:
|
||||||
|
if not mikrotik_client.is_my_config(
|
||||||
|
config_json = resource_json,
|
||||||
|
name_substring = self.NAME_PREFIX
|
||||||
|
): continue
|
||||||
|
|
||||||
|
# Extract the rollback information:
|
||||||
|
comment_json = mikrotik_client.parse_comment_json(resource_json.get("comment"))
|
||||||
|
roll_back_json = mikrotik_client.get_original_config(comment_json)
|
||||||
|
dot_id = resource_json.get(".id")
|
||||||
|
|
||||||
|
# If the resource has no rollback information:
|
||||||
|
if roll_back_json is None:
|
||||||
|
|
||||||
|
# We delete the resource:
|
||||||
|
await mikrotik_client.remove_ip_pool(dot_id = dot_id, use_https = use_https)
|
||||||
|
|
||||||
|
# Assess the result:
|
||||||
|
total_count += 1
|
||||||
|
if api_response.success: success_count += 1
|
||||||
|
|
||||||
|
# If the resource has rollback information:
|
||||||
|
else:
|
||||||
|
|
||||||
|
# We roll back to the previous configuration:
|
||||||
|
await mikrotik_client.update_ip_pool(
|
||||||
|
dot_id = dot_id,
|
||||||
|
json_payload = {
|
||||||
|
"name": roll_back_json.get("name"),
|
||||||
|
"ranges": roll_back_json.get("ranges"),
|
||||||
|
"comment": ""
|
||||||
|
},
|
||||||
|
use_https = use_https
|
||||||
|
)
|
||||||
|
|
||||||
|
# Assess the result:
|
||||||
|
total_count += 1
|
||||||
|
if api_response.success: success_count += 1
|
||||||
|
|
||||||
|
# Assess the overall results:
|
||||||
|
if success_count == total_count: step_response.success = True
|
||||||
|
step_response.message = f"Removed/rolled-back {success_count}/{total_count} IP pool(s)."
|
||||||
|
|
||||||
# Done here:
|
# Done here:
|
||||||
return step_response
|
return step_response
|
||||||
@@ -634,31 +794,34 @@ class MikroTikPPPoE1000Controller(MikroTikController):
|
|||||||
:return: A structured response to indicate what happened during authorization.
|
:return: A structured response to indicate what happened during authorization.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# # print("IN-AUTH:", json.to_string(mikrotik_auth.model_dump(), default = str))
|
|
||||||
# print("VLAN SUBNETS:", json.to_string(
|
|
||||||
# self.split_ipv4_range_among_targets(
|
|
||||||
# start_ip = mikrotik_auth.firstPrivateIp,
|
|
||||||
# end_ip = mikrotik_auth.lastPrivateIp,
|
|
||||||
# targets = mikrotik_auth.vlanRange
|
|
||||||
# ),
|
|
||||||
# default = str
|
|
||||||
# ))
|
|
||||||
|
|
||||||
# Start with a blank response:
|
# Start with a blank response:
|
||||||
auth_response = MikroTikAuthResponse()
|
auth_response = MikroTikAuthResponse()
|
||||||
|
|
||||||
# Try to configure the system:
|
# Create a MikroTik client:
|
||||||
config_response = await self.configure(mikrotik_auth)
|
mikrotik_client = AsyncMikroTik(
|
||||||
print("CONFIG RESPONSE:", config_response)
|
config_by = self.CREATED_BY_NAME,
|
||||||
auth_response = config_response
|
mikrotik_ip = mikrotik_auth.nasIp,
|
||||||
|
username = mikrotik_auth.username,
|
||||||
|
password = mikrotik_auth.password,
|
||||||
|
port = mikrotik_auth.nasPort,
|
||||||
|
use_https = True,
|
||||||
|
http_client = None,
|
||||||
|
action_log_conn = mongo_data_conn
|
||||||
|
)
|
||||||
|
|
||||||
await asyncio.sleep(10.0)
|
# Try to configure the system:
|
||||||
|
config_response = await self.configure(mikrotik_client, mikrotik_auth)
|
||||||
|
print("CONFIG RESPONSE:", config_response)
|
||||||
|
|
||||||
|
await asyncio.sleep(2.0)
|
||||||
|
|
||||||
# Try to roll all configuration back:
|
# Try to roll all configuration back:
|
||||||
roll_back_response = await self.roll_back(mikrotik_auth)
|
roll_back_response = await self.roll_back(mikrotik_client, mikrotik_auth)
|
||||||
print("ROLL-BACK RESPONSE:", roll_back_response)
|
print("ROLL-BACK RESPONSE:", roll_back_response)
|
||||||
|
config_response += roll_back_response
|
||||||
|
|
||||||
# Done here:
|
# Done here:
|
||||||
|
auth_response = config_response
|
||||||
return auth_response
|
return auth_response
|
||||||
|
|
||||||
# ┏┓ ┏•
|
# ┏┓ ┏•
|
||||||
@@ -668,30 +831,38 @@ class MikroTikPPPoE1000Controller(MikroTikController):
|
|||||||
|
|
||||||
async def roll_back(
|
async def roll_back(
|
||||||
self,
|
self,
|
||||||
mikrotik_auth: MikroTikPPPoE1000Auth | MikroTikHotspot1000Auth
|
mikrotik_client: AsyncMikroTik,
|
||||||
) -> MikroTikRollBackAttemptResponse:
|
mikrotik_auth: MikroTikPPPoE1000Auth
|
||||||
|
) -> MikroTikConfigAttemptResponse:
|
||||||
|
|
||||||
"""
|
"""
|
||||||
The rolling-back to the original state (as best as possible) in case the configurations fails midway after
|
The rolling-back to the original state (as best as possible) in case the configurations fails midway after
|
||||||
completing some no. of steps.
|
completing some no. of steps.
|
||||||
|
:param mikrotik_client: The client to use.
|
||||||
:param mikrotik_auth: The set of credentials as received from the UI/API.
|
:param mikrotik_auth: The set of credentials as received from the UI/API.
|
||||||
:return: A structured response to indicate what happened during the configuration attempt.
|
:return: A structured response to indicate what happened during the configuration attempt.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# Start with some variables:
|
# Start with some variables:
|
||||||
keep_going = True
|
keep_going = True
|
||||||
all_messages = []
|
all_messages = ["STARTING ROLLBACK."]
|
||||||
roll_back_response = MikroTikConfigAttemptResponse()
|
roll_back_response = MikroTikConfigAttemptResponse()
|
||||||
|
|
||||||
# Next we remove all the VLANs:
|
# Next, we roll back all the IP pools:
|
||||||
if keep_going:
|
if keep_going:
|
||||||
step_response = await self.roll_back_vlans(mikrotik_auth, use_https = False)
|
step_response = await self.roll_back_ip_pools(mikrotik_client, use_https = False)
|
||||||
|
all_messages.append(step_response.message)
|
||||||
|
keep_going = step_response.success
|
||||||
|
|
||||||
|
# Next we roll back all the VLANs:
|
||||||
|
if keep_going:
|
||||||
|
step_response = await self.roll_back_vlans(mikrotik_client, use_https = False)
|
||||||
all_messages.append(step_response.message)
|
all_messages.append(step_response.message)
|
||||||
keep_going = step_response.success
|
keep_going = step_response.success
|
||||||
|
|
||||||
# Next, we free-up the interface:
|
# Next, we free-up the interface:
|
||||||
if keep_going:
|
if keep_going:
|
||||||
step_response = await self.roll_back_interface(mikrotik_auth, use_https = False)
|
step_response = await self.roll_back_interface(mikrotik_client, use_https = False)
|
||||||
all_messages.append(step_response.message)
|
all_messages.append(step_response.message)
|
||||||
keep_going = step_response.success
|
keep_going = step_response.success
|
||||||
|
|
||||||
@@ -702,30 +873,39 @@ class MikroTikPPPoE1000Controller(MikroTikController):
|
|||||||
|
|
||||||
async def configure(
|
async def configure(
|
||||||
self,
|
self,
|
||||||
mikrotik_auth: MikroTikPPPoE1000Auth | MikroTikHotspot1000Auth
|
mikrotik_client: AsyncMikroTik,
|
||||||
|
mikrotik_auth: MikroTikPPPoE1000Auth
|
||||||
) -> MikroTikConfigAttemptResponse:
|
) -> MikroTikConfigAttemptResponse:
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Run the configuration steps for the system.
|
Run the configuration steps for the system.
|
||||||
|
:param mikrotik_client: The client to use.
|
||||||
:param mikrotik_auth: The set of credentials as received from the UI/API.
|
:param mikrotik_auth: The set of credentials as received from the UI/API.
|
||||||
:return: A structured response to indicate what happened during the configuration attempt.
|
:return: A structured response to indicate what happened during the configuration attempt.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# Start with some variables:
|
# Start with some variables:
|
||||||
keep_going = True
|
keep_going = True
|
||||||
all_messages = []
|
all_messages = ["STARTING CONFIG."]
|
||||||
config_response = MikroTikConfigAttemptResponse()
|
config_response = MikroTikConfigAttemptResponse(actionChain = mikrotik_client.action_chain)
|
||||||
|
|
||||||
# First, we arrange an interface:
|
# First, we arrange an interface:
|
||||||
if keep_going:
|
if keep_going:
|
||||||
step_response = await self.set_up_interface(mikrotik_auth, use_https = False)
|
step_response = await self.set_up_interface(mikrotik_client, dot_id = "*3", use_https = False)
|
||||||
all_messages.append(step_response.message)
|
all_messages.append(step_response.message)
|
||||||
keep_going = step_response.success
|
keep_going = step_response.success
|
||||||
|
|
||||||
# Next, we create the needed VLANs:
|
# Next, we create the needed VLANs:
|
||||||
if keep_going:
|
if keep_going:
|
||||||
await asyncio.sleep(0.25)
|
await asyncio.sleep(0.25)
|
||||||
step_response = await self.set_up_vlans(mikrotik_auth, use_https = False)
|
step_response = await self.set_up_vlans(mikrotik_client, mikrotik_auth, use_https = False)
|
||||||
|
all_messages.append(step_response.message)
|
||||||
|
keep_going = step_response.success
|
||||||
|
|
||||||
|
# Next, we create the Private IP Pools:
|
||||||
|
if keep_going:
|
||||||
|
await asyncio.sleep(0.25)
|
||||||
|
step_response = await self.set_up_ip_pools(mikrotik_client, mikrotik_auth, use_https = False)
|
||||||
all_messages.append(step_response.message)
|
all_messages.append(step_response.message)
|
||||||
keep_going = step_response.success
|
keep_going = step_response.success
|
||||||
|
|
||||||
|
|||||||
@@ -172,7 +172,7 @@ class MikroTikPPPoE1000Auth(BaseModel):
|
|||||||
frozen = True
|
frozen = True
|
||||||
)
|
)
|
||||||
|
|
||||||
vlanRange: List[int] = Field(
|
vlanIds: List[int] = Field(
|
||||||
description = "Don't know, and don't want to know.",
|
description = "Don't know, and don't want to know.",
|
||||||
min_length = 1,
|
min_length = 1,
|
||||||
frozen = True
|
frozen = True
|
||||||
@@ -234,7 +234,7 @@ class MikroTikPPPoE1000Auth(BaseModel):
|
|||||||
def validate_ip_range(cls, value):
|
def validate_ip_range(cls, value):
|
||||||
return ip.to_hyphen_notation(value)
|
return ip.to_hyphen_notation(value)
|
||||||
|
|
||||||
@field_validator("vlanRange", mode = "before")
|
@field_validator("vlanIds", mode = "before")
|
||||||
def validate_vlans(cls, value):
|
def validate_vlans(cls, value):
|
||||||
return parse_vlans(value)
|
return parse_vlans(value)
|
||||||
|
|
||||||
|
|||||||
@@ -77,6 +77,12 @@ REGEX_SESSION_TOKEN = r"^[a-f0-9]{8}-[a-f0-9]{4}-[1-5][a-f0-9]{3}-[89ab][a-f0-9]
|
|||||||
|
|
||||||
class MikroTikConfigAttemptResponse(BaseModel):
|
class MikroTikConfigAttemptResponse(BaseModel):
|
||||||
|
|
||||||
|
actionChain: str | int = Field(
|
||||||
|
description = "To be able to link all the actions of one process",
|
||||||
|
frozen = False,
|
||||||
|
default = None
|
||||||
|
)
|
||||||
|
|
||||||
success: bool = Field(
|
success: bool = Field(
|
||||||
description = "To indicate whether or not, the action was a success",
|
description = "To indicate whether or not, the action was a success",
|
||||||
frozen = False,
|
frozen = False,
|
||||||
@@ -107,7 +113,14 @@ class MikroTikConfigAttemptResponse(BaseModel):
|
|||||||
# ┃ ┓┏┏╋┏┓┏┳┓ ┣ ┓┏┏┓┏┏
|
# ┃ ┓┏┏╋┏┓┏┳┓ ┣ ┓┏┏┓┏┏
|
||||||
# ┗┛┗┻┛┗┗┛┛┗┗ ┻ ┗┻┛┗┗┛
|
# ┗┛┗┻┛┗┗┛┛┗┗ ┻ ┗┻┛┗┗┛
|
||||||
|
|
||||||
pass
|
def __add__(self, other):
|
||||||
|
|
||||||
|
return MikroTikConfigAttemptResponse(
|
||||||
|
actionChain = other.actionChain or self.actionChain,
|
||||||
|
success = self.success and other.success,
|
||||||
|
message = self.message + " -> " + other.message,
|
||||||
|
exception = other.exception or self.exception
|
||||||
|
)
|
||||||
|
|
||||||
# ┓┏ ┓• ┓ •
|
# ┓┏ ┓• ┓ •
|
||||||
# ┃┃┏┓┃┓┏┫┏┓╋┓┏┓┏┓
|
# ┃┃┏┓┃┓┏┫┏┓╋┓┏┓┏┓
|
||||||
@@ -121,6 +134,12 @@ class MikroTikConfigAttemptResponse(BaseModel):
|
|||||||
|
|
||||||
class MikroTikRollBackAttemptResponse(BaseModel):
|
class MikroTikRollBackAttemptResponse(BaseModel):
|
||||||
|
|
||||||
|
actionChain: str | int = Field(
|
||||||
|
description = "To be able to link all the actions of one process",
|
||||||
|
frozen = False,
|
||||||
|
default = None
|
||||||
|
)
|
||||||
|
|
||||||
success: bool = Field(
|
success: bool = Field(
|
||||||
description = "To indicate whether or not, the action was a success",
|
description = "To indicate whether or not, the action was a success",
|
||||||
frozen = False,
|
frozen = False,
|
||||||
|
|||||||
@@ -69,6 +69,9 @@ configuration, simply delete your record.
|
|||||||
You will receive the Private IP range in either CIDR notation or as a hyphen-separated string. You need to cut up this
|
You will receive the Private IP range in either CIDR notation or as a hyphen-separated string. You need to cut up this
|
||||||
larger IP pool into subnets that you will eventually assign to each VLAN (by way of creating PPPoE servers).
|
larger IP pool into subnets that you will eventually assign to each VLAN (by way of creating PPPoE servers).
|
||||||
|
|
||||||
|
**WARNING:** It is possible to have IP pools in a MikroTik device with overlapping IP addresses. It is not recommended,
|
||||||
|
though, because it may lead to conflicts that could be hard to trace back.
|
||||||
|
|
||||||
Use `PUT` or `PATCH` method on the path `/ip/pool`. Consider to the following example JSON:
|
Use `PUT` or `PATCH` method on the path `/ip/pool`. Consider to the following example JSON:
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
@@ -80,8 +83,8 @@ Use `PUT` or `PATCH` method on the path `/ip/pool`. Consider to the following ex
|
|||||||
|
|
||||||
**NOTE:** Looping needed.
|
**NOTE:** Looping needed.
|
||||||
|
|
||||||
**ROLL-BACK:** Save the original configuration as a JSON string in the `comment` field. If there was no original
|
**ROLL-BACK:** Identify your entry from the `name` field (contains 'easyfi') or from the contents of the `comment` field
|
||||||
configuration, simply delete your record.
|
and remove it.
|
||||||
|
|
||||||
### 4. Create PPPoE Profiles
|
### 4. Create PPPoE Profiles
|
||||||
|
|
||||||
@@ -115,7 +118,7 @@ up servers that use one VLAN and one private IP sub-pool to actually handle the
|
|||||||
Use `PUT` or `PATCH` method on the path `/interface/pppoe-server/server`. Consider to the following example JSON:
|
Use `PUT` or `PATCH` method on the path `/interface/pppoe-server/server`. Consider to the following example JSON:
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"interface": "vlan10", // ..................... The VLAN interface you had created earlier.
|
"interface": "easyfi-vlan-2001", // ........... The VLAN interface you had created earlier.
|
||||||
"profile": "easyfi-pppoe-prf-2001", // ........ The PPPoE profile you created earlier.
|
"profile": "easyfi-pppoe-prf-2001", // ........ The PPPoE profile you created earlier.
|
||||||
"service-name": "easyfi-pppoe-srv-2001", // ... Use the VLAN id in the name.
|
"service-name": "easyfi-pppoe-srv-2001", // ... Use the VLAN id in the name.
|
||||||
"comment": "{...}" // ......................... The JSON string to indicate automated config.
|
"comment": "{...}" // ......................... The JSON string to indicate automated config.
|
||||||
|
|||||||
+1
-1
@@ -20,8 +20,8 @@ if [[ $SELECTION -gt 0 && $SELECTION -le ${#SERVERS[@]} ]]; then
|
|||||||
SELECTED_SERVER=${SERVERS[$((SELECTION - 1))]}
|
SELECTED_SERVER=${SERVERS[$((SELECTION - 1))]}
|
||||||
|
|
||||||
# Ask the username and target port no. on the server::
|
# Ask the username and target port no. on the server::
|
||||||
read -rp "Your username on the server ..... : " USER
|
|
||||||
read -rp "The target port no. ............. : " PORT
|
read -rp "The target port no. ............. : " PORT
|
||||||
|
read -rp "Your username on the server ..... : " USER
|
||||||
|
|
||||||
# Run the command:
|
# Run the command:
|
||||||
ssh -p "$PORT" "$USER@$SELECTED_SERVER"
|
ssh -p "$PORT" "$USER@$SELECTED_SERVER"
|
||||||
|
|||||||
Reference in New Issue
Block a user