├── LICENSE ├── client.py ├── loadBalanceNode.py ├── localClient.py ├── log ├── log-c1-bal.txt ├── log-c1-load.txt ├── log-c2-bal.txt ├── log-c2-load.txt ├── log-c3-bal.txt ├── log-c3-load.txt └── log-local.txt ├── miniedit.py ├── plot.py ├── readme.md ├── results ├── star-RRBalancer.png ├── star-randomBalancer.png ├── starTopo.png ├── tree-RRBalancer.png ├── tree-randomBalancer.png └── treeTopo.png ├── server.py └── topo ├── starTopo.py ├── tests.py ├── tests.pyc └── treeTopo.py /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Sarthak Pranesh 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /client.py: -------------------------------------------------------------------------------- 1 | import requests 2 | import sys 3 | 4 | i = 100 5 | f = open("../log/log-" + sys.argv[1] + ".txt", "a") 6 | while i > 0: 7 | x = requests.get('http://10.0.0.4:9090') 8 | f.write(str(x.elapsed.total_seconds())+"\n") 9 | i = i - 1 10 | 11 | 12 | -------------------------------------------------------------------------------- /loadBalanceNode.py: -------------------------------------------------------------------------------- 1 | import SimpleHTTPServer 2 | import SocketServer 3 | import sys 4 | import random 5 | 6 | def RR(c, arr): 7 | n = c % 2 8 | return arr[n] 9 | 10 | def Random(arr): 11 | n = random.randint(0,1) 12 | return arr[n] 13 | 14 | class MyHandler(SimpleHTTPServer.SimpleHTTPRequestHandler): 15 | arr = ["http://10.0.0.5:8080", "http://10.0.0.6:8080"] 16 | 17 | def do_GET(self): 18 | global c 19 | self.send_response(301) 20 | if (sys.argv[1] == "bal"): 21 | add = RR(c, self.arr) 22 | else: 23 | add = self.arr[0] 24 | c = c + 1 25 | #print(add) 26 | self.send_header('Location', add) 27 | self.end_headers() 28 | 29 | c = 0 30 | 31 | pywebserver = SocketServer.TCPServer(("", 9090), MyHandler) 32 | 33 | pywebserver.serve_forever() 34 | -------------------------------------------------------------------------------- /localClient.py: -------------------------------------------------------------------------------- 1 | import requests 2 | import sys 3 | 4 | i = 100 5 | f = open("./log/log-" + sys.argv[1] + ".txt", "a") 6 | while i > 0: 7 | x = requests.get('http://192.168.56.101:9090') 8 | f.write(str(x.elapsed.total_seconds())+"\n") 9 | i = i - 1 10 | 11 | 12 | -------------------------------------------------------------------------------- /log/log-c1-bal.txt: -------------------------------------------------------------------------------- 1 | 0.105876 2 | 0.108246 3 | 0.163905 4 | 0.125306 5 | 0.171708 6 | 0.113891 7 | 0.171594 8 | 0.115644 9 | 0.172386 10 | 0.109397 11 | 0.168924 12 | 0.112791 13 | 0.170942 14 | 0.114406 15 | 0.173424 16 | 0.111618 17 | 0.161454 18 | 0.117425 19 | 0.168072 20 | 0.128995 21 | 0.159935 22 | 0.111021 23 | 0.171536 24 | 0.118174 25 | 0.165759 26 | 0.116936 27 | 0.16689 28 | 0.11604 29 | 0.166662 30 | 0.114997 31 | 0.170446 32 | 0.117857 33 | 0.167113 34 | 0.115692 35 | 0.170611 36 | 0.120302 37 | 0.163236 38 | 0.111847 39 | 0.16876 40 | 0.121475 41 | 0.153793 42 | 0.12628 43 | 0.14573 44 | 0.122121 45 | 0.161179 46 | 0.119987 47 | 0.166958 48 | 0.113421 49 | 0.169355 50 | 0.121319 51 | 0.163937 52 | 0.138553 53 | 0.152453 54 | 0.121655 55 | 0.161761 56 | 0.118726 57 | 0.162773 58 | 0.117773 59 | 0.146495 60 | 0.123219 61 | 0.162156 62 | 0.111233 63 | 0.163213 64 | 0.125261 65 | 0.164167 66 | 0.118734 67 | 0.163787 68 | 0.118846 69 | 0.164546 70 | 0.115408 71 | 0.167544 72 | 0.116189 73 | 0.163571 74 | 0.118072 75 | 0.155779 76 | 0.119811 77 | 0.166217 78 | 0.112501 79 | 0.160847 80 | 0.118271 81 | 0.165153 82 | 0.111226 83 | 0.169374 84 | 0.117257 85 | 0.169044 86 | 0.112522 87 | 0.167151 88 | 0.11732 89 | 0.165304 90 | 0.114469 91 | 0.166854 92 | 0.111474 93 | 0.164494 94 | 0.126936 95 | 0.166357 96 | 0.12638 97 | 0.156397 98 | 0.128019 99 | 0.162922 100 | 0.118137 101 | -------------------------------------------------------------------------------- /log/log-c1-load.txt: -------------------------------------------------------------------------------- 1 | 0.106636 2 | 0.295568 3 | 0.296958 4 | 0.292113 5 | 0.289068 6 | 0.302459 7 | 0.291071 8 | 0.292572 9 | 0.292132 10 | 0.291067 11 | 0.293025 12 | 0.290931 13 | 0.302157 14 | 0.292572 15 | 0.292165 16 | 0.292415 17 | 0.289879 18 | 0.291627 19 | 0.305498 20 | 0.292991 21 | 0.289596 22 | 0.297987 23 | 0.29507 24 | 0.293672 25 | 0.292936 26 | 0.291837 27 | 0.291054 28 | 0.293687 29 | 0.293835 30 | 0.299932 31 | 0.287567 32 | 0.290708 33 | 0.29794 34 | 0.297064 35 | 0.292632 36 | 0.292472 37 | 0.290901 38 | 0.29177 39 | 0.290521 40 | 0.288249 41 | 0.2914 42 | 0.291534 43 | 0.287002 44 | 0.291445 45 | 0.291174 46 | 0.288526 47 | 0.299833 48 | 0.290583 49 | 0.295841 50 | 0.299946 51 | 0.287279 52 | 0.293227 53 | 0.299123 54 | 0.292353 55 | 0.296128 56 | 0.298705 57 | 0.301443 58 | 0.296956 59 | 0.29245 60 | 0.28649 61 | 0.29662 62 | 0.291295 63 | 0.301163 64 | 0.28586 65 | 0.299146 66 | 0.299975 67 | 0.295974 68 | 0.301201 69 | 0.290613 70 | 0.295206 71 | 0.289893 72 | 0.298812 73 | 0.297397 74 | 0.296052 75 | 0.288826 76 | 0.291661 77 | 0.287175 78 | 0.285715 79 | 0.289549 80 | 0.292357 81 | 0.293147 82 | 0.289769 83 | 0.296417 84 | 0.292243 85 | 0.300072 86 | 0.283187 87 | 0.303198 88 | 0.291118 89 | 0.289705 90 | 0.298031 91 | 0.294489 92 | 0.281773 93 | 0.291694 94 | 0.29065 95 | 0.296099 96 | 0.299352 97 | 0.298523 98 | 0.298779 99 | 0.285752 100 | 0.293503 101 | -------------------------------------------------------------------------------- /log/log-c2-bal.txt: -------------------------------------------------------------------------------- 1 | 0.118148 2 | 0.17257 3 | 0.11885 4 | 0.156371 5 | 0.114634 6 | 0.167189 7 | 0.114361 8 | 0.172144 9 | 0.118101 10 | 0.173046 11 | 0.11397 12 | 0.168444 13 | 0.113627 14 | 0.170883 15 | 0.112826 16 | 0.166241 17 | 0.124104 18 | 0.167737 19 | 0.114204 20 | 0.149076 21 | 0.112949 22 | 0.169908 23 | 0.110075 24 | 0.17455 25 | 0.111763 26 | 0.169041 27 | 0.115635 28 | 0.165784 29 | 0.114773 30 | 0.16904 31 | 0.111712 32 | 0.173067 33 | 0.116876 34 | 0.163584 35 | 0.119868 36 | 0.165124 37 | 0.11594 38 | 0.166664 39 | 0.129004 40 | 0.145852 41 | 0.126557 42 | 0.157528 43 | 0.11774 44 | 0.159746 45 | 0.128364 46 | 0.165093 47 | 0.115752 48 | 0.16848 49 | 0.115615 50 | 0.16403 51 | 0.112572 52 | 0.146807 53 | 0.124269 54 | 0.161762 55 | 0.124818 56 | 0.161788 57 | 0.11896 58 | 0.158656 59 | 0.126069 60 | 0.153147 61 | 0.11818 62 | 0.163784 63 | 0.116973 64 | 0.161578 65 | 0.117604 66 | 0.159163 67 | 0.1179 68 | 0.165401 69 | 0.115857 70 | 0.16767 71 | 0.11374 72 | 0.16513 73 | 0.12127 74 | 0.164585 75 | 0.121919 76 | 0.159949 77 | 0.120567 78 | 0.161671 79 | 0.119073 80 | 0.162236 81 | 0.114722 82 | 0.168647 83 | 0.115042 84 | 0.168014 85 | 0.114014 86 | 0.16818 87 | 0.114009 88 | 0.162249 89 | 0.115439 90 | 0.166486 91 | 0.114178 92 | 0.163984 93 | 0.124372 94 | 0.166771 95 | 0.118192 96 | 0.160519 97 | 0.130875 98 | 0.160249 99 | 0.121372 100 | 0.162875 101 | -------------------------------------------------------------------------------- /log/log-c2-load.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sarthakpranesh/Mininet-Load-Balancing/a8c716779716bdcde57a1e12c2d995e69dcaaa1e/log/log-c2-load.txt -------------------------------------------------------------------------------- /log/log-c3-bal.txt: -------------------------------------------------------------------------------- 1 | 0.20117 2 | 0.118177 3 | 0.166044 4 | 0.115609 5 | 0.172801 6 | 0.113799 7 | 0.170512 8 | 0.11261 9 | 0.174036 10 | 0.11377 11 | 0.168608 12 | 0.113622 13 | 0.172522 14 | 0.111475 15 | 0.169894 16 | 0.126912 17 | 0.158645 18 | 0.114907 19 | 0.166842 20 | 0.122786 21 | 0.171687 22 | 0.110949 23 | 0.173464 24 | 0.115251 25 | 0.169285 26 | 0.116396 27 | 0.16791 28 | 0.114275 29 | 0.16721 30 | 0.115286 31 | 0.172344 32 | 0.116659 33 | 0.168127 34 | 0.114949 35 | 0.164498 36 | 0.117945 37 | 0.167552 38 | 0.117067 39 | 0.168314 40 | 0.128589 41 | 0.148522 42 | 0.129387 43 | 0.159738 44 | 0.124306 45 | 0.15774 46 | 0.118635 47 | 0.161571 48 | 0.111656 49 | 0.170773 50 | 0.114387 51 | 0.161161 52 | 0.122879 53 | 0.157123 54 | 0.126317 55 | 0.163379 56 | 0.117846 57 | 0.162248 58 | 0.137483 59 | 0.15016 60 | 0.11879 61 | 0.1652 62 | 0.114691 63 | 0.164488 64 | 0.117768 65 | 0.15895 66 | 0.1172 67 | 0.167658 68 | 0.11156 69 | 0.166284 70 | 0.112267 71 | 0.165678 72 | 0.118815 73 | 0.165185 74 | 0.120919 75 | 0.159821 76 | 0.114449 77 | 0.171176 78 | 0.11357 79 | 0.16169 80 | 0.119389 81 | 0.167107 82 | 0.127319 83 | 0.167611 84 | 0.114483 85 | 0.167456 86 | 0.114552 87 | 0.16693 88 | 0.116719 89 | 0.164613 90 | 0.123704 91 | 0.164651 92 | 0.116114 93 | 0.163864 94 | 0.126971 95 | 0.161788 96 | 0.1249 97 | 0.156297 98 | 0.118838 99 | 0.162368 100 | 0.130547 101 | -------------------------------------------------------------------------------- /log/log-c3-load.txt: -------------------------------------------------------------------------------- 1 | 0.205955 2 | 0.299011 3 | 0.293449 4 | 0.292292 5 | 0.300293 6 | 0.292085 7 | 0.28991 8 | 0.290937 9 | 0.291502 10 | 0.292883 11 | 0.292179 12 | 0.289092 13 | 0.301166 14 | 0.296717 15 | 0.291828 16 | 0.292118 17 | 0.291567 18 | 0.290912 19 | 0.306927 20 | 0.288723 21 | 0.289624 22 | 0.295607 23 | 0.293207 24 | 0.287158 25 | 0.293282 26 | 0.29067 27 | 0.290182 28 | 0.294158 29 | 0.278655 30 | 0.302147 31 | 0.303095 32 | 0.284054 33 | 0.304203 34 | 0.292301 35 | 0.287641 36 | 0.292033 37 | 0.290968 38 | 0.29206 39 | 0.292746 40 | 0.292019 41 | 0.290939 42 | 0.293387 43 | 0.292817 44 | 0.290798 45 | 0.289826 46 | 0.298889 47 | 0.28512 48 | 0.28919 49 | 0.295623 50 | 0.299423 51 | 0.297243 52 | 0.29443 53 | 0.29465 54 | 0.298108 55 | 0.298059 56 | 0.30296 57 | 0.288015 58 | 0.291952 59 | 0.287998 60 | 0.306878 61 | 0.291744 62 | 0.285399 63 | 0.294997 64 | 0.284164 65 | 0.299269 66 | 0.303603 67 | 0.291564 68 | 0.29759 69 | 0.288139 70 | 0.28861 71 | 0.299771 72 | 0.280504 73 | 0.297035 74 | 0.288931 75 | 0.303129 76 | 0.285585 77 | 0.289706 78 | 0.289059 79 | 0.300104 80 | 0.289126 81 | 0.29252 82 | 0.290064 83 | 0.293066 84 | 0.295239 85 | 0.288654 86 | 0.301734 87 | 0.292648 88 | 0.284813 89 | 0.297303 90 | 0.292401 91 | 0.292075 92 | 0.293829 93 | 0.297725 94 | 0.277569 95 | 0.298471 96 | 0.297984 97 | 0.298047 98 | 0.302434 99 | 0.292341 100 | 0.297188 101 | -------------------------------------------------------------------------------- /log/log-local.txt: -------------------------------------------------------------------------------- 1 | 0.109054 2 | 0.112618 3 | 0.113942 4 | 0.10606 5 | 0.109614 6 | 0.105562 7 | 0.107703 8 | 0.108944 9 | 0.113552 10 | 0.113387 11 | 0.112753 12 | 0.108038 13 | 0.109302 14 | 0.108304 15 | 0.10725 16 | 0.107063 17 | 0.111534 18 | 0.109248 19 | 0.107244 20 | 0.11034 21 | 0.108247 22 | 0.106726 23 | 0.107253 24 | 0.108033 25 | 0.110175 26 | 0.112973 27 | 0.108697 28 | 0.107778 29 | 0.109968 30 | 0.107767 31 | 0.113501 32 | 0.113257 33 | 0.114179 34 | 0.113417 35 | 0.112954 36 | 0.114958 37 | 0.111453 38 | 0.104701 39 | 0.10494 40 | 0.105789 41 | 0.112037 42 | 0.11388 43 | 0.112949 44 | 0.118037 45 | 0.112594 46 | 0.120413 47 | 0.109123 48 | 0.113335 49 | 0.11342 50 | 0.113966 51 | 0.11387 52 | 0.110512 53 | 0.113638 54 | 0.112935 55 | 0.114555 56 | 0.119075 57 | 0.107495 58 | 0.110029 59 | 0.112708 60 | 0.114976 61 | 0.113232 62 | 0.113221 63 | 0.113558 64 | 0.113252 65 | 0.114252 66 | 0.11336 67 | 0.113515 68 | 0.113012 69 | 0.112689 70 | 0.114223 71 | 0.113533 72 | 0.104298 73 | 0.105003 74 | 0.108371 75 | 0.113594 76 | 0.113968 77 | 0.112628 78 | 0.106914 79 | 0.114012 80 | 0.112543 81 | 0.112794 82 | 0.11539 83 | 0.11057 84 | 0.106956 85 | 0.113739 86 | 0.114598 87 | 0.113717 88 | 0.115311 89 | 0.113775 90 | 0.11297 91 | 0.112847 92 | 0.113754 93 | 0.113505 94 | 0.113908 95 | 0.1142 96 | 0.114343 97 | 0.113377 98 | 0.113379 99 | 0.113219 100 | 0.11415 101 | -------------------------------------------------------------------------------- /miniedit.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | """ 4 | MiniEdit: a simple network editor for Mininet 5 | 6 | This is a simple demonstration of how one might build a 7 | GUI application using Mininet as the network model. 8 | 9 | Bob Lantz, April 2010 10 | Gregory Gee, July 2013 11 | 12 | Controller icon from http://semlabs.co.uk/ 13 | OpenFlow icon from https://www.opennetworking.org/ 14 | """ 15 | 16 | # Miniedit needs some work in order to pass pylint... 17 | # pylint: disable=line-too-long,too-many-branches 18 | # pylint: disable=too-many-statements,attribute-defined-outside-init 19 | # pylint: disable=missing-docstring 20 | 21 | MINIEDIT_VERSION = '2.2.0.1' 22 | 23 | from optparse import OptionParser 24 | # from Tkinter import * 25 | from Tkinter import ( Frame, Label, LabelFrame, Entry, OptionMenu, Checkbutton, 26 | Menu, Toplevel, Button, BitmapImage, PhotoImage, Canvas, 27 | Scrollbar, Wm, TclError, StringVar, IntVar, 28 | E, W, EW, NW, Y, VERTICAL, SOLID, CENTER, 29 | RIGHT, LEFT, BOTH, TRUE, FALSE ) 30 | from ttk import Notebook 31 | from tkMessageBox import showerror 32 | from subprocess import call 33 | import tkFont 34 | import tkFileDialog 35 | import tkSimpleDialog 36 | import re 37 | import json 38 | from distutils.version import StrictVersion 39 | import os 40 | import sys 41 | from functools import partial 42 | 43 | if 'PYTHONPATH' in os.environ: 44 | sys.path = os.environ[ 'PYTHONPATH' ].split( ':' ) + sys.path 45 | 46 | # someday: from ttk import * 47 | 48 | from mininet.log import info, setLogLevel 49 | from mininet.net import Mininet, VERSION 50 | from mininet.util import netParse, ipAdd, quietRun 51 | from mininet.util import buildTopo 52 | from mininet.util import custom, customClass 53 | from mininet.term import makeTerm, cleanUpScreens 54 | from mininet.node import Controller, RemoteController, NOX, OVSController 55 | from mininet.node import CPULimitedHost, Host, Node 56 | from mininet.node import OVSSwitch, UserSwitch 57 | from mininet.link import TCLink, Intf, Link 58 | from mininet.cli import CLI 59 | from mininet.moduledeps import moduleDeps 60 | from mininet.topo import SingleSwitchTopo, LinearTopo, SingleSwitchReversedTopo 61 | from mininet.topolib import TreeTopo 62 | 63 | print 'MiniEdit running against Mininet '+VERSION 64 | MININET_VERSION = re.sub(r'[^\d\.]', '', VERSION) 65 | if StrictVersion(MININET_VERSION) > StrictVersion('2.0'): 66 | from mininet.node import IVSSwitch 67 | 68 | TOPODEF = 'none' 69 | TOPOS = { 'minimal': lambda: SingleSwitchTopo( k=2 ), 70 | 'linear': LinearTopo, 71 | 'reversed': SingleSwitchReversedTopo, 72 | 'single': SingleSwitchTopo, 73 | 'none': None, 74 | 'tree': TreeTopo } 75 | CONTROLLERDEF = 'ref' 76 | CONTROLLERS = { 'ref': Controller, 77 | 'ovsc': OVSController, 78 | 'nox': NOX, 79 | 'remote': RemoteController, 80 | 'none': lambda name: None } 81 | LINKDEF = 'default' 82 | LINKS = { 'default': Link, 83 | 'tc': TCLink } 84 | HOSTDEF = 'proc' 85 | HOSTS = { 'proc': Host, 86 | 'rt': custom( CPULimitedHost, sched='rt' ), 87 | 'cfs': custom( CPULimitedHost, sched='cfs' ) } 88 | 89 | 90 | class InbandController( RemoteController ): 91 | "RemoteController that ignores checkListening" 92 | def checkListening( self ): 93 | "Overridden to do nothing." 94 | return 95 | 96 | class CustomUserSwitch(UserSwitch): 97 | "Customized UserSwitch" 98 | def __init__( self, name, dpopts='--no-slicing', **kwargs ): 99 | UserSwitch.__init__( self, name, **kwargs ) 100 | self.switchIP = None 101 | 102 | def getSwitchIP(self): 103 | "Return management IP address" 104 | return self.switchIP 105 | 106 | def setSwitchIP(self, ip): 107 | "Set management IP address" 108 | self.switchIP = ip 109 | 110 | def start( self, controllers ): 111 | "Start and set management IP address" 112 | # Call superclass constructor 113 | UserSwitch.start( self, controllers ) 114 | # Set Switch IP address 115 | if self.switchIP is not None: 116 | if not self.inNamespace: 117 | self.cmd( 'ifconfig', self, self.switchIP ) 118 | else: 119 | self.cmd( 'ifconfig lo', self.switchIP ) 120 | 121 | class LegacyRouter( Node ): 122 | "Simple IP router" 123 | def __init__( self, name, inNamespace=True, **params ): 124 | Node.__init__( self, name, inNamespace, **params ) 125 | 126 | def config( self, **_params ): 127 | if self.intfs: 128 | self.setParam( _params, 'setIP', ip='0.0.0.0' ) 129 | r = Node.config( self, **_params ) 130 | self.cmd('sysctl -w net.ipv4.ip_forward=1') 131 | return r 132 | 133 | class LegacySwitch(OVSSwitch): 134 | "OVS switch in standalone/bridge mode" 135 | def __init__( self, name, **params ): 136 | OVSSwitch.__init__( self, name, failMode='standalone', **params ) 137 | self.switchIP = None 138 | 139 | class customOvs(OVSSwitch): 140 | "Customized OVS switch" 141 | 142 | def __init__( self, name, failMode='secure', datapath='kernel', **params ): 143 | OVSSwitch.__init__( self, name, failMode=failMode, datapath=datapath,**params ) 144 | self.switchIP = None 145 | 146 | def getSwitchIP(self): 147 | "Return management IP address" 148 | return self.switchIP 149 | 150 | def setSwitchIP(self, ip): 151 | "Set management IP address" 152 | self.switchIP = ip 153 | 154 | def start( self, controllers ): 155 | "Start and set management IP address" 156 | # Call superclass constructor 157 | OVSSwitch.start( self, controllers ) 158 | # Set Switch IP address 159 | if self.switchIP is not None: 160 | self.cmd( 'ifconfig', self, self.switchIP ) 161 | 162 | class PrefsDialog(tkSimpleDialog.Dialog): 163 | "Preferences dialog" 164 | 165 | def __init__(self, parent, title, prefDefaults): 166 | 167 | self.prefValues = prefDefaults 168 | 169 | tkSimpleDialog.Dialog.__init__(self, parent, title) 170 | 171 | def body(self, master): 172 | "Create dialog body" 173 | self.rootFrame = master 174 | self.leftfieldFrame = Frame(self.rootFrame, padx=5, pady=5) 175 | self.leftfieldFrame.grid(row=0, column=0, sticky='nswe', columnspan=2) 176 | self.rightfieldFrame = Frame(self.rootFrame, padx=5, pady=5) 177 | self.rightfieldFrame.grid(row=0, column=2, sticky='nswe', columnspan=2) 178 | 179 | # Field for Base IP 180 | Label(self.leftfieldFrame, text="IP Base:").grid(row=0, sticky=E) 181 | self.ipEntry = Entry(self.leftfieldFrame) 182 | self.ipEntry.grid(row=0, column=1) 183 | ipBase = self.prefValues['ipBase'] 184 | self.ipEntry.insert(0, ipBase) 185 | 186 | # Selection of terminal type 187 | Label(self.leftfieldFrame, text="Default Terminal:").grid(row=1, sticky=E) 188 | self.terminalVar = StringVar(self.leftfieldFrame) 189 | self.terminalOption = OptionMenu(self.leftfieldFrame, self.terminalVar, "xterm", "gterm") 190 | self.terminalOption.grid(row=1, column=1, sticky=W) 191 | terminalType = self.prefValues['terminalType'] 192 | self.terminalVar.set(terminalType) 193 | 194 | # Field for CLI 195 | Label(self.leftfieldFrame, text="Start CLI:").grid(row=2, sticky=E) 196 | self.cliStart = IntVar() 197 | self.cliButton = Checkbutton(self.leftfieldFrame, variable=self.cliStart) 198 | self.cliButton.grid(row=2, column=1, sticky=W) 199 | if self.prefValues['startCLI'] == '0': 200 | self.cliButton.deselect() 201 | else: 202 | self.cliButton.select() 203 | 204 | # Selection of switch type 205 | Label(self.leftfieldFrame, text="Default Switch:").grid(row=3, sticky=E) 206 | self.switchType = StringVar(self.leftfieldFrame) 207 | self.switchTypeMenu = OptionMenu(self.leftfieldFrame, self.switchType, "Open vSwitch Kernel Mode", "Indigo Virtual Switch", "Userspace Switch", "Userspace Switch inNamespace") 208 | self.switchTypeMenu.grid(row=3, column=1, sticky=W) 209 | switchTypePref = self.prefValues['switchType'] 210 | if switchTypePref == 'ivs': 211 | self.switchType.set("Indigo Virtual Switch") 212 | elif switchTypePref == 'userns': 213 | self.switchType.set("Userspace Switch inNamespace") 214 | elif switchTypePref == 'user': 215 | self.switchType.set("Userspace Switch") 216 | else: 217 | self.switchType.set("Open vSwitch Kernel Mode") 218 | 219 | 220 | # Fields for OVS OpenFlow version 221 | ovsFrame= LabelFrame(self.leftfieldFrame, text='Open vSwitch', padx=5, pady=5) 222 | ovsFrame.grid(row=4, column=0, columnspan=2, sticky=EW) 223 | Label(ovsFrame, text="OpenFlow 1.0:").grid(row=0, sticky=E) 224 | Label(ovsFrame, text="OpenFlow 1.1:").grid(row=1, sticky=E) 225 | Label(ovsFrame, text="OpenFlow 1.2:").grid(row=2, sticky=E) 226 | Label(ovsFrame, text="OpenFlow 1.3:").grid(row=3, sticky=E) 227 | 228 | self.ovsOf10 = IntVar() 229 | self.covsOf10 = Checkbutton(ovsFrame, variable=self.ovsOf10) 230 | self.covsOf10.grid(row=0, column=1, sticky=W) 231 | if self.prefValues['openFlowVersions']['ovsOf10'] == '0': 232 | self.covsOf10.deselect() 233 | else: 234 | self.covsOf10.select() 235 | 236 | self.ovsOf11 = IntVar() 237 | self.covsOf11 = Checkbutton(ovsFrame, variable=self.ovsOf11) 238 | self.covsOf11.grid(row=1, column=1, sticky=W) 239 | if self.prefValues['openFlowVersions']['ovsOf11'] == '0': 240 | self.covsOf11.deselect() 241 | else: 242 | self.covsOf11.select() 243 | 244 | self.ovsOf12 = IntVar() 245 | self.covsOf12 = Checkbutton(ovsFrame, variable=self.ovsOf12) 246 | self.covsOf12.grid(row=2, column=1, sticky=W) 247 | if self.prefValues['openFlowVersions']['ovsOf12'] == '0': 248 | self.covsOf12.deselect() 249 | else: 250 | self.covsOf12.select() 251 | 252 | self.ovsOf13 = IntVar() 253 | self.covsOf13 = Checkbutton(ovsFrame, variable=self.ovsOf13) 254 | self.covsOf13.grid(row=3, column=1, sticky=W) 255 | if self.prefValues['openFlowVersions']['ovsOf13'] == '0': 256 | self.covsOf13.deselect() 257 | else: 258 | self.covsOf13.select() 259 | 260 | # Field for DPCTL listen port 261 | Label(self.leftfieldFrame, text="dpctl port:").grid(row=5, sticky=E) 262 | self.dpctlEntry = Entry(self.leftfieldFrame) 263 | self.dpctlEntry.grid(row=5, column=1) 264 | if 'dpctl' in self.prefValues: 265 | self.dpctlEntry.insert(0, self.prefValues['dpctl']) 266 | 267 | # sFlow 268 | sflowValues = self.prefValues['sflow'] 269 | self.sflowFrame= LabelFrame(self.rightfieldFrame, text='sFlow Profile for Open vSwitch', padx=5, pady=5) 270 | self.sflowFrame.grid(row=0, column=0, columnspan=2, sticky=EW) 271 | 272 | Label(self.sflowFrame, text="Target:").grid(row=0, sticky=E) 273 | self.sflowTarget = Entry(self.sflowFrame) 274 | self.sflowTarget.grid(row=0, column=1) 275 | self.sflowTarget.insert(0, sflowValues['sflowTarget']) 276 | 277 | Label(self.sflowFrame, text="Sampling:").grid(row=1, sticky=E) 278 | self.sflowSampling = Entry(self.sflowFrame) 279 | self.sflowSampling.grid(row=1, column=1) 280 | self.sflowSampling.insert(0, sflowValues['sflowSampling']) 281 | 282 | Label(self.sflowFrame, text="Header:").grid(row=2, sticky=E) 283 | self.sflowHeader = Entry(self.sflowFrame) 284 | self.sflowHeader.grid(row=2, column=1) 285 | self.sflowHeader.insert(0, sflowValues['sflowHeader']) 286 | 287 | Label(self.sflowFrame, text="Polling:").grid(row=3, sticky=E) 288 | self.sflowPolling = Entry(self.sflowFrame) 289 | self.sflowPolling.grid(row=3, column=1) 290 | self.sflowPolling.insert(0, sflowValues['sflowPolling']) 291 | 292 | # NetFlow 293 | nflowValues = self.prefValues['netflow'] 294 | self.nFrame= LabelFrame(self.rightfieldFrame, text='NetFlow Profile for Open vSwitch', padx=5, pady=5) 295 | self.nFrame.grid(row=1, column=0, columnspan=2, sticky=EW) 296 | 297 | Label(self.nFrame, text="Target:").grid(row=0, sticky=E) 298 | self.nflowTarget = Entry(self.nFrame) 299 | self.nflowTarget.grid(row=0, column=1) 300 | self.nflowTarget.insert(0, nflowValues['nflowTarget']) 301 | 302 | Label(self.nFrame, text="Active Timeout:").grid(row=1, sticky=E) 303 | self.nflowTimeout = Entry(self.nFrame) 304 | self.nflowTimeout.grid(row=1, column=1) 305 | self.nflowTimeout.insert(0, nflowValues['nflowTimeout']) 306 | 307 | Label(self.nFrame, text="Add ID to Interface:").grid(row=2, sticky=E) 308 | self.nflowAddId = IntVar() 309 | self.nflowAddIdButton = Checkbutton(self.nFrame, variable=self.nflowAddId) 310 | self.nflowAddIdButton.grid(row=2, column=1, sticky=W) 311 | if nflowValues['nflowAddId'] == '0': 312 | self.nflowAddIdButton.deselect() 313 | else: 314 | self.nflowAddIdButton.select() 315 | 316 | # initial focus 317 | return self.ipEntry 318 | 319 | def apply(self): 320 | ipBase = self.ipEntry.get() 321 | terminalType = self.terminalVar.get() 322 | startCLI = str(self.cliStart.get()) 323 | sw = self.switchType.get() 324 | dpctl = self.dpctlEntry.get() 325 | 326 | ovsOf10 = str(self.ovsOf10.get()) 327 | ovsOf11 = str(self.ovsOf11.get()) 328 | ovsOf12 = str(self.ovsOf12.get()) 329 | ovsOf13 = str(self.ovsOf13.get()) 330 | 331 | sflowValues = {'sflowTarget':self.sflowTarget.get(), 332 | 'sflowSampling':self.sflowSampling.get(), 333 | 'sflowHeader':self.sflowHeader.get(), 334 | 'sflowPolling':self.sflowPolling.get()} 335 | nflowvalues = {'nflowTarget':self.nflowTarget.get(), 336 | 'nflowTimeout':self.nflowTimeout.get(), 337 | 'nflowAddId':str(self.nflowAddId.get())} 338 | self.result = {'ipBase':ipBase, 339 | 'terminalType':terminalType, 340 | 'dpctl':dpctl, 341 | 'sflow':sflowValues, 342 | 'netflow':nflowvalues, 343 | 'startCLI':startCLI} 344 | if sw == 'Indigo Virtual Switch': 345 | self.result['switchType'] = 'ivs' 346 | if StrictVersion(MININET_VERSION) < StrictVersion('2.1'): 347 | self.ovsOk = False 348 | showerror(title="Error", 349 | message='MiniNet version 2.1+ required. You have '+VERSION+'.') 350 | elif sw == 'Userspace Switch': 351 | self.result['switchType'] = 'user' 352 | elif sw == 'Userspace Switch inNamespace': 353 | self.result['switchType'] = 'userns' 354 | else: 355 | self.result['switchType'] = 'ovs' 356 | 357 | self.ovsOk = True 358 | if ovsOf11 == "1": 359 | ovsVer = self.getOvsVersion() 360 | if StrictVersion(ovsVer) < StrictVersion('2.0'): 361 | self.ovsOk = False 362 | showerror(title="Error", 363 | message='Open vSwitch version 2.0+ required. You have '+ovsVer+'.') 364 | if ovsOf12 == "1" or ovsOf13 == "1": 365 | ovsVer = self.getOvsVersion() 366 | if StrictVersion(ovsVer) < StrictVersion('1.10'): 367 | self.ovsOk = False 368 | showerror(title="Error", 369 | message='Open vSwitch version 1.10+ required. You have '+ovsVer+'.') 370 | 371 | if self.ovsOk: 372 | self.result['openFlowVersions']={'ovsOf10':ovsOf10, 373 | 'ovsOf11':ovsOf11, 374 | 'ovsOf12':ovsOf12, 375 | 'ovsOf13':ovsOf13} 376 | else: 377 | self.result = None 378 | 379 | @staticmethod 380 | def getOvsVersion(): 381 | "Return OVS version" 382 | outp = quietRun("ovs-vsctl show") 383 | r = r'ovs_version: "(.*)"' 384 | m = re.search(r, outp) 385 | if m is None: 386 | print 'Version check failed' 387 | return None 388 | else: 389 | print 'Open vSwitch version is '+m.group(1) 390 | return m.group(1) 391 | 392 | 393 | class CustomDialog(object): 394 | 395 | # TODO: Fix button placement and Title and window focus lock 396 | def __init__(self, master, _title): 397 | self.top=Toplevel(master) 398 | 399 | self.bodyFrame = Frame(self.top) 400 | self.bodyFrame.grid(row=0, column=0, sticky='nswe') 401 | self.body(self.bodyFrame) 402 | 403 | #return self.b # initial focus 404 | buttonFrame = Frame(self.top, relief='ridge', bd=3, bg='lightgrey') 405 | buttonFrame.grid(row=1 , column=0, sticky='nswe') 406 | 407 | okButton = Button(buttonFrame, width=8, text='OK', relief='groove', 408 | bd=4, command=self.okAction) 409 | okButton.grid(row=0, column=0, sticky=E) 410 | 411 | canlceButton = Button(buttonFrame, width=8, text='Cancel', relief='groove', 412 | bd=4, command=self.cancelAction) 413 | canlceButton.grid(row=0, column=1, sticky=W) 414 | 415 | def body(self, master): 416 | self.rootFrame = master 417 | 418 | def apply(self): 419 | self.top.destroy() 420 | 421 | def cancelAction(self): 422 | self.top.destroy() 423 | 424 | def okAction(self): 425 | self.apply() 426 | self.top.destroy() 427 | 428 | class HostDialog(CustomDialog): 429 | 430 | def __init__(self, master, title, prefDefaults): 431 | 432 | self.prefValues = prefDefaults 433 | self.result = None 434 | 435 | CustomDialog.__init__(self, master, title) 436 | 437 | def body(self, master): 438 | self.rootFrame = master 439 | n = Notebook(self.rootFrame) 440 | self.propFrame = Frame(n) 441 | self.vlanFrame = Frame(n) 442 | self.interfaceFrame = Frame(n) 443 | self.mountFrame = Frame(n) 444 | n.add(self.propFrame, text='Properties') 445 | n.add(self.vlanFrame, text='VLAN Interfaces') 446 | n.add(self.interfaceFrame, text='External Interfaces') 447 | n.add(self.mountFrame, text='Private Directories') 448 | n.pack() 449 | 450 | ### TAB 1 451 | # Field for Hostname 452 | Label(self.propFrame, text="Hostname:").grid(row=0, sticky=E) 453 | self.hostnameEntry = Entry(self.propFrame) 454 | self.hostnameEntry.grid(row=0, column=1) 455 | if 'hostname' in self.prefValues: 456 | self.hostnameEntry.insert(0, self.prefValues['hostname']) 457 | 458 | # Field for Switch IP 459 | Label(self.propFrame, text="IP Address:").grid(row=1, sticky=E) 460 | self.ipEntry = Entry(self.propFrame) 461 | self.ipEntry.grid(row=1, column=1) 462 | if 'ip' in self.prefValues: 463 | self.ipEntry.insert(0, self.prefValues['ip']) 464 | 465 | # Field for default route 466 | Label(self.propFrame, text="Default Route:").grid(row=2, sticky=E) 467 | self.routeEntry = Entry(self.propFrame) 468 | self.routeEntry.grid(row=2, column=1) 469 | if 'defaultRoute' in self.prefValues: 470 | self.routeEntry.insert(0, self.prefValues['defaultRoute']) 471 | 472 | # Field for CPU 473 | Label(self.propFrame, text="Amount CPU:").grid(row=3, sticky=E) 474 | self.cpuEntry = Entry(self.propFrame) 475 | self.cpuEntry.grid(row=3, column=1) 476 | if 'cpu' in self.prefValues: 477 | self.cpuEntry.insert(0, str(self.prefValues['cpu'])) 478 | # Selection of Scheduler 479 | if 'sched' in self.prefValues: 480 | sched = self.prefValues['sched'] 481 | else: 482 | sched = 'host' 483 | self.schedVar = StringVar(self.propFrame) 484 | self.schedOption = OptionMenu(self.propFrame, self.schedVar, "host", "cfs", "rt") 485 | self.schedOption.grid(row=3, column=2, sticky=W) 486 | self.schedVar.set(sched) 487 | 488 | # Selection of Cores 489 | Label(self.propFrame, text="Cores:").grid(row=4, sticky=E) 490 | self.coreEntry = Entry(self.propFrame) 491 | self.coreEntry.grid(row=4, column=1) 492 | if 'cores' in self.prefValues: 493 | self.coreEntry.insert(1, self.prefValues['cores']) 494 | 495 | # Start command 496 | Label(self.propFrame, text="Start Command:").grid(row=5, sticky=E) 497 | self.startEntry = Entry(self.propFrame) 498 | self.startEntry.grid(row=5, column=1, sticky='nswe', columnspan=3) 499 | if 'startCommand' in self.prefValues: 500 | self.startEntry.insert(0, str(self.prefValues['startCommand'])) 501 | # Stop command 502 | Label(self.propFrame, text="Stop Command:").grid(row=6, sticky=E) 503 | self.stopEntry = Entry(self.propFrame) 504 | self.stopEntry.grid(row=6, column=1, sticky='nswe', columnspan=3) 505 | if 'stopCommand' in self.prefValues: 506 | self.stopEntry.insert(0, str(self.prefValues['stopCommand'])) 507 | 508 | ### TAB 2 509 | # External Interfaces 510 | self.externalInterfaces = 0 511 | Label(self.interfaceFrame, text="External Interface:").grid(row=0, column=0, sticky=E) 512 | self.b = Button( self.interfaceFrame, text='Add', command=self.addInterface) 513 | self.b.grid(row=0, column=1) 514 | 515 | self.interfaceFrame = VerticalScrolledTable(self.interfaceFrame, rows=0, columns=1, title='External Interfaces') 516 | self.interfaceFrame.grid(row=1, column=0, sticky='nswe', columnspan=2) 517 | self.tableFrame = self.interfaceFrame.interior 518 | self.tableFrame.addRow(value=['Interface Name'], readonly=True) 519 | 520 | # Add defined interfaces 521 | externalInterfaces = [] 522 | if 'externalInterfaces' in self.prefValues: 523 | externalInterfaces = self.prefValues['externalInterfaces'] 524 | 525 | for externalInterface in externalInterfaces: 526 | self.tableFrame.addRow(value=[externalInterface]) 527 | 528 | ### TAB 3 529 | # VLAN Interfaces 530 | self.vlanInterfaces = 0 531 | Label(self.vlanFrame, text="VLAN Interface:").grid(row=0, column=0, sticky=E) 532 | self.vlanButton = Button( self.vlanFrame, text='Add', command=self.addVlanInterface) 533 | self.vlanButton.grid(row=0, column=1) 534 | 535 | self.vlanFrame = VerticalScrolledTable(self.vlanFrame, rows=0, columns=2, title='VLAN Interfaces') 536 | self.vlanFrame.grid(row=1, column=0, sticky='nswe', columnspan=2) 537 | self.vlanTableFrame = self.vlanFrame.interior 538 | self.vlanTableFrame.addRow(value=['IP Address','VLAN ID'], readonly=True) 539 | 540 | vlanInterfaces = [] 541 | if 'vlanInterfaces' in self.prefValues: 542 | vlanInterfaces = self.prefValues['vlanInterfaces'] 543 | for vlanInterface in vlanInterfaces: 544 | self.vlanTableFrame.addRow(value=vlanInterface) 545 | 546 | ### TAB 4 547 | # Private Directories 548 | self.privateDirectories = 0 549 | Label(self.mountFrame, text="Private Directory:").grid(row=0, column=0, sticky=E) 550 | self.mountButton = Button( self.mountFrame, text='Add', command=self.addDirectory) 551 | self.mountButton.grid(row=0, column=1) 552 | 553 | self.mountFrame = VerticalScrolledTable(self.mountFrame, rows=0, columns=2, title='Directories') 554 | self.mountFrame.grid(row=1, column=0, sticky='nswe', columnspan=2) 555 | self.mountTableFrame = self.mountFrame.interior 556 | self.mountTableFrame.addRow(value=['Mount','Persistent Directory'], readonly=True) 557 | 558 | directoryList = [] 559 | if 'privateDirectory' in self.prefValues: 560 | directoryList = self.prefValues['privateDirectory'] 561 | for privateDir in directoryList: 562 | if isinstance( privateDir, tuple ): 563 | self.mountTableFrame.addRow(value=privateDir) 564 | else: 565 | self.mountTableFrame.addRow(value=[privateDir,'']) 566 | 567 | 568 | def addDirectory( self ): 569 | self.mountTableFrame.addRow() 570 | 571 | def addVlanInterface( self ): 572 | self.vlanTableFrame.addRow() 573 | 574 | def addInterface( self ): 575 | self.tableFrame.addRow() 576 | 577 | def apply(self): 578 | externalInterfaces = [] 579 | for row in range(self.tableFrame.rows): 580 | if (len(self.tableFrame.get(row, 0)) > 0 and 581 | row > 0): 582 | externalInterfaces.append(self.tableFrame.get(row, 0)) 583 | vlanInterfaces = [] 584 | for row in range(self.vlanTableFrame.rows): 585 | if (len(self.vlanTableFrame.get(row, 0)) > 0 and 586 | len(self.vlanTableFrame.get(row, 1)) > 0 and 587 | row > 0): 588 | vlanInterfaces.append([self.vlanTableFrame.get(row, 0), self.vlanTableFrame.get(row, 1)]) 589 | privateDirectories = [] 590 | for row in range(self.mountTableFrame.rows): 591 | if len(self.mountTableFrame.get(row, 0)) > 0 and row > 0: 592 | if len(self.mountTableFrame.get(row, 1)) > 0: 593 | privateDirectories.append((self.mountTableFrame.get(row, 0), self.mountTableFrame.get(row, 1))) 594 | else: 595 | privateDirectories.append(self.mountTableFrame.get(row, 0)) 596 | 597 | results = {'cpu': self.cpuEntry.get(), 598 | 'cores':self.coreEntry.get(), 599 | 'sched':self.schedVar.get(), 600 | 'hostname':self.hostnameEntry.get(), 601 | 'ip':self.ipEntry.get(), 602 | 'defaultRoute':self.routeEntry.get(), 603 | 'startCommand':self.startEntry.get(), 604 | 'stopCommand':self.stopEntry.get(), 605 | 'privateDirectory':privateDirectories, 606 | 'externalInterfaces':externalInterfaces, 607 | 'vlanInterfaces':vlanInterfaces} 608 | self.result = results 609 | 610 | class SwitchDialog(CustomDialog): 611 | 612 | def __init__(self, master, title, prefDefaults): 613 | 614 | self.prefValues = prefDefaults 615 | self.result = None 616 | CustomDialog.__init__(self, master, title) 617 | 618 | def body(self, master): 619 | self.rootFrame = master 620 | self.leftfieldFrame = Frame(self.rootFrame) 621 | self.rightfieldFrame = Frame(self.rootFrame) 622 | self.leftfieldFrame.grid(row=0, column=0, sticky='nswe') 623 | self.rightfieldFrame.grid(row=0, column=1, sticky='nswe') 624 | 625 | rowCount = 0 626 | externalInterfaces = [] 627 | if 'externalInterfaces' in self.prefValues: 628 | externalInterfaces = self.prefValues['externalInterfaces'] 629 | 630 | # Field for Hostname 631 | Label(self.leftfieldFrame, text="Hostname:").grid(row=rowCount, sticky=E) 632 | self.hostnameEntry = Entry(self.leftfieldFrame) 633 | self.hostnameEntry.grid(row=rowCount, column=1) 634 | self.hostnameEntry.insert(0, self.prefValues['hostname']) 635 | rowCount+=1 636 | 637 | # Field for DPID 638 | Label(self.leftfieldFrame, text="DPID:").grid(row=rowCount, sticky=E) 639 | self.dpidEntry = Entry(self.leftfieldFrame) 640 | self.dpidEntry.grid(row=rowCount, column=1) 641 | if 'dpid' in self.prefValues: 642 | self.dpidEntry.insert(0, self.prefValues['dpid']) 643 | rowCount+=1 644 | 645 | # Field for Netflow 646 | Label(self.leftfieldFrame, text="Enable NetFlow:").grid(row=rowCount, sticky=E) 647 | self.nflow = IntVar() 648 | self.nflowButton = Checkbutton(self.leftfieldFrame, variable=self.nflow) 649 | self.nflowButton.grid(row=rowCount, column=1, sticky=W) 650 | if 'netflow' in self.prefValues: 651 | if self.prefValues['netflow'] == '0': 652 | self.nflowButton.deselect() 653 | else: 654 | self.nflowButton.select() 655 | else: 656 | self.nflowButton.deselect() 657 | rowCount+=1 658 | 659 | # Field for sflow 660 | Label(self.leftfieldFrame, text="Enable sFlow:").grid(row=rowCount, sticky=E) 661 | self.sflow = IntVar() 662 | self.sflowButton = Checkbutton(self.leftfieldFrame, variable=self.sflow) 663 | self.sflowButton.grid(row=rowCount, column=1, sticky=W) 664 | if 'sflow' in self.prefValues: 665 | if self.prefValues['sflow'] == '0': 666 | self.sflowButton.deselect() 667 | else: 668 | self.sflowButton.select() 669 | else: 670 | self.sflowButton.deselect() 671 | rowCount+=1 672 | 673 | # Selection of switch type 674 | Label(self.leftfieldFrame, text="Switch Type:").grid(row=rowCount, sticky=E) 675 | self.switchType = StringVar(self.leftfieldFrame) 676 | self.switchTypeMenu = OptionMenu(self.leftfieldFrame, self.switchType, "Default", "Open vSwitch Kernel Mode", "Indigo Virtual Switch", "Userspace Switch", "Userspace Switch inNamespace") 677 | self.switchTypeMenu.grid(row=rowCount, column=1, sticky=W) 678 | if 'switchType' in self.prefValues: 679 | switchTypePref = self.prefValues['switchType'] 680 | if switchTypePref == 'ivs': 681 | self.switchType.set("Indigo Virtual Switch") 682 | elif switchTypePref == 'userns': 683 | self.switchType.set("Userspace Switch inNamespace") 684 | elif switchTypePref == 'user': 685 | self.switchType.set("Userspace Switch") 686 | elif switchTypePref == 'ovs': 687 | self.switchType.set("Open vSwitch Kernel Mode") 688 | else: 689 | self.switchType.set("Default") 690 | else: 691 | self.switchType.set("Default") 692 | rowCount+=1 693 | 694 | # Field for Switch IP 695 | Label(self.leftfieldFrame, text="IP Address:").grid(row=rowCount, sticky=E) 696 | self.ipEntry = Entry(self.leftfieldFrame) 697 | self.ipEntry.grid(row=rowCount, column=1) 698 | if 'switchIP' in self.prefValues: 699 | self.ipEntry.insert(0, self.prefValues['switchIP']) 700 | rowCount+=1 701 | 702 | # Field for DPCTL port 703 | Label(self.leftfieldFrame, text="DPCTL port:").grid(row=rowCount, sticky=E) 704 | self.dpctlEntry = Entry(self.leftfieldFrame) 705 | self.dpctlEntry.grid(row=rowCount, column=1) 706 | if 'dpctl' in self.prefValues: 707 | self.dpctlEntry.insert(0, self.prefValues['dpctl']) 708 | rowCount+=1 709 | 710 | # External Interfaces 711 | Label(self.rightfieldFrame, text="External Interface:").grid(row=0, sticky=E) 712 | self.b = Button( self.rightfieldFrame, text='Add', command=self.addInterface) 713 | self.b.grid(row=0, column=1) 714 | 715 | self.interfaceFrame = VerticalScrolledTable(self.rightfieldFrame, rows=0, columns=1, title='External Interfaces') 716 | self.interfaceFrame.grid(row=1, column=0, sticky='nswe', columnspan=2) 717 | self.tableFrame = self.interfaceFrame.interior 718 | 719 | # Add defined interfaces 720 | for externalInterface in externalInterfaces: 721 | self.tableFrame.addRow(value=[externalInterface]) 722 | 723 | self.commandFrame = Frame(self.rootFrame) 724 | self.commandFrame.grid(row=1, column=0, sticky='nswe', columnspan=2) 725 | self.commandFrame.columnconfigure(1, weight=1) 726 | # Start command 727 | Label(self.commandFrame, text="Start Command:").grid(row=0, column=0, sticky=W) 728 | self.startEntry = Entry(self.commandFrame) 729 | self.startEntry.grid(row=0, column=1, sticky='nsew') 730 | if 'startCommand' in self.prefValues: 731 | self.startEntry.insert(0, str(self.prefValues['startCommand'])) 732 | # Stop command 733 | Label(self.commandFrame, text="Stop Command:").grid(row=1, column=0, sticky=W) 734 | self.stopEntry = Entry(self.commandFrame) 735 | self.stopEntry.grid(row=1, column=1, sticky='nsew') 736 | if 'stopCommand' in self.prefValues: 737 | self.stopEntry.insert(0, str(self.prefValues['stopCommand'])) 738 | 739 | def addInterface( self ): 740 | self.tableFrame.addRow() 741 | 742 | def defaultDpid( self, name): 743 | "Derive dpid from switch name, s1 -> 1" 744 | assert self # satisfy pylint and allow contextual override 745 | try: 746 | dpid = int( re.findall( r'\d+', name )[ 0 ] ) 747 | dpid = hex( dpid )[ 2: ] 748 | return dpid 749 | except IndexError: 750 | return None 751 | #raise Exception( 'Unable to derive default datapath ID - ' 752 | # 'please either specify a dpid or use a ' 753 | # 'canonical switch name such as s23.' ) 754 | 755 | def apply(self): 756 | externalInterfaces = [] 757 | for row in range(self.tableFrame.rows): 758 | #print 'Interface is ' + self.tableFrame.get(row, 0) 759 | if len(self.tableFrame.get(row, 0)) > 0: 760 | externalInterfaces.append(self.tableFrame.get(row, 0)) 761 | 762 | dpid = self.dpidEntry.get() 763 | if (self.defaultDpid(self.hostnameEntry.get()) is None 764 | and len(dpid) == 0): 765 | showerror(title="Error", 766 | message= 'Unable to derive default datapath ID - ' 767 | 'please either specify a DPID or use a ' 768 | 'canonical switch name such as s23.' ) 769 | 770 | 771 | results = {'externalInterfaces':externalInterfaces, 772 | 'hostname':self.hostnameEntry.get(), 773 | 'dpid':dpid, 774 | 'startCommand':self.startEntry.get(), 775 | 'stopCommand':self.stopEntry.get(), 776 | 'sflow':str(self.sflow.get()), 777 | 'netflow':str(self.nflow.get()), 778 | 'dpctl':self.dpctlEntry.get(), 779 | 'switchIP':self.ipEntry.get()} 780 | sw = self.switchType.get() 781 | if sw == 'Indigo Virtual Switch': 782 | results['switchType'] = 'ivs' 783 | if StrictVersion(MININET_VERSION) < StrictVersion('2.1'): 784 | self.ovsOk = False 785 | showerror(title="Error", 786 | message='MiniNet version 2.1+ required. You have '+VERSION+'.') 787 | elif sw == 'Userspace Switch inNamespace': 788 | results['switchType'] = 'userns' 789 | elif sw == 'Userspace Switch': 790 | results['switchType'] = 'user' 791 | elif sw == 'Open vSwitch Kernel Mode': 792 | results['switchType'] = 'ovs' 793 | else: 794 | results['switchType'] = 'default' 795 | self.result = results 796 | 797 | 798 | class VerticalScrolledTable(LabelFrame): 799 | """A pure Tkinter scrollable frame that actually works! 800 | 801 | * Use the 'interior' attribute to place widgets inside the scrollable frame 802 | * Construct and pack/place/grid normally 803 | * This frame only allows vertical scrolling 804 | 805 | """ 806 | def __init__(self, parent, rows=2, columns=2, title=None, *args, **kw): 807 | LabelFrame.__init__(self, parent, text=title, padx=5, pady=5, *args, **kw) 808 | 809 | # create a canvas object and a vertical scrollbar for scrolling it 810 | vscrollbar = Scrollbar(self, orient=VERTICAL) 811 | vscrollbar.pack(fill=Y, side=RIGHT, expand=FALSE) 812 | canvas = Canvas(self, bd=0, highlightthickness=0, 813 | yscrollcommand=vscrollbar.set) 814 | canvas.pack(side=LEFT, fill=BOTH, expand=TRUE) 815 | vscrollbar.config(command=canvas.yview) 816 | 817 | # reset the view 818 | canvas.xview_moveto(0) 819 | canvas.yview_moveto(0) 820 | 821 | # create a frame inside the canvas which will be scrolled with it 822 | self.interior = interior = TableFrame(canvas, rows=rows, columns=columns) 823 | interior_id = canvas.create_window(0, 0, window=interior, 824 | anchor=NW) 825 | 826 | # track changes to the canvas and frame width and sync them, 827 | # also updating the scrollbar 828 | def _configure_interior(_event): 829 | # update the scrollbars to match the size of the inner frame 830 | size = (interior.winfo_reqwidth(), interior.winfo_reqheight()) 831 | canvas.config(scrollregion="0 0 %s %s" % size) 832 | if interior.winfo_reqwidth() != canvas.winfo_width(): 833 | # update the canvas's width to fit the inner frame 834 | canvas.config(width=interior.winfo_reqwidth()) 835 | interior.bind('', _configure_interior) 836 | 837 | def _configure_canvas(_event): 838 | if interior.winfo_reqwidth() != canvas.winfo_width(): 839 | # update the inner frame's width to fill the canvas 840 | canvas.itemconfigure(interior_id, width=canvas.winfo_width()) 841 | canvas.bind('', _configure_canvas) 842 | 843 | return 844 | 845 | class TableFrame(Frame): 846 | def __init__(self, parent, rows=2, columns=2): 847 | 848 | Frame.__init__(self, parent, background="black") 849 | self._widgets = [] 850 | self.rows = rows 851 | self.columns = columns 852 | for row in range(rows): 853 | current_row = [] 854 | for column in range(columns): 855 | label = Entry(self, borderwidth=0) 856 | label.grid(row=row, column=column, sticky="wens", padx=1, pady=1) 857 | current_row.append(label) 858 | self._widgets.append(current_row) 859 | 860 | def set(self, row, column, value): 861 | widget = self._widgets[row][column] 862 | widget.insert(0, value) 863 | 864 | def get(self, row, column): 865 | widget = self._widgets[row][column] 866 | return widget.get() 867 | 868 | def addRow( self, value=None, readonly=False ): 869 | #print "Adding row " + str(self.rows +1) 870 | current_row = [] 871 | for column in range(self.columns): 872 | label = Entry(self, borderwidth=0) 873 | label.grid(row=self.rows, column=column, sticky="wens", padx=1, pady=1) 874 | if value is not None: 875 | label.insert(0, value[column]) 876 | if readonly == True: 877 | label.configure(state='readonly') 878 | current_row.append(label) 879 | self._widgets.append(current_row) 880 | self.update_idletasks() 881 | self.rows += 1 882 | 883 | class LinkDialog(tkSimpleDialog.Dialog): 884 | 885 | def __init__(self, parent, title, linkDefaults): 886 | 887 | self.linkValues = linkDefaults 888 | 889 | tkSimpleDialog.Dialog.__init__(self, parent, title) 890 | 891 | def body(self, master): 892 | 893 | self.var = StringVar(master) 894 | Label(master, text="Bandwidth:").grid(row=0, sticky=E) 895 | self.e1 = Entry(master) 896 | self.e1.grid(row=0, column=1) 897 | Label(master, text="Mbit").grid(row=0, column=2, sticky=W) 898 | if 'bw' in self.linkValues: 899 | self.e1.insert(0,str(self.linkValues['bw'])) 900 | 901 | Label(master, text="Delay:").grid(row=1, sticky=E) 902 | self.e2 = Entry(master) 903 | self.e2.grid(row=1, column=1) 904 | if 'delay' in self.linkValues: 905 | self.e2.insert(0, self.linkValues['delay']) 906 | 907 | Label(master, text="Loss:").grid(row=2, sticky=E) 908 | self.e3 = Entry(master) 909 | self.e3.grid(row=2, column=1) 910 | Label(master, text="%").grid(row=2, column=2, sticky=W) 911 | if 'loss' in self.linkValues: 912 | self.e3.insert(0, str(self.linkValues['loss'])) 913 | 914 | Label(master, text="Max Queue size:").grid(row=3, sticky=E) 915 | self.e4 = Entry(master) 916 | self.e4.grid(row=3, column=1) 917 | if 'max_queue_size' in self.linkValues: 918 | self.e4.insert(0, str(self.linkValues['max_queue_size'])) 919 | 920 | Label(master, text="Jitter:").grid(row=4, sticky=E) 921 | self.e5 = Entry(master) 922 | self.e5.grid(row=4, column=1) 923 | if 'jitter' in self.linkValues: 924 | self.e5.insert(0, self.linkValues['jitter']) 925 | 926 | Label(master, text="Speedup:").grid(row=5, sticky=E) 927 | self.e6 = Entry(master) 928 | self.e6.grid(row=5, column=1) 929 | if 'speedup' in self.linkValues: 930 | self.e6.insert(0, str(self.linkValues['speedup'])) 931 | 932 | return self.e1 # initial focus 933 | 934 | def apply(self): 935 | self.result = {} 936 | if len(self.e1.get()) > 0: 937 | self.result['bw'] = int(self.e1.get()) 938 | if len(self.e2.get()) > 0: 939 | self.result['delay'] = self.e2.get() 940 | if len(self.e3.get()) > 0: 941 | self.result['loss'] = int(self.e3.get()) 942 | if len(self.e4.get()) > 0: 943 | self.result['max_queue_size'] = int(self.e4.get()) 944 | if len(self.e5.get()) > 0: 945 | self.result['jitter'] = self.e5.get() 946 | if len(self.e6.get()) > 0: 947 | self.result['speedup'] = int(self.e6.get()) 948 | 949 | class ControllerDialog(tkSimpleDialog.Dialog): 950 | 951 | def __init__(self, parent, title, ctrlrDefaults=None): 952 | 953 | if ctrlrDefaults: 954 | self.ctrlrValues = ctrlrDefaults 955 | 956 | tkSimpleDialog.Dialog.__init__(self, parent, title) 957 | 958 | def body(self, master): 959 | 960 | self.var = StringVar(master) 961 | self.protcolvar = StringVar(master) 962 | 963 | rowCount=0 964 | # Field for Hostname 965 | Label(master, text="Name:").grid(row=rowCount, sticky=E) 966 | self.hostnameEntry = Entry(master) 967 | self.hostnameEntry.grid(row=rowCount, column=1) 968 | self.hostnameEntry.insert(0, self.ctrlrValues['hostname']) 969 | rowCount+=1 970 | 971 | # Field for Remove Controller Port 972 | Label(master, text="Controller Port:").grid(row=rowCount, sticky=E) 973 | self.e2 = Entry(master) 974 | self.e2.grid(row=rowCount, column=1) 975 | self.e2.insert(0, self.ctrlrValues['remotePort']) 976 | rowCount+=1 977 | 978 | # Field for Controller Type 979 | Label(master, text="Controller Type:").grid(row=rowCount, sticky=E) 980 | controllerType = self.ctrlrValues['controllerType'] 981 | self.o1 = OptionMenu(master, self.var, "Remote Controller", "In-Band Controller", "OpenFlow Reference", "OVS Controller") 982 | self.o1.grid(row=rowCount, column=1, sticky=W) 983 | if controllerType == 'ref': 984 | self.var.set("OpenFlow Reference") 985 | elif controllerType == 'inband': 986 | self.var.set("In-Band Controller") 987 | elif controllerType == 'remote': 988 | self.var.set("Remote Controller") 989 | else: 990 | self.var.set("OVS Controller") 991 | rowCount+=1 992 | 993 | # Field for Controller Protcol 994 | Label(master, text="Protocol:").grid(row=rowCount, sticky=E) 995 | if 'controllerProtocol' in self.ctrlrValues: 996 | controllerProtocol = self.ctrlrValues['controllerProtocol'] 997 | else: 998 | controllerProtocol = 'tcp' 999 | self.protcol = OptionMenu(master, self.protcolvar, "TCP", "SSL") 1000 | self.protcol.grid(row=rowCount, column=1, sticky=W) 1001 | if controllerProtocol == 'ssl': 1002 | self.protcolvar.set("SSL") 1003 | else: 1004 | self.protcolvar.set("TCP") 1005 | rowCount+=1 1006 | 1007 | # Field for Remove Controller IP 1008 | remoteFrame= LabelFrame(master, text='Remote/In-Band Controller', padx=5, pady=5) 1009 | remoteFrame.grid(row=rowCount, column=0, columnspan=2, sticky=W) 1010 | 1011 | Label(remoteFrame, text="IP Address:").grid(row=0, sticky=E) 1012 | self.e1 = Entry(remoteFrame) 1013 | self.e1.grid(row=0, column=1) 1014 | self.e1.insert(0, self.ctrlrValues['remoteIP']) 1015 | rowCount+=1 1016 | 1017 | return self.hostnameEntry # initial focus 1018 | 1019 | def apply(self): 1020 | self.result = { 'hostname': self.hostnameEntry.get(), 1021 | 'remoteIP': self.e1.get(), 1022 | 'remotePort': int(self.e2.get())} 1023 | 1024 | controllerType = self.var.get() 1025 | if controllerType == 'Remote Controller': 1026 | self.result['controllerType'] = 'remote' 1027 | elif controllerType == 'In-Band Controller': 1028 | self.result['controllerType'] = 'inband' 1029 | elif controllerType == 'OpenFlow Reference': 1030 | self.result['controllerType'] = 'ref' 1031 | else: 1032 | self.result['controllerType'] = 'ovsc' 1033 | controllerProtocol = self.protcolvar.get() 1034 | if controllerProtocol == 'SSL': 1035 | self.result['controllerProtocol'] = 'ssl' 1036 | else: 1037 | self.result['controllerProtocol'] = 'tcp' 1038 | 1039 | class ToolTip(object): 1040 | 1041 | def __init__(self, widget): 1042 | self.widget = widget 1043 | self.tipwindow = None 1044 | self.id = None 1045 | self.x = self.y = 0 1046 | 1047 | def showtip(self, text): 1048 | "Display text in tooltip window" 1049 | self.text = text 1050 | if self.tipwindow or not self.text: 1051 | return 1052 | x, y, _cx, cy = self.widget.bbox("insert") 1053 | x = x + self.widget.winfo_rootx() + 27 1054 | y = y + cy + self.widget.winfo_rooty() +27 1055 | self.tipwindow = tw = Toplevel(self.widget) 1056 | tw.wm_overrideredirect(1) 1057 | tw.wm_geometry("+%d+%d" % (x, y)) 1058 | try: 1059 | # For Mac OS 1060 | # pylint: disable=protected-access 1061 | tw.tk.call("::tk::unsupported::MacWindowStyle", 1062 | "style", tw._w, 1063 | "help", "noActivates") 1064 | # pylint: enable=protected-access 1065 | except TclError: 1066 | pass 1067 | label = Label(tw, text=self.text, justify=LEFT, 1068 | background="#ffffe0", relief=SOLID, borderwidth=1, 1069 | font=("tahoma", "8", "normal")) 1070 | label.pack(ipadx=1) 1071 | 1072 | def hidetip(self): 1073 | tw = self.tipwindow 1074 | self.tipwindow = None 1075 | if tw: 1076 | tw.destroy() 1077 | 1078 | class MiniEdit( Frame ): 1079 | 1080 | "A simple network editor for Mininet." 1081 | 1082 | def __init__( self, parent=None, cheight=600, cwidth=1000 ): 1083 | 1084 | self.defaultIpBase='10.0.0.0/8' 1085 | 1086 | self.nflowDefaults = {'nflowTarget':'', 1087 | 'nflowTimeout':'600', 1088 | 'nflowAddId':'0'} 1089 | self.sflowDefaults = {'sflowTarget':'', 1090 | 'sflowSampling':'400', 1091 | 'sflowHeader':'128', 1092 | 'sflowPolling':'30'} 1093 | 1094 | self.appPrefs={ 1095 | "ipBase": self.defaultIpBase, 1096 | "startCLI": "0", 1097 | "terminalType": 'xterm', 1098 | "switchType": 'ovs', 1099 | "dpctl": '', 1100 | 'sflow':self.sflowDefaults, 1101 | 'netflow':self.nflowDefaults, 1102 | 'openFlowVersions':{'ovsOf10':'1', 1103 | 'ovsOf11':'0', 1104 | 'ovsOf12':'0', 1105 | 'ovsOf13':'0'} 1106 | 1107 | } 1108 | 1109 | 1110 | Frame.__init__( self, parent ) 1111 | self.action = None 1112 | self.appName = 'MiniEdit' 1113 | self.fixedFont = tkFont.Font ( family="DejaVu Sans Mono", size="14" ) 1114 | 1115 | # Style 1116 | self.font = ( 'Geneva', 9 ) 1117 | self.smallFont = ( 'Geneva', 7 ) 1118 | self.bg = 'white' 1119 | 1120 | # Title 1121 | self.top = self.winfo_toplevel() 1122 | self.top.title( self.appName ) 1123 | 1124 | # Menu bar 1125 | self.createMenubar() 1126 | 1127 | # Editing canvas 1128 | self.cheight, self.cwidth = cheight, cwidth 1129 | self.cframe, self.canvas = self.createCanvas() 1130 | 1131 | # Toolbar 1132 | self.controllers = {} 1133 | 1134 | # Toolbar 1135 | self.images = miniEditImages() 1136 | self.buttons = {} 1137 | self.active = None 1138 | self.tools = ( 'Select', 'Host', 'Switch', 'LegacySwitch', 'LegacyRouter', 'NetLink', 'Controller' ) 1139 | self.customColors = { 'Switch': 'darkGreen', 'Host': 'blue' } 1140 | self.toolbar = self.createToolbar() 1141 | 1142 | # Layout 1143 | self.toolbar.grid( column=0, row=0, sticky='nsew') 1144 | self.cframe.grid( column=1, row=0 ) 1145 | self.columnconfigure( 1, weight=1 ) 1146 | self.rowconfigure( 0, weight=1 ) 1147 | self.pack( expand=True, fill='both' ) 1148 | 1149 | # About box 1150 | self.aboutBox = None 1151 | 1152 | # Initialize node data 1153 | self.nodeBindings = self.createNodeBindings() 1154 | self.nodePrefixes = { 'LegacyRouter': 'r', 'LegacySwitch': 's', 'Switch': 's', 'Host': 'h' , 'Controller': 'c'} 1155 | self.widgetToItem = {} 1156 | self.itemToWidget = {} 1157 | 1158 | # Initialize link tool 1159 | self.link = self.linkWidget = None 1160 | 1161 | # Selection support 1162 | self.selection = None 1163 | 1164 | # Keyboard bindings 1165 | self.bind( '', lambda event: self.quit() ) 1166 | self.bind( '', self.deleteSelection ) 1167 | self.bind( '', self.deleteSelection ) 1168 | self.focus() 1169 | 1170 | self.hostPopup = Menu(self.top, tearoff=0) 1171 | self.hostPopup.add_command(label='Host Options', font=self.font) 1172 | self.hostPopup.add_separator() 1173 | self.hostPopup.add_command(label='Properties', font=self.font, command=self.hostDetails ) 1174 | 1175 | self.hostRunPopup = Menu(self.top, tearoff=0) 1176 | self.hostRunPopup.add_command(label='Host Options', font=self.font) 1177 | self.hostRunPopup.add_separator() 1178 | self.hostRunPopup.add_command(label='Terminal', font=self.font, command=self.xterm ) 1179 | 1180 | self.legacyRouterRunPopup = Menu(self.top, tearoff=0) 1181 | self.legacyRouterRunPopup.add_command(label='Router Options', font=self.font) 1182 | self.legacyRouterRunPopup.add_separator() 1183 | self.legacyRouterRunPopup.add_command(label='Terminal', font=self.font, command=self.xterm ) 1184 | 1185 | self.switchPopup = Menu(self.top, tearoff=0) 1186 | self.switchPopup.add_command(label='Switch Options', font=self.font) 1187 | self.switchPopup.add_separator() 1188 | self.switchPopup.add_command(label='Properties', font=self.font, command=self.switchDetails ) 1189 | 1190 | self.switchRunPopup = Menu(self.top, tearoff=0) 1191 | self.switchRunPopup.add_command(label='Switch Options', font=self.font) 1192 | self.switchRunPopup.add_separator() 1193 | self.switchRunPopup.add_command(label='List bridge details', font=self.font, command=self.listBridge ) 1194 | 1195 | self.linkPopup = Menu(self.top, tearoff=0) 1196 | self.linkPopup.add_command(label='Link Options', font=self.font) 1197 | self.linkPopup.add_separator() 1198 | self.linkPopup.add_command(label='Properties', font=self.font, command=self.linkDetails ) 1199 | 1200 | self.linkRunPopup = Menu(self.top, tearoff=0) 1201 | self.linkRunPopup.add_command(label='Link Options', font=self.font) 1202 | self.linkRunPopup.add_separator() 1203 | self.linkRunPopup.add_command(label='Link Up', font=self.font, command=self.linkUp ) 1204 | self.linkRunPopup.add_command(label='Link Down', font=self.font, command=self.linkDown ) 1205 | 1206 | self.controllerPopup = Menu(self.top, tearoff=0) 1207 | self.controllerPopup.add_command(label='Controller Options', font=self.font) 1208 | self.controllerPopup.add_separator() 1209 | self.controllerPopup.add_command(label='Properties', font=self.font, command=self.controllerDetails ) 1210 | 1211 | 1212 | # Event handling initalization 1213 | self.linkx = self.linky = self.linkItem = None 1214 | self.lastSelection = None 1215 | 1216 | # Model initialization 1217 | self.links = {} 1218 | self.hostOpts = {} 1219 | self.switchOpts = {} 1220 | self.hostCount = 0 1221 | self.switchCount = 0 1222 | self.controllerCount = 0 1223 | self.net = None 1224 | 1225 | # Close window gracefully 1226 | Wm.wm_protocol( self.top, name='WM_DELETE_WINDOW', func=self.quit ) 1227 | 1228 | def quit( self ): 1229 | "Stop our network, if any, then quit." 1230 | self.stop() 1231 | Frame.quit( self ) 1232 | 1233 | def createMenubar( self ): 1234 | "Create our menu bar." 1235 | 1236 | font = self.font 1237 | 1238 | mbar = Menu( self.top, font=font ) 1239 | self.top.configure( menu=mbar ) 1240 | 1241 | 1242 | fileMenu = Menu( mbar, tearoff=False ) 1243 | mbar.add_cascade( label="File", font=font, menu=fileMenu ) 1244 | fileMenu.add_command( label="New", font=font, command=self.newTopology ) 1245 | fileMenu.add_command( label="Open", font=font, command=self.loadTopology ) 1246 | fileMenu.add_command( label="Save", font=font, command=self.saveTopology ) 1247 | fileMenu.add_command( label="Export Level 2 Script", font=font, command=self.exportScript ) 1248 | fileMenu.add_separator() 1249 | fileMenu.add_command( label='Quit', command=self.quit, font=font ) 1250 | 1251 | editMenu = Menu( mbar, tearoff=False ) 1252 | mbar.add_cascade( label="Edit", font=font, menu=editMenu ) 1253 | editMenu.add_command( label="Cut", font=font, 1254 | command=lambda: self.deleteSelection( None ) ) 1255 | editMenu.add_command( label="Preferences", font=font, command=self.prefDetails) 1256 | 1257 | runMenu = Menu( mbar, tearoff=False ) 1258 | mbar.add_cascade( label="Run", font=font, menu=runMenu ) 1259 | runMenu.add_command( label="Run", font=font, command=self.doRun ) 1260 | runMenu.add_command( label="Stop", font=font, command=self.doStop ) 1261 | fileMenu.add_separator() 1262 | runMenu.add_command( label='Show OVS Summary', font=font, command=self.ovsShow ) 1263 | runMenu.add_command( label='Root Terminal', font=font, command=self.rootTerminal ) 1264 | 1265 | # Application menu 1266 | appMenu = Menu( mbar, tearoff=False ) 1267 | mbar.add_cascade( label="Help", font=font, menu=appMenu ) 1268 | appMenu.add_command( label='About MiniEdit', command=self.about, 1269 | font=font) 1270 | # Canvas 1271 | 1272 | def createCanvas( self ): 1273 | "Create and return our scrolling canvas frame." 1274 | f = Frame( self ) 1275 | 1276 | canvas = Canvas( f, width=self.cwidth, height=self.cheight, 1277 | bg=self.bg ) 1278 | 1279 | # Scroll bars 1280 | xbar = Scrollbar( f, orient='horizontal', command=canvas.xview ) 1281 | ybar = Scrollbar( f, orient='vertical', command=canvas.yview ) 1282 | canvas.configure( xscrollcommand=xbar.set, yscrollcommand=ybar.set ) 1283 | 1284 | # Resize box 1285 | resize = Label( f, bg='white' ) 1286 | 1287 | # Layout 1288 | canvas.grid( row=0, column=1, sticky='nsew') 1289 | ybar.grid( row=0, column=2, sticky='ns') 1290 | xbar.grid( row=1, column=1, sticky='ew' ) 1291 | resize.grid( row=1, column=2, sticky='nsew' ) 1292 | 1293 | # Resize behavior 1294 | f.rowconfigure( 0, weight=1 ) 1295 | f.columnconfigure( 1, weight=1 ) 1296 | f.grid( row=0, column=0, sticky='nsew' ) 1297 | f.bind( '', lambda event: self.updateScrollRegion() ) 1298 | 1299 | # Mouse bindings 1300 | canvas.bind( '', self.clickCanvas ) 1301 | canvas.bind( '', self.dragCanvas ) 1302 | canvas.bind( '', self.releaseCanvas ) 1303 | 1304 | return f, canvas 1305 | 1306 | def updateScrollRegion( self ): 1307 | "Update canvas scroll region to hold everything." 1308 | bbox = self.canvas.bbox( 'all' ) 1309 | if bbox is not None: 1310 | self.canvas.configure( scrollregion=( 0, 0, bbox[ 2 ], 1311 | bbox[ 3 ] ) ) 1312 | 1313 | def canvasx( self, x_root ): 1314 | "Convert root x coordinate to canvas coordinate." 1315 | c = self.canvas 1316 | return c.canvasx( x_root ) - c.winfo_rootx() 1317 | 1318 | def canvasy( self, y_root ): 1319 | "Convert root y coordinate to canvas coordinate." 1320 | c = self.canvas 1321 | return c.canvasy( y_root ) - c.winfo_rooty() 1322 | 1323 | # Toolbar 1324 | 1325 | def activate( self, toolName ): 1326 | "Activate a tool and press its button." 1327 | # Adjust button appearance 1328 | if self.active: 1329 | self.buttons[ self.active ].configure( relief='raised' ) 1330 | self.buttons[ toolName ].configure( relief='sunken' ) 1331 | # Activate dynamic bindings 1332 | self.active = toolName 1333 | 1334 | 1335 | @staticmethod 1336 | def createToolTip(widget, text): 1337 | toolTip = ToolTip(widget) 1338 | def enter(_event): 1339 | toolTip.showtip(text) 1340 | def leave(_event): 1341 | toolTip.hidetip() 1342 | widget.bind('', enter) 1343 | widget.bind('', leave) 1344 | 1345 | def createToolbar( self ): 1346 | "Create and return our toolbar frame." 1347 | 1348 | toolbar = Frame( self ) 1349 | 1350 | # Tools 1351 | for tool in self.tools: 1352 | cmd = ( lambda t=tool: self.activate( t ) ) 1353 | b = Button( toolbar, text=tool, font=self.smallFont, command=cmd) 1354 | if tool in self.images: 1355 | b.config( height=35, image=self.images[ tool ] ) 1356 | self.createToolTip(b, str(tool)) 1357 | # b.config( compound='top' ) 1358 | b.pack( fill='x' ) 1359 | self.buttons[ tool ] = b 1360 | self.activate( self.tools[ 0 ] ) 1361 | 1362 | # Spacer 1363 | Label( toolbar, text='' ).pack() 1364 | 1365 | # Commands 1366 | for cmd, color in [ ( 'Stop', 'darkRed' ), ( 'Run', 'darkGreen' ) ]: 1367 | doCmd = getattr( self, 'do' + cmd ) 1368 | b = Button( toolbar, text=cmd, font=self.smallFont, 1369 | fg=color, command=doCmd ) 1370 | b.pack( fill='x', side='bottom' ) 1371 | 1372 | return toolbar 1373 | 1374 | def doRun( self ): 1375 | "Run command." 1376 | self.activate( 'Select' ) 1377 | for tool in self.tools: 1378 | self.buttons[ tool ].config( state='disabled' ) 1379 | self.start() 1380 | 1381 | def doStop( self ): 1382 | "Stop command." 1383 | self.stop() 1384 | for tool in self.tools: 1385 | self.buttons[ tool ].config( state='normal' ) 1386 | 1387 | def addNode( self, node, nodeNum, x, y, name=None): 1388 | "Add a new node to our canvas." 1389 | if 'Switch' == node: 1390 | self.switchCount += 1 1391 | if 'Host' == node: 1392 | self.hostCount += 1 1393 | if 'Controller' == node: 1394 | self.controllerCount += 1 1395 | if name is None: 1396 | name = self.nodePrefixes[ node ] + nodeNum 1397 | self.addNamedNode(node, name, x, y) 1398 | 1399 | def addNamedNode( self, node, name, x, y): 1400 | "Add a new node to our canvas." 1401 | icon = self.nodeIcon( node, name ) 1402 | item = self.canvas.create_window( x, y, anchor='c', window=icon, 1403 | tags=node ) 1404 | self.widgetToItem[ icon ] = item 1405 | self.itemToWidget[ item ] = icon 1406 | icon.links = {} 1407 | 1408 | def convertJsonUnicode(self, text): 1409 | "Some part of Mininet don't like Unicode" 1410 | if isinstance(text, dict): 1411 | return {self.convertJsonUnicode(key): self.convertJsonUnicode(value) for key, value in text.iteritems()} 1412 | elif isinstance(text, list): 1413 | return [self.convertJsonUnicode(element) for element in text] 1414 | elif isinstance(text, unicode): 1415 | return text.encode('utf-8') 1416 | else: 1417 | return text 1418 | 1419 | def loadTopology( self ): 1420 | "Load command." 1421 | c = self.canvas 1422 | 1423 | myFormats = [ 1424 | ('Mininet Topology','*.mn'), 1425 | ('All Files','*'), 1426 | ] 1427 | f = tkFileDialog.askopenfile(filetypes=myFormats, mode='rb') 1428 | if f == None: 1429 | return 1430 | self.newTopology() 1431 | loadedTopology = self.convertJsonUnicode(json.load(f)) 1432 | 1433 | # Load application preferences 1434 | if 'application' in loadedTopology: 1435 | self.appPrefs = dict(self.appPrefs.items() + loadedTopology['application'].items()) 1436 | if "ovsOf10" not in self.appPrefs["openFlowVersions"]: 1437 | self.appPrefs["openFlowVersions"]["ovsOf10"] = '0' 1438 | if "ovsOf11" not in self.appPrefs["openFlowVersions"]: 1439 | self.appPrefs["openFlowVersions"]["ovsOf11"] = '0' 1440 | if "ovsOf12" not in self.appPrefs["openFlowVersions"]: 1441 | self.appPrefs["openFlowVersions"]["ovsOf12"] = '0' 1442 | if "ovsOf13" not in self.appPrefs["openFlowVersions"]: 1443 | self.appPrefs["openFlowVersions"]["ovsOf13"] = '0' 1444 | if "sflow" not in self.appPrefs: 1445 | self.appPrefs["sflow"] = self.sflowDefaults 1446 | if "netflow" not in self.appPrefs: 1447 | self.appPrefs["netflow"] = self.nflowDefaults 1448 | 1449 | # Load controllers 1450 | if 'controllers' in loadedTopology: 1451 | if loadedTopology['version'] == '1': 1452 | # This is old location of controller info 1453 | hostname = 'c0' 1454 | self.controllers = {} 1455 | self.controllers[hostname] = loadedTopology['controllers']['c0'] 1456 | self.controllers[hostname]['hostname'] = hostname 1457 | self.addNode('Controller', 0, float(30), float(30), name=hostname) 1458 | icon = self.findWidgetByName(hostname) 1459 | icon.bind('', self.do_controllerPopup ) 1460 | else: 1461 | controllers = loadedTopology['controllers'] 1462 | for controller in controllers: 1463 | hostname = controller['opts']['hostname'] 1464 | x = controller['x'] 1465 | y = controller['y'] 1466 | self.addNode('Controller', 0, float(x), float(y), name=hostname) 1467 | self.controllers[hostname] = controller['opts'] 1468 | icon = self.findWidgetByName(hostname) 1469 | icon.bind('', self.do_controllerPopup ) 1470 | 1471 | 1472 | # Load hosts 1473 | hosts = loadedTopology['hosts'] 1474 | for host in hosts: 1475 | nodeNum = host['number'] 1476 | hostname = 'h'+nodeNum 1477 | if 'hostname' in host['opts']: 1478 | hostname = host['opts']['hostname'] 1479 | else: 1480 | host['opts']['hostname'] = hostname 1481 | if 'nodeNum' not in host['opts']: 1482 | host['opts']['nodeNum'] = int(nodeNum) 1483 | x = host['x'] 1484 | y = host['y'] 1485 | self.addNode('Host', nodeNum, float(x), float(y), name=hostname) 1486 | 1487 | # Fix JSON converting tuple to list when saving 1488 | if 'privateDirectory' in host['opts']: 1489 | newDirList = [] 1490 | for privateDir in host['opts']['privateDirectory']: 1491 | if isinstance( privateDir, list ): 1492 | newDirList.append((privateDir[0],privateDir[1])) 1493 | else: 1494 | newDirList.append(privateDir) 1495 | host['opts']['privateDirectory'] = newDirList 1496 | self.hostOpts[hostname] = host['opts'] 1497 | icon = self.findWidgetByName(hostname) 1498 | icon.bind('', self.do_hostPopup ) 1499 | 1500 | # Load switches 1501 | switches = loadedTopology['switches'] 1502 | for switch in switches: 1503 | nodeNum = switch['number'] 1504 | hostname = 's'+nodeNum 1505 | if 'controllers' not in switch['opts']: 1506 | switch['opts']['controllers'] = [] 1507 | if 'switchType' not in switch['opts']: 1508 | switch['opts']['switchType'] = 'default' 1509 | if 'hostname' in switch['opts']: 1510 | hostname = switch['opts']['hostname'] 1511 | else: 1512 | switch['opts']['hostname'] = hostname 1513 | if 'nodeNum' not in switch['opts']: 1514 | switch['opts']['nodeNum'] = int(nodeNum) 1515 | x = switch['x'] 1516 | y = switch['y'] 1517 | if switch['opts']['switchType'] == "legacyRouter": 1518 | self.addNode('LegacyRouter', nodeNum, float(x), float(y), name=hostname) 1519 | icon = self.findWidgetByName(hostname) 1520 | icon.bind('', self.do_legacyRouterPopup ) 1521 | elif switch['opts']['switchType'] == "legacySwitch": 1522 | self.addNode('LegacySwitch', nodeNum, float(x), float(y), name=hostname) 1523 | icon = self.findWidgetByName(hostname) 1524 | icon.bind('', self.do_legacySwitchPopup ) 1525 | else: 1526 | self.addNode('Switch', nodeNum, float(x), float(y), name=hostname) 1527 | icon = self.findWidgetByName(hostname) 1528 | icon.bind('', self.do_switchPopup ) 1529 | self.switchOpts[hostname] = switch['opts'] 1530 | 1531 | # create links to controllers 1532 | if int(loadedTopology['version']) > 1: 1533 | controllers = self.switchOpts[hostname]['controllers'] 1534 | for controller in controllers: 1535 | dest = self.findWidgetByName(controller) 1536 | dx, dy = self.canvas.coords( self.widgetToItem[ dest ] ) 1537 | self.link = self.canvas.create_line(float(x), 1538 | float(y), 1539 | dx, 1540 | dy, 1541 | width=4, 1542 | fill='red', 1543 | dash=(6, 4, 2, 4), 1544 | tag='link' ) 1545 | c.itemconfig(self.link, tags=c.gettags(self.link)+('control',)) 1546 | self.addLink( icon, dest, linktype='control' ) 1547 | self.createControlLinkBindings() 1548 | self.link = self.linkWidget = None 1549 | else: 1550 | dest = self.findWidgetByName('c0') 1551 | dx, dy = self.canvas.coords( self.widgetToItem[ dest ] ) 1552 | self.link = self.canvas.create_line(float(x), 1553 | float(y), 1554 | dx, 1555 | dy, 1556 | width=4, 1557 | fill='red', 1558 | dash=(6, 4, 2, 4), 1559 | tag='link' ) 1560 | c.itemconfig(self.link, tags=c.gettags(self.link)+('control',)) 1561 | self.addLink( icon, dest, linktype='control' ) 1562 | self.createControlLinkBindings() 1563 | self.link = self.linkWidget = None 1564 | 1565 | # Load links 1566 | links = loadedTopology['links'] 1567 | for link in links: 1568 | srcNode = link['src'] 1569 | src = self.findWidgetByName(srcNode) 1570 | sx, sy = self.canvas.coords( self.widgetToItem[ src ] ) 1571 | 1572 | destNode = link['dest'] 1573 | dest = self.findWidgetByName(destNode) 1574 | dx, dy = self.canvas.coords( self.widgetToItem[ dest] ) 1575 | 1576 | self.link = self.canvas.create_line( sx, sy, dx, dy, width=4, 1577 | fill='blue', tag='link' ) 1578 | c.itemconfig(self.link, tags=c.gettags(self.link)+('data',)) 1579 | self.addLink( src, dest, linkopts=link['opts'] ) 1580 | self.createDataLinkBindings() 1581 | self.link = self.linkWidget = None 1582 | 1583 | f.close() 1584 | 1585 | def findWidgetByName( self, name ): 1586 | for widget in self.widgetToItem: 1587 | if name == widget[ 'text' ]: 1588 | return widget 1589 | 1590 | def newTopology( self ): 1591 | "New command." 1592 | for widget in self.widgetToItem.keys(): 1593 | self.deleteItem( self.widgetToItem[ widget ] ) 1594 | self.hostCount = 0 1595 | self.switchCount = 0 1596 | self.controllerCount = 0 1597 | self.links = {} 1598 | self.hostOpts = {} 1599 | self.switchOpts = {} 1600 | self.controllers = {} 1601 | self.appPrefs["ipBase"]= self.defaultIpBase 1602 | 1603 | def saveTopology( self ): 1604 | "Save command." 1605 | myFormats = [ 1606 | ('Mininet Topology','*.mn'), 1607 | ('All Files','*'), 1608 | ] 1609 | 1610 | savingDictionary = {} 1611 | fileName = tkFileDialog.asksaveasfilename(filetypes=myFormats ,title="Save the topology as...") 1612 | if len(fileName ) > 0: 1613 | # Save Application preferences 1614 | savingDictionary['version'] = '2' 1615 | 1616 | # Save Switches and Hosts 1617 | hostsToSave = [] 1618 | switchesToSave = [] 1619 | controllersToSave = [] 1620 | for widget in self.widgetToItem: 1621 | name = widget[ 'text' ] 1622 | tags = self.canvas.gettags( self.widgetToItem[ widget ] ) 1623 | x1, y1 = self.canvas.coords( self.widgetToItem[ widget ] ) 1624 | if 'Switch' in tags or 'LegacySwitch' in tags or 'LegacyRouter' in tags: 1625 | nodeNum = self.switchOpts[name]['nodeNum'] 1626 | nodeToSave = {'number':str(nodeNum), 1627 | 'x':str(x1), 1628 | 'y':str(y1), 1629 | 'opts':self.switchOpts[name] } 1630 | switchesToSave.append(nodeToSave) 1631 | elif 'Host' in tags: 1632 | nodeNum = self.hostOpts[name]['nodeNum'] 1633 | nodeToSave = {'number':str(nodeNum), 1634 | 'x':str(x1), 1635 | 'y':str(y1), 1636 | 'opts':self.hostOpts[name] } 1637 | hostsToSave.append(nodeToSave) 1638 | elif 'Controller' in tags: 1639 | nodeToSave = {'x':str(x1), 1640 | 'y':str(y1), 1641 | 'opts':self.controllers[name] } 1642 | controllersToSave.append(nodeToSave) 1643 | else: 1644 | raise Exception( "Cannot create mystery node: " + name ) 1645 | savingDictionary['hosts'] = hostsToSave 1646 | savingDictionary['switches'] = switchesToSave 1647 | savingDictionary['controllers'] = controllersToSave 1648 | 1649 | # Save Links 1650 | linksToSave = [] 1651 | for link in self.links.values(): 1652 | src = link['src'] 1653 | dst = link['dest'] 1654 | linkopts = link['linkOpts'] 1655 | 1656 | srcName, dstName = src[ 'text' ], dst[ 'text' ] 1657 | linkToSave = {'src':srcName, 1658 | 'dest':dstName, 1659 | 'opts':linkopts} 1660 | if link['type'] == 'data': 1661 | linksToSave.append(linkToSave) 1662 | savingDictionary['links'] = linksToSave 1663 | 1664 | # Save Application preferences 1665 | savingDictionary['application'] = self.appPrefs 1666 | 1667 | try: 1668 | f = open(fileName, 'wb') 1669 | f.write(json.dumps(savingDictionary, sort_keys=True, indent=4, separators=(',', ': '))) 1670 | # pylint: disable=broad-except 1671 | except Exception as er: 1672 | print er 1673 | # pylint: enable=broad-except 1674 | finally: 1675 | f.close() 1676 | 1677 | def exportScript( self ): 1678 | "Export command." 1679 | myFormats = [ 1680 | ('Mininet Custom Topology','*.py'), 1681 | ('All Files','*'), 1682 | ] 1683 | 1684 | fileName = tkFileDialog.asksaveasfilename(filetypes=myFormats ,title="Export the topology as...") 1685 | if len(fileName ) > 0: 1686 | #print "Now saving under %s" % fileName 1687 | f = open(fileName, 'wb') 1688 | 1689 | f.write("#!/usr/bin/python\n") 1690 | f.write("\n") 1691 | f.write("from mininet.net import Mininet\n") 1692 | f.write("from mininet.node import Controller, RemoteController, OVSController\n") 1693 | f.write("from mininet.node import CPULimitedHost, Host, Node\n") 1694 | f.write("from mininet.node import OVSKernelSwitch, UserSwitch\n") 1695 | if StrictVersion(MININET_VERSION) > StrictVersion('2.0'): 1696 | f.write("from mininet.node import IVSSwitch\n") 1697 | f.write("from mininet.cli import CLI\n") 1698 | f.write("from mininet.log import setLogLevel, info\n") 1699 | f.write("from mininet.link import TCLink, Intf\n") 1700 | f.write("from subprocess import call\n") 1701 | 1702 | inBandCtrl = False 1703 | for widget in self.widgetToItem: 1704 | name = widget[ 'text' ] 1705 | tags = self.canvas.gettags( self.widgetToItem[ widget ] ) 1706 | 1707 | if 'Controller' in tags: 1708 | opts = self.controllers[name] 1709 | controllerType = opts['controllerType'] 1710 | if controllerType == 'inband': 1711 | inBandCtrl = True 1712 | 1713 | if inBandCtrl == True: 1714 | f.write("\n") 1715 | f.write("class InbandController( RemoteController ):\n") 1716 | f.write("\n") 1717 | f.write(" def checkListening( self ):\n") 1718 | f.write(" \"Overridden to do nothing.\"\n") 1719 | f.write(" return\n") 1720 | 1721 | f.write("\n") 1722 | f.write("def myNetwork():\n") 1723 | f.write("\n") 1724 | f.write(" net = Mininet( topo=None,\n") 1725 | if len(self.appPrefs['dpctl']) > 0: 1726 | f.write(" listenPort="+self.appPrefs['dpctl']+",\n") 1727 | f.write(" build=False,\n") 1728 | f.write(" ipBase='"+self.appPrefs['ipBase']+"')\n") 1729 | f.write("\n") 1730 | f.write(" info( '*** Adding controller\\n' )\n") 1731 | for widget in self.widgetToItem: 1732 | name = widget[ 'text' ] 1733 | tags = self.canvas.gettags( self.widgetToItem[ widget ] ) 1734 | 1735 | if 'Controller' in tags: 1736 | opts = self.controllers[name] 1737 | controllerType = opts['controllerType'] 1738 | if 'controllerProtocol' in opts: 1739 | controllerProtocol = opts['controllerProtocol'] 1740 | else: 1741 | controllerProtocol = 'tcp' 1742 | controllerIP = opts['remoteIP'] 1743 | controllerPort = opts['remotePort'] 1744 | 1745 | 1746 | f.write(" "+name+"=net.addController(name='"+name+"',\n") 1747 | 1748 | if controllerType == 'remote': 1749 | f.write(" controller=RemoteController,\n") 1750 | f.write(" ip='"+controllerIP+"',\n") 1751 | elif controllerType == 'inband': 1752 | f.write(" controller=InbandController,\n") 1753 | f.write(" ip='"+controllerIP+"',\n") 1754 | elif controllerType == 'ovsc': 1755 | f.write(" controller=OVSController,\n") 1756 | else: 1757 | f.write(" controller=Controller,\n") 1758 | 1759 | f.write(" protocol='"+controllerProtocol+"',\n") 1760 | f.write(" port="+str(controllerPort)+")\n") 1761 | f.write("\n") 1762 | 1763 | # Save Switches and Hosts 1764 | f.write(" info( '*** Add switches\\n')\n") 1765 | for widget in self.widgetToItem: 1766 | name = widget[ 'text' ] 1767 | tags = self.canvas.gettags( self.widgetToItem[ widget ] ) 1768 | if 'LegacyRouter' in tags: 1769 | f.write(" "+name+" = net.addHost('"+name+"', cls=Node, ip='0.0.0.0')\n") 1770 | f.write(" "+name+".cmd('sysctl -w net.ipv4.ip_forward=1')\n") 1771 | if 'LegacySwitch' in tags: 1772 | f.write(" "+name+" = net.addSwitch('"+name+"', cls=OVSKernelSwitch, failMode='standalone')\n") 1773 | if 'Switch' in tags: 1774 | opts = self.switchOpts[name] 1775 | nodeNum = opts['nodeNum'] 1776 | f.write(" "+name+" = net.addSwitch('"+name+"'") 1777 | if opts['switchType'] == 'default': 1778 | if self.appPrefs['switchType'] == 'ivs': 1779 | f.write(", cls=IVSSwitch") 1780 | elif self.appPrefs['switchType'] == 'user': 1781 | f.write(", cls=UserSwitch") 1782 | elif self.appPrefs['switchType'] == 'userns': 1783 | f.write(", cls=UserSwitch, inNamespace=True") 1784 | else: 1785 | f.write(", cls=OVSKernelSwitch") 1786 | elif opts['switchType'] == 'ivs': 1787 | f.write(", cls=IVSSwitch") 1788 | elif opts['switchType'] == 'user': 1789 | f.write(", cls=UserSwitch") 1790 | elif opts['switchType'] == 'userns': 1791 | f.write(", cls=UserSwitch, inNamespace=True") 1792 | else: 1793 | f.write(", cls=OVSKernelSwitch") 1794 | if 'dpctl' in opts: 1795 | f.write(", listenPort="+opts['dpctl']) 1796 | if 'dpid' in opts: 1797 | f.write(", dpid='"+opts['dpid']+"'") 1798 | f.write(")\n") 1799 | if 'externalInterfaces' in opts: 1800 | for extInterface in opts['externalInterfaces']: 1801 | f.write(" Intf( '"+extInterface+"', node="+name+" )\n") 1802 | 1803 | f.write("\n") 1804 | f.write(" info( '*** Add hosts\\n')\n") 1805 | for widget in self.widgetToItem: 1806 | name = widget[ 'text' ] 1807 | tags = self.canvas.gettags( self.widgetToItem[ widget ] ) 1808 | if 'Host' in tags: 1809 | opts = self.hostOpts[name] 1810 | ip = None 1811 | defaultRoute = None 1812 | if 'defaultRoute' in opts and len(opts['defaultRoute']) > 0: 1813 | defaultRoute = "'via "+opts['defaultRoute']+"'" 1814 | else: 1815 | defaultRoute = 'None' 1816 | if 'ip' in opts and len(opts['ip']) > 0: 1817 | ip = opts['ip'] 1818 | else: 1819 | nodeNum = self.hostOpts[name]['nodeNum'] 1820 | ipBaseNum, prefixLen = netParse( self.appPrefs['ipBase'] ) 1821 | ip = ipAdd(i=nodeNum, prefixLen=prefixLen, ipBaseNum=ipBaseNum) 1822 | 1823 | if 'cores' in opts or 'cpu' in opts: 1824 | f.write(" "+name+" = net.addHost('"+name+"', cls=CPULimitedHost, ip='"+ip+"', defaultRoute="+defaultRoute+")\n") 1825 | if 'cores' in opts: 1826 | f.write(" "+name+".setCPUs(cores='"+opts['cores']+"')\n") 1827 | if 'cpu' in opts: 1828 | f.write(" "+name+".setCPUFrac(f="+str(opts['cpu'])+", sched='"+opts['sched']+"')\n") 1829 | else: 1830 | f.write(" "+name+" = net.addHost('"+name+"', cls=Host, ip='"+ip+"', defaultRoute="+defaultRoute+")\n") 1831 | if 'externalInterfaces' in opts: 1832 | for extInterface in opts['externalInterfaces']: 1833 | f.write(" Intf( '"+extInterface+"', node="+name+" )\n") 1834 | f.write("\n") 1835 | 1836 | # Save Links 1837 | f.write(" info( '*** Add links\\n')\n") 1838 | for key,linkDetail in self.links.iteritems(): 1839 | tags = self.canvas.gettags(key) 1840 | if 'data' in tags: 1841 | optsExist = False 1842 | src = linkDetail['src'] 1843 | dst = linkDetail['dest'] 1844 | linkopts = linkDetail['linkOpts'] 1845 | srcName, dstName = src[ 'text' ], dst[ 'text' ] 1846 | bw = '' 1847 | # delay = '' 1848 | # loss = '' 1849 | # max_queue_size = '' 1850 | linkOpts = "{" 1851 | if 'bw' in linkopts: 1852 | bw = linkopts['bw'] 1853 | linkOpts = linkOpts + "'bw':"+str(bw) 1854 | optsExist = True 1855 | if 'delay' in linkopts: 1856 | # delay = linkopts['delay'] 1857 | if optsExist: 1858 | linkOpts = linkOpts + "," 1859 | linkOpts = linkOpts + "'delay':'"+linkopts['delay']+"'" 1860 | optsExist = True 1861 | if 'loss' in linkopts: 1862 | if optsExist: 1863 | linkOpts = linkOpts + "," 1864 | linkOpts = linkOpts + "'loss':"+str(linkopts['loss']) 1865 | optsExist = True 1866 | if 'max_queue_size' in linkopts: 1867 | if optsExist: 1868 | linkOpts = linkOpts + "," 1869 | linkOpts = linkOpts + "'max_queue_size':"+str(linkopts['max_queue_size']) 1870 | optsExist = True 1871 | if 'jitter' in linkopts: 1872 | if optsExist: 1873 | linkOpts = linkOpts + "," 1874 | linkOpts = linkOpts + "'jitter':'"+linkopts['jitter']+"'" 1875 | optsExist = True 1876 | if 'speedup' in linkopts: 1877 | if optsExist: 1878 | linkOpts = linkOpts + "," 1879 | linkOpts = linkOpts + "'speedup':"+str(linkopts['speedup']) 1880 | optsExist = True 1881 | 1882 | linkOpts = linkOpts + "}" 1883 | if optsExist: 1884 | f.write(" "+srcName+dstName+" = "+linkOpts+"\n") 1885 | f.write(" net.addLink("+srcName+", "+dstName) 1886 | if optsExist: 1887 | f.write(", cls=TCLink , **"+srcName+dstName) 1888 | f.write(")\n") 1889 | 1890 | f.write("\n") 1891 | f.write(" info( '*** Starting network\\n')\n") 1892 | f.write(" net.build()\n") 1893 | 1894 | f.write(" info( '*** Starting controllers\\n')\n") 1895 | f.write(" for controller in net.controllers:\n") 1896 | f.write(" controller.start()\n") 1897 | f.write("\n") 1898 | 1899 | f.write(" info( '*** Starting switches\\n')\n") 1900 | for widget in self.widgetToItem: 1901 | name = widget[ 'text' ] 1902 | tags = self.canvas.gettags( self.widgetToItem[ widget ] ) 1903 | if 'Switch' in tags or 'LegacySwitch' in tags: 1904 | opts = self.switchOpts[name] 1905 | ctrlList = ",".join(opts['controllers']) 1906 | f.write(" net.get('"+name+"').start(["+ctrlList+"])\n") 1907 | 1908 | f.write("\n") 1909 | 1910 | f.write(" info( '*** Post configure switches and hosts\\n')\n") 1911 | for widget in self.widgetToItem: 1912 | name = widget[ 'text' ] 1913 | tags = self.canvas.gettags( self.widgetToItem[ widget ] ) 1914 | if 'Switch' in tags: 1915 | opts = self.switchOpts[name] 1916 | if opts['switchType'] == 'default': 1917 | if self.appPrefs['switchType'] == 'user': 1918 | if 'switchIP' in opts: 1919 | if len(opts['switchIP']) > 0: 1920 | f.write(" "+name+".cmd('ifconfig "+name+" "+opts['switchIP']+"')\n") 1921 | elif self.appPrefs['switchType'] == 'userns': 1922 | if 'switchIP' in opts: 1923 | if len(opts['switchIP']) > 0: 1924 | f.write(" "+name+".cmd('ifconfig lo "+opts['switchIP']+"')\n") 1925 | elif self.appPrefs['switchType'] == 'ovs': 1926 | if 'switchIP' in opts: 1927 | if len(opts['switchIP']) > 0: 1928 | f.write(" "+name+".cmd('ifconfig "+name+" "+opts['switchIP']+"')\n") 1929 | elif opts['switchType'] == 'user': 1930 | if 'switchIP' in opts: 1931 | if len(opts['switchIP']) > 0: 1932 | f.write(" "+name+".cmd('ifconfig "+name+" "+opts['switchIP']+"')\n") 1933 | elif opts['switchType'] == 'userns': 1934 | if 'switchIP' in opts: 1935 | if len(opts['switchIP']) > 0: 1936 | f.write(" "+name+".cmd('ifconfig lo "+opts['switchIP']+"')\n") 1937 | elif opts['switchType'] == 'ovs': 1938 | if 'switchIP' in opts: 1939 | if len(opts['switchIP']) > 0: 1940 | f.write(" "+name+".cmd('ifconfig "+name+" "+opts['switchIP']+"')\n") 1941 | for widget in self.widgetToItem: 1942 | name = widget[ 'text' ] 1943 | tags = self.canvas.gettags( self.widgetToItem[ widget ] ) 1944 | if 'Host' in tags: 1945 | opts = self.hostOpts[name] 1946 | # Attach vlan interfaces 1947 | if 'vlanInterfaces' in opts: 1948 | for vlanInterface in opts['vlanInterfaces']: 1949 | f.write(" "+name+".cmd('vconfig add "+name+"-eth0 "+vlanInterface[1]+"')\n") 1950 | f.write(" "+name+".cmd('ifconfig "+name+"-eth0."+vlanInterface[1]+" "+vlanInterface[0]+"')\n") 1951 | # Run User Defined Start Command 1952 | if 'startCommand' in opts: 1953 | f.write(" "+name+".cmdPrint('"+opts['startCommand']+"')\n") 1954 | if 'Switch' in tags: 1955 | opts = self.switchOpts[name] 1956 | # Run User Defined Start Command 1957 | if 'startCommand' in opts: 1958 | f.write(" "+name+".cmdPrint('"+opts['startCommand']+"')\n") 1959 | 1960 | # Configure NetFlow 1961 | nflowValues = self.appPrefs['netflow'] 1962 | if len(nflowValues['nflowTarget']) > 0: 1963 | nflowEnabled = False 1964 | nflowSwitches = '' 1965 | for widget in self.widgetToItem: 1966 | name = widget[ 'text' ] 1967 | tags = self.canvas.gettags( self.widgetToItem[ widget ] ) 1968 | 1969 | if 'Switch' in tags: 1970 | opts = self.switchOpts[name] 1971 | if 'netflow' in opts: 1972 | if opts['netflow'] == '1': 1973 | nflowSwitches = nflowSwitches+' -- set Bridge '+name+' netflow=@MiniEditNF' 1974 | nflowEnabled=True 1975 | if nflowEnabled: 1976 | nflowCmd = 'ovs-vsctl -- --id=@MiniEditNF create NetFlow '+ 'target=\\\"'+nflowValues['nflowTarget']+'\\\" '+ 'active-timeout='+nflowValues['nflowTimeout'] 1977 | if nflowValues['nflowAddId'] == '1': 1978 | nflowCmd = nflowCmd + ' add_id_to_interface=true' 1979 | else: 1980 | nflowCmd = nflowCmd + ' add_id_to_interface=false' 1981 | f.write(" \n") 1982 | f.write(" call('"+nflowCmd+nflowSwitches+"', shell=True)\n") 1983 | 1984 | # Configure sFlow 1985 | sflowValues = self.appPrefs['sflow'] 1986 | if len(sflowValues['sflowTarget']) > 0: 1987 | sflowEnabled = False 1988 | sflowSwitches = '' 1989 | for widget in self.widgetToItem: 1990 | name = widget[ 'text' ] 1991 | tags = self.canvas.gettags( self.widgetToItem[ widget ] ) 1992 | 1993 | if 'Switch' in tags: 1994 | opts = self.switchOpts[name] 1995 | if 'sflow' in opts: 1996 | if opts['sflow'] == '1': 1997 | sflowSwitches = sflowSwitches+' -- set Bridge '+name+' sflow=@MiniEditSF' 1998 | sflowEnabled=True 1999 | if sflowEnabled: 2000 | sflowCmd = 'ovs-vsctl -- --id=@MiniEditSF create sFlow '+ 'target=\\\"'+sflowValues['sflowTarget']+'\\\" '+ 'header='+sflowValues['sflowHeader']+' '+ 'sampling='+sflowValues['sflowSampling']+' '+ 'polling='+sflowValues['sflowPolling'] 2001 | f.write(" \n") 2002 | f.write(" call('"+sflowCmd+sflowSwitches+"', shell=True)\n") 2003 | 2004 | f.write("\n") 2005 | f.write(" CLI(net)\n") 2006 | for widget in self.widgetToItem: 2007 | name = widget[ 'text' ] 2008 | tags = self.canvas.gettags( self.widgetToItem[ widget ] ) 2009 | if 'Host' in tags: 2010 | opts = self.hostOpts[name] 2011 | # Run User Defined Stop Command 2012 | if 'stopCommand' in opts: 2013 | f.write(" "+name+".cmdPrint('"+opts['stopCommand']+"')\n") 2014 | if 'Switch' in tags: 2015 | opts = self.switchOpts[name] 2016 | # Run User Defined Stop Command 2017 | if 'stopCommand' in opts: 2018 | f.write(" "+name+".cmdPrint('"+opts['stopCommand']+"')\n") 2019 | 2020 | f.write(" net.stop()\n") 2021 | f.write("\n") 2022 | f.write("if __name__ == '__main__':\n") 2023 | f.write(" setLogLevel( 'info' )\n") 2024 | f.write(" myNetwork()\n") 2025 | f.write("\n") 2026 | 2027 | 2028 | f.close() 2029 | 2030 | 2031 | # Generic canvas handler 2032 | # 2033 | # We could have used bindtags, as in nodeIcon, but 2034 | # the dynamic approach used here 2035 | # may actually require less code. In any case, it's an 2036 | # interesting introspection-based alternative to bindtags. 2037 | 2038 | def canvasHandle( self, eventName, event ): 2039 | "Generic canvas event handler" 2040 | if self.active is None: 2041 | return 2042 | toolName = self.active 2043 | handler = getattr( self, eventName + toolName, None ) 2044 | if handler is not None: 2045 | handler( event ) 2046 | 2047 | def clickCanvas( self, event ): 2048 | "Canvas click handler." 2049 | self.canvasHandle( 'click', event ) 2050 | 2051 | def dragCanvas( self, event ): 2052 | "Canvas drag handler." 2053 | self.canvasHandle( 'drag', event ) 2054 | 2055 | def releaseCanvas( self, event ): 2056 | "Canvas mouse up handler." 2057 | self.canvasHandle( 'release', event ) 2058 | 2059 | # Currently the only items we can select directly are 2060 | # links. Nodes are handled by bindings in the node icon. 2061 | 2062 | def findItem( self, x, y ): 2063 | "Find items at a location in our canvas." 2064 | items = self.canvas.find_overlapping( x, y, x, y ) 2065 | if len( items ) == 0: 2066 | return None 2067 | else: 2068 | return items[ 0 ] 2069 | 2070 | # Canvas bindings for Select, Host, Switch and Link tools 2071 | 2072 | def clickSelect( self, event ): 2073 | "Select an item." 2074 | self.selectItem( self.findItem( event.x, event.y ) ) 2075 | 2076 | def deleteItem( self, item ): 2077 | "Delete an item." 2078 | # Don't delete while network is running 2079 | if self.buttons[ 'Select' ][ 'state' ] == 'disabled': 2080 | return 2081 | # Delete from model 2082 | if item in self.links: 2083 | self.deleteLink( item ) 2084 | if item in self.itemToWidget: 2085 | self.deleteNode( item ) 2086 | # Delete from view 2087 | self.canvas.delete( item ) 2088 | 2089 | def deleteSelection( self, _event ): 2090 | "Delete the selected item." 2091 | if self.selection is not None: 2092 | self.deleteItem( self.selection ) 2093 | self.selectItem( None ) 2094 | 2095 | def nodeIcon( self, node, name ): 2096 | "Create a new node icon." 2097 | icon = Button( self.canvas, image=self.images[ node ], 2098 | text=name, compound='top' ) 2099 | # Unfortunately bindtags wants a tuple 2100 | bindtags = [ str( self.nodeBindings ) ] 2101 | bindtags += list( icon.bindtags() ) 2102 | icon.bindtags( tuple( bindtags ) ) 2103 | return icon 2104 | 2105 | def newNode( self, node, event ): 2106 | "Add a new node to our canvas." 2107 | c = self.canvas 2108 | x, y = c.canvasx( event.x ), c.canvasy( event.y ) 2109 | name = self.nodePrefixes[ node ] 2110 | if 'Switch' == node: 2111 | self.switchCount += 1 2112 | name = self.nodePrefixes[ node ] + str( self.switchCount ) 2113 | self.switchOpts[name] = {} 2114 | self.switchOpts[name]['nodeNum']=self.switchCount 2115 | self.switchOpts[name]['hostname']=name 2116 | self.switchOpts[name]['switchType']='default' 2117 | self.switchOpts[name]['controllers']=[] 2118 | if 'LegacyRouter' == node: 2119 | self.switchCount += 1 2120 | name = self.nodePrefixes[ node ] + str( self.switchCount ) 2121 | self.switchOpts[name] = {} 2122 | self.switchOpts[name]['nodeNum']=self.switchCount 2123 | self.switchOpts[name]['hostname']=name 2124 | self.switchOpts[name]['switchType']='legacyRouter' 2125 | if 'LegacySwitch' == node: 2126 | self.switchCount += 1 2127 | name = self.nodePrefixes[ node ] + str( self.switchCount ) 2128 | self.switchOpts[name] = {} 2129 | self.switchOpts[name]['nodeNum']=self.switchCount 2130 | self.switchOpts[name]['hostname']=name 2131 | self.switchOpts[name]['switchType']='legacySwitch' 2132 | self.switchOpts[name]['controllers']=[] 2133 | if 'Host' == node: 2134 | self.hostCount += 1 2135 | name = self.nodePrefixes[ node ] + str( self.hostCount ) 2136 | self.hostOpts[name] = {'sched':'host'} 2137 | self.hostOpts[name]['nodeNum']=self.hostCount 2138 | self.hostOpts[name]['hostname']=name 2139 | if 'Controller' == node: 2140 | name = self.nodePrefixes[ node ] + str( self.controllerCount ) 2141 | ctrlr = { 'controllerType': 'ref', 2142 | 'hostname': name, 2143 | 'controllerProtocol': 'tcp', 2144 | 'remoteIP': '127.0.0.1', 2145 | 'remotePort': 6633} 2146 | self.controllers[name] = ctrlr 2147 | # We want to start controller count at 0 2148 | self.controllerCount += 1 2149 | 2150 | icon = self.nodeIcon( node, name ) 2151 | item = self.canvas.create_window( x, y, anchor='c', window=icon, 2152 | tags=node ) 2153 | self.widgetToItem[ icon ] = item 2154 | self.itemToWidget[ item ] = icon 2155 | self.selectItem( item ) 2156 | icon.links = {} 2157 | if 'Switch' == node: 2158 | icon.bind('', self.do_switchPopup ) 2159 | if 'LegacyRouter' == node: 2160 | icon.bind('', self.do_legacyRouterPopup ) 2161 | if 'LegacySwitch' == node: 2162 | icon.bind('', self.do_legacySwitchPopup ) 2163 | if 'Host' == node: 2164 | icon.bind('', self.do_hostPopup ) 2165 | if 'Controller' == node: 2166 | icon.bind('', self.do_controllerPopup ) 2167 | 2168 | def clickController( self, event ): 2169 | "Add a new Controller to our canvas." 2170 | self.newNode( 'Controller', event ) 2171 | 2172 | def clickHost( self, event ): 2173 | "Add a new host to our canvas." 2174 | self.newNode( 'Host', event ) 2175 | 2176 | def clickLegacyRouter( self, event ): 2177 | "Add a new switch to our canvas." 2178 | self.newNode( 'LegacyRouter', event ) 2179 | 2180 | def clickLegacySwitch( self, event ): 2181 | "Add a new switch to our canvas." 2182 | self.newNode( 'LegacySwitch', event ) 2183 | 2184 | def clickSwitch( self, event ): 2185 | "Add a new switch to our canvas." 2186 | self.newNode( 'Switch', event ) 2187 | 2188 | def dragNetLink( self, event ): 2189 | "Drag a link's endpoint to another node." 2190 | if self.link is None: 2191 | return 2192 | # Since drag starts in widget, we use root coords 2193 | x = self.canvasx( event.x_root ) 2194 | y = self.canvasy( event.y_root ) 2195 | c = self.canvas 2196 | c.coords( self.link, self.linkx, self.linky, x, y ) 2197 | 2198 | def releaseNetLink( self, _event ): 2199 | "Give up on the current link." 2200 | if self.link is not None: 2201 | self.canvas.delete( self.link ) 2202 | self.linkWidget = self.linkItem = self.link = None 2203 | 2204 | # Generic node handlers 2205 | 2206 | def createNodeBindings( self ): 2207 | "Create a set of bindings for nodes." 2208 | bindings = { 2209 | '': self.clickNode, 2210 | '': self.dragNode, 2211 | '': self.releaseNode, 2212 | '': self.enterNode, 2213 | '': self.leaveNode 2214 | } 2215 | l = Label() # lightweight-ish owner for bindings 2216 | for event, binding in bindings.items(): 2217 | l.bind( event, binding ) 2218 | return l 2219 | 2220 | def selectItem( self, item ): 2221 | "Select an item and remember old selection." 2222 | self.lastSelection = self.selection 2223 | self.selection = item 2224 | 2225 | def enterNode( self, event ): 2226 | "Select node on entry." 2227 | self.selectNode( event ) 2228 | 2229 | def leaveNode( self, _event ): 2230 | "Restore old selection on exit." 2231 | self.selectItem( self.lastSelection ) 2232 | 2233 | def clickNode( self, event ): 2234 | "Node click handler." 2235 | if self.active is 'NetLink': 2236 | self.startLink( event ) 2237 | else: 2238 | self.selectNode( event ) 2239 | return 'break' 2240 | 2241 | def dragNode( self, event ): 2242 | "Node drag handler." 2243 | if self.active is 'NetLink': 2244 | self.dragNetLink( event ) 2245 | else: 2246 | self.dragNodeAround( event ) 2247 | 2248 | def releaseNode( self, event ): 2249 | "Node release handler." 2250 | if self.active is 'NetLink': 2251 | self.finishLink( event ) 2252 | 2253 | # Specific node handlers 2254 | 2255 | def selectNode( self, event ): 2256 | "Select the node that was clicked on." 2257 | item = self.widgetToItem.get( event.widget, None ) 2258 | self.selectItem( item ) 2259 | 2260 | def dragNodeAround( self, event ): 2261 | "Drag a node around on the canvas." 2262 | c = self.canvas 2263 | # Convert global to local coordinates; 2264 | # Necessary since x, y are widget-relative 2265 | x = self.canvasx( event.x_root ) 2266 | y = self.canvasy( event.y_root ) 2267 | w = event.widget 2268 | # Adjust node position 2269 | item = self.widgetToItem[ w ] 2270 | c.coords( item, x, y ) 2271 | # Adjust link positions 2272 | for dest in w.links: 2273 | link = w.links[ dest ] 2274 | item = self.widgetToItem[ dest ] 2275 | x1, y1 = c.coords( item ) 2276 | c.coords( link, x, y, x1, y1 ) 2277 | self.updateScrollRegion() 2278 | 2279 | def createControlLinkBindings( self ): 2280 | "Create a set of bindings for nodes." 2281 | # Link bindings 2282 | # Selection still needs a bit of work overall 2283 | # Callbacks ignore event 2284 | 2285 | def select( _event, link=self.link ): 2286 | "Select item on mouse entry." 2287 | self.selectItem( link ) 2288 | 2289 | def highlight( _event, link=self.link ): 2290 | "Highlight item on mouse entry." 2291 | self.selectItem( link ) 2292 | self.canvas.itemconfig( link, fill='green' ) 2293 | 2294 | def unhighlight( _event, link=self.link ): 2295 | "Unhighlight item on mouse exit." 2296 | self.canvas.itemconfig( link, fill='red' ) 2297 | #self.selectItem( None ) 2298 | 2299 | self.canvas.tag_bind( self.link, '', highlight ) 2300 | self.canvas.tag_bind( self.link, '', unhighlight ) 2301 | self.canvas.tag_bind( self.link, '', select ) 2302 | 2303 | def createDataLinkBindings( self ): 2304 | "Create a set of bindings for nodes." 2305 | # Link bindings 2306 | # Selection still needs a bit of work overall 2307 | # Callbacks ignore event 2308 | 2309 | def select( _event, link=self.link ): 2310 | "Select item on mouse entry." 2311 | self.selectItem( link ) 2312 | 2313 | def highlight( _event, link=self.link ): 2314 | "Highlight item on mouse entry." 2315 | self.selectItem( link ) 2316 | self.canvas.itemconfig( link, fill='green' ) 2317 | 2318 | def unhighlight( _event, link=self.link ): 2319 | "Unhighlight item on mouse exit." 2320 | self.canvas.itemconfig( link, fill='blue' ) 2321 | #self.selectItem( None ) 2322 | 2323 | self.canvas.tag_bind( self.link, '', highlight ) 2324 | self.canvas.tag_bind( self.link, '', unhighlight ) 2325 | self.canvas.tag_bind( self.link, '', select ) 2326 | self.canvas.tag_bind( self.link, '', self.do_linkPopup ) 2327 | 2328 | 2329 | def startLink( self, event ): 2330 | "Start a new link." 2331 | if event.widget not in self.widgetToItem: 2332 | # Didn't click on a node 2333 | return 2334 | 2335 | w = event.widget 2336 | item = self.widgetToItem[ w ] 2337 | x, y = self.canvas.coords( item ) 2338 | self.link = self.canvas.create_line( x, y, x, y, width=4, 2339 | fill='blue', tag='link' ) 2340 | self.linkx, self.linky = x, y 2341 | self.linkWidget = w 2342 | self.linkItem = item 2343 | 2344 | 2345 | def finishLink( self, event ): 2346 | "Finish creating a link" 2347 | if self.link is None: 2348 | return 2349 | source = self.linkWidget 2350 | c = self.canvas 2351 | # Since we dragged from the widget, use root coords 2352 | x, y = self.canvasx( event.x_root ), self.canvasy( event.y_root ) 2353 | target = self.findItem( x, y ) 2354 | dest = self.itemToWidget.get( target, None ) 2355 | if ( source is None or dest is None or source == dest 2356 | or dest in source.links or source in dest.links ): 2357 | self.releaseNetLink( event ) 2358 | return 2359 | # For now, don't allow hosts to be directly linked 2360 | stags = self.canvas.gettags( self.widgetToItem[ source ] ) 2361 | dtags = self.canvas.gettags( target ) 2362 | if (('Host' in stags and 'Host' in dtags) or 2363 | ('Controller' in dtags and 'LegacyRouter' in stags) or 2364 | ('Controller' in stags and 'LegacyRouter' in dtags) or 2365 | ('Controller' in dtags and 'LegacySwitch' in stags) or 2366 | ('Controller' in stags and 'LegacySwitch' in dtags) or 2367 | ('Controller' in dtags and 'Host' in stags) or 2368 | ('Controller' in stags and 'Host' in dtags) or 2369 | ('Controller' in stags and 'Controller' in dtags)): 2370 | self.releaseNetLink( event ) 2371 | return 2372 | 2373 | # Set link type 2374 | linkType='data' 2375 | if 'Controller' in stags or 'Controller' in dtags: 2376 | linkType='control' 2377 | c.itemconfig(self.link, dash=(6, 4, 2, 4), fill='red') 2378 | self.createControlLinkBindings() 2379 | else: 2380 | linkType='data' 2381 | self.createDataLinkBindings() 2382 | c.itemconfig(self.link, tags=c.gettags(self.link)+(linkType,)) 2383 | 2384 | x, y = c.coords( target ) 2385 | c.coords( self.link, self.linkx, self.linky, x, y ) 2386 | self.addLink( source, dest, linktype=linkType ) 2387 | if linkType == 'control': 2388 | controllerName = '' 2389 | switchName = '' 2390 | if 'Controller' in stags: 2391 | controllerName = source[ 'text' ] 2392 | switchName = dest[ 'text' ] 2393 | else: 2394 | controllerName = dest[ 'text' ] 2395 | switchName = source[ 'text' ] 2396 | 2397 | self.switchOpts[switchName]['controllers'].append(controllerName) 2398 | 2399 | # We're done 2400 | self.link = self.linkWidget = None 2401 | 2402 | # Menu handlers 2403 | 2404 | def about( self ): 2405 | "Display about box." 2406 | about = self.aboutBox 2407 | if about is None: 2408 | bg = 'white' 2409 | about = Toplevel( bg='white' ) 2410 | about.title( 'About' ) 2411 | desc = self.appName + ': a simple network editor for MiniNet' 2412 | version = 'MiniEdit '+MINIEDIT_VERSION 2413 | author = 'Originally by: Bob Lantz , April 2010' 2414 | enhancements = 'Enhancements by: Gregory Gee, Since July 2013' 2415 | www = 'http://gregorygee.wordpress.com/category/miniedit/' 2416 | line1 = Label( about, text=desc, font='Helvetica 10 bold', bg=bg ) 2417 | line2 = Label( about, text=version, font='Helvetica 9', bg=bg ) 2418 | line3 = Label( about, text=author, font='Helvetica 9', bg=bg ) 2419 | line4 = Label( about, text=enhancements, font='Helvetica 9', bg=bg ) 2420 | line5 = Entry( about, font='Helvetica 9', bg=bg, width=len(www), justify=CENTER ) 2421 | line5.insert(0, www) 2422 | line5.configure(state='readonly') 2423 | line1.pack( padx=20, pady=10 ) 2424 | line2.pack(pady=10 ) 2425 | line3.pack(pady=10 ) 2426 | line4.pack(pady=10 ) 2427 | line5.pack(pady=10 ) 2428 | hide = ( lambda about=about: about.withdraw() ) 2429 | self.aboutBox = about 2430 | # Hide on close rather than destroying window 2431 | Wm.wm_protocol( about, name='WM_DELETE_WINDOW', func=hide ) 2432 | # Show (existing) window 2433 | about.deiconify() 2434 | 2435 | def createToolImages( self ): 2436 | "Create toolbar (and icon) images." 2437 | 2438 | @staticmethod 2439 | def checkIntf( intf ): 2440 | "Make sure intf exists and is not configured." 2441 | if ( ' %s:' % intf ) not in quietRun( 'ip link show' ): 2442 | showerror(title="Error", 2443 | message='External interface ' +intf + ' does not exist! Skipping.') 2444 | return False 2445 | ips = re.findall( r'\d+\.\d+\.\d+\.\d+', quietRun( 'ifconfig ' + intf ) ) 2446 | if ips: 2447 | showerror(title="Error", 2448 | message= intf + ' has an IP address and is probably in use! Skipping.' ) 2449 | return False 2450 | return True 2451 | 2452 | def hostDetails( self, _ignore=None ): 2453 | if ( self.selection is None or 2454 | self.net is not None or 2455 | self.selection not in self.itemToWidget ): 2456 | return 2457 | widget = self.itemToWidget[ self.selection ] 2458 | name = widget[ 'text' ] 2459 | tags = self.canvas.gettags( self.selection ) 2460 | if 'Host' not in tags: 2461 | return 2462 | 2463 | prefDefaults = self.hostOpts[name] 2464 | hostBox = HostDialog(self, title='Host Details', prefDefaults=prefDefaults) 2465 | self.master.wait_window(hostBox.top) 2466 | if hostBox.result: 2467 | newHostOpts = {'nodeNum':self.hostOpts[name]['nodeNum']} 2468 | newHostOpts['sched'] = hostBox.result['sched'] 2469 | if len(hostBox.result['startCommand']) > 0: 2470 | newHostOpts['startCommand'] = hostBox.result['startCommand'] 2471 | if len(hostBox.result['stopCommand']) > 0: 2472 | newHostOpts['stopCommand'] = hostBox.result['stopCommand'] 2473 | if len(hostBox.result['cpu']) > 0: 2474 | newHostOpts['cpu'] = float(hostBox.result['cpu']) 2475 | if len(hostBox.result['cores']) > 0: 2476 | newHostOpts['cores'] = hostBox.result['cores'] 2477 | if len(hostBox.result['hostname']) > 0: 2478 | newHostOpts['hostname'] = hostBox.result['hostname'] 2479 | name = hostBox.result['hostname'] 2480 | widget[ 'text' ] = name 2481 | if len(hostBox.result['defaultRoute']) > 0: 2482 | newHostOpts['defaultRoute'] = hostBox.result['defaultRoute'] 2483 | if len(hostBox.result['ip']) > 0: 2484 | newHostOpts['ip'] = hostBox.result['ip'] 2485 | if len(hostBox.result['externalInterfaces']) > 0: 2486 | newHostOpts['externalInterfaces'] = hostBox.result['externalInterfaces'] 2487 | if len(hostBox.result['vlanInterfaces']) > 0: 2488 | newHostOpts['vlanInterfaces'] = hostBox.result['vlanInterfaces'] 2489 | if len(hostBox.result['privateDirectory']) > 0: 2490 | newHostOpts['privateDirectory'] = hostBox.result['privateDirectory'] 2491 | self.hostOpts[name] = newHostOpts 2492 | print 'New host details for ' + name + ' = ' + str(newHostOpts) 2493 | 2494 | def switchDetails( self, _ignore=None ): 2495 | if ( self.selection is None or 2496 | self.net is not None or 2497 | self.selection not in self.itemToWidget ): 2498 | return 2499 | widget = self.itemToWidget[ self.selection ] 2500 | name = widget[ 'text' ] 2501 | tags = self.canvas.gettags( self.selection ) 2502 | if 'Switch' not in tags: 2503 | return 2504 | 2505 | prefDefaults = self.switchOpts[name] 2506 | switchBox = SwitchDialog(self, title='Switch Details', prefDefaults=prefDefaults) 2507 | self.master.wait_window(switchBox.top) 2508 | if switchBox.result: 2509 | newSwitchOpts = {'nodeNum':self.switchOpts[name]['nodeNum']} 2510 | newSwitchOpts['switchType'] = switchBox.result['switchType'] 2511 | newSwitchOpts['controllers'] = self.switchOpts[name]['controllers'] 2512 | if len(switchBox.result['startCommand']) > 0: 2513 | newSwitchOpts['startCommand'] = switchBox.result['startCommand'] 2514 | if len(switchBox.result['stopCommand']) > 0: 2515 | newSwitchOpts['stopCommand'] = switchBox.result['stopCommand'] 2516 | if len(switchBox.result['dpctl']) > 0: 2517 | newSwitchOpts['dpctl'] = switchBox.result['dpctl'] 2518 | if len(switchBox.result['dpid']) > 0: 2519 | newSwitchOpts['dpid'] = switchBox.result['dpid'] 2520 | if len(switchBox.result['hostname']) > 0: 2521 | newSwitchOpts['hostname'] = switchBox.result['hostname'] 2522 | name = switchBox.result['hostname'] 2523 | widget[ 'text' ] = name 2524 | if len(switchBox.result['externalInterfaces']) > 0: 2525 | newSwitchOpts['externalInterfaces'] = switchBox.result['externalInterfaces'] 2526 | newSwitchOpts['switchIP'] = switchBox.result['switchIP'] 2527 | newSwitchOpts['sflow'] = switchBox.result['sflow'] 2528 | newSwitchOpts['netflow'] = switchBox.result['netflow'] 2529 | self.switchOpts[name] = newSwitchOpts 2530 | print 'New switch details for ' + name + ' = ' + str(newSwitchOpts) 2531 | 2532 | def linkUp( self ): 2533 | if ( self.selection is None or 2534 | self.net is None): 2535 | return 2536 | link = self.selection 2537 | linkDetail = self.links[link] 2538 | src = linkDetail['src'] 2539 | dst = linkDetail['dest'] 2540 | srcName, dstName = src[ 'text' ], dst[ 'text' ] 2541 | self.net.configLinkStatus(srcName, dstName, 'up') 2542 | self.canvas.itemconfig(link, dash=()) 2543 | 2544 | def linkDown( self ): 2545 | if ( self.selection is None or 2546 | self.net is None): 2547 | return 2548 | link = self.selection 2549 | linkDetail = self.links[link] 2550 | src = linkDetail['src'] 2551 | dst = linkDetail['dest'] 2552 | srcName, dstName = src[ 'text' ], dst[ 'text' ] 2553 | self.net.configLinkStatus(srcName, dstName, 'down') 2554 | self.canvas.itemconfig(link, dash=(4, 4)) 2555 | 2556 | def linkDetails( self, _ignore=None ): 2557 | if ( self.selection is None or 2558 | self.net is not None): 2559 | return 2560 | link = self.selection 2561 | 2562 | linkDetail = self.links[link] 2563 | # src = linkDetail['src'] 2564 | # dest = linkDetail['dest'] 2565 | linkopts = linkDetail['linkOpts'] 2566 | linkBox = LinkDialog(self, title='Link Details', linkDefaults=linkopts) 2567 | if linkBox.result is not None: 2568 | linkDetail['linkOpts'] = linkBox.result 2569 | print 'New link details = ' + str(linkBox.result) 2570 | 2571 | def prefDetails( self ): 2572 | prefDefaults = self.appPrefs 2573 | prefBox = PrefsDialog(self, title='Preferences', prefDefaults=prefDefaults) 2574 | print 'New Prefs = ' + str(prefBox.result) 2575 | if prefBox.result: 2576 | self.appPrefs = prefBox.result 2577 | 2578 | 2579 | def controllerDetails( self ): 2580 | if ( self.selection is None or 2581 | self.net is not None or 2582 | self.selection not in self.itemToWidget ): 2583 | return 2584 | widget = self.itemToWidget[ self.selection ] 2585 | name = widget[ 'text' ] 2586 | tags = self.canvas.gettags( self.selection ) 2587 | oldName = name 2588 | if 'Controller' not in tags: 2589 | return 2590 | 2591 | ctrlrBox = ControllerDialog(self, title='Controller Details', ctrlrDefaults=self.controllers[name]) 2592 | if ctrlrBox.result: 2593 | #print 'Controller is ' + ctrlrBox.result[0] 2594 | if len(ctrlrBox.result['hostname']) > 0: 2595 | name = ctrlrBox.result['hostname'] 2596 | widget[ 'text' ] = name 2597 | else: 2598 | ctrlrBox.result['hostname'] = name 2599 | self.controllers[name] = ctrlrBox.result 2600 | print 'New controller details for ' + name + ' = ' + str(self.controllers[name]) 2601 | # Find references to controller and change name 2602 | if oldName != name: 2603 | for widget in self.widgetToItem: 2604 | switchName = widget[ 'text' ] 2605 | tags = self.canvas.gettags( self.widgetToItem[ widget ] ) 2606 | if 'Switch' in tags: 2607 | switch = self.switchOpts[switchName] 2608 | if oldName in switch['controllers']: 2609 | switch['controllers'].remove(oldName) 2610 | switch['controllers'].append(name) 2611 | 2612 | 2613 | def listBridge( self, _ignore=None ): 2614 | if ( self.selection is None or 2615 | self.net is None or 2616 | self.selection not in self.itemToWidget ): 2617 | return 2618 | name = self.itemToWidget[ self.selection ][ 'text' ] 2619 | tags = self.canvas.gettags( self.selection ) 2620 | 2621 | if name not in self.net.nameToNode: 2622 | return 2623 | if 'Switch' in tags or 'LegacySwitch' in tags: 2624 | call(["xterm -T 'Bridge Details' -sb -sl 2000 -e 'ovs-vsctl list bridge " + name + "; read -p \"Press Enter to close\"' &"], shell=True) 2625 | 2626 | @staticmethod 2627 | def ovsShow( _ignore=None ): 2628 | call(["xterm -T 'OVS Summary' -sb -sl 2000 -e 'ovs-vsctl show; read -p \"Press Enter to close\"' &"], shell=True) 2629 | 2630 | @staticmethod 2631 | def rootTerminal( _ignore=None ): 2632 | call(["xterm -T 'Root Terminal' -sb -sl 2000 &"], shell=True) 2633 | 2634 | # Model interface 2635 | # 2636 | # Ultimately we will either want to use a topo or 2637 | # mininet object here, probably. 2638 | 2639 | def addLink( self, source, dest, linktype='data', linkopts=None ): 2640 | "Add link to model." 2641 | if linkopts is None: 2642 | linkopts = {} 2643 | source.links[ dest ] = self.link 2644 | dest.links[ source ] = self.link 2645 | self.links[ self.link ] = {'type' :linktype, 2646 | 'src':source, 2647 | 'dest':dest, 2648 | 'linkOpts':linkopts} 2649 | 2650 | def deleteLink( self, link ): 2651 | "Delete link from model." 2652 | pair = self.links.get( link, None ) 2653 | if pair is not None: 2654 | source=pair['src'] 2655 | dest=pair['dest'] 2656 | del source.links[ dest ] 2657 | del dest.links[ source ] 2658 | stags = self.canvas.gettags( self.widgetToItem[ source ] ) 2659 | # dtags = self.canvas.gettags( self.widgetToItem[ dest ] ) 2660 | ltags = self.canvas.gettags( link ) 2661 | 2662 | if 'control' in ltags: 2663 | controllerName = '' 2664 | switchName = '' 2665 | if 'Controller' in stags: 2666 | controllerName = source[ 'text' ] 2667 | switchName = dest[ 'text' ] 2668 | else: 2669 | controllerName = dest[ 'text' ] 2670 | switchName = source[ 'text' ] 2671 | 2672 | if controllerName in self.switchOpts[switchName]['controllers']: 2673 | self.switchOpts[switchName]['controllers'].remove(controllerName) 2674 | 2675 | 2676 | if link is not None: 2677 | del self.links[ link ] 2678 | 2679 | def deleteNode( self, item ): 2680 | "Delete node (and its links) from model." 2681 | 2682 | widget = self.itemToWidget[ item ] 2683 | tags = self.canvas.gettags(item) 2684 | if 'Controller' in tags: 2685 | # remove from switch controller lists 2686 | for serachwidget in self.widgetToItem: 2687 | name = serachwidget[ 'text' ] 2688 | tags = self.canvas.gettags( self.widgetToItem[ serachwidget ] ) 2689 | if 'Switch' in tags: 2690 | if widget['text'] in self.switchOpts[name]['controllers']: 2691 | self.switchOpts[name]['controllers'].remove(widget['text']) 2692 | 2693 | for link in widget.links.values(): 2694 | # Delete from view and model 2695 | self.deleteItem( link ) 2696 | del self.itemToWidget[ item ] 2697 | del self.widgetToItem[ widget ] 2698 | 2699 | def buildNodes( self, net): 2700 | # Make nodes 2701 | print "Getting Hosts and Switches." 2702 | for widget in self.widgetToItem: 2703 | name = widget[ 'text' ] 2704 | tags = self.canvas.gettags( self.widgetToItem[ widget ] ) 2705 | #print name+' has '+str(tags) 2706 | 2707 | if 'Switch' in tags: 2708 | opts = self.switchOpts[name] 2709 | #print str(opts) 2710 | 2711 | # Create the correct switch class 2712 | switchClass = customOvs 2713 | switchParms={} 2714 | if 'dpctl' in opts: 2715 | switchParms['listenPort']=int(opts['dpctl']) 2716 | if 'dpid' in opts: 2717 | switchParms['dpid']=opts['dpid'] 2718 | if opts['switchType'] == 'default': 2719 | if self.appPrefs['switchType'] == 'ivs': 2720 | switchClass = IVSSwitch 2721 | elif self.appPrefs['switchType'] == 'user': 2722 | switchClass = CustomUserSwitch 2723 | elif self.appPrefs['switchType'] == 'userns': 2724 | switchParms['inNamespace'] = True 2725 | switchClass = CustomUserSwitch 2726 | else: 2727 | switchClass = customOvs 2728 | elif opts['switchType'] == 'user': 2729 | switchClass = CustomUserSwitch 2730 | elif opts['switchType'] == 'userns': 2731 | switchClass = CustomUserSwitch 2732 | switchParms['inNamespace'] = True 2733 | elif opts['switchType'] == 'ivs': 2734 | switchClass = IVSSwitch 2735 | else: 2736 | switchClass = customOvs 2737 | 2738 | if switchClass == customOvs: 2739 | # Set OpenFlow versions 2740 | self.openFlowVersions = [] 2741 | if self.appPrefs['openFlowVersions']['ovsOf10'] == '1': 2742 | self.openFlowVersions.append('OpenFlow10') 2743 | if self.appPrefs['openFlowVersions']['ovsOf11'] == '1': 2744 | self.openFlowVersions.append('OpenFlow11') 2745 | if self.appPrefs['openFlowVersions']['ovsOf12'] == '1': 2746 | self.openFlowVersions.append('OpenFlow12') 2747 | if self.appPrefs['openFlowVersions']['ovsOf13'] == '1': 2748 | self.openFlowVersions.append('OpenFlow13') 2749 | protoList = ",".join(self.openFlowVersions) 2750 | switchParms['protocols'] = protoList 2751 | newSwitch = net.addSwitch( name , cls=switchClass, **switchParms) 2752 | 2753 | # Some post startup config 2754 | if switchClass == CustomUserSwitch: 2755 | if 'switchIP' in opts: 2756 | if len(opts['switchIP']) > 0: 2757 | newSwitch.setSwitchIP(opts['switchIP']) 2758 | if switchClass == customOvs: 2759 | if 'switchIP' in opts: 2760 | if len(opts['switchIP']) > 0: 2761 | newSwitch.setSwitchIP(opts['switchIP']) 2762 | 2763 | # Attach external interfaces 2764 | if 'externalInterfaces' in opts: 2765 | for extInterface in opts['externalInterfaces']: 2766 | if self.checkIntf(extInterface): 2767 | Intf( extInterface, node=newSwitch ) 2768 | 2769 | elif 'LegacySwitch' in tags: 2770 | newSwitch = net.addSwitch( name , cls=LegacySwitch) 2771 | elif 'LegacyRouter' in tags: 2772 | newSwitch = net.addHost( name , cls=LegacyRouter) 2773 | elif 'Host' in tags: 2774 | opts = self.hostOpts[name] 2775 | #print str(opts) 2776 | ip = None 2777 | defaultRoute = None 2778 | if 'defaultRoute' in opts and len(opts['defaultRoute']) > 0: 2779 | defaultRoute = 'via '+opts['defaultRoute'] 2780 | if 'ip' in opts and len(opts['ip']) > 0: 2781 | ip = opts['ip'] 2782 | else: 2783 | nodeNum = self.hostOpts[name]['nodeNum'] 2784 | ipBaseNum, prefixLen = netParse( self.appPrefs['ipBase'] ) 2785 | ip = ipAdd(i=nodeNum, prefixLen=prefixLen, ipBaseNum=ipBaseNum) 2786 | 2787 | # Create the correct host class 2788 | if 'cores' in opts or 'cpu' in opts: 2789 | if 'privateDirectory' in opts: 2790 | hostCls = partial( CPULimitedHost, 2791 | privateDirs=opts['privateDirectory'] ) 2792 | else: 2793 | hostCls=CPULimitedHost 2794 | else: 2795 | if 'privateDirectory' in opts: 2796 | hostCls = partial( Host, 2797 | privateDirs=opts['privateDirectory'] ) 2798 | else: 2799 | hostCls=Host 2800 | print hostCls 2801 | newHost = net.addHost( name, 2802 | cls=hostCls, 2803 | ip=ip, 2804 | defaultRoute=defaultRoute 2805 | ) 2806 | 2807 | # Set the CPULimitedHost specific options 2808 | if 'cores' in opts: 2809 | newHost.setCPUs(cores = opts['cores']) 2810 | if 'cpu' in opts: 2811 | newHost.setCPUFrac(f=opts['cpu'], sched=opts['sched']) 2812 | 2813 | # Attach external interfaces 2814 | if 'externalInterfaces' in opts: 2815 | for extInterface in opts['externalInterfaces']: 2816 | if self.checkIntf(extInterface): 2817 | Intf( extInterface, node=newHost ) 2818 | if 'vlanInterfaces' in opts: 2819 | if len(opts['vlanInterfaces']) > 0: 2820 | print 'Checking that OS is VLAN prepared' 2821 | self.pathCheck('vconfig', moduleName='vlan package') 2822 | moduleDeps( add='8021q' ) 2823 | elif 'Controller' in tags: 2824 | opts = self.controllers[name] 2825 | 2826 | # Get controller info from panel 2827 | controllerType = opts['controllerType'] 2828 | if 'controllerProtocol' in opts: 2829 | controllerProtocol = opts['controllerProtocol'] 2830 | else: 2831 | controllerProtocol = 'tcp' 2832 | opts['controllerProtocol'] = 'tcp' 2833 | controllerIP = opts['remoteIP'] 2834 | controllerPort = opts['remotePort'] 2835 | 2836 | # Make controller 2837 | print 'Getting controller selection:'+controllerType 2838 | if controllerType == 'remote': 2839 | net.addController(name=name, 2840 | controller=RemoteController, 2841 | ip=controllerIP, 2842 | protocol=controllerProtocol, 2843 | port=controllerPort) 2844 | elif controllerType == 'inband': 2845 | net.addController(name=name, 2846 | controller=InbandController, 2847 | ip=controllerIP, 2848 | protocol=controllerProtocol, 2849 | port=controllerPort) 2850 | elif controllerType == 'ovsc': 2851 | net.addController(name=name, 2852 | controller=OVSController, 2853 | protocol=controllerProtocol, 2854 | port=controllerPort) 2855 | else: 2856 | net.addController(name=name, 2857 | controller=Controller, 2858 | protocol=controllerProtocol, 2859 | port=controllerPort) 2860 | 2861 | else: 2862 | raise Exception( "Cannot create mystery node: " + name ) 2863 | 2864 | @staticmethod 2865 | def pathCheck( *args, **kwargs ): 2866 | "Make sure each program in *args can be found in $PATH." 2867 | moduleName = kwargs.get( 'moduleName', 'it' ) 2868 | for arg in args: 2869 | if not quietRun( 'which ' + arg ): 2870 | showerror(title="Error", 2871 | message= 'Cannot find required executable %s.\n' % arg + 2872 | 'Please make sure that %s is installed ' % moduleName + 2873 | 'and available in your $PATH.' ) 2874 | 2875 | def buildLinks( self, net): 2876 | # Make links 2877 | print "Getting Links." 2878 | for key,link in self.links.iteritems(): 2879 | tags = self.canvas.gettags(key) 2880 | if 'data' in tags: 2881 | src=link['src'] 2882 | dst=link['dest'] 2883 | linkopts=link['linkOpts'] 2884 | srcName, dstName = src[ 'text' ], dst[ 'text' ] 2885 | srcNode, dstNode = net.nameToNode[ srcName ], net.nameToNode[ dstName ] 2886 | if linkopts: 2887 | net.addLink(srcNode, dstNode, cls=TCLink, **linkopts) 2888 | else: 2889 | #print str(srcNode) 2890 | #print str(dstNode) 2891 | net.addLink(srcNode, dstNode) 2892 | self.canvas.itemconfig(key, dash=()) 2893 | 2894 | 2895 | def build( self ): 2896 | print "Build network based on our topology." 2897 | 2898 | dpctl = None 2899 | if len(self.appPrefs['dpctl']) > 0: 2900 | dpctl = int(self.appPrefs['dpctl']) 2901 | net = Mininet( topo=None, 2902 | listenPort=dpctl, 2903 | build=False, 2904 | ipBase=self.appPrefs['ipBase'] ) 2905 | 2906 | self.buildNodes(net) 2907 | self.buildLinks(net) 2908 | 2909 | # Build network (we have to do this separately at the moment ) 2910 | net.build() 2911 | 2912 | return net 2913 | 2914 | 2915 | def postStartSetup( self ): 2916 | 2917 | # Setup host details 2918 | for widget in self.widgetToItem: 2919 | name = widget[ 'text' ] 2920 | tags = self.canvas.gettags( self.widgetToItem[ widget ] ) 2921 | if 'Host' in tags: 2922 | newHost = self.net.get(name) 2923 | opts = self.hostOpts[name] 2924 | # Attach vlan interfaces 2925 | if 'vlanInterfaces' in opts: 2926 | for vlanInterface in opts['vlanInterfaces']: 2927 | print 'adding vlan interface '+vlanInterface[1] 2928 | newHost.cmdPrint('ifconfig '+name+'-eth0.'+vlanInterface[1]+' '+vlanInterface[0]) 2929 | # Run User Defined Start Command 2930 | if 'startCommand' in opts: 2931 | newHost.cmdPrint(opts['startCommand']) 2932 | if 'Switch' in tags: 2933 | newNode = self.net.get(name) 2934 | opts = self.switchOpts[name] 2935 | # Run User Defined Start Command 2936 | if 'startCommand' in opts: 2937 | newNode.cmdPrint(opts['startCommand']) 2938 | 2939 | 2940 | # Configure NetFlow 2941 | nflowValues = self.appPrefs['netflow'] 2942 | if len(nflowValues['nflowTarget']) > 0: 2943 | nflowEnabled = False 2944 | nflowSwitches = '' 2945 | for widget in self.widgetToItem: 2946 | name = widget[ 'text' ] 2947 | tags = self.canvas.gettags( self.widgetToItem[ widget ] ) 2948 | 2949 | if 'Switch' in tags: 2950 | opts = self.switchOpts[name] 2951 | if 'netflow' in opts: 2952 | if opts['netflow'] == '1': 2953 | print name+' has Netflow enabled' 2954 | nflowSwitches = nflowSwitches+' -- set Bridge '+name+' netflow=@MiniEditNF' 2955 | nflowEnabled=True 2956 | if nflowEnabled: 2957 | nflowCmd = 'ovs-vsctl -- --id=@MiniEditNF create NetFlow '+ 'target=\\\"'+nflowValues['nflowTarget']+'\\\" '+ 'active-timeout='+nflowValues['nflowTimeout'] 2958 | if nflowValues['nflowAddId'] == '1': 2959 | nflowCmd = nflowCmd + ' add_id_to_interface=true' 2960 | else: 2961 | nflowCmd = nflowCmd + ' add_id_to_interface=false' 2962 | print 'cmd = '+nflowCmd+nflowSwitches 2963 | call(nflowCmd+nflowSwitches, shell=True) 2964 | 2965 | else: 2966 | print 'No switches with Netflow' 2967 | else: 2968 | print 'No NetFlow targets specified.' 2969 | 2970 | # Configure sFlow 2971 | sflowValues = self.appPrefs['sflow'] 2972 | if len(sflowValues['sflowTarget']) > 0: 2973 | sflowEnabled = False 2974 | sflowSwitches = '' 2975 | for widget in self.widgetToItem: 2976 | name = widget[ 'text' ] 2977 | tags = self.canvas.gettags( self.widgetToItem[ widget ] ) 2978 | 2979 | if 'Switch' in tags: 2980 | opts = self.switchOpts[name] 2981 | if 'sflow' in opts: 2982 | if opts['sflow'] == '1': 2983 | print name+' has sflow enabled' 2984 | sflowSwitches = sflowSwitches+' -- set Bridge '+name+' sflow=@MiniEditSF' 2985 | sflowEnabled=True 2986 | if sflowEnabled: 2987 | sflowCmd = 'ovs-vsctl -- --id=@MiniEditSF create sFlow '+ 'target=\\\"'+sflowValues['sflowTarget']+'\\\" '+ 'header='+sflowValues['sflowHeader']+' '+ 'sampling='+sflowValues['sflowSampling']+' '+ 'polling='+sflowValues['sflowPolling'] 2988 | print 'cmd = '+sflowCmd+sflowSwitches 2989 | call(sflowCmd+sflowSwitches, shell=True) 2990 | 2991 | else: 2992 | print 'No switches with sflow' 2993 | else: 2994 | print 'No sFlow targets specified.' 2995 | 2996 | ## NOTE: MAKE SURE THIS IS LAST THING CALLED 2997 | # Start the CLI if enabled 2998 | if self.appPrefs['startCLI'] == '1': 2999 | info( "\n\n NOTE: PLEASE REMEMBER TO EXIT THE CLI BEFORE YOU PRESS THE STOP BUTTON. Not exiting will prevent MiniEdit from quitting and will prevent you from starting the network again during this sessoin.\n\n") 3000 | CLI(self.net) 3001 | 3002 | def start( self ): 3003 | "Start network." 3004 | if self.net is None: 3005 | self.net = self.build() 3006 | 3007 | # Since I am going to inject per switch controllers. 3008 | # I can't call net.start(). I have to replicate what it 3009 | # does and add the controller options. 3010 | #self.net.start() 3011 | info( '**** Starting %s controllers\n' % len( self.net.controllers ) ) 3012 | for controller in self.net.controllers: 3013 | info( str(controller) + ' ') 3014 | controller.start() 3015 | info('\n') 3016 | info( '**** Starting %s switches\n' % len( self.net.switches ) ) 3017 | #for switch in self.net.switches: 3018 | # info( switch.name + ' ') 3019 | # switch.start( self.net.controllers ) 3020 | for widget in self.widgetToItem: 3021 | name = widget[ 'text' ] 3022 | tags = self.canvas.gettags( self.widgetToItem[ widget ] ) 3023 | if 'Switch' in tags: 3024 | opts = self.switchOpts[name] 3025 | switchControllers = [] 3026 | for ctrl in opts['controllers']: 3027 | switchControllers.append(self.net.get(ctrl)) 3028 | info( name + ' ') 3029 | # Figure out what controllers will manage this switch 3030 | self.net.get(name).start( switchControllers ) 3031 | if 'LegacySwitch' in tags: 3032 | self.net.get(name).start( [] ) 3033 | info( name + ' ') 3034 | info('\n') 3035 | 3036 | self.postStartSetup() 3037 | 3038 | def stop( self ): 3039 | "Stop network." 3040 | if self.net is not None: 3041 | # Stop host details 3042 | for widget in self.widgetToItem: 3043 | name = widget[ 'text' ] 3044 | tags = self.canvas.gettags( self.widgetToItem[ widget ] ) 3045 | if 'Host' in tags: 3046 | newHost = self.net.get(name) 3047 | opts = self.hostOpts[name] 3048 | # Run User Defined Stop Command 3049 | if 'stopCommand' in opts: 3050 | newHost.cmdPrint(opts['stopCommand']) 3051 | if 'Switch' in tags: 3052 | newNode = self.net.get(name) 3053 | opts = self.switchOpts[name] 3054 | # Run User Defined Stop Command 3055 | if 'stopCommand' in opts: 3056 | newNode.cmdPrint(opts['stopCommand']) 3057 | 3058 | self.net.stop() 3059 | cleanUpScreens() 3060 | self.net = None 3061 | 3062 | def do_linkPopup(self, event): 3063 | # display the popup menu 3064 | if self.net is None: 3065 | try: 3066 | self.linkPopup.tk_popup(event.x_root, event.y_root, 0) 3067 | finally: 3068 | # make sure to release the grab (Tk 8.0a1 only) 3069 | self.linkPopup.grab_release() 3070 | else: 3071 | try: 3072 | self.linkRunPopup.tk_popup(event.x_root, event.y_root, 0) 3073 | finally: 3074 | # make sure to release the grab (Tk 8.0a1 only) 3075 | self.linkRunPopup.grab_release() 3076 | 3077 | def do_controllerPopup(self, event): 3078 | # display the popup menu 3079 | if self.net is None: 3080 | try: 3081 | self.controllerPopup.tk_popup(event.x_root, event.y_root, 0) 3082 | finally: 3083 | # make sure to release the grab (Tk 8.0a1 only) 3084 | self.controllerPopup.grab_release() 3085 | 3086 | def do_legacyRouterPopup(self, event): 3087 | # display the popup menu 3088 | if self.net is not None: 3089 | try: 3090 | self.legacyRouterRunPopup.tk_popup(event.x_root, event.y_root, 0) 3091 | finally: 3092 | # make sure to release the grab (Tk 8.0a1 only) 3093 | self.legacyRouterRunPopup.grab_release() 3094 | 3095 | def do_hostPopup(self, event): 3096 | # display the popup menu 3097 | if self.net is None: 3098 | try: 3099 | self.hostPopup.tk_popup(event.x_root, event.y_root, 0) 3100 | finally: 3101 | # make sure to release the grab (Tk 8.0a1 only) 3102 | self.hostPopup.grab_release() 3103 | else: 3104 | try: 3105 | self.hostRunPopup.tk_popup(event.x_root, event.y_root, 0) 3106 | finally: 3107 | # make sure to release the grab (Tk 8.0a1 only) 3108 | self.hostRunPopup.grab_release() 3109 | 3110 | def do_legacySwitchPopup(self, event): 3111 | # display the popup menu 3112 | if self.net is not None: 3113 | try: 3114 | self.switchRunPopup.tk_popup(event.x_root, event.y_root, 0) 3115 | finally: 3116 | # make sure to release the grab (Tk 8.0a1 only) 3117 | self.switchRunPopup.grab_release() 3118 | 3119 | def do_switchPopup(self, event): 3120 | # display the popup menu 3121 | if self.net is None: 3122 | try: 3123 | self.switchPopup.tk_popup(event.x_root, event.y_root, 0) 3124 | finally: 3125 | # make sure to release the grab (Tk 8.0a1 only) 3126 | self.switchPopup.grab_release() 3127 | else: 3128 | try: 3129 | self.switchRunPopup.tk_popup(event.x_root, event.y_root, 0) 3130 | finally: 3131 | # make sure to release the grab (Tk 8.0a1 only) 3132 | self.switchRunPopup.grab_release() 3133 | 3134 | def xterm( self, _ignore=None ): 3135 | "Make an xterm when a button is pressed." 3136 | if ( self.selection is None or 3137 | self.net is None or 3138 | self.selection not in self.itemToWidget ): 3139 | return 3140 | name = self.itemToWidget[ self.selection ][ 'text' ] 3141 | if name not in self.net.nameToNode: 3142 | return 3143 | term = makeTerm( self.net.nameToNode[ name ], 'Host', term=self.appPrefs['terminalType'] ) 3144 | if StrictVersion(MININET_VERSION) > StrictVersion('2.0'): 3145 | self.net.terms += term 3146 | else: 3147 | self.net.terms.append(term) 3148 | 3149 | def iperf( self, _ignore=None ): 3150 | "Make an xterm when a button is pressed." 3151 | if ( self.selection is None or 3152 | self.net is None or 3153 | self.selection not in self.itemToWidget ): 3154 | return 3155 | name = self.itemToWidget[ self.selection ][ 'text' ] 3156 | if name not in self.net.nameToNode: 3157 | return 3158 | self.net.nameToNode[ name ].cmd( 'iperf -s -p 5001 &' ) 3159 | 3160 | ### BELOW HERE IS THE TOPOLOGY IMPORT CODE ### 3161 | 3162 | def parseArgs( self ): 3163 | """Parse command-line args and return options object. 3164 | returns: opts parse options dict""" 3165 | 3166 | if '--custom' in sys.argv: 3167 | index = sys.argv.index( '--custom' ) 3168 | if len( sys.argv ) > index + 1: 3169 | filename = sys.argv[ index + 1 ] 3170 | self.parseCustomFile( filename ) 3171 | else: 3172 | raise Exception( 'Custom file name not found' ) 3173 | 3174 | desc = ( "The %prog utility creates Mininet network from the\n" 3175 | "command line. It can create parametrized topologies,\n" 3176 | "invoke the Mininet CLI, and run tests." ) 3177 | 3178 | usage = ( '%prog [options]\n' 3179 | '(type %prog -h for details)' ) 3180 | 3181 | opts = OptionParser( description=desc, usage=usage ) 3182 | 3183 | addDictOption( opts, TOPOS, TOPODEF, 'topo' ) 3184 | addDictOption( opts, LINKS, LINKDEF, 'link' ) 3185 | 3186 | opts.add_option( '--custom', type='string', default=None, 3187 | help='read custom topo and node params from .py' + 3188 | 'file' ) 3189 | 3190 | self.options, self.args = opts.parse_args() 3191 | # We don't accept extra arguments after the options 3192 | if self.args: 3193 | opts.print_help() 3194 | exit() 3195 | 3196 | def setCustom( self, name, value ): 3197 | "Set custom parameters for MininetRunner." 3198 | if name in ( 'topos', 'switches', 'hosts', 'controllers' ): 3199 | # Update dictionaries 3200 | param = name.upper() 3201 | globals()[ param ].update( value ) 3202 | elif name == 'validate': 3203 | # Add custom validate function 3204 | self.validate = value 3205 | else: 3206 | # Add or modify global variable or class 3207 | globals()[ name ] = value 3208 | 3209 | def parseCustomFile( self, fileName ): 3210 | "Parse custom file and add params before parsing cmd-line options." 3211 | customs = {} 3212 | if os.path.isfile( fileName ): 3213 | execfile( fileName, customs, customs ) 3214 | for name, val in customs.iteritems(): 3215 | self.setCustom( name, val ) 3216 | else: 3217 | raise Exception( 'could not find custom file: %s' % fileName ) 3218 | 3219 | def importTopo( self ): 3220 | print 'topo='+self.options.topo 3221 | if self.options.topo == 'none': 3222 | return 3223 | self.newTopology() 3224 | topo = buildTopo( TOPOS, self.options.topo ) 3225 | link = customClass( LINKS, self.options.link ) 3226 | importNet = Mininet(topo=topo, build=False, link=link) 3227 | importNet.build() 3228 | 3229 | c = self.canvas 3230 | rowIncrement = 100 3231 | currentY = 100 3232 | 3233 | # Add Controllers 3234 | print 'controllers:'+str(len(importNet.controllers)) 3235 | for controller in importNet.controllers: 3236 | name = controller.name 3237 | x = self.controllerCount*100+100 3238 | self.addNode('Controller', self.controllerCount, 3239 | float(x), float(currentY), name=name) 3240 | icon = self.findWidgetByName(name) 3241 | icon.bind('', self.do_controllerPopup ) 3242 | ctrlr = { 'controllerType': 'ref', 3243 | 'hostname': name, 3244 | 'controllerProtocol': controller.protocol, 3245 | 'remoteIP': controller.ip, 3246 | 'remotePort': controller.port} 3247 | self.controllers[name] = ctrlr 3248 | 3249 | 3250 | 3251 | currentY = currentY + rowIncrement 3252 | 3253 | # Add switches 3254 | print 'switches:'+str(len(importNet.switches)) 3255 | columnCount = 0 3256 | for switch in importNet.switches: 3257 | name = switch.name 3258 | self.switchOpts[name] = {} 3259 | self.switchOpts[name]['nodeNum']=self.switchCount 3260 | self.switchOpts[name]['hostname']=name 3261 | self.switchOpts[name]['switchType']='default' 3262 | self.switchOpts[name]['controllers']=[] 3263 | 3264 | x = columnCount*100+100 3265 | self.addNode('Switch', self.switchCount, 3266 | float(x), float(currentY), name=name) 3267 | icon = self.findWidgetByName(name) 3268 | icon.bind('', self.do_switchPopup ) 3269 | # Now link to controllers 3270 | for controller in importNet.controllers: 3271 | self.switchOpts[name]['controllers'].append(controller.name) 3272 | dest = self.findWidgetByName(controller.name) 3273 | dx, dy = c.coords( self.widgetToItem[ dest ] ) 3274 | self.link = c.create_line(float(x), 3275 | float(currentY), 3276 | dx, 3277 | dy, 3278 | width=4, 3279 | fill='red', 3280 | dash=(6, 4, 2, 4), 3281 | tag='link' ) 3282 | c.itemconfig(self.link, tags=c.gettags(self.link)+('control',)) 3283 | self.addLink( icon, dest, linktype='control' ) 3284 | self.createControlLinkBindings() 3285 | self.link = self.linkWidget = None 3286 | if columnCount == 9: 3287 | columnCount = 0 3288 | currentY = currentY + rowIncrement 3289 | else: 3290 | columnCount =columnCount+1 3291 | 3292 | 3293 | currentY = currentY + rowIncrement 3294 | # Add hosts 3295 | print 'hosts:'+str(len(importNet.hosts)) 3296 | columnCount = 0 3297 | for host in importNet.hosts: 3298 | name = host.name 3299 | self.hostOpts[name] = {'sched':'host'} 3300 | self.hostOpts[name]['nodeNum']=self.hostCount 3301 | self.hostOpts[name]['hostname']=name 3302 | self.hostOpts[name]['ip']=host.IP() 3303 | 3304 | x = columnCount*100+100 3305 | self.addNode('Host', self.hostCount, 3306 | float(x), float(currentY), name=name) 3307 | icon = self.findWidgetByName(name) 3308 | icon.bind('', self.do_hostPopup ) 3309 | if columnCount == 9: 3310 | columnCount = 0 3311 | currentY = currentY + rowIncrement 3312 | else: 3313 | columnCount =columnCount+1 3314 | 3315 | print 'links:'+str(len(topo.links())) 3316 | #[('h1', 's3'), ('h2', 's4'), ('s3', 's4')] 3317 | for link in topo.links(): 3318 | print str(link) 3319 | srcNode = link[0] 3320 | src = self.findWidgetByName(srcNode) 3321 | sx, sy = self.canvas.coords( self.widgetToItem[ src ] ) 3322 | 3323 | destNode = link[1] 3324 | dest = self.findWidgetByName(destNode) 3325 | dx, dy = self.canvas.coords( self.widgetToItem[ dest] ) 3326 | 3327 | params = topo.linkInfo( srcNode, destNode ) 3328 | print 'Link Parameters='+str(params) 3329 | 3330 | self.link = self.canvas.create_line( sx, sy, dx, dy, width=4, 3331 | fill='blue', tag='link' ) 3332 | c.itemconfig(self.link, tags=c.gettags(self.link)+('data',)) 3333 | self.addLink( src, dest, linkopts=params ) 3334 | self.createDataLinkBindings() 3335 | self.link = self.linkWidget = None 3336 | 3337 | importNet.stop() 3338 | 3339 | def miniEditImages(): 3340 | "Create and return images for MiniEdit." 3341 | 3342 | # Image data. Git will be unhappy. However, the alternative 3343 | # is to keep track of separate binary files, which is also 3344 | # unappealing. 3345 | 3346 | return { 3347 | 'Select': BitmapImage( 3348 | file='/usr/include/X11/bitmaps/left_ptr' ), 3349 | 3350 | 'Switch': PhotoImage( data=r""" 3351 | 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 3382 | """), 3383 | 3384 | 'LegacySwitch': PhotoImage( data=r""" 3385 | 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 3416 | 4BE2eIRYeHAEIBwBP0Y4Qn41YWRSCQgAOw== 3417 | """), 3418 | 3419 | 'LegacyRouter': PhotoImage( data=r""" 3420 | R0lGODlhMgAYAPcAAAEBAXZ8gQNAgL29vQNctjl/xVSa4j1dfCF+3QFq1DmL3wJMmAMzZZW11dnZ 3421 | 2SFrtyNdmTSO6gIZMUKa8gJVqEOHzR9Pf5W74wFjxgFx4jltn+np6Eyi+DuT6qKiohdtwwUPGWiq 3422 | 6ymF4LHH3Rh11CV81kKT5AMoUA9dq1ap/mV0gxdXlytRdR1ptRNPjTt9vwNgvwJZsX+69gsXJQFH 3423 | jTtjizF0tvHx8VOm9z2V736Dhz2N3QM2acPZ70qe8gFo0HS19wVRnTiR6hMpP0eP1i6J5iNlqAtg 3424 | tktjfQFu3TNxryx4xAMTIzOE1XqAh1uf5SWC4AcfNy1XgQJny93n8a2trRh312Gt+VGm/AQIDTmB 3425 | yAF37QJasydzvxM/ayF3zhdLf8zLywFdu4i56gFlyi2J4yV/1w8wUo2/8j+X8D2Q5Eee9jeR7Uia 3426 | 7DpeggFt2QNPm97e3jRong9bpziH2DuT7aipqQoVICmG45vI9R5720eT4Q1hs1er/yVVhwJJktPh 3427 | 70tfdbHP7Xev5xs5V7W1sz9jhz11rUVZcQ9WoCVVhQk7cRdtwWuw9QYOFyFHbSBnr0dznxtWkS18 3428 | zKfP9wwcLAMHCwFFiS5UeqGtuRNNiwMfPS1hlQMtWRE5XzGM5yhxusLCwCljnwMdOFWh7cve8pG/ 3429 | 7Tlxp+Tr8g9bpXF3f0lheStrrYu13QEXLS1ppTV3uUuR1RMjNTF3vU2X4TZupwRSolNne4nB+T+L 3430 | 2YGz4zJ/zYe99YGHjRdDcT95sx09XQldsgMLEwMrVc/X3yN3yQ1JhTRbggsdMQNfu9HPz6WlpW2t 3431 | 7RctQ0GFyeHh4dvl8SBZklCb5kOO2kWR3Vmt/zdjkQIQHi90uvPz8wIVKBp42SV5zbfT7wtXpStV 3432 | fwFWrBVvyTt3swFz5kGBv2+1/QlbrVFjdQM7d1+j54i67UmX51qn9i1vsy+D2TuR5zddhQsjOR1t 3433 | u0GV6ghbsDVZf4+76RRisent8Xd9hQFBgwFNmwJLlcPDwwFr1z2T5yH5BAEAAAAALAAAAAAyABgA 3434 | Bwj/AAEIHEiQYJY7Qwg9UsTplRIbENuxEiXJgpcz8e5YKsixY8Essh7JcbbOBwcOa1JOmJAmTY4c 3435 | HeoIabJrCShI0XyB8YRso0eOjoAdWpciBZajJ1GuWcnSZY46Ed5N8hPATqEBoRB9gVJsxRlhPwHI 3436 | 0kDkVywcRpGe9LF0adOnMpt8CxDnxg1o9lphKoEACoIvmlxxvHOKVg0n/Tzku2WoVoU2J1P6WNkS 3437 | rtwADuxCG/MOjwgRUEIjGG3FhaOBzaThiDSCil27G8Isc3LLjZwXsA6YYJmDjhTMmseoKQIFDx7R 3438 | oxHo2abnwygAlUj1mV6tWjlelEpRwfd6gzI7VeJQ/2vZoVaDUqigqftXpH0R46H9Kl++zUo4JnKq 3439 | 9dGvv09RHFhcIUMe0NiFDyql0OJUHWywMc87TXRhhCRGiHAccvNZUR8JxpDTH38p9HEUFhxgMSAv 3440 | jbBjQge8PSXEC6uo0IsHA6gAAShmgCbffNtsQwIJifhRHX/TpUUiSijlUk8AqgQixSwdNBjCa7CF 3441 | oVggmEgCyRf01WcFCYvYUgB104k4YlK5HONEXXfpokYdMrXRAzMhmNINNNzB9p0T57AgyZckpKKP 3442 | GFNgw06ZWKR10jTw6MAmFWj4AJcQQkQQwSefvFeGCemMIQggeaJywSQ/wgHOAmJskQEfWqBlFBEH 3443 | 1P/QaGY3QOpDZXA2+A6m7hl3IRQKGDCIAj6iwE8yGKC6xbJv8IHNHgACQQybN2QiTi5NwdlBpZdi 3444 | isd7vyanByOJ7CMGGRhgwE+qyy47DhnBPLDLEzLIAEQjBtChRmVPNWgpr+Be+Nc9icARww9TkIEu 3445 | DAsQ0O7DzGIQzD2QdDEJHTsIAROc3F7qWQncyHPPHN5QQAAG/vjzw8oKp8sPPxDH3O44/kwBQzLB 3446 | xBCMOTzzHEMMBMBARgJvZJBBEm/4k0ACKydMBgwYoKNNEjJXbTXE42Q9jtFIp8z0Dy1jQMA1AGzi 3447 | z9VoW7310V0znYDTGMQgwUDXLDBO2nhvoTXbbyRk/XXL+pxWkAT8UJ331WsbnbTSK8MggDZhCTOM 3448 | LQkcjvXeSPedAAw0nABWWARZIgEDfyTzxt15Z53BG1PEcEknrvgEelhZMDHKCTwI8EcQFHBBAAFc 3449 | gGPLHwLwcMIo12Qxu0ABAQA7 3450 | """), 3451 | 3452 | 'Controller': PhotoImage( data=r""" 3453 | R0lGODlhMAAwAPcAAAEBAWfNAYWFhcfHx+3t6/f390lJUaWlpfPz8/Hx72lpaZGRke/v77m5uc0B 3454 | AeHh4e/v7WNjY3t7e5eXlyMjI4mJidPT0+3t7f///09PT7Ozs/X19fHx8ZWTk8HBwX9/fwAAAAAA 3455 | AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA 3456 | AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA 3457 | AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA 3458 | AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA 3459 | AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA 3460 | AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA 3461 | AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA 3462 | AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA 3463 | AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA 3464 | AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA 3465 | AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA 3466 | AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAEAAAAALAAAAAAwADAA 3467 | Bwj/AAEIHEiwoMGDCBMqXMiwocOHECNKnEixosWLGAEIeMCxo8ePHwVkBGABg8mTKFOmtDByAIYN 3468 | MGPCRCCzQIENNzEMGOkBAwIKQIMKpYCgKAIHCDB4GNkAA4OnUJ9++CDhQ1QGFzA0GKkBA4GvYMOK 3469 | BYtBA1cNaNOqXcuWq8q3b81m7Cqzbk2bMMu6/Tl0qFEEAZLKxdj1KlSqVA3rnet1rOOwiwmznUzZ 3470 | LdzLJgdfpIv3pmebN2Pm1GyRbocNp1PLNMDaAM3Im1/alQk4gO28pCt2RdCBt+/eRg8IP1AUdmmf 3471 | f5MrL56bYlcOvaP7Xo6Ag3HdGDho3869u/YE1507t+3AgLz58ujPMwg/sTBUCAzgy49PH0LW5u0x 3472 | XFiwvz////5dcJ9bjxVIAHsSdUXAAgs2yOCDDn6FYEQaFGDgYxNCpEFfHHKIX4IDhCjiiCSS+CGF 3473 | FlCmogYpcnVABTDGKGOMAlRQYwUHnKjhAjX2aOOPN8LImgAL6PiQBhLMqCSNAThQgQRGOqRBBD1W 3474 | aaOVAggnQARRNqRBBxmEKeaYZIrZQZcMKbDiigqM5OabcMYp55x01ilnQAA7 3475 | """), 3476 | 3477 | 'Host': PhotoImage( data=r""" 3478 | R0lGODlhIAAYAPcAMf//////zP//mf//Zv//M///AP/M///MzP/M 3479 | mf/MZv/MM//MAP+Z//+ZzP+Zmf+ZZv+ZM/+ZAP9m//9mzP9mmf9m 3480 | Zv9mM/9mAP8z//8zzP8zmf8zZv8zM/8zAP8A//8AzP8Amf8AZv8A 3481 | M/8AAMz//8z/zMz/mcz/Zsz/M8z/AMzM/8zMzMzMmczMZszMM8zM 3482 | AMyZ/8yZzMyZmcyZZsyZM8yZAMxm/8xmzMxmmcxmZsxmM8xmAMwz 3483 | /8wzzMwzmcwzZswzM8wzAMwA/8wAzMwAmcwAZswAM8wAAJn//5n/ 3484 | zJn/mZn/Zpn/M5n/AJnM/5nMzJnMmZnMZpnMM5nMAJmZ/5mZzJmZ 3485 | mZmZZpmZM5mZAJlm/5lmzJlmmZlmZplmM5lmAJkz/5kzzJkzmZkz 3486 | ZpkzM5kzAJkA/5kAzJkAmZkAZpkAM5kAAGb//2b/zGb/mWb/Zmb/ 3487 | M2b/AGbM/2bMzGbMmWbMZmbMM2bMAGaZ/2aZzGaZmWaZZmaZM2aZ 3488 | AGZm/2ZmzGZmmWZmZmZmM2ZmAGYz/2YzzGYzmWYzZmYzM2YzAGYA 3489 | /2YAzGYAmWYAZmYAM2YAADP//zP/zDP/mTP/ZjP/MzP/ADPM/zPM 3490 | zDPMmTPMZjPMMzPMADOZ/zOZzDOZmTOZZjOZMzOZADNm/zNmzDNm 3491 | mTNmZjNmMzNmADMz/zMzzDMzmTMzZjMzMzMzADMA/zMAzDMAmTMA 3492 | ZjMAMzMAAAD//wD/zAD/mQD/ZgD/MwD/AADM/wDMzADMmQDMZgDM 3493 | MwDMAACZ/wCZzACZmQCZZgCZMwCZAABm/wBmzABmmQBmZgBmMwBm 3494 | AAAz/wAzzAAzmQAzZgAzMwAzAAAA/wAAzAAAmQAAZgAAM+4AAN0A 3495 | ALsAAKoAAIgAAHcAAFUAAEQAACIAABEAAADuAADdAAC7AACqAACI 3496 | AAB3AABVAABEAAAiAAARAAAA7gAA3QAAuwAAqgAAiAAAdwAAVQAA 3497 | RAAAIgAAEe7u7t3d3bu7u6qqqoiIiHd3d1VVVURERCIiIhEREQAA 3498 | ACH5BAEAAAAALAAAAAAgABgAAAiNAAH8G0iwoMGDCAcKTMiw4UBw 3499 | BPXVm0ixosWLFvVBHFjPoUeC9Tb+6/jRY0iQ/8iVbHiS40CVKxG2 3500 | HEkQZsyCM0mmvGkw50uePUV2tEnOZkyfQA8iTYpTKNOgKJ+C3AhO 3501 | p9SWVaVOfWj1KdauTL9q5UgVbFKsEjGqXVtP40NwcBnCjXtw7tx/ 3502 | C8cSBBAQADs= 3503 | """ ), 3504 | 3505 | 'OldSwitch': PhotoImage( data=r""" 3506 | R0lGODlhIAAYAPcAMf//////zP//mf//Zv//M///AP/M///MzP/M 3507 | mf/MZv/MM//MAP+Z//+ZzP+Zmf+ZZv+ZM/+ZAP9m//9mzP9mmf9m 3508 | Zv9mM/9mAP8z//8zzP8zmf8zZv8zM/8zAP8A//8AzP8Amf8AZv8A 3509 | M/8AAMz//8z/zMz/mcz/Zsz/M8z/AMzM/8zMzMzMmczMZszMM8zM 3510 | AMyZ/8yZzMyZmcyZZsyZM8yZAMxm/8xmzMxmmcxmZsxmM8xmAMwz 3511 | /8wzzMwzmcwzZswzM8wzAMwA/8wAzMwAmcwAZswAM8wAAJn//5n/ 3512 | zJn/mZn/Zpn/M5n/AJnM/5nMzJnMmZnMZpnMM5nMAJmZ/5mZzJmZ 3513 | mZmZZpmZM5mZAJlm/5lmzJlmmZlmZplmM5lmAJkz/5kzzJkzmZkz 3514 | ZpkzM5kzAJkA/5kAzJkAmZkAZpkAM5kAAGb//2b/zGb/mWb/Zmb/ 3515 | M2b/AGbM/2bMzGbMmWbMZmbMM2bMAGaZ/2aZzGaZmWaZZmaZM2aZ 3516 | AGZm/2ZmzGZmmWZmZmZmM2ZmAGYz/2YzzGYzmWYzZmYzM2YzAGYA 3517 | /2YAzGYAmWYAZmYAM2YAADP//zP/zDP/mTP/ZjP/MzP/ADPM/zPM 3518 | zDPMmTPMZjPMMzPMADOZ/zOZzDOZmTOZZjOZMzOZADNm/zNmzDNm 3519 | mTNmZjNmMzNmADMz/zMzzDMzmTMzZjMzMzMzADMA/zMAzDMAmTMA 3520 | ZjMAMzMAAAD//wD/zAD/mQD/ZgD/MwD/AADM/wDMzADMmQDMZgDM 3521 | MwDMAACZ/wCZzACZmQCZZgCZMwCZAABm/wBmzABmmQBmZgBmMwBm 3522 | AAAz/wAzzAAzmQAzZgAzMwAzAAAA/wAAzAAAmQAAZgAAM+4AAN0A 3523 | ALsAAKoAAIgAAHcAAFUAAEQAACIAABEAAADuAADdAAC7AACqAACI 3524 | AAB3AABVAABEAAAiAAARAAAA7gAA3QAAuwAAqgAAiAAAdwAAVQAA 3525 | RAAAIgAAEe7u7t3d3bu7u6qqqoiIiHd3d1VVVURERCIiIhEREQAA 3526 | ACH5BAEAAAAALAAAAAAgABgAAAhwAAEIHEiwoMGDCBMqXMiwocOH 3527 | ECNKnEixosWB3zJq3Mixo0eNAL7xG0mypMmTKPl9Cznyn8uWL/m5 3528 | /AeTpsyYI1eKlBnO5r+eLYHy9Ck0J8ubPmPOrMmUpM6UUKMa/Ui1 3529 | 6saLWLNq3cq1q9evYB0GBAA7 3530 | """ ), 3531 | 3532 | 'NetLink': PhotoImage( data=r""" 3533 | R0lGODlhFgAWAPcAMf//////zP//mf//Zv//M///AP/M///MzP/M 3534 | mf/MZv/MM//MAP+Z//+ZzP+Zmf+ZZv+ZM/+ZAP9m//9mzP9mmf9m 3535 | Zv9mM/9mAP8z//8zzP8zmf8zZv8zM/8zAP8A//8AzP8Amf8AZv8A 3536 | M/8AAMz//8z/zMz/mcz/Zsz/M8z/AMzM/8zMzMzMmczMZszMM8zM 3537 | AMyZ/8yZzMyZmcyZZsyZM8yZAMxm/8xmzMxmmcxmZsxmM8xmAMwz 3538 | /8wzzMwzmcwzZswzM8wzAMwA/8wAzMwAmcwAZswAM8wAAJn//5n/ 3539 | zJn/mZn/Zpn/M5n/AJnM/5nMzJnMmZnMZpnMM5nMAJmZ/5mZzJmZ 3540 | mZmZZpmZM5mZAJlm/5lmzJlmmZlmZplmM5lmAJkz/5kzzJkzmZkz 3541 | ZpkzM5kzAJkA/5kAzJkAmZkAZpkAM5kAAGb//2b/zGb/mWb/Zmb/ 3542 | M2b/AGbM/2bMzGbMmWbMZmbMM2bMAGaZ/2aZzGaZmWaZZmaZM2aZ 3543 | AGZm/2ZmzGZmmWZmZmZmM2ZmAGYz/2YzzGYzmWYzZmYzM2YzAGYA 3544 | /2YAzGYAmWYAZmYAM2YAADP//zP/zDP/mTP/ZjP/MzP/ADPM/zPM 3545 | zDPMmTPMZjPMMzPMADOZ/zOZzDOZmTOZZjOZMzOZADNm/zNmzDNm 3546 | mTNmZjNmMzNmADMz/zMzzDMzmTMzZjMzMzMzADMA/zMAzDMAmTMA 3547 | ZjMAMzMAAAD//wD/zAD/mQD/ZgD/MwD/AADM/wDMzADMmQDMZgDM 3548 | MwDMAACZ/wCZzACZmQCZZgCZMwCZAABm/wBmzABmmQBmZgBmMwBm 3549 | AAAz/wAzzAAzmQAzZgAzMwAzAAAA/wAAzAAAmQAAZgAAM+4AAN0A 3550 | ALsAAKoAAIgAAHcAAFUAAEQAACIAABEAAADuAADdAAC7AACqAACI 3551 | AAB3AABVAABEAAAiAAARAAAA7gAA3QAAuwAAqgAAiAAAdwAAVQAA 3552 | RAAAIgAAEe7u7t3d3bu7u6qqqoiIiHd3d1VVVURERCIiIhEREQAA 3553 | ACH5BAEAAAAALAAAAAAWABYAAAhIAAEIHEiwoEGBrhIeXEgwoUKG 3554 | Cx0+hGhQoiuKBy1irChxY0GNHgeCDAlgZEiTHlFuVImRJUWXEGEy 3555 | lBmxI8mSNknm1Dnx5sCAADs= 3556 | """ ) 3557 | } 3558 | 3559 | def addDictOption( opts, choicesDict, default, name, helpStr=None ): 3560 | """Convenience function to add choices dicts to OptionParser. 3561 | opts: OptionParser instance 3562 | choicesDict: dictionary of valid choices, must include default 3563 | default: default choice key 3564 | name: long option name 3565 | help: string""" 3566 | if default not in choicesDict: 3567 | raise Exception( 'Invalid default %s for choices dict: %s' % 3568 | ( default, name ) ) 3569 | if not helpStr: 3570 | helpStr = ( '|'.join( sorted( choicesDict.keys() ) ) + 3571 | '[,param=value...]' ) 3572 | opts.add_option( '--' + name, 3573 | type='string', 3574 | default = default, 3575 | help = helpStr ) 3576 | 3577 | if __name__ == '__main__': 3578 | setLogLevel( 'info' ) 3579 | app = MiniEdit() 3580 | ### import topology if specified ### 3581 | app.parseArgs() 3582 | app.importTopo() 3583 | 3584 | app.mainloop() 3585 | -------------------------------------------------------------------------------- /plot.py: -------------------------------------------------------------------------------- 1 | import csv 2 | import matplotlib.pyplot as plt 3 | import numpy as np 4 | 5 | data = [] 6 | 7 | # loading load data without balance 8 | load = [] 9 | 10 | for i in range(0, 100): 11 | load.append(0) 12 | 13 | for i in range(1,4): 14 | fload = open("./log/log-c"+str(i)+"-load.txt") 15 | j = 0 16 | for row in csv.reader(fload): 17 | load[j] = load[j] + float(row[0])*1000 18 | j = j + 1 19 | 20 | 21 | # loading load data with balance 22 | bal = [] 23 | 24 | for i in range(0, 100): 25 | bal.append(0) 26 | 27 | for i in range(1,4): 28 | fbal = open("./log/log-c"+str(i)+"-bal.txt") 29 | j = 0 30 | for row in csv.reader(fbal): 31 | bal[j] = bal[j] + float(row[0])*1000 32 | j = j + 1 33 | 34 | 35 | for i in range(0, 100): 36 | load[i] = load[i]/3 37 | bal[i] = bal[i]/3 38 | 39 | data.append(load) 40 | data.append(bal) 41 | 42 | f = open("./log/log-local.txt") 43 | c = [] 44 | for row in csv.reader(f): 45 | c.append(float(row[0])*1000) 46 | 47 | data.append(c) 48 | 49 | # plotting graph 50 | x = np.linspace(0,len(data[0]), len(data[0])) 51 | for i in data: 52 | plt.plot(x, i) 53 | 54 | plt.legend(['Non-Balanced under load', 'Balanced under load', 'Off Load'], prop={'size': 6}) 55 | plt.xlabel("Number of Requests") 56 | plt.ylabel("Average Response Time (ms)") 57 | plt.show() 58 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Mininet SDNs and load balancing 2 | 3 | ### Introduction 4 | [Mininet](http://mininet.org/) creates a virtualized realistic Software defined Network and provides a feature rich Python API. This projects/repository deals with developing, testing and experimenting with emulated networks using Mininet. As of now two topologies Tree and Star having a total of 6 Nodes are created with different parameters to simulate actual networks. On these two topologies load testing and load balancing was simulated using python scripting. I apologize as the implementation as of now is not as per industry standards on many grounds but I look to improve that in the future. 5 | 6 |
7 | 8 | ### Topologies 9 | For the task of creation and development of the topologies the `Mini Edit` tool was used. It is python program that helps in creation of Network Topologies (also included in the repo `miniedit.py`). The following topologies where finally created with this tool each having 6 Nodes (hosts). For the purpose of service and client simulation, 2 Nodes out of 6 are used as server and the remaining 4 as clients. 10 | 11 |
12 | 13 |
14 | 15 | #### Star Topology 16 | 17 | 18 |
19 | 20 | #### Tree Topology 21 | 22 | 23 |
24 | 25 |
26 | 27 | ### Host/Node Programs 28 | Each host or node in this emulated network runs a python script. This python script describes the nature of the node. We have a client and server script that can be used to emulate a client making 100 requests repeatably to a simple HTTP server respectively. These scripts are configured to work with the topologies mentioned above (assuming that the service is present at 10.0.0.4). Additionally the client scripts are configured to log the response time from the server which will be used later to visualize results. 29 | 30 |
31 | 32 | ### Tests 33 | Their are three kind of tests that are run on both the topologies (tree and star) which are `base`, `load` and `bal`. The `base` test is basically a single client making request to a single server, this would act as the baseline for the rest of the two tests. The `load` test involves three clients making requests to the service ip which then redirects them to the actual single server, doing this we retrieve the latency being caused by the single server architecture in handling multiple requests. Last not the least `bal` test involves three client making requests to the service ip which then redirects them to server, out off 2 total servers available, by doing so we are able to determine how the response time reduces on addition of another server. 34 | 35 |
36 | 37 | ### Load balance 38 | We are using two algorithms for load balancing in the `bal` test. One is a simple Random algorithm and the other a bit more complex Round Robin. Though the implementation of service load balancing can be improved by the better use of controllers, better programming and knowledge of Networks (I am a newbie in this domain on Software Development), we are still able to get convincing results. 39 | 40 |
41 | 42 | ### Results 43 | Though I am using a simple redirect as a mean to load balance and escape from the complex logic required to program controllers (maybe in the future I'll update the project to use controllers efficiently) we still see promising out comes. The results show why load balancing is so important in the industry applications involving high traffic, high availability and low latency, as just adding one more server in the equation and providing load balancing (using algorithms like Round Robin) improves the response time by roughly 60% even in our trivial implementation. 44 | 45 | | Algorithm | Star Topology | Tree Topology | 46 | | --- | --- | --- | 47 | | Random | | | 48 | | Round Robin | | | 49 | 50 | Our results also show that using Random approach which does not provide any context to the service about the load on servers can lead to inconsistent response times, whereas Round Robin which at least promises to equally distribute requests among servers seems more promising. 51 | 52 |
53 | 54 | ### Future Work 55 | In the future I am looking to improve the quality of my existing work (using controllers more efficiently) including improving tests and configurations for the project. I also look to experiment with dynamic server allocation on increasing server load and sleeping servers when load reduces and other dynamic techniques that may help in increasing the server utilization, reduce the costs for providing services, reduce server response time and reduce overall latency in the client - server architecture and cloud applications. 56 | 57 |
58 | 59 |
60 | 61 | ##### Made with ❤️ 62 | 63 |
64 | -------------------------------------------------------------------------------- /results/star-RRBalancer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sarthakpranesh/Mininet-Load-Balancing/a8c716779716bdcde57a1e12c2d995e69dcaaa1e/results/star-RRBalancer.png -------------------------------------------------------------------------------- /results/star-randomBalancer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sarthakpranesh/Mininet-Load-Balancing/a8c716779716bdcde57a1e12c2d995e69dcaaa1e/results/star-randomBalancer.png -------------------------------------------------------------------------------- /results/starTopo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sarthakpranesh/Mininet-Load-Balancing/a8c716779716bdcde57a1e12c2d995e69dcaaa1e/results/starTopo.png -------------------------------------------------------------------------------- /results/tree-RRBalancer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sarthakpranesh/Mininet-Load-Balancing/a8c716779716bdcde57a1e12c2d995e69dcaaa1e/results/tree-RRBalancer.png -------------------------------------------------------------------------------- /results/tree-randomBalancer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sarthakpranesh/Mininet-Load-Balancing/a8c716779716bdcde57a1e12c2d995e69dcaaa1e/results/tree-randomBalancer.png -------------------------------------------------------------------------------- /results/treeTopo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sarthakpranesh/Mininet-Load-Balancing/a8c716779716bdcde57a1e12c2d995e69dcaaa1e/results/treeTopo.png -------------------------------------------------------------------------------- /server.py: -------------------------------------------------------------------------------- 1 | import SimpleHTTPServer 2 | import SocketServer 3 | import sys 4 | import time 5 | 6 | 7 | class MyHandler(SimpleHTTPServer.SimpleHTTPRequestHandler): 8 | def handle_one_request(self): 9 | time.sleep(0.1) 10 | return SimpleHTTPServer.SimpleHTTPRequestHandler.handle_one_request(self) 11 | 12 | print("Serving local directory") 13 | httpd = SocketServer.TCPServer(("", 8080), MyHandler) 14 | 15 | while True: 16 | httpd.handle_request() 17 | -------------------------------------------------------------------------------- /topo/starTopo.py: -------------------------------------------------------------------------------- 1 | import time 2 | from mininet.cli import CLI 3 | from mininet.net import Mininet 4 | from mininet.node import Controller, RemoteController, OVSController 5 | from mininet.node import CPULimitedHost, Host, Node 6 | from mininet.node import OVSKernelSwitch, UserSwitch 7 | from mininet.node import IVSSwitch 8 | from mininet.log import setLogLevel, info 9 | from mininet.link import TCLink, Intf 10 | import sys 11 | from tests import * 12 | 13 | def MyNetwork(): 14 | 15 | net = Mininet( topo=None, 16 | build=False, 17 | link=TCLink, 18 | ipBase='10.0.0.0/8') 19 | 20 | info( '*** Adding controller\n' ) 21 | con1=net.addController(name='con1', 22 | controller=Controller, 23 | protocol='tcp', 24 | port=6633) 25 | 26 | info( '*** Add switches\n') 27 | s1 = net.addSwitch('s1', cls=OVSKernelSwitch) 28 | 29 | info( '*** Add hosts\n') 30 | c4 = net.addHost('c4', cls=Host, ip='10.0.0.4', defaultRoute=None, cpu=0.2) 31 | 32 | c3 = net.addHost('c3', cls=Host, ip='10.0.0.3', defaultRoute=None, cpu=0.2) 33 | c2 = net.addHost('c2', cls=Host, ip='10.0.0.2', defaultRoute=None, cpu=0.2) 34 | c1 = net.addHost('c1', cls=Host, ip='10.0.0.1', defaultRoute=None, cpu=0.2) 35 | 36 | serv1 = net.addHost('serv1', cls=Host, ip='10.0.0.5', defaultRoute=None) 37 | serv2 = net.addHost('serv2', cls=Host, ip='10.0.0.6', defaultRoute=None) 38 | 39 | info( '*** Add links\n') 40 | net.addLink(c3, s1, bw=1) 41 | net.addLink(c2, s1, bw=1) 42 | net.addLink(c1, s1, bw=1) 43 | 44 | net.addLink(c4, s1, bw=10) 45 | net.addLink(s1, serv1, bw=4) 46 | net.addLink(s1, serv2, bw=4) 47 | 48 | info( '*** Starting network\n') 49 | net.build() 50 | info( '*** Starting controllers\n') 51 | for controller in net.controllers: 52 | controller.start() 53 | 54 | info( '*** Starting switches\n') 55 | net.get('s1').start([con1]) 56 | 57 | info( '*** Post configure switches and hosts\n') 58 | 59 | net.pingAll() 60 | net.iperf() 61 | 62 | if len(sys.argv) >= 2: 63 | if sys.argv[1] == "base": 64 | testbase(net) 65 | elif sys.argv[1] == "load": 66 | testload(net) 67 | elif sys.argv[1] == "bal": 68 | testloadbal(net) 69 | else: 70 | print("Unregonised test, starting cli") 71 | CLI(net) 72 | 73 | net.stop() 74 | 75 | 76 | if __name__ == '__main__': 77 | setLogLevel( 'info' ) 78 | MyNetwork() 79 | 80 | -------------------------------------------------------------------------------- /topo/tests.py: -------------------------------------------------------------------------------- 1 | import time 2 | 3 | def testbase(net): 4 | serv1 = net.get("serv1") 5 | serv1.sendCmd("python ../server.py &") 6 | 7 | net.get("c4").sendCmd("python ../loadBalanceNode.py n &") 8 | 9 | print("--> Starting load with one client - for base performance") 10 | 11 | net.get("c3").cmd("python ../client.py local") 12 | 13 | time.sleep(4) 14 | 15 | net.get("c4").monitor() 16 | print("---> Done") 17 | return 18 | 19 | def testload(net): 20 | serv1 = net.get("serv1") 21 | serv1.sendCmd('python ../server.py &') 22 | 23 | net.get("c4").sendCmd("python ../loadBalanceNode.py n &") 24 | 25 | print("--> Starting load with three client - for testing performance under extensive load") 26 | 27 | net.get("c1").sendCmd("python ../client.py c1-load &") 28 | net.get("c2").sendCmd("python ../client.py c2-load &") 29 | net.get("c3").sendCmd("python ../client.py c3-load") 30 | 31 | time.sleep(20) 32 | 33 | net.get("c1").monitor() 34 | net.get("c2").monitor() 35 | net.get("c3").monitor() 36 | net.get("c4").monitor() 37 | 38 | print("---> Done") 39 | return 40 | 41 | def testloadbal(net): 42 | serv1 = net.get("serv1") 43 | serv2 = net.get("serv2") 44 | serv1.sendCmd('python ../server.py &') 45 | serv2.sendCmd('python ../server.py &') 46 | 47 | net.get("c4").sendCmd("python ../loadBalanceNode.py bal &") 48 | 49 | print("--> Starting load balance and three client - for testing performance") 50 | 51 | net.get("c1").sendCmd("python ../client.py c1-bal &") 52 | net.get("c2").sendCmd("python ../client.py c2-bal &") 53 | net.get("c3").sendCmd("python ../client.py c3-bal") 54 | 55 | time.sleep(20) 56 | 57 | net.get("c1").monitor() 58 | net.get("c2").monitor() 59 | net.get("c3").monitor() 60 | net.get("c4").monitor() 61 | 62 | print("---> Done") 63 | return 64 | -------------------------------------------------------------------------------- /topo/tests.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sarthakpranesh/Mininet-Load-Balancing/a8c716779716bdcde57a1e12c2d995e69dcaaa1e/topo/tests.pyc -------------------------------------------------------------------------------- /topo/treeTopo.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | from mininet.net import Mininet 4 | from mininet.node import Controller, RemoteController, OVSController 5 | from mininet.node import CPULimitedHost, Host, Node 6 | from mininet.node import OVSKernelSwitch, UserSwitch 7 | from mininet.node import IVSSwitch 8 | from mininet.cli import CLI 9 | from mininet.log import setLogLevel, info 10 | from mininet.link import TCLink, Intf 11 | from subprocess import call 12 | 13 | import sys 14 | from tests import * 15 | 16 | def myNetwork(): 17 | 18 | net = Mininet( topo=None, 19 | build=False, 20 | link=TCLink, 21 | ipBase='10.0.0.0/8') 22 | 23 | info( '*** Adding controller\n' ) 24 | con1=net.addController(name='con1', 25 | controller=Controller, 26 | protocol='tcp', 27 | port=6633) 28 | 29 | con2=net.addController(name='con2', 30 | controller=Controller, 31 | protocol='tcp', 32 | port=6633) 33 | 34 | con3=net.addController(name='con3', 35 | controller=Controller, 36 | protocol='tcp', 37 | port=6633) 38 | 39 | info( '*** Add switches\n') 40 | s2 = net.addSwitch('s2', cls=OVSKernelSwitch) 41 | s3 = net.addSwitch('s3', cls=OVSKernelSwitch) 42 | s1 = net.addSwitch('s1', cls=OVSKernelSwitch) 43 | 44 | c1 = net.addHost('c1', cls=Host, ip='10.0.0.1', defaultRoute=None, cpu=0.2) 45 | c2 = net.addHost('c2', cls=Host, ip='10.0.0.2', defaultRoute=None, cpu=0.2) 46 | c3 = net.addHost('c3', cls=Host, ip='10.0.0.3', defaultRoute=None, cpu=0.2) 47 | 48 | c4 = net.addHost('c4', cls=Host, ip='10.0.0.4', defaultRoute=None, cpu=0.2) 49 | 50 | serv1 = net.addHost('serv1', cls=Host, ip='10.0.0.5', defaultRoute=None) 51 | serv2 = net.addHost('serv2', cls=Host, ip='10.0.0.6', defaultRoute=None) 52 | 53 | info( '*** Add links\n') 54 | net.addLink(c1, s1, bw=1) 55 | net.addLink(c2, s1, bw=1) 56 | net.addLink(c3, s2, bw=1) 57 | net.addLink(s1, s3, bw=2) 58 | net.addLink(s2, s3, bw=2) 59 | 60 | net.addLink(s3, c4, bw=10) 61 | net.addLink(s3, serv1, bw=10) 62 | net.addLink(s3, serv2, bw=10) 63 | 64 | info( '*** Starting network\n') 65 | net.build() 66 | info( '*** Starting controllers\n') 67 | for controller in net.controllers: 68 | controller.start() 69 | 70 | info( '*** Starting switches\n') 71 | net.get('s2').start([con2]) 72 | net.get('s3').start([con1]) 73 | net.get('s1').start([con3]) 74 | 75 | info( '*** Post configure switches and hosts\n') 76 | 77 | net.pingAll() 78 | net.iperf() 79 | 80 | if len(sys.argv) >= 2: 81 | if sys.argv[1] == "base": 82 | testbase(net) 83 | elif sys.argv[1] == "load": 84 | testload(net) 85 | elif sys.argv[1] == "bal": 86 | testloadbal(net) 87 | else: 88 | print("Unregonised test") 89 | else: 90 | print("No test specified, starting CLI") 91 | CLI(net) 92 | 93 | net.stop() 94 | 95 | if __name__ == '__main__': 96 | setLogLevel( 'info' ) 97 | myNetwork() 98 | 99 | --------------------------------------------------------------------------------