Brian O'Connor | 195191b | 2014-10-22 01:09:36 -0700 | [diff] [blame] | 1 | #!/usr/bin/env python |
| 2 | |
| 3 | # TODO add onos-app-fwd to features |
| 4 | # TODO check if service is running... i think this might already be done by mn |
| 5 | |
| 6 | from mininet.node import Controller, OVSSwitch, CPULimitedHost, RemoteController |
| 7 | from mininet.net import Mininet |
| 8 | from mininet.cli import CLI |
| 9 | from mininet.topo import LinearTopo, Topo |
| 10 | from mininet.log import setLogLevel, info, warn |
| 11 | from mininet.util import quietRun, numCores |
| 12 | |
| 13 | from shutil import copyfile |
| 14 | from os import environ, path |
| 15 | from functools import partial |
| 16 | import time |
| 17 | from sys import argv |
| 18 | from time import sleep |
Brian O'Connor | 67eb380 | 2014-10-22 19:55:38 -0700 | [diff] [blame] | 19 | from sets import Set |
Brian O'Connor | 195191b | 2014-10-22 01:09:36 -0700 | [diff] [blame] | 20 | |
| 21 | class ONOS( Controller ): |
Brian O'Connor | 67eb380 | 2014-10-22 19:55:38 -0700 | [diff] [blame] | 22 | "TODO" |
| 23 | |
| 24 | onosDir = '/opt/onos/' |
| 25 | |
| 26 | def __init__( self, name, onosDir=onosDir, |
| 27 | reactive=True, features=[ 'onos-app-tvue' ], |
| 28 | **kwargs ): |
| 29 | '''TODO''' |
| 30 | |
| 31 | Controller.__init__( self, name, **kwargs ) |
| 32 | # the following have been done for us: |
| 33 | #self.ip = ip ('127.0.0.1') |
| 34 | #self.port = port (6633) |
| 35 | #self.protocol = protocol ('tcp') |
| 36 | #self.checkListening() |
| 37 | |
| 38 | self.onosDir = onosDir |
Pavlin Radoslavov | c522705 | 2014-11-04 10:55:19 -0800 | [diff] [blame] | 39 | self.karafDir = onosDir + 'apache-karaf-3.0.2/' |
Brian O'Connor | 67eb380 | 2014-10-22 19:55:38 -0700 | [diff] [blame] | 40 | self.instanceDir = self.karafDir |
| 41 | |
| 42 | # add default modules |
| 43 | # TODO: consider an ordered set |
| 44 | self.features = Set([ 'webconsole', |
Jonathan Hart | 7b1f5cb | 2014-11-11 16:05:29 -0800 | [diff] [blame] | 45 | 'onos-rest', |
Brian O'Connor | 67eb380 | 2014-10-22 19:55:38 -0700 | [diff] [blame] | 46 | 'onos-api', |
| 47 | 'onos-cli', |
| 48 | 'onos-openflow' ]) |
| 49 | self.features.update( features ) |
| 50 | # add reactive forwarding modules |
| 51 | if reactive: |
| 52 | self.features.update( ['onos-app-fwd', |
| 53 | 'onos-app-proxyarp', |
| 54 | 'onos-app-mobility' ] ) |
| 55 | # add the distributed core if we are in a namespace with no trivial core |
| 56 | if self.inNamespace and 'onos-core-trivial' not in self.features: |
| 57 | self.features.add( 'onos-core' ) |
| 58 | # if there is no core, add the trivial one |
| 59 | if 'onos-core' not in self.features: |
| 60 | self.features.add( 'onos-core-trivial' ) |
| 61 | print self.features |
Brian O'Connor | 195191b | 2014-10-22 01:09:36 -0700 | [diff] [blame] | 62 | |
| 63 | def start( self ): |
Brian O'Connor | 195191b | 2014-10-22 01:09:36 -0700 | [diff] [blame] | 64 | if self.inNamespace: |
Brian O'Connor | 6ea48a2 | 2015-03-17 20:07:20 -0700 | [diff] [blame] | 65 | instanceOpts = ( '-furl mvn:org.onosproject/onos-features/1.2.0-SNAPSHOT/xml/features ' |
Brian O'Connor | 67eb380 | 2014-10-22 19:55:38 -0700 | [diff] [blame] | 66 | '-s 8101' ) |
Jonathan Hart | 7b1f5cb | 2014-11-11 16:05:29 -0800 | [diff] [blame] | 67 | if self.ip is not None: |
| 68 | instanceOpts += (' -a %s' % self.IP() ) |
Brian O'Connor | 67eb380 | 2014-10-22 19:55:38 -0700 | [diff] [blame] | 69 | self.userCmd( self.karafDir + 'bin/instance create %s %s' % ( instanceOpts, self.name ) ) |
| 70 | self.instanceDir = self.karafDir + 'instances/%s/' % self.name |
Brian O'Connor | 195191b | 2014-10-22 01:09:36 -0700 | [diff] [blame] | 71 | else: |
| 72 | # we are running in the root namespace, so let's use the root instance |
Brian O'Connor | 67eb380 | 2014-10-22 19:55:38 -0700 | [diff] [blame] | 73 | # clean up the data directory |
| 74 | #self.userCmd( 'rm -rf '+ self.karafDir + 'data/' ) |
| 75 | pass |
Brian O'Connor | 195191b | 2014-10-22 01:09:36 -0700 | [diff] [blame] | 76 | |
Brian O'Connor | 67eb380 | 2014-10-22 19:55:38 -0700 | [diff] [blame] | 77 | self.userCmd( 'rm -rf '+ self.instanceDir + 'data/' ) |
| 78 | |
| 79 | # Update etc/org.apache.karaf.features.cfg |
| 80 | self.updateFeatures() |
| 81 | |
| 82 | # TODO 2. Update etc/hazelcast.xml : interface lines |
| 83 | #cp etc/hazelcast.xml instances/c1/etc/ |
| 84 | self.updateHazelcast() |
| 85 | |
| 86 | # TODO 3. Update etc/system.properties : onos.ip |
| 87 | # TODO 4. Update config/cluster.json : with all nodes |
| 88 | |
| 89 | # start onos |
Jonathan Hart | 205f4b1 | 2015-01-19 14:18:10 -0800 | [diff] [blame] | 90 | self.userCmd( '%sbin/instance start -d %s' % ( self.karafDir, self.name ) ) |
Brian O'Connor | 195191b | 2014-10-22 01:09:36 -0700 | [diff] [blame] | 91 | #TODO we should wait for startup... |
| 92 | |
| 93 | def stop( self ): |
Brian O'Connor | 67eb380 | 2014-10-22 19:55:38 -0700 | [diff] [blame] | 94 | self.userCmd( self.instanceDir + 'bin/stop' ) |
| 95 | #if self.inNamespace: |
| 96 | # self.userCmd( self.karafDir + 'bin/instance destroy %s' % self.name ) |
Brian O'Connor | 195191b | 2014-10-22 01:09:36 -0700 | [diff] [blame] | 97 | self.terminate() |
| 98 | |
Brian O'Connor | 67eb380 | 2014-10-22 19:55:38 -0700 | [diff] [blame] | 99 | def updateHazelcast( self ): |
Jonathan Hart | 626ac96 | 2015-02-25 20:31:49 -0800 | [diff] [blame] | 100 | hz = '192.168.123.*' |
| 101 | if self.ip is not None: |
| 102 | hz = '.'.join(self.ip.split('.')[:-1]) + '.*' |
| 103 | |
Brian O'Connor | 67eb380 | 2014-10-22 19:55:38 -0700 | [diff] [blame] | 104 | readfile = self.karafDir + 'etc/hazelcast.xml' |
| 105 | writefile = self.instanceDir + 'etc/hazelcast.xml' |
| 106 | with open( readfile, 'r' ) as r: |
| 107 | with open( writefile, 'w' ) as w: |
| 108 | for line in r.readlines(): |
| 109 | if '<interface>' in line: |
Jonathan Hart | 626ac96 | 2015-02-25 20:31:49 -0800 | [diff] [blame] | 110 | line = '<interface>' + hz + '</interface>\n' |
Brian O'Connor | 67eb380 | 2014-10-22 19:55:38 -0700 | [diff] [blame] | 111 | w.write( line ) |
| 112 | |
| 113 | def updateFeatures( self ): |
| 114 | filename = self.instanceDir + 'etc/org.apache.karaf.features.cfg' |
Brian O'Connor | 195191b | 2014-10-22 01:09:36 -0700 | [diff] [blame] | 115 | with open( filename, 'r+' ) as f: |
| 116 | lines = f.readlines() |
| 117 | f.seek(0) |
| 118 | f.truncate() |
| 119 | for line in lines: |
| 120 | #print '?', line, |
| 121 | if 'featuresBoot=' in line: |
Brian O'Connor | 67eb380 | 2014-10-22 19:55:38 -0700 | [diff] [blame] | 122 | # parse the features from the line |
| 123 | features = line.rstrip().split('=')[1].split(',') |
| 124 | # add the features to our features set |
| 125 | self.features.update( features ) |
| 126 | # generate the new features line |
| 127 | line = 'featuresBoot=' + ','.join( self.features ) + '\n' |
Brian O'Connor | 195191b | 2014-10-22 01:09:36 -0700 | [diff] [blame] | 128 | #print '!', line, |
| 129 | f.write( line ) |
| 130 | |
Brian O'Connor | 67eb380 | 2014-10-22 19:55:38 -0700 | [diff] [blame] | 131 | |
Brian O'Connor | 195191b | 2014-10-22 01:09:36 -0700 | [diff] [blame] | 132 | @classmethod |
| 133 | def isAvailable( self ): |
Brian O'Connor | 67eb380 | 2014-10-22 19:55:38 -0700 | [diff] [blame] | 134 | return quietRun( 'ls %s' % self.onosDir ) |
| 135 | |
| 136 | def userCmd( self, cmd ): |
| 137 | # switch to the non-root user because karaf gets upset otherwise |
| 138 | # because the .m2repo is not stored with root |
| 139 | cmd = 'sudo -u %s %s' % ( self.findUser(), cmd ) |
| 140 | return self.cmd( cmd ) |
Brian O'Connor | 195191b | 2014-10-22 01:09:36 -0700 | [diff] [blame] | 141 | |
| 142 | @staticmethod |
| 143 | def findUser(): |
| 144 | "Try to return logged-in (usually non-root) user" |
| 145 | try: |
| 146 | # If we're running sudo |
| 147 | return os.environ[ 'SUDO_USER' ] |
| 148 | except: |
| 149 | try: |
| 150 | # Logged-in user (if we have a tty) |
| 151 | return quietRun( 'who am i' ).split()[ 0 ] |
| 152 | except: |
| 153 | # Give up and return effective user |
| 154 | return quietRun( 'whoami' ) |
| 155 | |
| 156 | |
| 157 | class ControlNetwork( Topo ): |
| 158 | "Control Network Topology" |
| 159 | def __init__( self, n, dataController=ONOS, **kwargs ): |
| 160 | """n: number of data network controller nodes |
| 161 | dataController: class for data network controllers""" |
| 162 | Topo.__init__( self, **kwargs ) |
| 163 | # Connect everything to a single switch |
| 164 | cs0 = self.addSwitch( 'cs0' ) |
| 165 | # Add hosts which will serve as data network controllers |
Brian O'Connor | 67eb380 | 2014-10-22 19:55:38 -0700 | [diff] [blame] | 166 | for i in range( 1, n+1 ): |
Brian O'Connor | 195191b | 2014-10-22 01:09:36 -0700 | [diff] [blame] | 167 | c = self.addHost( 'c%s' % i, cls=dataController, |
| 168 | inNamespace=True ) |
| 169 | self.addLink( c, cs0 ) |
| 170 | # Connect switch to root namespace so that data network |
| 171 | # switches will be able to talk to us |
| 172 | root = self.addHost( 'root', inNamespace=False ) |
| 173 | self.addLink( root, cs0 ) |
| 174 | |
| 175 | class ONOSCluster( Controller ): |
| 176 | # TODO |
Brian O'Connor | 67eb380 | 2014-10-22 19:55:38 -0700 | [diff] [blame] | 177 | n = 3 |
Brian O'Connor | 195191b | 2014-10-22 01:09:36 -0700 | [diff] [blame] | 178 | |
| 179 | def start( self ): |
| 180 | ctopo = ControlNetwork( n=self.n, dataController=ONOS ) |
| 181 | self.cnet = Mininet( topo=ctopo, ipBase='192.168.123.0/24', controller=None ) |
| 182 | self.cnet.addController( 'cc0', controller=Controller ) |
| 183 | self.cnet.start() |
| 184 | |
| 185 | self.ctrls = [] |
| 186 | for host in self.cnet.hosts: |
| 187 | if isinstance( host, Controller ): |
| 188 | self.ctrls.append( host ) |
| 189 | host.start() |
| 190 | |
| 191 | def stop( self ): |
Brian O'Connor | 67eb380 | 2014-10-22 19:55:38 -0700 | [diff] [blame] | 192 | for host in self.cnet.hosts: |
| 193 | if isinstance( host, Controller ): |
| 194 | host.stop() |
Brian O'Connor | 195191b | 2014-10-22 01:09:36 -0700 | [diff] [blame] | 195 | self.cnet.stop() |
| 196 | |
| 197 | def clist( self ): |
| 198 | "Return list of Controller proxies for this ONOS cluster" |
| 199 | print 'controllers:', self.ctrls |
| 200 | return self.ctrls |
| 201 | |
| 202 | class OVSSwitchONOS( OVSSwitch ): |
| 203 | "OVS switch which connects to multiple controllers" |
| 204 | def start( self, controllers ): |
| 205 | assert len( controllers ) == 1 |
| 206 | c0 = controllers[ 0 ] |
| 207 | assert type( c0 ) == ONOSCluster |
| 208 | controllers = c0.clist() |
| 209 | OVSSwitch.start( self, controllers ) |
| 210 | |
| 211 | controllers = { 'onos': ONOS } |
| 212 | switches = { 'ovso': OVSSwitchONOS } |
| 213 | |
| 214 | if __name__ == '__main__': |
| 215 | # Simple test for ONOS() controller class |
Brian O'Connor | 67eb380 | 2014-10-22 19:55:38 -0700 | [diff] [blame] | 216 | setLogLevel( 'info' ) #TODO info |
Brian O'Connor | 195191b | 2014-10-22 01:09:36 -0700 | [diff] [blame] | 217 | size = 2 if len( argv ) != 2 else int( argv[ 1 ] ) |
| 218 | net = Mininet( topo=LinearTopo( size ), |
Brian O'Connor | 67eb380 | 2014-10-22 19:55:38 -0700 | [diff] [blame] | 219 | #controller=ONOS, |
| 220 | controller=partial( ONOSCluster, n=3 ), #TODO |
Brian O'Connor | 195191b | 2014-10-22 01:09:36 -0700 | [diff] [blame] | 221 | switch=OVSSwitchONOS ) |
| 222 | net.start() |
| 223 | #waitConnected( net.switches ) |
| 224 | CLI( net ) |
| 225 | net.stop() |