functional runner

This commit is contained in:
Jake Hillion 2020-11-04 22:53:36 +00:00
parent b032d9692b
commit 0916f6ba16
4 changed files with 343 additions and 68 deletions

View File

@ -13,7 +13,7 @@
"\n",
"import runners\n",
"from structure import Bridge\n",
"from structure import SpeedTestServer, RemoteServer, LocalServer\n",
"from structure import RemotePortal, LocalPortal\n",
"from structure import Interface, IpMethod\n",
"\n",
"%load_ext dotenv\n",
@ -28,19 +28,7 @@
"name": "#%%\n"
}
},
"outputs": [
{
"ename": "NameError",
"evalue": "name 'runners' is not defined",
"output_type": "error",
"traceback": [
"\u001B[0;31m---------------------------------------------------------------------------\u001B[0m",
"\u001B[0;31mNameError\u001B[0m Traceback (most recent call last)",
"\u001B[0;32m<ipython-input-2-5781debf60ce>\u001B[0m in \u001B[0;36m<module>\u001B[0;34m\u001B[0m\n\u001B[0;32m----> 1\u001B[0;31m runner = runners.ProxmoxRunner(\n\u001B[0m\u001B[1;32m 2\u001B[0m \u001B[0mhost\u001B[0m\u001B[0;34m=\u001B[0m\u001B[0mos\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0mgetenv\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0;34m'PROXMOX_HOST'\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m,\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[1;32m 3\u001B[0m \u001B[0mnode\u001B[0m\u001B[0;34m=\u001B[0m\u001B[0mos\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0mgetenv\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0;34m'PROXMOX_NODE'\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m,\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[1;32m 4\u001B[0m \u001B[0muser\u001B[0m\u001B[0;34m=\u001B[0m\u001B[0mos\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0mgetenv\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0;34m'PROXMOX_USER'\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m,\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[1;32m 5\u001B[0m \u001B[0mtoken_name\u001B[0m\u001B[0;34m=\u001B[0m\u001B[0mos\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0mgetenv\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0;34m'PROXMOX_TOKEN_NAME'\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m,\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n",
"\u001B[0;31mNameError\u001B[0m: name 'runners' is not defined"
]
}
],
"outputs": [],
"source": [
"runner = runners.ProxmoxRunner(\n",
" host=os.getenv('PROXMOX_HOST'),\n",
@ -52,47 +40,63 @@
" template_id=9000,\n",
" initial_vm_id=21002,\n",
"\n",
" internet_bridge='vmbr2',\n",
" internet_bridge=os.getenv('INTERNET_BRIDGE'),\n",
"\n",
" management_bridge='vmbr4',\n",
" management_initial_ip=ipaddress.ip_address('10.21.12.2'),\n",
")"
" management_bridge=os.getenv('MANAGEMENT_BRIDGE'),\n",
" management_gateway=ipaddress.ip_address(os.getenv('MANAGEMENT_GATEWAY')),\n",
" management_initial_ip=ipaddress.ip_address(os.getenv('MANAGEMENT_INITIAL_IP')),\n",
")\n",
"\n",
"setup_params = {\n",
" 'access_key': os.getenv('S3_ACCESS_KEY'),\n",
" 'secret_key': os.getenv('S3_SECRET_KEY'),\n",
" 'branch': os.getenv('TARGET_BRANCH'),\n",
"}"
]
},
{
"cell_type": "code",
"execution_count": 5,
"execution_count": 3,
"metadata": {
"pycharm": {
"name": "#%%\n"
}
},
"outputs": [],
"outputs": [
{
"ename": "KeyboardInterrupt",
"evalue": "",
"output_type": "error",
"traceback": [
"\u001B[0;31m---------------------------------------------------------------------------\u001B[0m",
"\u001B[0;31mKeyboardInterrupt\u001B[0m Traceback (most recent call last)",
"\u001B[0;32m<ipython-input-3-bc701b222680>\u001B[0m in \u001B[0;36m<module>\u001B[0;34m\u001B[0m\n\u001B[1;32m 12\u001B[0m \u001B[0;34m*\u001B[0m\u001B[0mlp\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0mget_interfaces\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m[\u001B[0m\u001B[0;36m0\u001B[0m\u001B[0;34m:\u001B[0m\u001B[0;36m2\u001B[0m\u001B[0;34m]\u001B[0m\u001B[0;34m,\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[1;32m 13\u001B[0m ])\n\u001B[0;32m---> 14\u001B[0;31m \u001B[0mrunner\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0mbuild\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0mtop_level_bridge\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[0m\u001B[1;32m 15\u001B[0m \u001B[0;34m\u001B[0m\u001B[0m\n",
"\u001B[0;32m~/sync/school/exercises/paper-0/dissertation/3-evaluation/runners/runners.py\u001B[0m in \u001B[0;36mbuild\u001B[0;34m(self, bridge)\u001B[0m\n\u001B[1;32m 137\u001B[0m \u001B[0mself\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0m_build_bridges\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[1;32m 138\u001B[0m \u001B[0;32mfor\u001B[0m \u001B[0mnode\u001B[0m \u001B[0;32min\u001B[0m \u001B[0mnodes\u001B[0m\u001B[0;34m:\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[0;32m--> 139\u001B[0;31m \u001B[0mself\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0m_build_node\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0mnode\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[0m\u001B[1;32m 140\u001B[0m \u001B[0;34m\u001B[0m\u001B[0m\n\u001B[1;32m 141\u001B[0m \u001B[0;32mdef\u001B[0m \u001B[0m_await_task\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0mself\u001B[0m\u001B[0;34m,\u001B[0m \u001B[0mupid\u001B[0m\u001B[0;34m,\u001B[0m \u001B[0mtimeout\u001B[0m\u001B[0;34m=\u001B[0m\u001B[0;36m10\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m:\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n",
"\u001B[0;32m~/sync/school/exercises/paper-0/dissertation/3-evaluation/runners/runners.py\u001B[0m in \u001B[0;36m_build_node\u001B[0;34m(self, node)\u001B[0m\n\u001B[1;32m 262\u001B[0m \u001B[0mself\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0m_await_task\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0mstart_task\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[1;32m 263\u001B[0m \u001B[0;34m\u001B[0m\u001B[0m\n\u001B[0;32m--> 264\u001B[0;31m \u001B[0mself\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0m_open_ssh\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0mnode\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[0m\u001B[1;32m 265\u001B[0m \u001B[0mself\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0mssh\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0mnode\u001B[0m\u001B[0;34m,\u001B[0m \u001B[0mnode\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0mget_internet_setup\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[1;32m 266\u001B[0m \u001B[0mself\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0m_close_ssh\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0mnode\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n",
"\u001B[0;32m~/sync/school/exercises/paper-0/dissertation/3-evaluation/runners/runners.py\u001B[0m in \u001B[0;36m_open_ssh\u001B[0;34m(self, node, interface)\u001B[0m\n\u001B[1;32m 206\u001B[0m \u001B[0;32mwhile\u001B[0m \u001B[0;34m(\u001B[0m\u001B[0mdatetime\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0mnow\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0;34m)\u001B[0m \u001B[0;34m-\u001B[0m \u001B[0mt1\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0mseconds\u001B[0m \u001B[0;34m<\u001B[0m \u001B[0mProxmoxRunner\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0mssh_timeout\u001B[0m\u001B[0;34m:\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[1;32m 207\u001B[0m \u001B[0;32mtry\u001B[0m\u001B[0;34m:\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[0;32m--> 208\u001B[0;31m client.connect(\n\u001B[0m\u001B[1;32m 209\u001B[0m \u001B[0mhostname\u001B[0m\u001B[0;34m=\u001B[0m\u001B[0mstr\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0minterface\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0mget_address\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m,\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[1;32m 210\u001B[0m \u001B[0musername\u001B[0m\u001B[0;34m=\u001B[0m\u001B[0;34m'python'\u001B[0m\u001B[0;34m,\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n",
"\u001B[0;32m~/sync/school/exercises/paper-0/dissertation/3-evaluation/venv/lib/python3.8/site-packages/paramiko/client.py\u001B[0m in \u001B[0;36mconnect\u001B[0;34m(self, hostname, port, username, password, pkey, key_filename, timeout, allow_agent, look_for_keys, compress, sock, gss_auth, gss_kex, gss_deleg_creds, gss_host, banner_timeout, auth_timeout, gss_trust_dns, passphrase, disabled_algorithms)\u001B[0m\n\u001B[1;32m 347\u001B[0m \u001B[0;32mexcept\u001B[0m\u001B[0;34m:\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[1;32m 348\u001B[0m \u001B[0;32mpass\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[0;32m--> 349\u001B[0;31m \u001B[0mretry_on_signal\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0;32mlambda\u001B[0m\u001B[0;34m:\u001B[0m \u001B[0msock\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0mconnect\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0maddr\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[0m\u001B[1;32m 350\u001B[0m \u001B[0;31m# Break out of the loop on success\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[1;32m 351\u001B[0m \u001B[0;32mbreak\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n",
"\u001B[0;32m~/sync/school/exercises/paper-0/dissertation/3-evaluation/venv/lib/python3.8/site-packages/paramiko/util.py\u001B[0m in \u001B[0;36mretry_on_signal\u001B[0;34m(function)\u001B[0m\n\u001B[1;32m 281\u001B[0m \u001B[0;32mwhile\u001B[0m \u001B[0;32mTrue\u001B[0m\u001B[0;34m:\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[1;32m 282\u001B[0m \u001B[0;32mtry\u001B[0m\u001B[0;34m:\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[0;32m--> 283\u001B[0;31m \u001B[0;32mreturn\u001B[0m \u001B[0mfunction\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[0m\u001B[1;32m 284\u001B[0m \u001B[0;32mexcept\u001B[0m \u001B[0mEnvironmentError\u001B[0m \u001B[0;32mas\u001B[0m \u001B[0me\u001B[0m\u001B[0;34m:\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[1;32m 285\u001B[0m \u001B[0;32mif\u001B[0m \u001B[0me\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0merrno\u001B[0m \u001B[0;34m!=\u001B[0m \u001B[0merrno\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0mEINTR\u001B[0m\u001B[0;34m:\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n",
"\u001B[0;32m~/sync/school/exercises/paper-0/dissertation/3-evaluation/venv/lib/python3.8/site-packages/paramiko/client.py\u001B[0m in \u001B[0;36m<lambda>\u001B[0;34m()\u001B[0m\n\u001B[1;32m 347\u001B[0m \u001B[0;32mexcept\u001B[0m\u001B[0;34m:\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[1;32m 348\u001B[0m \u001B[0;32mpass\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[0;32m--> 349\u001B[0;31m \u001B[0mretry_on_signal\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0;32mlambda\u001B[0m\u001B[0;34m:\u001B[0m \u001B[0msock\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0mconnect\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0maddr\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[0m\u001B[1;32m 350\u001B[0m \u001B[0;31m# Break out of the loop on success\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[1;32m 351\u001B[0m \u001B[0;32mbreak\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n",
"\u001B[0;31mKeyboardInterrupt\u001B[0m: "
]
}
],
"source": [
"g_st = SpeedTestServer([Interface(IpMethod.Auto4)])\n",
"l_st = SpeedTestServer([Interface(IpMethod.Dhcp4)])\n",
"rp = RemotePortal([Interface(IpMethod.Auto4)], setup_params=setup_params)\n",
"lp = LocalPortal([\n",
" Interface(IpMethod.Auto4, limit=1),\n",
" Interface(IpMethod.Auto4, limit=1),\n",
"], None, setup_params=setup_params)\n",
"\n",
"rs = RemoteServer([Interface(IpMethod.Auto4)])\n",
"ls = LocalServer([\n",
" Interface(IpMethod.Auto4, limit=1),\n",
" Interface(IpMethod.Auto4, limit=1),\n",
"], l_st)\n",
"rp.set_local_portal(lp)\n",
"lp.set_remote_portal(rp)\n",
"\n",
"top_level_bridge = Bridge(*[\n",
" g_st.get_interfaces()[0],\n",
" rs.get_interfaces()[0],\n",
" *ls.get_interfaces()[0:2],\n",
" rp.get_interfaces()[0],\n",
" *lp.get_interfaces()[0:2],\n",
"])\n",
"runner.build(top_level_bridge)\n",
"\n",
"# Test from the client to the global network via the proxy\n",
"g_st.server()\n",
"l_st.client(rs.get_interfaces()[0])\n",
"\n",
"# Test from the global network to the client via the proxy\n",
"g_st.server()\n",
"l_st.client(rs.get_interfaces()[0])\n",
"\n",
"# Clean up\n",
"runner.teardown()"
]

View File

@ -1,9 +1,12 @@
import ipaddress
import os
import time
from datetime import datetime
from typing import Callable, List, Tuple
from urllib.parse import quote
import proxmoxer
import paramiko
import structure
@ -15,7 +18,7 @@ def check_env(*names: str) -> bool:
return True
def bridge_node_dfs(
def bridge_node_search(
first: structure.Bridge,
bridge_name_generator: Callable[[structure.Bridge], str],
node_id_generator: Callable[[structure.Node], int],
@ -59,7 +62,7 @@ class PrintRunner:
self._last_node_id = 0
def build(self, bridge: structure.Bridge):
bridges, nodes = bridge_node_dfs(bridge, lambda _: self.name_bridge(), lambda _: self.id_node())
bridges, nodes = bridge_node_search(bridge, lambda _: self.name_bridge(), lambda _: self.id_node())
print(bridges)
print(nodes)
@ -77,6 +80,8 @@ class PrintRunner:
class ProxmoxRunner:
ssh_timeout = 300
def __init__(
self,
host: str,
@ -92,6 +97,7 @@ class ProxmoxRunner:
management_bridge: str,
management_initial_ip: ipaddress,
management_gateway: ipaddress,
verify_ssl: bool = False,
):
@ -111,20 +117,33 @@ class ProxmoxRunner:
self._proxmox_node = node
self._template_id = template_id
self._initial_vm_id = initial_vm_id - 1
self._initial_vm_id = initial_vm_id
self._internet_bridge = internet_bridge
self._internet_bridge = structure.Bridge()
self._internet_bridge.set_name(internet_bridge)
self._management_bridge = management_bridge
self._management_bridge = structure.Bridge()
self._management_bridge.set_name(management_bridge)
self._management_initial_ip = management_initial_ip
self._management_gateway = management_gateway
# generate a single use SSH key (we can use any with Proxmox)
self._private_key = paramiko.RSAKey.generate(3072)
self._client = paramiko.SSHClient()
def build(self, bridge: structure.Bridge):
bridges, nodes = bridge_node_dfs(bridge, lambda x: self._create_bridge(x), lambda x: self._create_node(x))
bridges, nodes = bridge_node_search(bridge, lambda x: self._create_bridge(x), lambda x: self._create_node(x))
self._build_bridges()
for node in nodes:
self._build_node(node)
# guarantee that setup is not called until all of the nodes are built
# this means that all will have their final IPs by this point
for node in nodes:
self._setup_node(node)
def _await_task(self, upid, timeout=10):
t1 = datetime.now()
while (datetime.now() - t1).seconds < timeout:
@ -133,8 +152,6 @@ class ProxmoxRunner:
raise TimeoutError
def _create_bridge(self, bridge: structure.Bridge) -> str:
self._last_bridge += 1
while True:
try:
self._proxmox.nodes(self._proxmox_node).network.post(
@ -143,6 +160,7 @@ class ProxmoxRunner:
autostart=1,
comments='Automatically created by Python evaluation',
)
self._last_bridge += 1
break
except proxmoxer.core.ResourceException as e:
if 'interface already exists' in str(e):
@ -150,7 +168,7 @@ class ProxmoxRunner:
else:
raise e
bridge_name = 'vmbr{}'.format(self._last_bridge)
bridge_name = 'vmbr{}'.format(self._last_bridge - 1)
self._created_bridges.append(bridge_name)
return bridge_name
@ -159,14 +177,13 @@ class ProxmoxRunner:
self._await_task(network_task)
def _create_node(self, node: structure.Node) -> int:
self._last_node_id += 1
while True:
try:
clone_task = self._proxmox.nodes(self._proxmox_node).qemu(self._template_id).clone.post(
newid=self._initial_vm_id + self._last_node_id,
name='Diss-{}-Testing'.format(node.__class__.__name__),
)
self._last_node_id += 1
break
except proxmoxer.core.ResourceException as e:
if 'config file already exists' in str(e):
@ -176,13 +193,130 @@ class ProxmoxRunner:
self._await_task(clone_task)
new_id = self._initial_vm_id + self._last_node_id
self._created_nodes.append(new_id)
return new_id
self._created_nodes.append(new_id - 1)
return new_id - 1
def _open_ssh(self, node: structure.Node, interface: structure.Interface = None):
if interface is None:
for iface in node.get_interfaces():
if iface.get_method() == structure.IpMethod.Management:
interface = iface
break
if interface is None:
raise RuntimeError('no management interface available')
client = paramiko.SSHClient()
client.set_missing_host_key_policy(paramiko.AutoAddPolicy)
t1 = datetime.now()
while (datetime.now() - t1).seconds < ProxmoxRunner.ssh_timeout:
try:
client.connect(
hostname=str(interface.get_address()),
username='python',
pkey=self._private_key,
banner_timeout=15,
)
client.set_missing_host_key_policy(paramiko.RejectPolicy)
break
except (paramiko.ssh_exception.AuthenticationException, paramiko.ssh_exception.NoValidConnectionsError):
time.sleep(10)
node.client = client
def _close_ssh(self, node: structure.Node):
node.client.close()
del node.client
def ssh(self, node: structure.Node, command: str, error_stderr=False, error_stdout=False) -> int:
chan = node.client.get_transport().open_session()
chan.exec_command(command)
exit_status = chan.recv_exit_status()
if exit_status != 0:
if error_stderr and error_stdout:
raise Exception(
'stdout:\n{}\n\nstderr:\n{}\n'.format(chan.recv(2048).decode(), chan.recv_stderr(2048).decode()))
if error_stderr:
raise Exception(chan.recv_stderr(2048).decode())
if error_stdout:
raise Exception(chan.recv(2048).decode())
return exit_status
def _build_node(self, node: structure.Node):
# Step 1: connect to Internet bridge with DHCP to install packages
# Step 2: connect to management bridge for correct setup
pass
# Step 1: Configure access
self._proxmox.nodes(self._proxmox_node).qemu(node.get_id()).config.put(
ciuser='python',
sshkeys=quote('ssh-rsa ' + self._private_key.get_base64(), ''),
cores=node.get_core_count(),
sockets=1,
memory=node.get_memory_mb(),
)
# Step 2: connect to Internet bridge with DHCP to install packages
if node.get_internet_setup() is not None:
interfaces = node.get_interfaces()
internet_interface = structure.Interface(structure.IpMethod.Dhcp4)
internet_interface.set_bridge(self._internet_bridge)
temp_interfaces = [internet_interface, interfaces[len(interfaces)-1]]
self._setup_node_interfaces(node, temp_interfaces)
start_task = self._proxmox.nodes(self._proxmox_node).qemu(node.get_id()).status.start.post()
self._await_task(start_task)
self._open_ssh(node)
self.ssh(node, node.get_internet_setup(), error_stdout=True, error_stderr=True)
self._close_ssh(node)
stop_task = self._proxmox.nodes(self._proxmox_node).qemu(node.get_id()).status.shutdown.post()
self._await_task(stop_task)
# Step 3: connect to management bridge for final setup
self._setup_node_interfaces(node)
start_task = self._proxmox.nodes(self._proxmox_node).qemu(node.get_id()).status.start.post()
self._await_task(start_task)
self._open_ssh(node)
def _setup_node_interfaces(self, node: structure.Node, interfaces: List[structure.Interface] = None):
if interfaces is None:
interfaces = node.get_interfaces()
kwargs = dict()
for i in range(len(interfaces)):
interface = interfaces[i]
method = interface.get_method()
if method == structure.IpMethod.Manual:
pass
elif method == structure.IpMethod.Management:
interface.set_bridge(self._management_bridge)
addr = self._management_initial_ip + node.get_id() - self._initial_vm_id
kwargs['ipconfig{}'.format(i)] = 'ip={}/24,gw={}'.format(addr, self._management_gateway)
interface.set_address(addr)
elif method == structure.IpMethod.Auto4:
bridge = interface.get_bridge()
addr = bridge.get_ip_address()
kwargs['ipconfig{}'.format(i)] = 'ip={}/{}'.format(addr, bridge.netmask)
interface.set_address(addr)
elif method == structure.IpMethod.Dhcp4:
kwargs['ipconfig{}'.format(i)] = 'ip=dhcp'
else:
raise RuntimeError('not implemented')
kwargs['net{}'.format(i)] = 'model=virtio,bridge={}'.format(interface.get_bridge().get_name())
self._proxmox.nodes(self._proxmox_node).qemu(node.get_id()).config.put(**kwargs)
def _setup_node(self, node: structure.Node):
if node.get_setup() is not None:
self.ssh(node, node.get_setup(), error_stdout=True, error_stderr=True)
def teardown(self):
for node in self._created_nodes:
@ -193,6 +327,7 @@ class ProxmoxRunner:
for bridge in self._created_bridges:
self._proxmox.nodes(self._proxmox_node).network(bridge).delete()
network_task = self._proxmox.nodes(self._proxmox_node).network.put()
self._await_task(network_task)

View File

@ -2,4 +2,4 @@ from .structure import Node
from .structure import IpMethod, Interface, Bridge
from .structure import SpeedTestServer, LocalServer, RemoteServer
from .structure import SpeedTestServer, LocalPortal, RemotePortal

View File

@ -1,8 +1,10 @@
import ipaddress
import textwrap
from enum import Enum
from typing import List, Optional, Union
import random
from typing import List, Optional, Union, Dict
# Enums
class IpMethod(Enum):
Manual = 0
Management = 1
@ -22,6 +24,7 @@ class Interface:
self._method = method
self._limit = limit
self._address: ipaddress.ip_address = None
def get_method(self):
return self._method
@ -38,6 +41,12 @@ class Interface:
def get_bridge(self):
return self._bridge
def set_address(self, addr: ipaddress.ip_address):
self._address = addr
def get_address(self) -> ipaddress.ip_address:
return self._address
class Bridge:
def __init__(self, *interfaces: Interface):
@ -48,6 +57,11 @@ class Bridge:
self._interfaces.append(interface)
interface.set_bridge(self)
# Generate a random class c private range by default (10.0.0.0)
self.netmask = 24
self._addr: ipaddress.ip_address = ipaddress.ip_address('10.0.0.0') + random.randint(0, 16777216)
self._network_iterator = ipaddress.ip_network('{}/{}'.format(self._addr, self.netmask), False).hosts()
def get_interfaces(self) -> List[Interface]:
return self._interfaces
@ -57,9 +71,16 @@ class Bridge:
def get_name(self) -> str:
return self._name
def set_netmask(self, mask: int):
self.netmask = mask
self._network_iterator = ipaddress.ip_network('{}/{}'.format(self._addr, self.netmask), False).hosts()
def get_ip_address(self) -> ipaddress.ip_address:
return next(self._network_iterator)
class Node:
def __init__(self, interfaces: List[Interface]):
def __init__(self, interfaces: List[Interface], setup_params: Dict = None):
self._id: Union[int, None] = None
self._interfaces: List[Interface] = interfaces
self._interfaces.append(Interface(IpMethod.Management))
@ -67,6 +88,8 @@ class Node:
for interface in self._interfaces:
interface.set_node(self)
self.setup_params = {} if setup_params is None else setup_params
def get_interfaces(self):
return self._interfaces
@ -76,22 +99,135 @@ class Node:
def get_id(self):
return self._id
def get_core_count(self) -> int:
return 2
def get_memory_mb(self) -> int:
return 2048
def get_internet_setup(self) -> Optional[str]:
return None
def get_setup(self) -> Optional[str]:
return None
class SpeedTestServer(Node):
def server(self):
pass
def client(self, server: Interface):
pass
# Entry method for running the serve with `with speedtest:`
def __enter__(self):
pass
class RemoteServer(Node):
pass
def __exit__(self, exc_type, exc_val, exc_tb):
pass
class LocalServer(Node):
def __init__(self, wan_interfaces: List[Interface], child: Node):
lan_interface = Interface(IpMethod.Manual)
super().__init__([*wan_interfaces, lan_interface])
class RemotePortal(Node):
def __init__(self, interfaces, **kwargs):
super(RemotePortal, self).__init__(interfaces, **kwargs)
Bridge(lan_interface, child.get_interfaces()[0])
self.local_portal = None
def set_local_portal(self, local_portal):
self.local_portal = local_portal
def get_internet_setup(self) -> Optional[str]:
return textwrap.dedent('''
set -e
wget -q http://10.20.0.11/minio-client
chmod +x minio-client
./minio-client alias set s3 http://10.20.0.25:3900 {access_key} {secret_key} || \
./minio-client alias set s3 s3.us-west-001.backblazeb2.com {access_key} {secret_key}
./minio-client cp s3/dissertation/binaries/debian/{branch} mpbl3p
cloud-init status --wait
sudo apt-get install -y iperf3
chmod +x mpbl3p
''').format(**self.setup_params)
def get_setup(self) -> Optional[str]:
return textwrap.dedent('''
set -e
cat << EOF > config.ini
[Host]
PrivateKey = INVALID
[Peer]
PublicKey = INVALID
Method = TCP
LocalHost = {local_host}
LocalPort = 1234
EOF
''').format(
local_host=self.get_interfaces()[0].get_address(),
**self.setup_params,
)
class LocalPortal(Node):
def __init__(self, wan_interfaces: List[Interface], child: Optional[Node], **kwargs):
if child is not None:
lan_interface = Interface(IpMethod.Manual)
Bridge(lan_interface, child.get_interfaces()[0])
super().__init__([*wan_interfaces, lan_interface], **kwargs)
else:
super().__init__(wan_interfaces, **kwargs)
self.remote_portal = None
def set_remote_portal(self, remote_portal):
self.remote_portal = remote_portal
def get_internet_setup(self) -> Optional[str]:
return textwrap.dedent('''
set -e
wget -q http://10.20.0.11/minio-client
chmod +x minio-client
./minio-client alias set s3 http://10.20.0.25:3900 {access_key} {secret_key} || \
./minio-client alias set s3 s3.us-west-001.backblazeb2.com {access_key} {secret_key}
./minio-client cp s3/dissertation/binaries/debian/{branch} mpbl3p
cloud-init status --wait
sudo apt-get install -y iperf3
chmod +x mpbl3p
''').format(**self.setup_params)
def get_setup(self) -> str:
peer_string = textwrap.dedent('''
[Peer]
PublicKey = INVALID
Method = TCP
LocalHost = {local_host}
RemoteHost = {remote_host}
RemotePort = 1234
''')
peers = '\n\n'.join([peer_string.format(
local_host=x.get_address(),
remote_host=self.remote_portal.get_interfaces()[0].get_address(),
) for x in self.get_interfaces()[:-1]])
return textwrap.dedent('''
set -e
cat << EOF > config.ini
[Host]
PrivateKey = INVALID
{peers}
EOF
''').format(**self.setup_params, peers=peers)