Prechádzať zdrojové kódy

Create check.yml (#13)

* Create check.yml

* add black code format

* apply code format

* add action for docker build

* replace apt-get with apt

* fix tab and space mixes

* add docker compose

* fix actions yml
Markus Hofbauer 6 rokov pred
rodič
commit
be2fa96916
8 zmenil súbory, kde vykonal 196 pridanie a 120 odobranie
  1. 22 0
      .github/workflows/check.yml
  2. 13 0
      .github/workflows/docker.yml
  3. 10 0
      Makefile
  4. 9 6
      README.md
  5. 10 10
      Vagrantfile
  6. 11 0
      docker-compose.yml
  7. 119 104
      main.py
  8. 2 0
      requirements.txt

+ 22 - 0
.github/workflows/check.yml

@@ -0,0 +1,22 @@
+name: Check
+
+on: [push]
+
+jobs:
+  build:
+
+    runs-on: ubuntu-latest
+
+    steps:
+    - uses: actions/checkout@v2
+    - name: Set up Python 3.8
+      uses: actions/setup-python@v1
+      with:
+        python-version: 3.8
+    - name: Install dependencies
+      run: |
+        python -m pip install --upgrade pip
+        pip install -r requirements.txt
+    - name: Check code format
+      run: |
+        make check_format

+ 13 - 0
.github/workflows/docker.yml

@@ -0,0 +1,13 @@
+name: Docker
+on: [push]
+
+jobs:
+
+  build:
+
+    runs-on: ubuntu-latest
+
+    steps:
+    - uses: actions/checkout@v2
+    - name: Docker Build
+      run: docker build . --tag tcgui:$(date +%s)

+ 10 - 0
Makefile

@@ -0,0 +1,10 @@
+
+file_finder = find . -type f $(1) -not -path './venv/*'
+
+PY_FILES = $(call file_finder,-name "*.py")
+
+format:
+	$(PY_FILES) | xargs black
+
+check_format:
+	$(PY_FILES) | xargs black --diff --check

+ 9 - 6
README.md

@@ -1,5 +1,9 @@
 # tcgui
 
+[![Actions Status](https://github.com/tum-lkn/tcgui/workflows/Check/badge.svg)](https://github.com/tum-lkn/tcgui)
+[![Actions Status](https://github.com/tum-lkn/tcgui/workflows/Docker/badge.svg)](https://github.com/tum-lkn/tcgui)
+[![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)
+
 A lightweight Python-based Web-GUI for Linux traffic control (`tc`) to set, view and delete traffic shaping rules. The Web-GUI is intended for short-term isolated testbeds or classroom scenarios and does not contain any security mechanisms.
 
 No further changes are planned right now, but pull requests are welcome.
@@ -10,8 +14,8 @@ No further changes are planned right now, but pull requests are welcome.
 
 - Tested with Ubuntu 16.04 LTS & Ubuntu 18.04 LTS & Raspbian 4.14.98-v7+ (stretch, Debian 9.8)
 - `netem` tools & `python3-flask` are required
-    - Ubuntu 16.04 : Install with `sudo apt-get install iproute python3-flask`
-    - Ubuntu 18.04 : Install with `sudo apt-get install iproute2 python3-flask`
+    - Ubuntu 16.04 : Install with `sudo apt install iproute python3-flask`
+    - Ubuntu 18.04 : Install with `sudo apt install iproute2 python3-flask`
 - More information:
     - https://calomel.org/network_loss_emulation.html
     - https://wiki.linuxfoundation.org/networking/netem
@@ -19,10 +23,10 @@ No further changes are planned right now, but pull requests are welcome.
 ## Usage
 
 - Execute the main.py file and go to http://localhost:5000:
-    
+
     ```
     sudo python3 main.py
-    
+
     --ip IP               The IP where the server is listening
     --port PORT           The port where the server is listening
     --dev [DEV [DEV ...]] The interfaces to restrict to
@@ -69,11 +73,10 @@ Start a receiver in the receiving VM:
 
 	vagrant ssh receiver
 	iperf3 -s
-	
+
 Send a packet stream from the sender to the receiver:
 
 	vagrant ssh sender
 	iperf3 -c 192.168.210.3 -t 300
 
 Now access the GUI at http://192.168.210.2:5000/ and change the rate of interface eth1. You should see the sending/receiving rate to decrease to the set amount.
-

+ 10 - 10
Vagrantfile

@@ -5,18 +5,18 @@ Vagrant.configure("2") do |config|
   config.vm.box = "debian/contrib-jessie64"
 
   config.vm.define "sender" do |cfg|
-  	config.vm.provision "shell", inline: <<-SHELL
-            apt-get update
-            apt-get install -y iproute python3 python3-flask iperf3
-	SHELL
-	cfg.vm.network "private_network", ip: "192.168.210.2"
+    config.vm.provision "shell", inline: <<-SHELL
+            apt update
+            apt install -y iproute python3 python3-flask iperf3
+    SHELL
+    cfg.vm.network "private_network", ip: "192.168.210.2"
   end
 
   config.vm.define "receiver" do |cfg|
-  	config.vm.provision "shell", inline: <<-SHELL
-            apt-get update
-            apt-get install -y iperf3
-	SHELL
-	cfg.vm.network "private_network", ip: "192.168.210.3"
+    config.vm.provision "shell", inline: <<-SHELL
+            apt update
+            apt install -y iperf3
+    SHELL
+    cfg.vm.network "private_network", ip: "192.168.210.3"
   end
 end

+ 11 - 0
docker-compose.yml

@@ -0,0 +1,11 @@
+version: '3.7'
+services:
+  server:
+    build: .
+    environment:
+      TCGUI_IP: 0.0.0.0
+      TCGUI_PORT: 5000
+    ports:
+      - '5000:5000'
+    cap_add:
+      - NET_ADMIN

+ 119 - 104
main.py

@@ -7,16 +7,16 @@ from flask import Flask, render_template, redirect, request, url_for
 
 
 BANDWIDTH_UNITS = [
-    "bit",   # Bits per second
+    "bit",  # Bits per second
     "kbit",  # Kilobits per second
     "mbit",  # Megabits per second
     "gbit",  # Gigabits per second
     "tbit",  # Terabits per second
-    "bps",   # Bytes per second
+    "bps",  # Bytes per second
     "kbps",  # Kilobytes per second
     "mbps",  # Megabytes per second
     "gbps",  # Gigabytes per second
-    "tbps"   # Terabytes per second
+    "tbps",  # Terabytes per second
 ]
 
 STANDARD_UNIT = "mbit"
@@ -28,155 +28,170 @@ dev_list = None
 
 
 def parse_arguments():
-    parser = argparse.ArgumentParser(description='TC web GUI')
-    parser.add_argument('--ip', type=str, required=False,
-                        help='The IP where the server is listening')
-    parser.add_argument('--port', type=int, required=False,
-                        help='The port where the server is listening')
-    parser.add_argument('--dev', type=str, nargs='*', required=False,
-                        help='The interfaces to restrict to')
-    parser.add_argument('--regex', type=str, required=False,
-                        help='A regex to match interfaces')
-    parser.add_argument('--debug', action='store_true',
-                        help='Run Flask in debug mode')
+    parser = argparse.ArgumentParser(description="TC web GUI")
+    parser.add_argument(
+        "--ip", type=str, required=False, help="The IP where the server is listening"
+    )
+    parser.add_argument(
+        "--port",
+        type=int,
+        required=False,
+        help="The port where the server is listening",
+    )
+    parser.add_argument(
+        "--dev",
+        type=str,
+        nargs="*",
+        required=False,
+        help="The interfaces to restrict to",
+    )
+    parser.add_argument(
+        "--regex", type=str, required=False, help="A regex to match interfaces"
+    )
+    parser.add_argument("--debug", action="store_true", help="Run Flask in debug mode")
     return parser.parse_args()
 
 
 @app.route("/")
 def main():
     rules = get_active_rules()
-    return render_template('main.html', rules=rules, units=BANDWIDTH_UNITS,
-                           standard_unit=STANDARD_UNIT)
+    return render_template(
+        "main.html", rules=rules, units=BANDWIDTH_UNITS, standard_unit=STANDARD_UNIT
+    )
 
 
-@app.route('/new_rule/<interface>', methods=['POST'])
+@app.route("/new_rule/<interface>", methods=["POST"])
 def new_rule(interface):
-    delay = request.form['Delay']
-    delay_variance = request.form['DelayVariance']
-    loss = request.form['Loss']
-    loss_correlation = request.form['LossCorrelation']
-    duplicate = request.form['Duplicate']
-    reorder = request.form['Reorder']
-    reorder_correlation = request.form['ReorderCorrelation']
-    corrupt = request.form['Corrupt']
-    limit = request.form['Limit']
-    rate = request.form['Rate']
-    rate_unit = request.form['rate_unit']
+    delay = request.form["Delay"]
+    delay_variance = request.form["DelayVariance"]
+    loss = request.form["Loss"]
+    loss_correlation = request.form["LossCorrelation"]
+    duplicate = request.form["Duplicate"]
+    reorder = request.form["Reorder"]
+    reorder_correlation = request.form["ReorderCorrelation"]
+    corrupt = request.form["Corrupt"]
+    limit = request.form["Limit"]
+    rate = request.form["Rate"]
+    rate_unit = request.form["rate_unit"]
 
     # remove old setup
-    command = 'tc qdisc del dev %s root netem' % interface
-    command = command.split(' ')
+    command = "tc qdisc del dev %s root netem" % interface
+    command = command.split(" ")
     proc = subprocess.Popen(command)
     proc.wait()
 
     # apply new setup
-    command = 'tc qdisc add dev %s root netem' % interface
-    if rate != '':
-        command += ' rate %s%s' % (rate, rate_unit)
-    if delay != '':
-        command += ' delay %sms' % delay
-        if delay_variance != '':
-            command += ' %sms' % delay_variance
-    if loss != '':
-        command += ' loss %s%%' % loss
-        if loss_correlation != '':
-            command += ' %s%%' % loss_correlation
-    if duplicate != '':
-        command += ' duplicate %s%%' % duplicate
-    if reorder != '':
-        command += ' reorder %s%%' % reorder
-        if reorder_correlation != '':
-            command += ' %s%%' % reorder_correlation
-    if corrupt != '':
-        command += ' corrupt %s%%' % corrupt
-    if limit != '':
-        command += ' limit %s' % limit
+    command = "tc qdisc add dev %s root netem" % interface
+    if rate != "":
+        command += " rate %s%s" % (rate, rate_unit)
+    if delay != "":
+        command += " delay %sms" % delay
+        if delay_variance != "":
+            command += " %sms" % delay_variance
+    if loss != "":
+        command += " loss %s%%" % loss
+        if loss_correlation != "":
+            command += " %s%%" % loss_correlation
+    if duplicate != "":
+        command += " duplicate %s%%" % duplicate
+    if reorder != "":
+        command += " reorder %s%%" % reorder
+        if reorder_correlation != "":
+            command += " %s%%" % reorder_correlation
+    if corrupt != "":
+        command += " corrupt %s%%" % corrupt
+    if limit != "":
+        command += " limit %s" % limit
     print(command)
-    command = command.split(' ')
+    command = command.split(" ")
     proc = subprocess.Popen(command)
     proc.wait()
-    return redirect(url_for('main'))
+    return redirect(url_for("main"))
 
 
-@app.route('/remove_rule/<interface>', methods=['POST'])
+@app.route("/remove_rule/<interface>", methods=["POST"])
 def remove_rule(interface):
     # remove old setup
-    command = 'tc qdisc del dev %s root netem' % interface
-    command = command.split(' ')
+    command = "tc qdisc del dev %s root netem" % interface
+    command = command.split(" ")
     proc = subprocess.Popen(command)
     proc.wait()
-    return redirect(url_for('main'))
+    return redirect(url_for("main"))
 
 
 def get_active_rules():
-    proc = subprocess.Popen(['tc', 'qdisc'], stdout=subprocess.PIPE)
+    proc = subprocess.Popen(["tc", "qdisc"], stdout=subprocess.PIPE)
     output = proc.communicate()[0].decode()
-    lines = output.split('\n')[:-1]
+    lines = output.split("\n")[:-1]
     rules = []
     dev = set()
     for line in lines:
         arguments = line.split()
         rule = parse_rule(arguments)
-        if rule['name'] and rule['name'] not in dev:
+        if rule["name"] and rule["name"] not in dev:
             rules.append(rule)
-            dev.add(rule['name'])
+            dev.add(rule["name"])
     return rules
 
 
 def parse_rule(split_rule):
-    rule = {'name':               None,
-            'rate':               None,
-            'delay':              None,
-            'delayVariance':      None,
-            'loss':               None,
-            'lossCorrelation':    None,
-            'duplicate':          None,
-            'reorder':            None,
-            'reorderCorrelation': None,
-            'corrupt':            None,
-            'limit':              None}
+    rule = {
+        "name": None,
+        "rate": None,
+        "delay": None,
+        "delayVariance": None,
+        "loss": None,
+        "lossCorrelation": None,
+        "duplicate": None,
+        "reorder": None,
+        "reorderCorrelation": None,
+        "corrupt": None,
+        "limit": None,
+    }
     i = 0
     for argument in split_rule:
-        if argument == 'dev':
+        if argument == "dev":
             # Both regex pattern and dev name can be given
             # An interface could match the pattern and/or
             # be in the interface list
             if pattern is None and dev_list is None:
-                rule['name'] = split_rule[i + 1]
+                rule["name"] = split_rule[i + 1]
             if pattern:
                 if pattern.match(split_rule[i + 1]):
-                    rule['name'] = split_rule[i + 1]
+                    rule["name"] = split_rule[i + 1]
             if dev_list:
                 if split_rule[i + 1] in dev_list:
-                    rule['name'] = split_rule[i + 1]
-        elif argument == 'rate':
-            rule['rate'] = split_rule[i + 1].split('Mbit')[0]
-        elif argument == 'delay':
-            rule['delay'] = split_rule[i + 1]
-            if len(split_rule) > (i + 2) and 'ms' in split_rule[i + 2]:
-                rule['delayVariance'] = split_rule[i + 2]
-        elif argument == 'loss':
-            rule['loss'] = split_rule[i + 1]
-            if len(split_rule) > (i + 2) and '%' in split_rule[i + 2]:
-                rule['lossCorrelation'] = split_rule[i + 2]
-        elif argument == 'duplicate':
-            rule['duplicate'] = split_rule[i + 1]
-        elif argument == 'reorder':
-            rule['reorder'] = split_rule[i + 1]
-            if len(split_rule) > (i + 2) and '%' in split_rule[i + 2]:
-                rule['reorderCorrelation'] = split_rule[i + 2]
-        elif argument == 'corrupt':
-            rule['corrupt'] = split_rule[i + 1]
-        elif argument == 'limit':
-            rule['limit'] = split_rule[i + 1]
+                    rule["name"] = split_rule[i + 1]
+        elif argument == "rate":
+            rule["rate"] = split_rule[i + 1].split("Mbit")[0]
+        elif argument == "delay":
+            rule["delay"] = split_rule[i + 1]
+            if len(split_rule) > (i + 2) and "ms" in split_rule[i + 2]:
+                rule["delayVariance"] = split_rule[i + 2]
+        elif argument == "loss":
+            rule["loss"] = split_rule[i + 1]
+            if len(split_rule) > (i + 2) and "%" in split_rule[i + 2]:
+                rule["lossCorrelation"] = split_rule[i + 2]
+        elif argument == "duplicate":
+            rule["duplicate"] = split_rule[i + 1]
+        elif argument == "reorder":
+            rule["reorder"] = split_rule[i + 1]
+            if len(split_rule) > (i + 2) and "%" in split_rule[i + 2]:
+                rule["reorderCorrelation"] = split_rule[i + 2]
+        elif argument == "corrupt":
+            rule["corrupt"] = split_rule[i + 1]
+        elif argument == "limit":
+            rule["limit"] = split_rule[i + 1]
         i += 1
     return rule
 
 
 if __name__ == "__main__":
     if os.geteuid() != 0:
-        print("You need to have root privileges to run this script.\n"
-              "Please try again, this time using 'sudo'. Exiting.")
+        print(
+            "You need to have root privileges to run this script.\n"
+            "Please try again, this time using 'sudo'. Exiting."
+        )
         exit(1)
 
     # TC Variables
@@ -185,7 +200,7 @@ if __name__ == "__main__":
     pattern = os.environ.get("TCGUI_REGEX")
     if args.regex:
         pattern = re.compile(args.regex)
-    
+
     dev_list = os.environ.get("TCGUI_DEV")
     if args.dev:
         dev_list = args.dev
@@ -193,14 +208,14 @@ if __name__ == "__main__":
     # Flask Variable
     app_args = {}
 
-    app_args['host'] = os.environ.get("TCGUI_IP")
-    app_args['port'] = os.environ.get("TCGUI_PORT")
+    app_args["host"] = os.environ.get("TCGUI_IP")
+    app_args["port"] = os.environ.get("TCGUI_PORT")
 
     if args.ip:
-        app_args['host'] = args.ip
+        app_args["host"] = args.ip
     if args.port:
-        app_args['port'] = args.port
+        app_args["port"] = args.port
     if not args.debug:
-        app_args['debug'] = False
+        app_args["debug"] = False
     app.debug = True
     app.run(**app_args)

+ 2 - 0
requirements.txt

@@ -0,0 +1,2 @@
+black
+Flask